明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 6079|回复: 10

[Kean专集] Kean专题(13)—Geometry

   关闭 [复制链接]
发表于 2009-7-4 20:27:00 | 显示全部楼层 |阅读模式
本帖最后由 lzh741206 于 2010-12-19 18:30 编辑

http://through-the-interface.typepad.com/through_the_interface/fractals/
一、生成Koch分形
July 30, 2007
Generating Koch fractals in AutoCAD using .NET - Part 1
I'm currently waiting to get my RealDWG license through, so I'll interrupt the previous series on side databases to focus on something a little different. I'll get back to it, in due course, I promise. :-)
A long time ago, back during my first few years at Autodesk (which logically must have been some time in the mid- to late-90s, but I forget now), I developed an ObjectARX application to create fractals from linear geometry. I first got interested in the subject when I stumbled across something called the  Koch curve: a very basic fractal - in fact one of the first ever described, back in the early 20th century - which also happens to be very easy to have AutoCAD generate.
Let's take a quick look at what a Koch curve is. Basically it's what you get when you take a line and split it into 3 segments of equal length. You keep the ones at either end, but replace the middle segment with 2 more segments the same length as all the others, each rotated outwards by 60 degrees to form the other two sides of an equilateral triangle. So for each "level" you get 4 lines from a single line.
Here it is in pictures.
A line...

... becomes four lines...

... which, in turn, becomes sixteen...

... etc. ...

... etc. ...

... etc. ...

From here on you don't see much change at this resolution. :-)
I also worked out how to perform the same process on arcs:

The original ObjectARX application I wrote implemented a few different commands which could work either on the whole drawing or on selected objects. Both types of command asked the user for two pieces of information:
The direction of the operation
Left means that the pointy bit will be added to the left of the line or arc, going from start to end point
Right means the opposite
The level of the recursion
I call it recursion, but it's actually performed iteratively. But the point is, the algorithm loops, replacing layers of geometry with their decomposed (or "Kochized") equivalents
Aside from the fun aspect of this (something I like to have in my samples, when I can), the project taught me a number of ObjectARX fundamentals:
Geometry library - how to use the ObjectARX geometry library to perform calculations and transform AutoCAD geometry
Deep operations on transient geometry - how to work on lots (and I mean lots) of intermediate, non-database resident AutoCAD geometry, only adding the "results" (the final output) to the AutoCAD database
Protocol extensions - how to extend the built-in protocol of existing classes (AcDbLine, AcDbArc, etc.) to create an extensible plugin framework (for example)
In my original implementation I implemented Protocol Extensions for a number of objects, allowing to "Kochize" anything from an entire DWG down to individual lines, arcs and polylines. This would also have allowed someone to come in and hook their own modules into my commands, allowing them to also work on custom objects (or on standard objects I hadn't implemented).
Progress meters - how to implement a UI that kept the user informed of progress and gave them the option to cancel long operations
Today I spent some time converting the code across to .NET. A few notes on this:
A mechanism that's comparable with ObjectARX Protocol Extensions is not currently available in .NET (I believe something similar is coming in Visual Studio 2008/C# 3.0/VB 9, where we'll get extension methods)
I ended up creating a very basic set of functions with a similar protocol, and using the one accepting an Entity to dispatch calls to the different versions, depending on the object type.
I've just focused on Lines and Arcs in the initial port, but plan on adding support for complex (Polyline) entities soon
Ditto for the progress meter - I've left long operations to complete in their own sweet time, for now, but plan on hooking the code into AutoCAD's progress meter at some point
Here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using System.Collections.Generic;
  7. using System;
  8. namespace Kochizer
  9. {
  10.   public class Commands
  11.   {
  12.     // We generate 4 new entities for every old entity
  13.     // (unless a complex entity such as a polyline)
  14.     const int newEntsPerOldEnt = 4;
  15.     [CommandMethod("KA")]
  16.     public void KochizeAll()
  17.     {
  18.       Document doc =
  19.         Application.DocumentManager.MdiActiveDocument;
  20.       Database db = doc.Database;
  21.       Editor ed = doc.Editor;
  22.       // Acquire user input - whether to create the
  23.       // new geometry to the left or the right...
  24.       PromptKeywordOptions pko =
  25.         new PromptKeywordOptions(
  26.           "\nCreate fractal to side (Left/<Right>): "
  27.         );
  28.       pko.Keywords.Add("Left");
  29.       pko.Keywords.Add("Right");
  30.       PromptResult pr =
  31.         ed.GetKeywords(pko);
  32.       bool bLeft = false;
  33.       if (pr.Status != PromptStatus.None &&
  34.           pr.Status != PromptStatus.OK)
  35.         return;
  36.       if ((string)pr.StringResult == "Left")
  37.         bLeft = true;
  38.       // ... and the recursion depth for the command.
  39.       PromptIntegerOptions pio =
  40.         new PromptIntegerOptions(
  41.           "\nEnter recursion level <1>: "
  42.         );
  43.       pio.AllowZero = false;
  44.       pio.AllowNegative = false;
  45.       pio.AllowNone = true;
  46.       PromptIntegerResult pir =
  47.         ed.GetInteger(pio);
  48.       int recursionLevel = 1;
  49.       if (pir.Status != PromptStatus.None &&
  50.           pir.Status != PromptStatus.OK)
  51.         return;
  52.       if (pir.Status == PromptStatus.OK)
  53.         recursionLevel = pir.Value;
  54.       // Note: strictly speaking we're not recursing,
  55.       // we're iterating, but the effect to the user
  56.       // is the same.
  57.       Transaction tr =
  58.         doc.TransactionManager.StartTransaction();
  59.       using (tr)
  60.       {
  61.         BlockTable bt =
  62.           (BlockTable)tr.GetObject(
  63.             db.BlockTableId,
  64.             OpenMode.ForRead
  65.           );
  66.         using (bt)
  67.         {
  68.           // No need to open the block table record
  69.           // for write, as we're just reading data
  70.           // for now
  71.           BlockTableRecord btr =
  72.             (BlockTableRecord)tr.GetObject(
  73.               bt[BlockTableRecord.ModelSpace],
  74.               OpenMode.ForRead
  75.             );
  76.           using (btr)
  77.           {
  78.             // List of changed entities
  79.             // (will contain complex entities, such as
  80.             // polylines"
  81.             ObjectIdCollection modified =
  82.               new ObjectIdCollection();
  83.             // List of entities to erase
  84.             // (will contain replaced entities)
  85.             ObjectIdCollection toErase =
  86.               new ObjectIdCollection();
  87.             // List of new entitites to add
  88.             // (will be processed recursively or
  89.             // assed to the open block table record)
  90.             List<Entity> newEntities =
  91.               new List<Entity>(
  92.                 db.ApproxNumObjects * newEntsPerOldEnt
  93.               );
  94.             // Kochize each entity in the open block
  95.             // table record
  96.             foreach (ObjectId objId in btr)
  97.             {
  98.               Entity ent =
  99.                 (Entity)tr.GetObject(
  100.                   objId,
  101.                   OpenMode.ForRead
  102.                 );
  103.               Kochize(
  104.                 ent,
  105.                 modified,
  106.                 toErase,
  107.                 newEntities,
  108.                 bLeft
  109.               );
  110.             }
  111.             // If we need to loop,
  112.             // work on the returned entities
  113.             while (--recursionLevel > 0)
  114.             {
  115.               // Create an output array
  116.               List<Entity> newerEntities =
  117.                 new List<Entity>(
  118.                   newEntities.Count * newEntsPerOldEnt
  119.                 );
  120.               // Kochize all the modified (complex) entities
  121.               foreach (ObjectId objId in modified)
  122.               {
  123.                 Entity ent =
  124.                   (Entity)tr.GetObject(
  125.                     objId,
  126.                     OpenMode.ForRead
  127.                   );
  128.                 Kochize(
  129.                   ent,
  130.                   modified,
  131.                   toErase,
  132.                   newerEntities,
  133.                   bLeft
  134.                 );
  135.               }
  136.               // Kochize all the non-db resident entities
  137.               foreach (Entity ent in newEntities)
  138.               {
  139.                 Kochize(
  140.                   ent,
  141.                   modified,
  142.                   toErase,
  143.                   newerEntities,
  144.                   bLeft
  145.                 );
  146.               }
  147.               // We now longer need the intermediate entities
  148.               // previously output for the level above,
  149.               // we replace them with the latest output
  150.               newEntities.Clear();
  151.               newEntities = newerEntities;
  152.             }
  153.             // Erase each of the replaced db-resident entities
  154.             foreach (ObjectId objId in toErase)
  155.             {
  156.               Entity ent =
  157.                 (Entity)tr.GetObject(
  158.                   objId,
  159.                   OpenMode.ForWrite
  160.                 );
  161.               ent.Erase();
  162.             }
  163.             // Add the new entities
  164.             btr.UpgradeOpen();
  165.             foreach (Entity ent in newEntities)
  166.             {
  167.               btr.AppendEntity(ent);
  168.               tr.AddNewlyCreatedDBObject(ent, true);
  169.             }
  170.             tr.Commit();
  171.           }
  172.         }
  173.       }
  174.     }
  175.     // Dispatch function to call through to various per-type
  176.     // functions
  177.     private void Kochize(
  178.       Entity ent,
  179.       ObjectIdCollection modified,
  180.       ObjectIdCollection toErase,
  181.       List<Entity> toAdd,
  182.       bool bLeft
  183.     )
  184.     {
  185.       Line ln = ent as Line;
  186.       if (ln != null)
  187.       {
  188.         Kochize(ln, modified, toErase, toAdd, bLeft);
  189.         return;
  190.       }
  191.       Arc arc = ent as Arc;
  192.       if (arc != null)
  193.       {
  194.         Kochize(arc, modified, toErase, toAdd, bLeft);
  195.         return;
  196.       }
  197.     }
  198.     // Create 4 new lines from a line passed in
  199.     private void Kochize(
  200.       Line ln,
  201.       ObjectIdCollection modified,
  202.       ObjectIdCollection toErase,
  203.       List<Entity> toAdd,
  204.       bool bLeft
  205.     )
  206.     {
  207.       // Get general info about the line
  208.       // and calculate the main 5 points
  209.       Point3d pt1 = ln.StartPoint,
  210.               pt5 = ln.EndPoint;
  211.       Vector3d vec1 = pt5 - pt1,
  212.               norm1 = vec1.GetNormal();
  213.       double d_3 = vec1.Length / 3;
  214.       Point3d pt2 = pt1 + (norm1 * d_3),
  215.               pt4 = pt1 + (2 * norm1 * d_3);
  216.       Vector3d vec2 = pt4 - pt2;
  217.       if (bLeft)
  218.         vec2 =
  219.           vec2.RotateBy(
  220.             Math.PI / 3, new Vector3d(0, 0, 1)
  221.           );
  222.       else
  223.         vec2 =
  224.           vec2.RotateBy(
  225.             5 * Math.PI / 3, new Vector3d(0, 0, 1)
  226.           );
  227.       Point3d pt3 = pt2 + vec2;
  228.       // Mark the original to be erased
  229.       if (ln.ObjectId != ObjectId.Null)
  230.         toErase.Add(ln.ObjectId);
  231.       // Create the first line
  232.       Line ln1 = new Line(pt1, pt2);
  233.       ln1.SetPropertiesFrom(ln);
  234.       ln1.Thickness = ln.Thickness;
  235.       toAdd.Add(ln1);
  236.       // Create the second line
  237.       Line ln2 = new Line(pt2, pt3);
  238.       ln2.SetPropertiesFrom(ln);
  239.       ln2.Thickness = ln.Thickness;
  240.       toAdd.Add(ln2);
  241.       // Create the third line
  242.       Line ln3 = new Line(pt3, pt4);
  243.       ln3.SetPropertiesFrom(ln);
  244.       ln3.Thickness = ln.Thickness;
  245.       toAdd.Add(ln3);
  246.       // Create the fourth line
  247.       Line ln4 = new Line(pt4, pt5);
  248.       ln4.SetPropertiesFrom(ln);
  249.       ln4.Thickness = ln.Thickness;
  250.       toAdd.Add(ln4);
  251.     }
  252.     // Create 4 new arcs from an arc passed in
  253.     private void Kochize(
  254.       Arc arc,
  255.       ObjectIdCollection modified,
  256.       ObjectIdCollection toErase,
  257.       List<Entity> toAdd,
  258.       bool bLeft
  259.     )
  260.     {
  261.       // Get general info about the arc
  262.       // and calculate the main 5 points
  263.       Point3d pt1 = arc.StartPoint,
  264.               pt5 = arc.EndPoint;
  265.       double length = arc.GetDistAtPoint(pt5),
  266.             angle = arc.StartAngle;
  267.       //bool bLocalLeft = false;
  268.       Vector3d full = pt5 - pt1;
  269.       //if (full.GetAngleTo(Vector3d.XAxis) > angle)
  270.         //bLocalLeft = true;
  271.       Point3d pt2 = arc.GetPointAtDist(length / 3),
  272.               pt4 = arc.GetPointAtDist(2 * length / 3);
  273.       // Mark the original to be erased
  274.       if (arc.ObjectId != ObjectId.Null)
  275.         toErase.Add(arc.ObjectId);
  276.       // Create the first arc
  277.       Point3d mid = arc.GetPointAtDist(length / 6);
  278.       CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);
  279.       Arc arc1 = circArc2Arc(tmpArc);
  280.       arc1.SetPropertiesFrom(arc);
  281.       arc1.Thickness = arc.Thickness;
  282.       toAdd.Add(arc1);
  283.       // Create the second arc
  284.       mid = arc.GetPointAtDist(length / 2);
  285.       tmpArc.Set(pt2, mid, pt4);
  286.       if (bLeft)
  287.         tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt2);
  288.       else
  289.         tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt2);
  290.       Arc arc2 = circArc2Arc(tmpArc);
  291.       arc2.SetPropertiesFrom(arc);
  292.       arc2.Thickness = arc.Thickness;
  293.       toAdd.Add(arc2);
  294.       // Create the third arc
  295.       mid = arc.GetPointAtDist(length / 2);
  296.       tmpArc.Set(pt2, mid, pt4);
  297.       if (bLeft)
  298.         tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt4);
  299.       else
  300.         tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt4);
  301.       Arc arc3 = circArc2Arc(tmpArc);
  302.       arc3.SetPropertiesFrom(arc);
  303.       arc3.Thickness = arc.Thickness;
  304.       toAdd.Add(arc3);
  305.       // Create the fourth arc
  306.       mid = arc.GetPointAtDist(5 * length / 6);
  307.       Arc arc4 =
  308.         circArc2Arc(new CircularArc3d(pt4, mid, pt5));
  309.       arc4.SetPropertiesFrom(arc);
  310.       arc4.Thickness = arc.Thickness;
  311.       toAdd.Add(arc4);
  312.     }
  313.     Arc circArc2Arc(CircularArc3d circArc)
  314.     {
  315.       double ang, start, end;
  316.       ang =
  317.         circArc.ReferenceVector.GetAngleTo(Vector3d.XAxis);
  318.       ang =
  319.         (circArc.ReferenceVector.Y < 0 ? -ang : ang);
  320.       start = circArc.StartAngle + ang;
  321.       end = circArc.EndAngle + ang;
  322.       return (
  323.         new Arc(
  324.           circArc.Center,
  325.           circArc.Normal,
  326.           circArc.Radius,
  327.           start,
  328.           end
  329.         )
  330.       );
  331.     }
  332.   }
  333. }
Here's how it works for lines and arcs in a drawing. I took the example of an equilateral triangle (and something quite like it, made out of arcs), which is the classic case that makes a Koch snowflake or Koch star. I used a recursion level of 6 - once again, more detail than is needed at this resolution.

Next time I'll look at some of the missing pieces - perhaps adding the progress meter or support for complex types, such as polylines. Or then again I may switch back to the RealDWG sample, if I get the license through.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-7 15:06:00 | 显示全部楼层
August 02, 2007
Generating Koch fractals in AutoCAD using .NET - Part 2
This post continues on from the last one, which introduced some code that creates "Koch curves" inside AutoCAD. Not in itself something you'll want to do to your drawings, but the techniques shown may well prove helpful for your applications.
Last time we implemented support for Lines and Arcs - in this post we extend that to Polylines. These are a different animal, as rather than replacing the original entities with 4 times as many for each "level", in this case we add 3 new segments to each original segment. So we don't then mark the entities for erasure, either.
In addition to the code needed to support polylines, I also had to make some minor modifications. I've marked them below in red. Basically there were a few areas where I wasn't as careful as I might have been when it comes to supporting modifying planar entities in arbitrary 3D spaces. So I fixed a few bugs, and also reimplemented the helper function that converts CircularArc3ds to Arcs.
Here's the updated C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using System.Collections.Generic;
  7. using System;
  8. namespace Kochizer
  9. {
  10.   public class Commands
  11.   {
  12.     // We generate 4 new entities for every old entity
  13.     // (unless a complex entity such as a polyline)
  14.     const int newEntsPerOldEnt = 4;
  15.     [CommandMethod("KA")]
  16.     public void KochizeAll()
  17.     {
  18.       Document doc =
  19.         Application.DocumentManager.MdiActiveDocument;
  20.       Database db = doc.Database;
  21.       Editor ed = doc.Editor;
  22.       // Acquire user input - whether to create the
  23.       // new geometry to the left or the right...
  24.       PromptKeywordOptions pko =
  25.         new PromptKeywordOptions(
  26.           "\nCreate fractal to side (Left/<Right>): "
  27.         );
  28.       pko.Keywords.Add("Left");
  29.       pko.Keywords.Add("Right");
  30.       PromptResult pr =
  31.         ed.GetKeywords(pko);
  32.       bool bLeft = false;
  33.       if (pr.Status != PromptStatus.None &&
  34.           pr.Status != PromptStatus.OK)
  35.         return;
  36.       if ((string)pr.StringResult == "Left")
  37.         bLeft = true;
  38.       // ... and the recursion depth for the command.
  39.       PromptIntegerOptions pio =
  40.         new PromptIntegerOptions(
  41.           "\nEnter recursion level <1>: "
  42.         );
  43.       pio.AllowZero = false;
  44.       pio.AllowNegative = false;
  45.       pio.AllowNone = true;
  46.       PromptIntegerResult pir =
  47.         ed.GetInteger(pio);
  48.       int recursionLevel = 1;
  49.       if (pir.Status != PromptStatus.None &&
  50.           pir.Status != PromptStatus.OK)
  51.         return;
  52.       if (pir.Status == PromptStatus.OK)
  53.         recursionLevel = pir.Value;
  54.       // Note: strictly speaking we're not recursing,
  55.       // we're iterating, but the effect to the user
  56.       // is the same.
  57.       Transaction tr =
  58.         doc.TransactionManager.StartTransaction();
  59.       using (tr)
  60.       {
  61.         BlockTable bt =
  62.           (BlockTable)tr.GetObject(
  63.             db.BlockTableId,
  64.             OpenMode.ForRead
  65.           );
  66.         using (bt)
  67.         {
  68.           // No need to open the block table record
  69.           // for write, as we're just reading data
  70.           // for now
  71.           BlockTableRecord btr =
  72.             (BlockTableRecord)tr.GetObject(
  73.               bt[BlockTableRecord.ModelSpace],
  74.               OpenMode.ForRead
  75.             );
  76.           using (btr)
  77.           {
  78.             // List of changed entities
  79.             // (will contain complex entities, such as
  80.             // polylines"
  81.             ObjectIdCollection modified =
  82.               new ObjectIdCollection();
  83.             // List of entities to erase
  84.             // (will contain replaced entities)
  85.             ObjectIdCollection toErase =
  86.               new ObjectIdCollection();
  87.             // List of new entitites to add
  88.             // (will be processed recursively or
  89.             // assed to the open block table record)
  90.             List<Entity> newEntities =
  91.               new List<Entity>(
  92.                 db.ApproxNumObjects * newEntsPerOldEnt
  93.               );
  94.             // Kochize each entity in the open block
  95.             // table record
  96.             foreach (ObjectId objId in btr)
  97.             {
  98.               Entity ent =
  99.                 (Entity)tr.GetObject(
  100.                   objId,
  101.                   OpenMode.ForRead
  102.                 );
  103.               Kochize(
  104.                 ent,
  105.                 modified,
  106.                 toErase,
  107.                 newEntities,
  108.                 bLeft
  109.               );
  110.             }
  111.             // If we need to loop,
  112.             // work on the returned entities
  113.             while (--recursionLevel > 0)
  114.             {
  115.               // Create an output array
  116.               List<Entity> newerEntities =
  117.                 new List<Entity>(
  118.                   newEntities.Count * newEntsPerOldEnt
  119.                 );
  120.               // Kochize all the modified (complex) entities
  121.               foreach (ObjectId objId in modified)
  122.               {
  123.                 Entity ent =
  124.                   (Entity)tr.GetObject(
  125.                     objId,
  126.                     OpenMode.ForRead
  127.                   );
  128.                 Kochize(
  129.                   ent,
  130.                   modified,
  131.                   toErase,
  132.                   newerEntities,
  133.                   bLeft
  134.                 );
  135.               }
  136.               // Kochize all the non-db resident entities
  137.               foreach (Entity ent in newEntities)
  138.               {
  139.                 Kochize(
  140.                   ent,
  141.                   modified,
  142.                   toErase,
  143.                   newerEntities,
  144.                   bLeft
  145.                 );
  146.               }
  147.               // We now longer need the intermediate entities
  148.               // previously output for the level above,
  149.               // we replace them with the latest output
  150.               newEntities.Clear();
  151.               newEntities = newerEntities;
  152.             }
  153.             // Erase each of the replaced db-resident entities
  154.             foreach (ObjectId objId in toErase)
  155.             {
  156.               Entity ent =
  157.                 (Entity)tr.GetObject(
  158.                   objId,
  159.                   OpenMode.ForWrite
  160.                 );
  161.               ent.Erase();
  162.             }
  163.             // Add the new entities
  164.             btr.UpgradeOpen();
  165.             foreach (Entity ent in newEntities)
  166.             {
  167.               btr.AppendEntity(ent);
  168.               tr.AddNewlyCreatedDBObject(ent, true);
  169.             }
  170.             tr.Commit();
  171.           }
  172.         }
  173.       }
  174.     }
  175.     // Dispatch function to call through to various per-type
  176.     // functions
  177.     private void Kochize(
  178.       Entity ent,
  179.       ObjectIdCollection modified,
  180.       ObjectIdCollection toErase,
  181.       List<Entity> toAdd,
  182.       bool bLeft
  183.     )
  184.     {
  185.       Line ln = ent as Line;
  186.       if (ln != null)
  187.       {
  188.         Kochize(ln, modified, toErase, toAdd, bLeft);
  189.         return;
  190.       }
  191.       Arc arc = ent as Arc;
  192.       if (arc != null)
  193.       {
  194.         Kochize(arc, modified, toErase, toAdd, bLeft);
  195.         return;
  196.       }
  197.       Polyline pl = ent as Polyline;
  198.       if (pl != null)
  199.       {
  200.         Kochize(pl, modified, toErase, toAdd, bLeft);
  201.         return;
  202.       }
  203.     }
  204.     // Create 4 new lines from a line passed in
  205.     private void Kochize(
  206.       Line ln,
  207.       ObjectIdCollection modified,
  208.       ObjectIdCollection toErase,
  209.       List<Entity> toAdd,
  210.       bool bLeft
  211.     )
  212.     {
  213.       // Get general info about the line
  214.       // and calculate the main 5 points
  215.       Point3d pt1 = ln.StartPoint,
  216.               pt5 = ln.EndPoint;
  217.       Vector3d vec1 = pt5 - pt1,
  218.                norm1 = vec1.GetNormal();
  219.       double d_3 = vec1.Length / 3;
  220.       Point3d pt2 = pt1 + (norm1 * d_3),
  221.               pt4 = pt1 + (2 * norm1 * d_3);
  222.       Vector3d vec2 = pt4 - pt2;
  223.       if (bLeft)
  224.         vec2 =
  225.           vec2.RotateBy(
  226.             Math.PI / 3, new Vector3d(0, 0, 1)
  227.           );
  228.       else
  229.         vec2 =
  230.           vec2.RotateBy(
  231.             5 * Math.PI / 3, new Vector3d(0, 0, 1)
  232.           );
  233.       Point3d pt3 = pt2 + vec2;
  234.       // Mark the original to be erased
  235.       if (ln.ObjectId != ObjectId.Null)
  236.         toErase.Add(ln.ObjectId);
  237.       // Create the first line
  238.       Line ln1 = new Line(pt1, pt2);
  239.       ln1.SetPropertiesFrom(ln);
  240.       ln1.Thickness = ln.Thickness;
  241.       toAdd.Add(ln1);
  242.       // Create the second line
  243.       Line ln2 = new Line(pt2, pt3);
  244.       ln2.SetPropertiesFrom(ln);
  245.       ln2.Thickness = ln.Thickness;
  246.       toAdd.Add(ln2);
  247.       // Create the third line
  248.       Line ln3 = new Line(pt3, pt4);
  249.       ln3.SetPropertiesFrom(ln);
  250.       ln3.Thickness = ln.Thickness;
  251.       toAdd.Add(ln3);
  252.       // Create the fourth line
  253.       Line ln4 = new Line(pt4, pt5);
  254.       ln4.SetPropertiesFrom(ln);
  255.       ln4.Thickness = ln.Thickness;
  256.       toAdd.Add(ln4);
  257.     }
  258.     // Create 4 new arcs from an arc passed in
  259.     private void Kochize(
  260.       Arc arc,
  261.       ObjectIdCollection modified,
  262.       ObjectIdCollection toErase,
  263.       List<Entity> toAdd,
  264.       bool bLeft
  265.     )
  266.     {
  267.       // Get general info about the arc
  268.       // and calculate the main 5 points
  269.       Point3d pt1 = arc.StartPoint,
  270.               pt5 = arc.EndPoint;
  271.       double length = arc.GetDistAtPoint(pt5),
  272.              angle = arc.StartAngle;
  273.       Vector3d full = pt5 - pt1;
  274.       Point3d pt2 = arc.GetPointAtDist(length / 3),
  275.               pt4 = arc.GetPointAtDist(2 * length / 3);
  276.       // Mark the original to be erased
  277.       if (arc.ObjectId != ObjectId.Null)
  278.         toErase.Add(arc.ObjectId);
  279.       // Create the first arc
  280.       Point3d mid = arc.GetPointAtDist(length / 6);
  281.       CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);
  282.       Arc arc1 = circArc2Arc(tmpArc);
  283.       arc1.SetPropertiesFrom(arc);
  284.       arc1.Thickness = arc.Thickness;
  285.       toAdd.Add(arc1);
  286.       // Create the second arc
  287.       mid = arc.GetPointAtDist(length / 2);
  288.       tmpArc.Set(pt2, mid, pt4);
  289.       if (bLeft)
  290.         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt2);
  291.       else
  292.         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt2);
  293.       Arc arc2 = circArc2Arc(tmpArc);
  294.       arc2.SetPropertiesFrom(arc);
  295.       arc2.Thickness = arc.Thickness;
  296.       toAdd.Add(arc2);
  297.       // Create the third arc
  298.       tmpArc.Set(pt2, mid, pt4);
  299.       if (bLeft)
  300.         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
  301.       else
  302.         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
  303.       Arc arc3 = circArc2Arc(tmpArc);
  304.       arc3.SetPropertiesFrom(arc);
  305.       arc3.Thickness = arc.Thickness;
  306.       toAdd.Add(arc3);
  307.       // Create the fourth arc
  308.       mid = arc.GetPointAtDist(5 * length / 6);
  309.       Arc arc4 =
  310.         circArc2Arc(new CircularArc3d(pt4, mid, pt5));
  311.       arc4.SetPropertiesFrom(arc);
  312.       arc4.Thickness = arc.Thickness;
  313.       toAdd.Add(arc4);
  314.     }
  315.     Arc circArc2Arc(CircularArc3d circArc)
  316.     {
  317.       Point3d center = circArc.Center;
  318.       Vector3d normal = circArc.Normal;
  319.       Vector3d refVec = circArc.ReferenceVector;
  320.       Plane plane = new Plane(center, normal);
  321.       double ang = refVec.AngleOnPlane(plane);
  322.       return new Arc(
  323.         center,
  324.         normal,
  325.         circArc.Radius,
  326.         circArc.StartAngle + ang,
  327.         circArc.EndAngle + ang
  328.       );
  329.     }
  330.     private void Kochize(
  331.       Polyline pl,
  332.       ObjectIdCollection modified,
  333.       ObjectIdCollection toErase,
  334.       List<Entity> toAdd,
  335.       bool bLeft
  336.     )
  337.     {
  338.       pl.UpgradeOpen();
  339.       if (pl.ObjectId != ObjectId.Null &&
  340.           !modified.Contains(pl.ObjectId))
  341.       {
  342.         modified.Add(pl.ObjectId);
  343.       }
  344.       for(int vn = 0; vn < pl.NumberOfVertices; vn++)
  345.       {
  346.         SegmentType st = pl.GetSegmentType(vn);
  347.         if (st != SegmentType.Line && st != SegmentType.Arc)
  348.           continue;
  349.         double sw = pl.GetStartWidthAt(vn),
  350.                ew = pl.GetEndWidthAt(vn);
  351.         if (st == SegmentType.Line)
  352.         {
  353.           if (vn + 1 == pl.NumberOfVertices)
  354.             continue;
  355.           LineSegment2d ls = pl.GetLineSegment2dAt(vn);
  356.           Point2d pt1 = ls.StartPoint,
  357.                   pt5 = ls.EndPoint;
  358.           Vector2d vec = pt5 - pt1;
  359.           double d_3 = vec.Length / 3;
  360.           Point2d pt2 = pt1 + (vec.GetNormal() * d_3),
  361.                   pt4 = pt1 + (vec.GetNormal() * 2 * d_3);
  362.           Vector2d vec2 = pt4 - pt2;
  363.           if (bLeft)
  364.             vec2 = vec2.RotateBy(Math.PI / 3);
  365.           else
  366.             vec2 = vec2.RotateBy(5 * Math.PI / 3);
  367.           Point2d pt3 = pt2 + vec2;
  368.           pl.AddVertexAt(++vn, pt2, 0, sw, ew);
  369.           pl.AddVertexAt(++vn, pt3, 0, sw, ew);
  370.           pl.AddVertexAt(++vn, pt4, 0, sw, ew);
  371.         }
  372.         else if (st == SegmentType.Arc)
  373.         {
  374.           CircularArc3d ca = pl.GetArcSegmentAt(vn);
  375.           double oldBulge = pl.GetBulgeAt(vn);
  376.           // Build a standard arc and use that for the calcs
  377.           Arc arc = circArc2Arc(ca);
  378.           // Get the main 5 points
  379.           Point3d pt1 = arc.StartPoint,
  380.                   pt5 = arc.EndPoint;
  381.          
  382.           double ln = arc.GetDistAtPoint(pt5);
  383.           Point3d pt2 = arc.GetPointAtDist(ln / 3),
  384.                   pt4 = arc.GetPointAtDist(2 * ln / 3);
  385.           Point3d mid = arc.GetPointAtDist(ln / 2);
  386.           CircularArc3d tmpArc = new CircularArc3d(pt2, mid, pt4);
  387.           if (bLeft)
  388.             tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
  389.           else
  390.             tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
  391.           Point3d pt3 = tmpArc.StartPoint;
  392.           // Now add the new segments, setting the bulge
  393.           // for the existing one and the new ones to a third
  394.           // (as the segments are a third as big as the old one)
  395.           CoordinateSystem3d ecs = pl.Ecs.CoordinateSystem3d;
  396.           Plane pn = new Plane(ecs.Origin, pl.Normal);
  397.           double bu = oldBulge / 3;
  398.           pl.SetBulgeAt(vn, bu);
  399.           pl.AddVertexAt(++vn, pt2.Convert2d(pn), bu, sw, ew);
  400.           pl.AddVertexAt(++vn, pt3.Convert2d(pn), bu, sw, ew);
  401.           pl.AddVertexAt(++vn, pt4.Convert2d(pn), bu, sw, ew);
  402.         }
  403.       }
  404.       pl.DowngradeOpen();
  405.     }
  406.   }
  407. }
Here are the results of running the KA command on a drawing containing a single polyline with arc and line segments, choosing a recursion depth of 6:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-8 11:46:00 | 显示全部楼层
二、长事务
August 06, 2007
A handy .NET class to help manage long operations in AutoCAD
This post was almost called "Generating Koch fractals in AutoCAD using .NET - Part 3", following on from Parts 1 & 2 of the series. But by the time I'd completed the code, I realised it to be of more general appeal and decided to provide it with a more representative title.
I started off by adding a progress meter and an escape key handler to the code in the last post. Then, while refactoring the code, I decided to encapsulate the functionality in a standalone class that could be dropped into pretty much any AutoCAD .NET project (although I've implemented it in C#, as usual).
So what we have is a new class called LongOperationManager, which does the following:
Displays and updates a progress meter (at the bottom left of AutoCAD's window)
Allowing you to set an arbitrary message and total number of operations
Listens for "escape" in case the user wants to interrupt the current operation
Here's the class implementation:
  1. public class LongOperationManager :
  2.   IDisposable, System.Windows.Forms.IMessageFilter
  3. {
  4.   // The message code corresponding to a keypress
  5.   const int WM_KEYDOWN = 0x0100;
  6.   // The number of times to update the progress meter
  7.   // (for some reason you need 600 to tick through
  8.   //  for each percent)
  9.   const int progressMeterIncrements = 600;
  10.   // Internal members for metering progress
  11.   private ProgressMeter pm;
  12.   private long updateIncrement;
  13.   private long currentInc;
  14.   // External flag for checking cancelled status
  15.   public bool cancelled = false;
  16.   // Constructor
  17.   public LongOperationManager(string message)
  18.   {
  19.     System.Windows.Forms.Application.
  20.       AddMessageFilter(this);
  21.     pm = new ProgressMeter();
  22.     pm.Start(message);
  23.     pm.SetLimit(progressMeterIncrements);
  24.     currentInc = 0;
  25.   }
  26.   // System.IDisposable.Dispose
  27.   public void Dispose()
  28.   {
  29.     pm.Stop();
  30.     pm.Dispose();
  31.     System.Windows.Forms.Application.
  32.       RemoveMessageFilter(this);
  33.   }
  34.   // Set the total number of operations
  35.   public void SetTotalOperations(long totalOps)
  36.   {
  37.     // We really just care about when we need
  38.     // to update the timer
  39.     updateIncrement =
  40.       (totalOps > progressMeterIncrements ?
  41.         totalOps / progressMeterIncrements :
  42.         totalOps
  43.       );
  44.   }
  45.   // This function is called whenever an operation
  46.   // is performed
  47.   public bool Tick()
  48.   {
  49.     if (++currentInc == updateIncrement)
  50.     {
  51.       pm.MeterProgress();
  52.       currentInc = 0;
  53.       System.Windows.Forms.Application.DoEvents();
  54.     }
  55.     // Check whether the filter has set the flag
  56.     if (cancelled)
  57.       pm.Stop();
  58.     return !cancelled;
  59.   }
  60.   // The message filter callback
  61.   public bool PreFilterMessage(
  62.     ref System.Windows.Forms.Message m
  63.   )
  64.   {
  65.     if (m.Msg == WM_KEYDOWN)
  66.     {
  67.       // Check for the Escape keypress
  68.       System.Windows.Forms.Keys kc =
  69.         (System.Windows.Forms.Keys)(int)m.WParam &
  70.         System.Windows.Forms.Keys.KeyCode;
  71.       if (m.Msg == WM_KEYDOWN &&
  72.           kc == System.Windows.Forms.Keys.Escape)
  73.       {
  74.         cancelled = true;
  75.       }
  76.       // Return true to filter all keypresses
  77.       return true;
  78.     }
  79.     // Return false to let other messages through
  80.     return false;
  81.   }
  82. }
In terms of how to use the class... first of all you create an instance of it, setting the string to be shown on the progress meter (just like AutoCAD's ProgressMeter class). As the LongOperationManager implements IDisposable, then at the end you should either call Dispose or manage it's scope with the using() statement.
I chose to separate the setting of the total number of operations to be completed from the object's construction, as in our example we need to an initial pass before we know how many objects we're working with (and we want to at least put the label on the progress meter while we perform that initial pass).
Then we just call the Tick() method whenever we perform an operation - this updates the progress meter and checks for use of the escape key. The idea is that you set the total number of operations and then call Tick() for each one of those individual operations - the class takes care of how often it needs to update the progress meter. If it finds escape has been used, the Tick() method will return false.
That's about it, aside from the fact you can also query the "cancelled" property to see whether escape has been used.
Here's the basic approach:
  1. LongOperationManager lom =
  2.   new LongOperationManager("Fractalizing entities");
  3. using (lom)
  4. {
  5.   ...
  6.   lom.SetTotalOperations(totalOps);
  7.   ...
  8.   while (true)
  9.   {
  10.     ...
  11.     if (!lom.Tick())
  12.     {
  13.       ed.WriteMessage("\nFractalization cancelled.\n");
  14.       break;
  15.     }
  16.   }
  17. }
Here's the code integrated into the previous example
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using System.Collections.Generic;
  7. using System;
  8. namespace Kochizer
  9. {
  10.   public class Commands
  11.   {
  12.     // We generate 4 new entities for every old entity
  13.     // (unless a complex entity such as a polyline)
  14.     const int newEntsPerOldEnt = 4;
  15.     [CommandMethod("KA")]
  16.     public void KochizeAll()
  17.     {
  18.       Document doc =
  19.         Application.DocumentManager.MdiActiveDocument;
  20.       Database db = doc.Database;
  21.       Editor ed = doc.Editor;
  22.       // Acquire user input - whether to create the
  23.       // new geometry to the left or the right...
  24.       PromptKeywordOptions pko =
  25.         new PromptKeywordOptions(
  26.           "\nCreate fractal to side (Left/<Right>): "
  27.         );
  28.       pko.Keywords.Add("Left");
  29.       pko.Keywords.Add("Right");
  30.       PromptResult pr =
  31.         ed.GetKeywords(pko);
  32.       bool bLeft = false;
  33.       if (pr.Status != PromptStatus.None &&
  34.           pr.Status != PromptStatus.OK)
  35.         return;
  36.       if ((string)pr.StringResult == "Left")
  37.         bLeft = true;
  38.       // ... and the recursion depth for the command.
  39.       PromptIntegerOptions pio =
  40.         new PromptIntegerOptions(
  41.           "\nEnter recursion level <1>: "
  42.         );
  43.       pio.AllowZero = false;
  44.       pio.AllowNegative = false;
  45.       pio.AllowNone = true;
  46.       PromptIntegerResult pir =
  47.         ed.GetInteger(pio);
  48.       int recursionLevel = 1;
  49.       if (pir.Status != PromptStatus.None &&
  50.           pir.Status != PromptStatus.OK)
  51.         return;
  52.       if (pir.Status == PromptStatus.OK)
  53.         recursionLevel = pir.Value;
  54.       // Create and add our long operation handler
  55.       LongOperationManager lom =
  56.         new LongOperationManager("Fractalizing entities");
  57.       using (lom)
  58.       {
  59.         // Note: strictly speaking we're not recursing,
  60.         // we're iterating, but the effect to the user
  61.         // is the same.
  62.         Transaction tr =
  63.           doc.TransactionManager.StartTransaction();
  64.         using (tr)
  65.         {
  66.           BlockTable bt =
  67.             (BlockTable)tr.GetObject(
  68.               db.BlockTableId,
  69.               OpenMode.ForRead
  70.             );
  71.           using (bt)
  72.           {
  73.             // No need to open the block table record
  74.             // for write, as we're just reading data
  75.             // for now
  76.             BlockTableRecord btr =
  77.               (BlockTableRecord)tr.GetObject(
  78.                 bt[BlockTableRecord.ModelSpace],
  79.                 OpenMode.ForRead
  80.               );
  81.             using (btr)
  82.             {
  83.               // List of changed entities
  84.               // (will contain complex entities, such as
  85.               // polylines"
  86.               ObjectIdCollection modified =
  87.                 new ObjectIdCollection();
  88.               // List of entities to erase
  89.               // (will contain replaced entities)
  90.               ObjectIdCollection toErase =
  91.                 new ObjectIdCollection();
  92.               // List of new entitites to add
  93.               // (will be processed recursively or
  94.               // assed to the open block table record)
  95.               List<Entity> newEntities =
  96.                 new List<Entity>(
  97.                   db.ApproxNumObjects * newEntsPerOldEnt
  98.                 );
  99.               // Kochize each entity in the open block
  100.               // table record
  101.               foreach (ObjectId objId in btr)
  102.               {
  103.                 Entity ent =
  104.                   (Entity)tr.GetObject(
  105.                     objId,
  106.                     OpenMode.ForRead
  107.                   );
  108.                 Kochize(
  109.                   ent,
  110.                   modified,
  111.                   toErase,
  112.                   newEntities,
  113.                   bLeft
  114.                 );
  115.               }
  116.               // The number of operations is...
  117.               //  The number of complex entities multiplied
  118.               //  by the recursion level (they each get
  119.               //  "kochized" once per level,
  120.               //  even if that's a long operation)
  121.               // plus
  122.               //  (4^0 + 4^1 + 4^2 + 4^3... + 4^n) multiplied
  123.               //  by the number of db-resident ents
  124.               // where n is the recursion level. Phew!
  125.               long totalOps =
  126.                     modified.Count * recursionLevel +
  127.                     operationCount(recursionLevel) * toErase.Count;
  128.               lom.SetTotalOperations(totalOps);
  129.               // If we need to loop,
  130.               // work on the returned entities
  131.               while (--recursionLevel > 0)
  132.               {
  133.                 // Create an output array
  134.                 List<Entity> newerEntities =
  135.                   new List<Entity>(
  136.                     newEntities.Count * newEntsPerOldEnt
  137.                   );
  138.                 // Kochize all the modified (complex) entities
  139.                 foreach (ObjectId objId in modified)
  140.                 {
  141.                   if (!lom.Tick())
  142.                   {
  143.                     ed.WriteMessage(
  144.                       "\nFractalization cancelled.\n"
  145.                     );
  146.                     break;
  147.                   }
  148.                   Entity ent =
  149.                     (Entity)tr.GetObject(
  150.                       objId,
  151.                       OpenMode.ForRead
  152.                     );
  153.                   Kochize(
  154.                     ent,
  155.                     modified,
  156.                     toErase,
  157.                     newerEntities,
  158.                     bLeft
  159.                   );
  160.                 }
  161.                 // Kochize all the non-db resident entities
  162.                 if (!lom.cancelled)
  163.                 {
  164.                   foreach (Entity ent in newEntities)
  165.                   {
  166.                     if (!lom.Tick())
  167.                     {
  168.                       ed.WriteMessage(
  169.                         "\nFractalization cancelled.\n"
  170.                       );
  171.                       break;
  172.                     }
  173.                     Kochize(
  174.                       ent,
  175.                       modified,
  176.                       toErase,
  177.                       newerEntities,
  178.                       bLeft
  179.                     );
  180.                   }
  181.                 }
  182.                 // We now longer need the intermediate entities
  183.                 // previously output for the level above,
  184.                 // we replace them with the latest output
  185.                 newEntities.Clear();
  186.                 newEntities = newerEntities;
  187.               }
  188.               lom.Tick();
  189.               if (!lom.cancelled)
  190.               {
  191.                 // Erase each of the replaced db-resident ents
  192.                 foreach (ObjectId objId in toErase)
  193.                 {
  194.                   Entity ent =
  195.                     (Entity)tr.GetObject(
  196.                       objId,
  197.                       OpenMode.ForWrite
  198.                     );
  199.                   ent.Erase();
  200.                 }
  201.                 // Add the new entities
  202.                 btr.UpgradeOpen();
  203.                 foreach (Entity ent in newEntities)
  204.                 {
  205.                   btr.AppendEntity(ent);
  206.                   tr.AddNewlyCreatedDBObject(ent, true);
  207.                 }
  208.               }
  209.               tr.Commit();
  210.             }
  211.           }
  212.         }
  213.       }
  214.     }
  215.     static long
  216.     operationCount(int nRecurse)
  217.     {
  218.       if (1 >= nRecurse)
  219.         return 1;
  220.       return
  221.         (long)Math.Pow(
  222.           newEntsPerOldEnt,
  223.           nRecurse - 1
  224.         )
  225.         + operationCount(nRecurse - 1);
  226.     }
  227.     // Dispatch function to call through to various per-type
  228.     // functions
  229.     private void Kochize(
  230.       Entity ent,
  231.       ObjectIdCollection modified,
  232.       ObjectIdCollection toErase,
  233.       List<Entity> toAdd,
  234.       bool bLeft
  235.     )
  236.     {
  237.       Line ln = ent as Line;
  238.       if (ln != null)
  239.       {
  240.         Kochize(ln, modified, toErase, toAdd, bLeft);
  241.         return;
  242.       }
  243.       Arc arc = ent as Arc;
  244.       if (arc != null)
  245.       {
  246.         Kochize(arc, modified, toErase, toAdd, bLeft);
  247.         return;
  248.       }
  249.       Polyline pl = ent as Polyline;
  250.       if (pl != null)
  251.       {
  252.         Kochize(pl, modified, toErase, toAdd, bLeft);
  253.         return;
  254.       }
  255.     }
  256.     // Create 4 new lines from a line passed in
  257.     private void Kochize(
  258.       Line ln,
  259.       ObjectIdCollection modified,
  260.       ObjectIdCollection toErase,
  261.       List<Entity> toAdd,
  262.       bool bLeft
  263.     )
  264.     {
  265.       // Get general info about the line
  266.       // and calculate the main 5 points
  267.       Point3d pt1 = ln.StartPoint,
  268.               pt5 = ln.EndPoint;
  269.       Vector3d vec1 = pt5 - pt1,
  270.                norm1 = vec1.GetNormal();
  271.       double d_3 = vec1.Length / 3;
  272.       Point3d pt2 = pt1 + (norm1 * d_3),
  273.               pt4 = pt1 + (2 * norm1 * d_3);
  274.       Vector3d vec2 = pt4 - pt2;
  275.       if (bLeft)
  276.         vec2 =
  277.           vec2.RotateBy(
  278.             Math.PI / 3, new Vector3d(0, 0, 1)
  279.           );
  280.       else
  281.         vec2 =
  282.           vec2.RotateBy(
  283.             5 * Math.PI / 3, new Vector3d(0, 0, 1)
  284.           );
  285.       Point3d pt3 = pt2 + vec2;
  286.       // Mark the original to be erased
  287.       if (ln.ObjectId != ObjectId.Null)
  288.         toErase.Add(ln.ObjectId);
  289.       // Create the first line
  290.       Line ln1 = new Line(pt1, pt2);
  291.       ln1.SetPropertiesFrom(ln);
  292.       ln1.Thickness = ln.Thickness;
  293.       toAdd.Add(ln1);
  294.       // Create the second line
  295.       Line ln2 = new Line(pt2, pt3);
  296.       ln2.SetPropertiesFrom(ln);
  297.       ln2.Thickness = ln.Thickness;
  298.       toAdd.Add(ln2);
  299.       // Create the third line
  300.       Line ln3 = new Line(pt3, pt4);
  301.       ln3.SetPropertiesFrom(ln);
  302.       ln3.Thickness = ln.Thickness;
  303.       toAdd.Add(ln3);
  304.       // Create the fourth line
  305.       Line ln4 = new Line(pt4, pt5);
  306.       ln4.SetPropertiesFrom(ln);
  307.       ln4.Thickness = ln.Thickness;
  308.       toAdd.Add(ln4);
  309.     }
  310.     // Create 4 new arcs from an arc passed in
  311.     private void Kochize(
  312.       Arc arc,
  313.       ObjectIdCollection modified,
  314.       ObjectIdCollection toErase,
  315.       List<Entity> toAdd,
  316.       bool bLeft
  317.     )
  318.     {
  319.       // Get general info about the arc
  320.       // and calculate the main 5 points
  321.       Point3d pt1 = arc.StartPoint,
  322.               pt5 = arc.EndPoint;
  323.       double length = arc.GetDistAtPoint(pt5),
  324.              angle = arc.StartAngle;
  325.       Vector3d full = pt5 - pt1;
  326.       Point3d pt2 = arc.GetPointAtDist(length / 3),
  327.               pt4 = arc.GetPointAtDist(2 * length / 3);
  328.       // Mark the original to be erased
  329.       if (arc.ObjectId != ObjectId.Null)
  330.         toErase.Add(arc.ObjectId);
  331.       // Create the first arc
  332.       Point3d mid = arc.GetPointAtDist(length / 6);
  333.       CircularArc3d tmpArc =
  334.         new CircularArc3d(pt1, mid, pt2);
  335.       Arc arc1 = circArc2Arc(tmpArc);
  336.       arc1.SetPropertiesFrom(arc);
  337.       arc1.Thickness = arc.Thickness;
  338.       toAdd.Add(arc1);
  339.       // Create the second arc
  340.       mid = arc.GetPointAtDist(length / 2);
  341.       tmpArc.Set(pt2, mid, pt4);
  342.       if (bLeft)
  343.         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt2);
  344.       else
  345.         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt2);
  346.       Arc arc2 = circArc2Arc(tmpArc);
  347.       arc2.SetPropertiesFrom(arc);
  348.       arc2.Thickness = arc.Thickness;
  349.       toAdd.Add(arc2);
  350.       // Create the third arc
  351.       tmpArc.Set(pt2, mid, pt4);
  352.       if (bLeft)
  353.         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
  354.       else
  355.         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
  356.       Arc arc3 = circArc2Arc(tmpArc);
  357.       arc3.SetPropertiesFrom(arc);
  358.       arc3.Thickness = arc.Thickness;
  359.       toAdd.Add(arc3);
  360.       // Create the fourth arc
  361.       mid = arc.GetPointAtDist(5 * length / 6);
  362.       Arc arc4 =
  363.         circArc2Arc(new CircularArc3d(pt4, mid, pt5));
  364.       arc4.SetPropertiesFrom(arc);
  365.       arc4.Thickness = arc.Thickness;
  366.       toAdd.Add(arc4);
  367.     }
  368.     Arc circArc2Arc(CircularArc3d circArc)
  369.     {
  370.       Point3d center = circArc.Center;
  371.       Vector3d normal = circArc.Normal;
  372.       Vector3d refVec = circArc.ReferenceVector;
  373.       Plane plane = new Plane(center, normal);
  374.       double ang = refVec.AngleOnPlane(plane);
  375.       return new Arc(
  376.         center,
  377.         normal,
  378.         circArc.Radius,
  379.         circArc.StartAngle + ang,
  380.         circArc.EndAngle + ang
  381.       );
  382.     }
  383.     private void Kochize(
  384.       Polyline pl,
  385.       ObjectIdCollection modified,
  386.       ObjectIdCollection toErase,
  387.       List<Entity> toAdd,
  388.       bool bLeft
  389.     )
  390.     {
  391.       pl.UpgradeOpen();
  392.       if (pl.ObjectId != ObjectId.Null &&
  393.           !modified.Contains(pl.ObjectId))
  394.       {
  395.         modified.Add(pl.ObjectId);
  396.       }
  397.       for(int vn = 0; vn < pl.NumberOfVertices; vn++)
  398.       {
  399.         SegmentType st = pl.GetSegmentType(vn);
  400.         if (st != SegmentType.Line && st != SegmentType.Arc)
  401.           continue;
  402.         double sw = pl.GetStartWidthAt(vn),
  403.                ew = pl.GetEndWidthAt(vn);
  404.         if (st == SegmentType.Line)
  405.         {
  406.           if (vn + 1 == pl.NumberOfVertices)
  407.             continue;
  408.           LineSegment2d ls = pl.GetLineSegment2dAt(vn);
  409.           Point2d pt1 = ls.StartPoint,
  410.                   pt5 = ls.EndPoint;
  411.           Vector2d vec = pt5 - pt1;
  412.           double d_3 = vec.Length / 3;
  413.           Point2d pt2 = pt1 + (vec.GetNormal() * d_3),
  414.                   pt4 = pt1 + (vec.GetNormal() * 2 * d_3);
  415.           Vector2d vec2 = pt4 - pt2;
  416.           if (bLeft)
  417.             vec2 = vec2.RotateBy(Math.PI / 3);
  418.           else
  419.             vec2 = vec2.RotateBy(5 * Math.PI / 3);
  420.           Point2d pt3 = pt2 + vec2;
  421.           pl.AddVertexAt(++vn, pt2, 0, sw, ew);
  422.           pl.AddVertexAt(++vn, pt3, 0, sw, ew);
  423.           pl.AddVertexAt(++vn, pt4, 0, sw, ew);
  424.         }
  425.         else if (st == SegmentType.Arc)
  426.         {
  427.           CircularArc3d ca = pl.GetArcSegmentAt(vn);
  428.           double oldBulge = pl.GetBulgeAt(vn);
  429.           // Build a standard arc and use that for the calcs
  430.           Arc arc = circArc2Arc(ca);
  431.           // Get the main 5 points
  432.           Point3d pt1 = arc.StartPoint,
  433.                   pt5 = arc.EndPoint;
  434.          
  435.           double ln = arc.GetDistAtPoint(pt5);
  436.           Point3d pt2 = arc.GetPointAtDist(ln / 3),
  437.                   pt4 = arc.GetPointAtDist(2 * ln / 3);
  438.           Point3d mid = arc.GetPointAtDist(ln / 2);
  439.           CircularArc3d tmpArc =
  440.             new CircularArc3d(pt2, mid, pt4);
  441.           if (bLeft)
  442.             tmpArc.RotateBy(5*Math.PI/3, tmpArc.Normal, pt4);
  443.           else
  444.             tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
  445.           Point3d pt3 = tmpArc.StartPoint;
  446.           // Now add the new segments, setting the bulge
  447.           // for the existing one and the new ones to a third
  448.           // (as the segs are a third as big as the old one)
  449.           CoordinateSystem3d ecs = pl.Ecs.CoordinateSystem3d;
  450.           Plane pn = new Plane(ecs.Origin, pl.Normal);
  451.           double bu = oldBulge / 3;
  452.           pl.SetBulgeAt(vn, bu);
  453.           pl.AddVertexAt(
  454.             ++vn, pt2.Convert2d(pn), bu, sw, ew);
  455.           pl.AddVertexAt(
  456.             ++vn, pt3.Convert2d(pn), bu, sw, ew);
  457.           pl.AddVertexAt(
  458.             ++vn, pt4.Convert2d(pn), bu, sw, ew);
  459.         }
  460.       }
  461.       pl.DowngradeOpen();
  462.     }
  463.     public class LongOperationManager :
  464.       IDisposable, System.Windows.Forms.IMessageFilter
  465.     {
  466.       // The message code corresponding to a keypress
  467.       const int WM_KEYDOWN = 0x0100;
  468.       // The number of times to update the progress meter
  469.       // (for some reason you need 600 to tick through
  470.       //  for each percent)
  471.       const int progressMeterIncrements = 600;
  472.       // Internal members for metering progress
  473.       private ProgressMeter pm;
  474.       private long updateIncrement;
  475.       private long currentInc;
  476.       // External flag for checking cancelled status
  477.       public bool cancelled = false;
  478.       // Constructor
  479.       public LongOperationManager(string message)
  480.       {
  481.         System.Windows.Forms.Application.
  482.           AddMessageFilter(this);
  483.         pm = new ProgressMeter();
  484.         pm.Start(message);
  485.         pm.SetLimit(progressMeterIncrements);
  486.         currentInc = 0;
  487.       }
  488.       
  489.       // System.IDisposable.Dispose
  490.       public void Dispose()
  491.       {
  492.         pm.Stop();
  493.         pm.Dispose();
  494.         System.Windows.Forms.Application.
  495.           RemoveMessageFilter(this);
  496.       }
  497.       // Set the total number of operations
  498.       public void SetTotalOperations(long totalOps)
  499.       {
  500.         // We really just care about when we need
  501.         // to update the timer
  502.         updateIncrement =
  503.           (totalOps > progressMeterIncrements ?
  504.             totalOps / progressMeterIncrements :
  505.             totalOps
  506.           );
  507.       }
  508.       // This function is called whenever an operation
  509.       // is performed
  510.       public bool Tick()
  511.       {
  512.         if (++currentInc == updateIncrement)
  513.         {
  514.           pm.MeterProgress();
  515.           currentInc = 0;
  516.           System.Windows.Forms.Application.DoEvents();
  517.         }
  518.         // Check whether the filter has set the flag
  519.         if (cancelled)
  520.           pm.Stop();
  521.         return !cancelled;
  522.       }
  523.       // The message filter callback
  524.       public bool PreFilterMessage(
  525.         ref System.Windows.Forms.Message m
  526.       )
  527.       {
  528.         if (m.Msg == WM_KEYDOWN)
  529.         {
  530.           // Check for the Escape keypress
  531.           System.Windows.Forms.Keys kc =
  532.             (System.Windows.Forms.Keys)(int)m.WParam &
  533.             System.Windows.Forms.Keys.KeyCode;
  534.           if (m.Msg == WM_KEYDOWN &&
  535.               kc == System.Windows.Forms.Keys.Escape)
  536.           {
  537.             cancelled = true;
  538.           }
  539.           // Return true to filter all keypresses
  540.           return true;
  541.         }
  542.         // Return false to let other messages through
  543.         return false;
  544.       }
  545.     }
  546.   }
  547. }

 楼主| 发表于 2009-7-9 13:49:00 | 显示全部楼层
三、
June 18, 2008
A simple turtle graphics implementation in AutoCAD using .NET
Like many thirty-something Brits (and possible non-Brits, for all I know) my earliest introduction to the world of graphics programming was via a language called  Logo running on the  BBC Microcomputer.
This machine and its educational software were commonplace in UK schools in the 1980s, and I have clear memories of staying late at primary school (which I suppose means I was somewhere between 8 and 10) to fool around with  BBC BASIC. With a friend I used to try to write text-based adventures ("you are in a cave, there are exits North, South, East and West" - you know the kind of thing) which we padded with loads and loads of comments to stop them from loading embarrassingly quickly from tape. Oh, the memories. :-)
Anyway, I digress. Logo provided a really great introduction to vector graphics: even as we were being taught trigonometry we were exposed to an environment that allowed us to translate these concepts into graphics on a computer screen. Too cool.
Thinking back about all this, I decided to implement a simple  turtle graphics engine inside AutoCAD, to allow eventual integration of a Logo-like language into it. The idea was this: to implement an engine exposing a series of methods (Move(), Turn(), PenUp(), PenDown(), SetPenColor() and SetPenWidth(), to start with) that could be used to implement a subset of the Logo language. I decided to write the engine in C#, whose object-orientation makes it well-suited to this type of task, and then look into using F# for implementing the language to drive it in a future post.
F# is very good for implementing new or existing programming languages: the whole area of  Domain Specific Languages (DSLs) is a major selling point for functional languages, generally. They're very well-suited to the tasks that make up the interpretation/compilation process:  lexical analysis,  syntactic analysis, etc., as much of the hard work is around tokenizing strings, processing lists and mapping behaviours (and these activities are most functional programming languages' "bread and butter").
Only after deciding this approach did I stumble across an existing Logo implementation in F#, so it appears that integrating the Logo language may prove simpler than I expected.
A little more on the implementation of the turtle graphics engine: I decided to implement a 2D system, to start with, generating geometry as a series of Polyline objects. Every time the PenUp() function is called we tell the system to create a new object. We also do this for SetPenColor() in the cases where the colour is changed, as Polylines do not support per-segment colours (if we were to implement a custom object that did so, we could change the implementation to keep contiguous, multi-coloured segments in a single object). The current implementation reopens the previous object each time, and therefore creates a new transaction for every single Move() operation. This is clearly sub-optimal, but I quite like the old-school effect of watching the graphics get revealed segment by segment. :-) A more optimal technique would be to keep the object open while the pen is down and the engine is in use, and this is currently left as an exercise for the reader (or until I hit a use case where I can no longer be bothered to wait for the execution to complete :-).
It would be quite simple to take this engine into 3D, and I may well do so at some point in the future. There seem to be a number of successful 3D Logo implementations out there, and they really have great potential when generating organic models, as you can see from this page. Languages such as Logo can be applied very effectively to the task of generating fractal geometry by interpreting (and implementing)  Lindenmayer (or L-) systems, for instance.
OK, enough background information... here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine
  46.   {
  47.     // Private members
  48.     private ObjectId m_currentObject;
  49.     private Pen m_pen;
  50.     private Point3d m_position;
  51.     private Vector3d m_direction;
  52.     // Public properties
  53.     public Point3d Position
  54.     {
  55.       get { return m_position; }
  56.       set { m_position = value; }
  57.     }
  58.     public Vector3d Direction
  59.     {
  60.       get { return m_direction; }
  61.       set { m_direction = value; }
  62.     }
  63.     // Constructor
  64.     public TurtleEngine()
  65.     {
  66.       m_pen = new Pen();
  67.       m_currentObject = ObjectId.Null;
  68.       m_position = Point3d.Origin;
  69.       m_direction = new Vector3d(1.0, 0.0, 0.0);
  70.     }
  71.     // Public methods
  72.     public void Turn(double angle)
  73.     {
  74.       // Rotate our direction by the
  75.       // specified angle
  76.       Matrix3d mat =
  77.         Matrix3d.Rotation(
  78.           angle,
  79.           Vector3d.ZAxis,
  80.           Position
  81.         );
  82.       Direction =
  83.         Direction.TransformBy(mat);
  84.     }
  85.     public void Move(double distance)
  86.     {
  87.       // Move the cursor by a specified
  88.       // distance in the direction in
  89.       // which we're pointing
  90.       Point3d oldPos = Position;
  91.       Position += Direction * distance;
  92.       // If the pen is down, we draw something
  93.       if (m_pen.Down)
  94.         GenerateSegment(oldPos, Position);
  95.     }
  96.     public void PenDown()
  97.     {
  98.       m_pen.Down = true;
  99.     }
  100.     public void PenUp()
  101.     {
  102.       m_pen.Down = false;
  103.       // We'll start a new entity with the next
  104.       // use of the pen
  105.       m_currentObject =
  106.         ObjectId.Null;
  107.     }
  108.     public void SetPenWidth(double width)
  109.     {
  110.       m_pen.Width = width;
  111.     }
  112.     public void SetPenColor(int idx)
  113.     {
  114.       // Right now we just use an ACI,
  115.       // to make the code simpler
  116.       Color col =
  117.         Color.FromColorIndex(
  118.           ColorMethod.ByAci,
  119.           (short)idx
  120.         );
  121.       // If we have to change the color,
  122.       // we'll start a new entity
  123.       // (if the entity type we're creating
  124.       // supports per-segment colors, we
  125.       // don't need to do this)
  126.       if (col != m_pen.Color)
  127.       {
  128.         m_currentObject =
  129.           ObjectId.Null;
  130.         m_pen.Color = col;
  131.       }
  132.     }
  133.     // Internal helper to generate geometry
  134.     // (this could be optimised to keep the
  135.     // object we're generating open, rather
  136.     // than having to reopen it each time)
  137.     private void GenerateSegment(
  138.       Point3d oldPos, Point3d newPos)
  139.     {
  140.       Document doc =
  141.         Application.DocumentManager.MdiActiveDocument;
  142.       Database db = doc.Database;
  143.       Editor ed = doc.Editor;
  144.       Autodesk.AutoCAD.ApplicationServices.
  145.       TransactionManager tm =
  146.         doc.TransactionManager;
  147.       Transaction tr =
  148.         tm.StartTransaction();
  149.       using (tr)
  150.       {
  151.         Polyline pl;
  152.         Plane plane;
  153.         // Create the current object, if there is none
  154.         if (m_currentObject == ObjectId.Null)
  155.         {
  156.           BlockTable bt =
  157.             (BlockTable)tr.GetObject(
  158.               db.BlockTableId,
  159.               OpenMode.ForRead
  160.             );
  161.           BlockTableRecord ms =
  162.             (BlockTableRecord)tr.GetObject(
  163.               bt[BlockTableRecord.ModelSpace],
  164.               OpenMode.ForWrite
  165.             );
  166.           // Create the polyline
  167.           pl = new Polyline();
  168.           pl.Color = m_pen.Color;
  169.           // Define its plane
  170.           plane = new Plane(
  171.             pl.Ecs.CoordinateSystem3d.Origin,
  172.             pl.Ecs.CoordinateSystem3d.Zaxis
  173.           );
  174.           // Add the first vertex
  175.           pl.AddVertexAt(
  176.             0, oldPos.Convert2d(plane),
  177.             0.0, m_pen.Width, m_pen.Width
  178.           );
  179.           // Add the polyline to the database
  180.           m_currentObject =
  181.             ms.AppendEntity(pl);
  182.           tr.AddNewlyCreatedDBObject(pl, true);
  183.         }
  184.         else
  185.         {
  186.           // Get the current object, if there is one
  187.           pl =
  188.             (Polyline)tr.GetObject(
  189.               m_currentObject,
  190.               OpenMode.ForWrite
  191.             );
  192.           // Calculate its plane
  193.           plane = new Plane(
  194.             pl.Ecs.CoordinateSystem3d.Origin,
  195.             pl.Ecs.CoordinateSystem3d.Zaxis
  196.           );
  197.         }
  198.         // Now we have our current object open,
  199.         // add the new vertex
  200.         pl.AddVertexAt(
  201.           pl.NumberOfVertices,
  202.           newPos.Convert2d(plane),
  203.           0.0, m_pen.Width, m_pen.Width
  204.         );
  205.         // Display the graphics, to avoid long,
  206.         // black-box operations
  207.         tm.QueueForGraphicsFlush();
  208.         tm.FlushGraphics();
  209.         ed.UpdateScreen();
  210.         tr.Commit();
  211.       }
  212.     }
  213.   }
  214.   public class Commands
  215.   {
  216.     // A command to create some simple geometry
  217.     [CommandMethod("DTG")]
  218.     static public void DrawTurtleGraphics()
  219.     {
  220.       TurtleEngine te = new TurtleEngine();
  221.       // Draw a red circle
  222.       te.SetPenColor(1);
  223.       te.SetPenWidth(7);
  224.       te.PenDown();
  225.       for (int i = 0; i < 360; i++)
  226.       {
  227.         te.Move(2);
  228.         te.Turn(Math.PI / 180);
  229.       }
  230.       // Move to the next space
  231.       te.PenUp();
  232.       te.Move(200);
  233.       // Draw a green square
  234.       te.SetPenColor(3);
  235.       te.SetPenWidth(5);
  236.       te.PenDown();
  237.       for (int i = 0; i < 4; i++)
  238.       {
  239.         te.Move(230);
  240.         te.Turn(Math.PI / 2);
  241.       }
  242.       // Move to the next space
  243.       te.PenUp();
  244.       te.Move(300);
  245.       // Draw a blue triangle
  246.       te.SetPenColor(5);
  247.       te.SetPenWidth(3);
  248.       te.PenDown();
  249.       for (int i = 0; i < 3; i++)
  250.       {
  251.         te.Move(266);
  252.         te.Turn(2 * Math.PI / 3);
  253.       }
  254.       // Move to the next space
  255.       te.PenUp();
  256.       te.Move(400);
  257.       te.Turn(Math.PI / 2);
  258.       te.Move(115);
  259.       te.Turn(Math.PI / -2);
  260.       // Draw a multi-colored, spirograph-like shape
  261.       te.SetPenWidth(1);
  262.       te.PenDown();
  263.       for (int i = 0; i < 36; i++)
  264.       {
  265.         te.Turn(Math.PI / 18);
  266.         te.SetPenColor(i);
  267.         for (int j = 0; j < 360; j++)
  268.         {
  269.           te.Move(1);
  270.           te.Turn(Math.PI / 180);
  271.         }
  272.       }
  273.     }
  274.   }
  275. }
Here are the results of running the DTG command, which simply calls into the TurtleEngine to test out its capabilities, creating a series of shapes of different pen colours and widths:

The first three objects are single objects, but are multi-segment polylines (don't expect the engine to generate circles, for instance: most turtle graphics code to generate circles actually create objects with 360 segments). The fourth object is a series of 36 circles: as mentioned earlier, Polyline objects do not support per-segment colours, but if this was all a uniform colour (by commenting out the call to te.SetPenColor(i)), it would all be a single Polyline with 36 * 360 segments.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-9 13:53:00 | 显示全部楼层
四、Turtle 分形
June 23, 2008
Turtle fractals in AutoCAD using .NET - Part 1
This topic started in this recent post, where I introduced a simple turtle graphics engine for AutoCAD implemented in C#. My eventual aim is still to implement a subset of the Logo programming language in F#, calling through to this C# engine, but for now I've been side-tracked: I'm simply having too much fun with the engine itself and exploring the possibilities of turtle graphics inside AutoCAD - especially around the use of recursive algorithms to generate fractals.
This post and the next (and maybe more, depending on how much fun I continue to have) will be devoted to recursive algorithms for fractal generation with a cursor-based, turtle graphics system: in this post we'll start with performance-related improvements to the engine alluded to in the last post, and move on to code to generate precise fractals. The next post will take this further by looking at adding randomness to fractals, allowing us to create more organic forms.
So let's start with the engine enhancements: I mentioned last time that the previous implementation was sub-optimal, as it used a new transaction for each segment created. I did some benchmarking, to see what improvement gains were to be had by changing the implementation, and - interestingly - there was very little change when I modified the engine to maintain a single transaction when testing with the DTG command in the previous post. It turns out that the bottleneck for that particular scenario was the graphics update: refreshing the graphics after each segment drawn is extremely time-consuming, so the overhead of using separate transactions per segment didn't really impact performance. As we move on to much larger models - as we will in this post - it makes sense to perform two optimizations to the engine:
Turn off display of graphics during update
We will not flush any graphics and simply let AutoCAD take care of updating the display when we're done
A single transaction per execution
We will maintain references to a Transaction and a Polyline within our engine, instead of an ObjectId
Implementing this is very simple, as you can see below, and the client code only really needs to make one change: it needs to start its own transaction and pass that into the constructor of the TurtleEngine. This will be used, as needed, and must, of course, be committed and disposed of by the client at the end (we'll use the using statement to take care of the disposal).
Once the graphics flushing has been disabled (again, with a trivial change: I've added a Boolean variable - with a default value of false - to check before updating the graphics), we really start to see the relevance of maintaining a separate transaction, as our previous performance bottleneck has been removed. More on this later.
Onto the fractal algorithm we're implementing today, to put the engine through its paces: some of you may remember this previous pair of posts, where we looked at an implementation for generating Koch curves inside AutoCAD. In this case we're going to generate something called the  Koch snowflake, which is basically a triangle with each segment divided a series of times:

The formula for calculating the number of sides is: 3 * 4^l, where l is the recursion level (at least from our perspective, a triangle is level 0, while the right-most shape above is level 5). I have kept two implementations of the engine in my project, for comparison: when using the old implementation, a level 7 Koch snowflake - which for us means a Polyline of 49,152 segments - takes 5 minutes to generate. With the new approach of maintaining a single transaction, this reduces to under 2 seconds. Which is clearly a significant change. [Note: neither of these implementations update graphics until the end - if we were to update the screen for each of the ~50K segments, we'd be waiting for hours, I suspect.]
A level 8 snowflake took just 7 seconds for my system to generate with the new version of the engine. I limited the level selection to 8, not because it would take too long to generate, but because unless you disable rollover highlighting AutoCAD is likely to grind to a halt for a number of seconds when you hover over an object of this complexity: a level 8 snowflake does have 196,608 segments, after all.
Here's the C# code with the modified engine and the algorithm for creating Koch snowflakes:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine
  46.   {
  47.     // Private members
  48.     private Transaction m_trans;
  49.     private Polyline m_poly;
  50.     private Pen m_pen;
  51.     private Point3d m_position;
  52.     private Vector3d m_direction;
  53.     private bool m_updateGraphics;
  54.     // Public properties
  55.     public Point3d Position
  56.     {
  57.       get { return m_position; }
  58.       set { m_position = value; }
  59.     }
  60.     public Vector3d Direction
  61.     {
  62.       get { return m_direction; }
  63.       set { m_direction = value; }
  64.     }
  65.     // Constructor
  66.     public TurtleEngine(Transaction tr)
  67.     {
  68.       m_pen = new Pen();
  69.       m_trans = tr;
  70.       m_poly = null;
  71.       m_position = Point3d.Origin;
  72.       m_direction = new Vector3d(1.0, 0.0, 0.0);
  73.       m_updateGraphics = false;
  74.     }
  75.     // Public methods
  76.     public void Turn(double angle)
  77.     {
  78.       // Rotate our direction by the
  79.       // specified angle
  80.       Matrix3d mat =
  81.         Matrix3d.Rotation(
  82.           angle,
  83.           Vector3d.ZAxis,
  84.           Position
  85.         );
  86.       Direction =
  87.         Direction.TransformBy(mat);
  88.     }
  89.     public void Move(double distance)
  90.     {
  91.       // Move the cursor by a specified
  92.       // distance in the direction in
  93.       // which we're pointing
  94.       Point3d oldPos = Position;
  95.       Position += Direction * distance;
  96.       // If the pen is down, we draw something
  97.       if (m_pen.Down)
  98.         GenerateSegment(oldPos, Position);
  99.     }
  100.     public void PenDown()
  101.     {
  102.       m_pen.Down = true;
  103.     }
  104.     public void PenUp()
  105.     {
  106.       m_pen.Down = false;
  107.       // We'll start a new entity with the
  108.       // next use of the pen
  109.       m_poly = null;
  110.     }
  111.     public void SetPenWidth(double width)
  112.     {
  113.       m_pen.Width = width;
  114.     }
  115.     public void SetPenColor(int idx)
  116.     {
  117.       // Right now we just use an ACI,
  118.       // to make the code simpler
  119.       Color col =
  120.         Color.FromColorIndex(
  121.           ColorMethod.ByAci,
  122.           (short)idx
  123.         );
  124.       // If we have to change the color,
  125.       // we'll start a new entity
  126.       // (if the entity type we're creating
  127.       // supports per-segment colors, we
  128.       // don't need to do this)
  129.       if (col != m_pen.Color)
  130.       {
  131.         m_poly = null;
  132.         m_pen.Color = col;
  133.       }
  134.     }
  135.     // Internal helper to generate geometry
  136.     // (this could be optimised to keep the
  137.     // object we're generating open, rather
  138.     // than having to reopen it each time)
  139.     private void GenerateSegment(
  140.       Point3d oldPos, Point3d newPos)
  141.     {
  142.       Document doc =
  143.         Application.DocumentManager.MdiActiveDocument;
  144.       Database db = doc.Database;
  145.       Editor ed = doc.Editor;
  146.       Autodesk.AutoCAD.ApplicationServices.
  147.       TransactionManager tm =
  148.         doc.TransactionManager;
  149.       Plane plane;
  150.       // Create the current object, if there is none
  151.       if (m_poly == null)
  152.       {
  153.         BlockTable bt =
  154.           (BlockTable)m_trans.GetObject(
  155.             db.BlockTableId,
  156.             OpenMode.ForRead
  157.           );
  158.         BlockTableRecord ms =
  159.           (BlockTableRecord)m_trans.GetObject(
  160.             bt[BlockTableRecord.ModelSpace],
  161.             OpenMode.ForWrite
  162.           );
  163.         // Create the polyline
  164.         m_poly = new Polyline();
  165.         m_poly.Color = m_pen.Color;
  166.         // Define its plane
  167.         plane = new Plane(
  168.           m_poly.Ecs.CoordinateSystem3d.Origin,
  169.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  170.         );
  171.         // Add the first vertex
  172.         m_poly.AddVertexAt(
  173.           0, oldPos.Convert2d(plane),
  174.           0.0, m_pen.Width, m_pen.Width
  175.         );
  176.         // Add the polyline to the database
  177.         ms.AppendEntity(m_poly);
  178.         m_trans.AddNewlyCreatedDBObject(m_poly, true);
  179.       }
  180.       else
  181.       {
  182.         // Calculate its plane
  183.         plane = new Plane(
  184.           m_poly.Ecs.CoordinateSystem3d.Origin,
  185.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  186.         );
  187.       }
  188.       // Now we have our current object open,
  189.       // add the new vertex
  190.       m_poly.AddVertexAt(
  191.         m_poly.NumberOfVertices,
  192.         newPos.Convert2d(plane),
  193.         0.0, m_pen.Width, m_pen.Width
  194.       );
  195.       // Display the graphics, to avoid long,
  196.       // black-box operations
  197.       if (m_updateGraphics)
  198.       {
  199.         tm.QueueForGraphicsFlush();
  200.         tm.FlushGraphics();
  201.         ed.UpdateScreen();
  202.       }
  203.     }
  204.   }
  205.   public class Commands
  206.   {
  207.     static void KochIsland(
  208.       TurtleEngine te,
  209.       double size,
  210.       int level
  211.     )
  212.     {
  213.       for (int i = 0; i < 3; i++)
  214.       {
  215.         // Draw a side
  216.         KSide(te, size, level);
  217.         // Turn 120 degrees to the left
  218.         te.Turn(2 * Math.PI / 3);
  219.       }
  220.     }
  221.     static void KSide(
  222.       TurtleEngine te,
  223.       double size,
  224.       int level
  225.     )
  226.     {
  227.       // When at level 0, draw the side and exit
  228.       if (level == 0)
  229.         te.Move(size);
  230.       else
  231.       {
  232.         // Else recurse for each segment of the side
  233.         KSide(te, size / 3, level - 1);
  234.         te.Turn(Math.PI / -3);
  235.         KSide(te, size / 3, level - 1);
  236.         te.Turn(2 * Math.PI / 3);
  237.         KSide(te, size / 3, level - 1);
  238.         te.Turn(Math.PI / -3);
  239.         KSide(te, size / 3, level - 1);
  240.       }
  241.     }
  242.     [CommandMethod("KSF")]
  243.     static public void KochSnowFlake()
  244.     {
  245.       Document doc =
  246.         Application.DocumentManager.MdiActiveDocument;
  247.       Editor ed = doc.Editor;
  248.       // Ask for the recursion level
  249.       int level = 6;
  250.       PromptIntegerOptions pio =
  251.         new PromptIntegerOptions(
  252.           "\nEnter recursion level <6>: "
  253.         );
  254.       pio.AllowNone = true;
  255.       pio.LowerLimit = 0;
  256.       pio.UpperLimit = 8;
  257.       PromptIntegerResult pir =
  258.         ed.GetInteger(pio);
  259.       if (pir.Status != PromptStatus.OK &&
  260.           pir.Status != PromptStatus.None)
  261.         return;
  262.       if (pir.Status == PromptStatus.OK)
  263.         level = pir.Value;
  264.       Transaction tr =
  265.         doc.TransactionManager.StartTransaction();
  266.       using (tr)
  267.       {
  268.         TurtleEngine te = new TurtleEngine(tr);
  269.         // Draw a Koch snowflake
  270.         te.SetPenColor(0);
  271.         te.SetPenWidth(0);
  272.         te.PenDown();
  273.         // 100 is an arbitrary value:
  274.         // you could also prompt the user for this
  275.         KochIsland(te, 100, level);
  276.         tr.Commit();
  277.       }
  278.     }
  279.   }
  280. }
Here are the results of the KSF command, when used to generate a level 6 snowflake. Although frankly you wouldn't see a difference between a level 5 and a level 10 snowflake at this resolution. :-)

Next time we'll look at some recursive code to generate less precise, more natural forms.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-10 16:32:00 | 显示全部楼层
June 25, 2008
Turtle fractals in AutoCAD using .NET - Part 2
This series start with this initial post, where we looked at an implementation of a simple turtle graphics engine inside AutoCAD, and followed on with this previous post, where we refined the engine and looked at how we could use it to generate complex fractals with relatively little code.
In this post we take a further look at fractal generation using the turtle graphic engine, with the particular focus on introducing randomness to generate more realistic, organic forms. On a side note, fractals and the use of randomness in design are two of my favourite topics, so this post is hitting a sweet spot, for me. :-)
So where to start when generating organic forms? The simplest, "classic" example, in my view, is to generate trees. Trees lend themselves to automatic generation, as - in 2D, at least - they are a sequence of simple 2-way forks (at least the way I draw them, they are :-).
I picked up some simple Logo code from this site (yes, it does indeed say "fractals for children" in the title :-)
  1. if :distance < 5 [stop]
  2. forward :distance
  3. right 30
  4. tree :distance-10
  5. left 60
  6. tree :distance-10
  7. right 30
  8. back :distance
复制代码
This is easy to turn into C# code harnessing our TurtleEngine, with the addition of a proportional trunk/branch width (we take the width as a tenth of the length). See the Tree() function in the code listing below. The results of this procedure (which you call via the FT command) are interesting enough, if a little perfect:

You will notice that the tree is created as a single Polyline, which is a result of us back-tracking over previous segments with the pen down, rather than up. You can see this from this image showing the tree selected:

The FT command will create the same results every single time (assuming you specify the same tree length), which may or may not be what you're after.
So let's go and add some randomness, to make life a little more interesting. This modified function in the below code, named RandomTree(), generates a separate random factor to apply to the trunk/branch length (and therefore the width, as this is proportional to the length), and to the angle of each of the two branches sprouting from the current trunk or branch. The "variability" is specified by the user for all our random factors, but we could go further and tweak it for the length and for each of the angles.
Here's the C# code, including command definitions and the TurtleEngine we refined in the last post:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine
  46.   {
  47.     // Private members
  48.     private Transaction m_trans;
  49.     private Polyline m_poly;
  50.     private Pen m_pen;
  51.     private Point3d m_position;
  52.     private Vector3d m_direction;
  53.     private bool m_updateGraphics;
  54.     // Public properties
  55.     public Point3d Position
  56.     {
  57.       get { return m_position; }
  58.       set { m_position = value; }
  59.     }
  60.     public Vector3d Direction
  61.     {
  62.       get { return m_direction; }
  63.       set { m_direction = value; }
  64.     }
  65.     // Constructor
  66.     public TurtleEngine(Transaction tr)
  67.     {
  68.       m_pen = new Pen();
  69.       m_trans = tr;
  70.       m_poly = null;
  71.       m_position = Point3d.Origin;
  72.       m_direction = new Vector3d(1.0, 0.0, 0.0);
  73.       m_updateGraphics = false;
  74.     }
  75.     // Public methods
  76.     public void Turn(double angle)
  77.     {
  78.       // Rotate our direction by the
  79.       // specified angle
  80.       Matrix3d mat =
  81.         Matrix3d.Rotation(
  82.           angle,
  83.           Vector3d.ZAxis,
  84.           Position
  85.         );
  86.       Direction =
  87.         Direction.TransformBy(mat);
  88.     }
  89.     public void Move(double distance)
  90.     {
  91.       // Move the cursor by a specified
  92.       // distance in the direction in
  93.       // which we're pointing
  94.       Point3d oldPos = Position;
  95.       Position += Direction * distance;
  96.       // If the pen is down, we draw something
  97.       if (m_pen.Down)
  98.         GenerateSegment(oldPos, Position);
  99.     }
  100.     public void PenDown()
  101.     {
  102.       m_pen.Down = true;
  103.     }
  104.     public void PenUp()
  105.     {
  106.       m_pen.Down = false;
  107.       // We'll start a new entity with the next
  108.       // use of the pen
  109.       m_poly = null;
  110.     }
  111.     public void SetPenWidth(double width)
  112.     {
  113.       m_pen.Width = width;
  114.     }
  115.     public void SetPenColor(int idx)
  116.     {
  117.       // Right now we just use an ACI,
  118.       // to make the code simpler
  119.       Color col =
  120.         Color.FromColorIndex(
  121.           ColorMethod.ByAci,
  122.           (short)idx
  123.         );
  124.       // If we have to change the color,
  125.       // we'll start a new entity
  126.       // (if the entity type we're creating
  127.       // supports per-segment colors, we
  128.       // don't need to do this)
  129.       if (col != m_pen.Color)
  130.       {
  131.         m_poly = null;
  132.         m_pen.Color = col;
  133.       }
  134.     }
  135.     // Internal helper to generate geometry
  136.     // (this could be optimised to keep the
  137.     // object we're generating open, rather
  138.     // than having to reopen it each time)
  139.     private void GenerateSegment(
  140.       Point3d oldPos, Point3d newPos)
  141.     {
  142.       Document doc =
  143.         Application.DocumentManager.MdiActiveDocument;
  144.       Database db = doc.Database;
  145.       Editor ed = doc.Editor;
  146.       Autodesk.AutoCAD.ApplicationServices.
  147.       TransactionManager tm =
  148.         doc.TransactionManager;
  149.       Plane plane;
  150.       // Create the current object, if there is none
  151.       if (m_poly == null)
  152.       {
  153.         BlockTable bt =
  154.           (BlockTable)m_trans.GetObject(
  155.             db.BlockTableId,
  156.             OpenMode.ForRead
  157.           );
  158.         BlockTableRecord ms =
  159.           (BlockTableRecord)m_trans.GetObject(
  160.             bt[BlockTableRecord.ModelSpace],
  161.             OpenMode.ForWrite
  162.           );
  163.         // Create the polyline
  164.         m_poly = new Polyline();
  165.         m_poly.Color = m_pen.Color;
  166.         // Define its plane
  167.         plane = new Plane(
  168.           m_poly.Ecs.CoordinateSystem3d.Origin,
  169.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  170.         );
  171.         // Add the first vertex
  172.         m_poly.AddVertexAt(
  173.           0, oldPos.Convert2d(plane),
  174.           0.0, m_pen.Width, m_pen.Width
  175.         );
  176.         // Add the polyline to the database
  177.         ms.AppendEntity(m_poly);
  178.         m_trans.AddNewlyCreatedDBObject(m_poly, true);
  179.       }
  180.       else
  181.       {
  182.         // Calculate its plane
  183.         plane = new Plane(
  184.           m_poly.Ecs.CoordinateSystem3d.Origin,
  185.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  186.         );
  187.       }
  188.       // Make sure the previous vertex has its
  189.       // width set appropriately
  190.       if (m_pen.Width > 0.0)
  191.       {
  192.         m_poly.SetStartWidthAt(
  193.           m_poly.NumberOfVertices - 1,
  194.           m_pen.Width
  195.         );
  196.         m_poly.SetEndWidthAt(
  197.           m_poly.NumberOfVertices - 1,
  198.           m_pen.Width
  199.         );
  200.       }
  201.       // Add the new vertex
  202.       m_poly.AddVertexAt(
  203.         m_poly.NumberOfVertices,
  204.         newPos.Convert2d(plane),
  205.         0.0, m_pen.Width, m_pen.Width
  206.       );
  207.       // Display the graphics, to avoid long,
  208.       // black-box operations
  209.       if (m_updateGraphics)
  210.       {
  211.         tm.QueueForGraphicsFlush();
  212.         tm.FlushGraphics();
  213.         ed.UpdateScreen();
  214.       }
  215.     }
  216.   }
  217.   public class Commands
  218.   {
  219.     static public bool GetTreeInfo(
  220.       out Point3d position,
  221.       out double treeLength
  222.     )
  223.     {
  224.       Document doc =
  225.         Application.DocumentManager.MdiActiveDocument;
  226.       Editor ed = doc.Editor;
  227.       treeLength = 0;
  228.       position = Point3d.Origin;
  229.       PromptPointOptions ppo =
  230.         new PromptPointOptions(
  231.           "\nSelect base point of tree: "
  232.         );
  233.       PromptPointResult ppr =
  234.         ed.GetPoint(ppo);
  235.       if (ppr.Status != PromptStatus.OK)
  236.         return false;
  237.       position = ppr.Value;
  238.       PromptDoubleOptions pdo =
  239.         new PromptDoubleOptions(
  240.           "\nEnter tree length <70>: "
  241.         );
  242.       pdo.AllowNone = true;
  243.       PromptDoubleResult pdr =
  244.         ed.GetDouble(pdo);
  245.       if (pdr.Status != PromptStatus.None &&
  246.           pdr.Status != PromptStatus.OK)
  247.         return false;
  248.       if (pdr.Status == PromptStatus.OK)
  249.         treeLength = pdr.Value;
  250.       else
  251.         treeLength = 70;
  252.       return true;
  253.     }
  254.     static void Tree(
  255.       TurtleEngine te,
  256.       double distance
  257.     )
  258.     {
  259.       if (distance < 5.0)
  260.         return;
  261.       // Width of the trunk/branch is a tenth of
  262.       // of the length
  263.       te.SetPenWidth(distance / 10);
  264.       // Draw the main trunk/branch
  265.       te.Move(distance);
  266.       // Draw the left-hand sub-tree
  267.       te.Turn(Math.PI / 6);
  268.       Tree(te, distance - 10);
  269.       // Draw the right-hand sub-tree
  270.       te.Turn(Math.PI / -3);
  271.       Tree(te, distance - 10);
  272.       // Turn back to the original angle
  273.       te.Turn(Math.PI / 6);
  274.       // Draw back down to the start of this sub-
  275.       // tree, with the same thickness, as this
  276.       // may have changed in deeper sub-trees
  277.       te.SetPenWidth(distance / 10);
  278.       te.Move(-distance);
  279.     }
  280.     static void RandomTree(
  281.       TurtleEngine te,
  282.       double distance,
  283.       int variability
  284.     )
  285.     {
  286.       if (distance < 5.0)
  287.         return;
  288.       // Generate 3 random factors, each on the same basis:
  289.       //  a base amount = 100 - half the variability
  290.       //  + a random amount from 0 to the variability
  291.       // So a variability of 20 results in 90 to 110 (0.9-1.1)
  292.       Random rnd = new Random();
  293.       int basic = 100 - (variability / 2);
  294.       int num = rnd.Next(variability);
  295.       double factor = (basic + num) / 100.0;
  296.       num = rnd.Next(variability);
  297.       double factor1 = (basic + num) / 100.0;
  298.       num = rnd.Next(variability);
  299.       double factor2 = (basic + num) / 100.0;
  300.       // Multiple out the various items by the factors
  301.       double distance1 = factor * distance;
  302.       double angle1 = factor1 * Math.PI / 6;
  303.       double angle2 = factor2 * Math.PI / -3;
  304.       // The last angle is the total angle
  305.       double angle3 = angle1 + angle2;
  306.       // Width of the trunk/branch is a tenth of
  307.       // of the length
  308.       te.SetPenWidth(distance1 / 10);
  309.       // Draw the main trunk/branch
  310.       te.Move(distance1);
  311.       // Draw the left-hand sub-tree
  312.       te.Turn(angle1);
  313.       RandomTree(te, distance - 10, variability);
  314.       // Draw the right-hand sub-tree
  315.       te.Turn(angle2);
  316.       RandomTree(te, distance - 10, variability);
  317.       // Turn back to the original angle
  318.       te.Turn(-angle3);
  319.       // Draw back down to the start of this sub-
  320.       // tree, with the same thickness, as this
  321.       // may have changed in deeper sub-trees
  322.       te.SetPenWidth(distance1 / 10);
  323.       te.Move(-distance1);
  324.     }
  325.     [CommandMethod("FT")]
  326.     static public void FractalTree()
  327.     {
  328.       Document doc =
  329.         Application.DocumentManager.MdiActiveDocument;
  330.       double treeLength;
  331.       Point3d position;
  332.       if (!GetTreeInfo(out position, out treeLength))
  333.         return;
  334.       Transaction tr =
  335.         doc.TransactionManager.StartTransaction();
  336.       using (tr)
  337.       {
  338.         TurtleEngine te = new TurtleEngine(tr);
  339.         // Draw a fractal tree
  340.         te.Position = position;
  341.         te.SetPenColor(0);
  342.         te.SetPenWidth(0);
  343.         te.Turn(Math.PI / 2);
  344.         te.PenDown();
  345.         Tree(te, treeLength);
  346.         tr.Commit();
  347.       }
  348.     }
  349.     [CommandMethod("RFT")]
  350.     static public void RandomFractalTree()
  351.     {
  352.       Document doc =
  353.         Application.DocumentManager.MdiActiveDocument;
  354.       Editor ed = doc.Editor;
  355.       double treeLength;
  356.       Point3d position;
  357.       if (!GetTreeInfo(out position, out treeLength))
  358.         return;
  359.       int variability = 20;
  360.       PromptIntegerOptions pio =
  361.         new PromptIntegerOptions(
  362.           "\nEnter variability percentage <20>: "
  363.         );
  364.       pio.AllowNone = true;
  365.       PromptIntegerResult pir =
  366.         ed.GetInteger(pio);
  367.       if (pir.Status != PromptStatus.None &&
  368.           pir.Status != PromptStatus.OK)
  369.         return;
  370.       if (pir.Status == PromptStatus.OK)
  371.         variability = pir.Value;
  372.       Transaction tr =
  373.         doc.TransactionManager.StartTransaction();
  374.       using (tr)
  375.       {
  376.         TurtleEngine te = new TurtleEngine(tr);
  377.         // Draw a random fractal tree
  378.         te.Position = position;
  379.         te.SetPenColor(0);
  380.         te.SetPenWidth(0);
  381.         te.Turn(Math.PI / 2);
  382.         te.PenDown();
  383.         RandomTree(te, treeLength, variability);
  384.         tr.Commit();
  385.       }
  386.     }
  387.   }
  388. }
This is the first time the turtle engine has been used to apply widths to segments, so I did make a very minor change in the GenerateSegment() function: we need to apply the current pen width to the previous Polyline vertex, and not just the one we're adding. A minor change, but one that makes the engine behave in a more expected way.
When we run the RFT command, we can see a variety of trees get created - here's a quick sample:

These were created with the standard options (tree length of 70, variability of 20), but with different choices here you can get quite different results.
I hope this demonstrates the interesting capabilities turtle graphics bring to the area of modeling organic models via recursive fractals + randomness: while this was deliberately quite a simple example, this type of approach could be used/extended to generate other, more elaborate types of "natural" design in two and three dimensions.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-13 15:07:00 | 显示全部楼层
June 27, 2008
Turtle fractals in AutoCAD using .NET - Part 3
In the introductory post we first looked at a simple turtle graphics engine for AutoCAD, which was followed up by this series looking at using it to generate fractals (here are parts 1 & 2).
This post continues the organic fractal theme, by looking at another fractal found in nature, the humble fern. I found some simple Logo code in  a presentation on the web:
  1. to fern :size
  2. if :size < 4 [stop]
  3. fd :size / 25
  4. lt 90 fern :size * .3
  5. rt 90
  6. rt 90 fern :size * .3
  7. lt 90 fern :size * .85
  8. bk :size / 25
  9. end
复制代码
This - when translated to use our turtle engine inside AutoCAD - creates a somewhat straight, unnatural-looking fern:

To make things a little more interesting, I generalised out some of the parameters to allow easy tweaking within the code. Here's the C# code including the complete TurtleEngine implementation, as before:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine
  46.   {
  47.     // Private members
  48.     private Transaction m_trans;
  49.     private Polyline m_poly;
  50.     private Pen m_pen;
  51.     private Point3d m_position;
  52.     private Vector3d m_direction;
  53.     private bool m_updateGraphics;
  54.     // Public properties
  55.     public Point3d Position
  56.     {
  57.       get { return m_position; }
  58.       set { m_position = value; }
  59.     }
  60.     public Vector3d Direction
  61.     {
  62.       get { return m_direction; }
  63.       set { m_direction = value; }
  64.     }
  65.     // Constructor
  66.     public TurtleEngine(Transaction tr)
  67.     {
  68.       m_pen = new Pen();
  69.       m_trans = tr;
  70.       m_poly = null;
  71.       m_position = Point3d.Origin;
  72.       m_direction = new Vector3d(1.0, 0.0, 0.0);
  73.       m_updateGraphics = false;
  74.     }
  75.     // Public methods
  76.     public void Turn(double angle)
  77.     {
  78.       // Rotate our direction by the
  79.       // specified angle
  80.       Matrix3d mat =
  81.         Matrix3d.Rotation(
  82.           angle,
  83.           Vector3d.ZAxis,
  84.           Position
  85.         );
  86.       Direction =
  87.         Direction.TransformBy(mat);
  88.     }
  89.     public void Move(double distance)
  90.     {
  91.       // Move the cursor by a specified
  92.       // distance in the direction in
  93.       // which we're pointing
  94.       Point3d oldPos = Position;
  95.       Position += Direction * distance;
  96.       // If the pen is down, we draw something
  97.       if (m_pen.Down)
  98.         GenerateSegment(oldPos, Position);
  99.     }
  100.     public void PenDown()
  101.     {
  102.       m_pen.Down = true;
  103.     }
  104.     public void PenUp()
  105.     {
  106.       m_pen.Down = false;
  107.       // We'll start a new entity with the next
  108.       // use of the pen
  109.       m_poly = null;
  110.     }
  111.     public void SetPenWidth(double width)
  112.     {
  113.       m_pen.Width = width;
  114.     }
  115.     public void SetPenColor(int idx)
  116.     {
  117.       // Right now we just use an ACI,
  118.       // to make the code simpler
  119.       Color col =
  120.         Color.FromColorIndex(
  121.           ColorMethod.ByAci,
  122.           (short)idx
  123.         );
  124.       // If we have to change the color,
  125.       // we'll start a new entity
  126.       // (if the entity type we're creating
  127.       // supports per-segment colors, we
  128.       // don't need to do this)
  129.       if (col != m_pen.Color)
  130.       {
  131.         m_poly = null;
  132.         m_pen.Color = col;
  133.       }
  134.     }
  135.     // Internal helper to generate geometry
  136.     // (this could be optimised to keep the
  137.     // object we're generating open, rather
  138.     // than having to reopen it each time)
  139.     private void GenerateSegment(
  140.       Point3d oldPos, Point3d newPos)
  141.     {
  142.       Document doc =
  143.         Application.DocumentManager.MdiActiveDocument;
  144.       Database db = doc.Database;
  145.       Editor ed = doc.Editor;
  146.       Autodesk.AutoCAD.ApplicationServices.
  147.       TransactionManager tm =
  148.         doc.TransactionManager;
  149.       Plane plane;
  150.       // Create the current object, if there is none
  151.       if (m_poly == null)
  152.       {
  153.         BlockTable bt =
  154.           (BlockTable)m_trans.GetObject(
  155.             db.BlockTableId,
  156.             OpenMode.ForRead
  157.           );
  158.         BlockTableRecord ms =
  159.           (BlockTableRecord)m_trans.GetObject(
  160.             bt[BlockTableRecord.ModelSpace],
  161.             OpenMode.ForWrite
  162.           );
  163.         // Create the polyline
  164.         m_poly = new Polyline();
  165.         m_poly.Color = m_pen.Color;
  166.         // Define its plane
  167.         plane = new Plane(
  168.           m_poly.Ecs.CoordinateSystem3d.Origin,
  169.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  170.         );
  171.         // Add the first vertex
  172.         m_poly.AddVertexAt(
  173.           0, oldPos.Convert2d(plane),
  174.           0.0, m_pen.Width, m_pen.Width
  175.         );
  176.         // Add the polyline to the database
  177.         ms.AppendEntity(m_poly);
  178.         m_trans.AddNewlyCreatedDBObject(m_poly, true);
  179.       }
  180.       else
  181.       {
  182.         // Calculate its plane
  183.         plane = new Plane(
  184.           m_poly.Ecs.CoordinateSystem3d.Origin,
  185.           m_poly.Ecs.CoordinateSystem3d.Zaxis
  186.         );
  187.       }
  188.       // Make sure the previous vertex has its
  189.       // width set appropriately
  190.       if (m_pen.Width > 0.0)
  191.       {
  192.         m_poly.SetStartWidthAt(
  193.           m_poly.NumberOfVertices - 1,
  194.           m_pen.Width
  195.         );
  196.         m_poly.SetEndWidthAt(
  197.           m_poly.NumberOfVertices - 1,
  198.           m_pen.Width
  199.         );
  200.       }
  201.       // Add the new vertex
  202.       m_poly.AddVertexAt(
  203.         m_poly.NumberOfVertices,
  204.         newPos.Convert2d(plane),
  205.         0.0, m_pen.Width, m_pen.Width
  206.       );
  207.       // Display the graphics, to avoid long,
  208.       // black-box operations
  209.       if (m_updateGraphics)
  210.       {
  211.         tm.QueueForGraphicsFlush();
  212.         tm.FlushGraphics();
  213.         ed.UpdateScreen();
  214.       }
  215.     }
  216.   }
  217.   public class Commands
  218.   {
  219.     static void Fern(
  220.       TurtleEngine te,
  221.       double distance
  222.     )
  223.     {
  224.       const double minDist = 0.3;
  225.       const double widthFactor = 0.1;
  226.       const double stemFactor = 0.04;
  227.       const double restFactor = 0.85;
  228.       const double branchFactor = 0.3;
  229.       const int stemSegs = 5;
  230.       const int stemSegAngle = 1;
  231.       if (distance < minDist)
  232.         return;
  233.       // Width of the trunk/branch is a fraction
  234.       // of the length
  235.       te.SetPenWidth(
  236.         distance * stemFactor * widthFactor
  237.       );
  238.       // Draw the stem
  239.       for (int i = 0; i < stemSegs; i++)
  240.       {
  241.         te.Move(distance * stemFactor / stemSegs);
  242.         if (i < stemSegs - 1)
  243.           te.Turn(-stemSegAngle * Math.PI / 180);
  244.       }
  245.       // Draw the left-hand sub-fern
  246.       te.Turn(Math.PI / 2);
  247.       Fern(te, distance * branchFactor);
  248.       // Draw the right-hand sub-fern
  249.       te.Turn(-Math.PI);
  250.       Fern(te, distance * branchFactor);
  251.       // Draw the rest of the fern to the front
  252.       te.Turn(Math.PI / 2);
  253.       Fern(te, distance * restFactor);
  254.       // Draw back down to the start of this sub-
  255.       // fern, with the same thickness, as this
  256.       // may have changed in deeper sub-ferns
  257.       te.SetPenWidth(
  258.         distance * stemFactor * widthFactor
  259.       );
  260.       for (int i = 0; i < stemSegs; i++)
  261.       {
  262.         te.Move(-distance * stemFactor / stemSegs);
  263.         if (i < stemSegs - 1)
  264.           te.Turn(stemSegAngle * Math.PI / 180);
  265.       }
  266.     }
  267.     static public bool GetFernInfo(
  268.       out Point3d position,
  269.       out double treeLength
  270.     )
  271.     {
  272.       Document doc =
  273.         Application.DocumentManager.MdiActiveDocument;
  274.       Editor ed = doc.Editor;
  275.       treeLength = 0;
  276.       position = Point3d.Origin;
  277.       PromptPointOptions ppo =
  278.         new PromptPointOptions(
  279.           "\nSelect base point of fern: "
  280.         );
  281.       PromptPointResult ppr =
  282.         ed.GetPoint(ppo);
  283.       if (ppr.Status != PromptStatus.OK)
  284.         return false;
  285.       position = ppr.Value;
  286.       PromptDoubleOptions pdo =
  287.         new PromptDoubleOptions(
  288.           "\nEnter fern length <100>: "
  289.         );
  290.       pdo.AllowNone = true;
  291.       PromptDoubleResult pdr =
  292.         ed.GetDouble(pdo);
  293.       if (pdr.Status != PromptStatus.None &&
  294.           pdr.Status != PromptStatus.OK)
  295.         return false;
  296.       if (pdr.Status == PromptStatus.OK)
  297.         treeLength = pdr.Value;
  298.       else
  299.         treeLength = 100;
  300.       return true;
  301.     }
  302.     [CommandMethod("FF")]
  303.     static public void FractalFern()
  304.     {
  305.       Document doc =
  306.         Application.DocumentManager.MdiActiveDocument;
  307.       double fernLength;
  308.       Point3d position;
  309.       if (!GetFernInfo(out position, out fernLength))
  310.         return;
  311.       Transaction tr =
  312.         doc.TransactionManager.StartTransaction();
  313.       using (tr)
  314.       {
  315.         TurtleEngine te = new TurtleEngine(tr);
  316.         // Draw a fractal fern
  317.         te.Position = position;
  318.         te.SetPenColor(92);
  319.         te.SetPenWidth(0);
  320.         te.Turn(Math.PI / 2);
  321.         te.PenDown();
  322.         Fern(te, fernLength);
  323.         tr.Commit();
  324.       }
  325.     }
  326.   }
  327. }
When we run the FF command, selecting a location and the default tree length, we see a more natural (although in no way random) form:

Tweaking the stemSegAngle constant to be -2 instead of 1 gives a further differentiated result:

Incidentally, to get the original, "straight" fern, simply change the stemSegs constant to 1. The curved ferns will each take n times as much space in memory/on disk as the straight ones (where n is the value of stemSegs). This is because we're not storing actual curves, but using multiple straight line segments.
Any of the constants in the Fern() function could be presented for the user to enter, of course (i.e. populated by the GetFernInfo() function and passed as parameters into the Fern() function).

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-13 15:10:00 | 显示全部楼层

July 15, 2008
Turtle fractals in AutoCAD using .NET - Part 4
I just couldn't resist coming back to this fun (at least for me :-) series... for reference here are parts 1, 2 and 3, while the series really started here.

There are two main places I wanted to take this implementation: firstly it really needed to be made 3D, which is the focus of this post, and I still then want to take it further by implementing a turtle graphics-oriented language (one could probably call it a Domain Specific Language, the domain being turtle graphics), which is likely to be a subset or variant of Logo. This is likely to be done using F#, but we'll see when I get around to it... :-)

Firstly, some concepts: with a 2D turtle graphics system we only really need two operations, Move and Turn. As we update our implementation to cope with that pesky third dimension we need to extend the set of operations to include Pitch and Roll. So our "turtle" needs to have more than just a position and a direction, it needs its own positioning matrix (essentially its own coordinate system). Think of the turtle as having its own little UCS icon travelling around with it (the direction being the X axis): we can then implement Turn as a rotation around its current Z axis, Pitch as a rotation around its Y axis and Roll as a rotation around its X axis. Each of these operations will, of course, update the coordinate system so that it's pointing somewhere different.

The below implementation maintains the Direction property, but it's now read-only: the underlying implementation is now via a CoordinateSystem3d object member variable (m_ecs). Each of the Move, Turn, Pitch and Roll operations adjusts the coordinate system, as does setting the Position property.

As for the geometry that we create via the turtle's movements: Polyline objects are inherently 2D, so the new implementation makes use of Polyline3d objects instead. Polyline3d objects contain PolylineVertex3d objects, and have slightly different requirements about database residency as we're adding our vertices, but the fundamental approach is not really any different. A couple of points to note... as pen thickness doesn't really make sense in 3D (at least for Polyline3d objects - we could, of course, get clever with extruding profiles along our paths, if we really wanted to), I've decided to ignore it, for now. The same is true of pen colour. These are both left as enhancements for the future.

Here's the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Colors;

using System;

namespace TurtleGraphics

{

  // This class encapsulates pen

  // information and will be

  // used by our TurtleEngine

  class Pen

  {

    // Private members

    private Color m_color;

    private double m_width;

    private bool m_down;

    // Public properties

    public Color Color

    {

      get { return m_color; }

      set { m_color = value; }

    }

    public double Width

    {

      get { return m_width; }

      set { m_width = value; }

    }

    public bool Down

    {

      get { return m_down; }

      set { m_down = value; }

    }

    // Constructor

    public Pen()

    {

      m_color =

        Color.FromColorIndex(ColorMethod.ByAci, 0);

      m_width = 0.0;

      m_down = false;

    }

  }

  // The main Turtle Graphics engine

  class TurtleEngine

  {

    // Private members

    private Transaction m_trans;

    private Polyline3d m_poly;

    private Pen m_pen;

    private CoordinateSystem3d m_ecs;

    private bool m_updateGraphics;

    // Public properties

    public Point3d Position

    {

      get { return m_ecs.Origin; }

      set {

        m_ecs =

          new CoordinateSystem3d(

            value,

            m_ecs.Xaxis,

            m_ecs.Yaxis

          );

      }

    }

    public Vector3d Direction

    {

      get { return m_ecs.Xaxis; }

    }

    // Constructor

    public TurtleEngine(Transaction tr)

    {

      m_pen = new Pen();

      m_trans = tr;

      m_poly = null;

      m_ecs =

        new CoordinateSystem3d(

          Point3d.Origin,

          Vector3d.XAxis,

          Vector3d.YAxis

        );

      m_updateGraphics = false;

    }

    // Public methods

    public void Turn(double angle)

    {

      // Rotate our direction by the

      // specified angle

      Matrix3d mat =

        Matrix3d.Rotation(

          angle,

          m_ecs.Zaxis,

          Position

        );

      m_ecs =

        new CoordinateSystem3d(

          m_ecs.Origin,

          m_ecs.Xaxis.TransformBy(mat),

          m_ecs.Yaxis.TransformBy(mat)

        );

    }

    public void Pitch(double angle)

    {

      // Pitch in our direction by the

      // specified angle

      Matrix3d mat =

        Matrix3d.Rotation(

          angle,

          m_ecs.Yaxis,

          m_ecs.Origin

        );

      m_ecs =

        new CoordinateSystem3d(

          m_ecs.Origin,

          m_ecs.Xaxis.TransformBy(mat),

          m_ecs.Yaxis

        );

    }

    public void Roll(double angle)

    {

      // Roll along our direction by the

      // specified angle

      Matrix3d mat =

        Matrix3d.Rotation(

          angle,

          m_ecs.Xaxis,

          m_ecs.Origin

        );

      m_ecs =

        new CoordinateSystem3d(

          m_ecs.Origin,

          m_ecs.Xaxis,

          m_ecs.Yaxis.TransformBy(mat)

        );

    }

    public void Move(double distance)

    {

      // Move the cursor by a specified

      // distance in the direction in

      // which we're pointing

      Point3d oldPos = m_ecs.Origin;

      Point3d newPos = oldPos + m_ecs.Xaxis * distance;

      m_ecs =

        new CoordinateSystem3d(

          newPos,

          m_ecs.Xaxis,

          m_ecs.Yaxis

        );

      // If the pen is down, we draw something

      if (m_pen.Down)

        GenerateSegment(oldPos, newPos);

    }

    public void PenDown()

    {

      m_pen.Down = true;

    }

    public void PenUp()

    {

      m_pen.Down = false;

      // We'll start a new entity with the next

      // use of the pen

      m_poly = null;

    }

    public void SetPenWidth(double width)

    {

      // Pen width is not currently implemented in 3D

      //m_pen.Width = width;

    }

    public void SetPenColor(int idx)

    {

      // Right now we just use an ACI,

      // to make the code simpler

      Color col =

        Color.FromColorIndex(

          ColorMethod.ByAci,

          (short)idx

        );

      // If we have to change the color,

      // we'll start a new entity

      // (if the entity type we're creating

      // supports per-segment colors, we

      // don't need to do this)

      if (col != m_pen.Color)

      {

        m_poly = null;

        m_pen.Color = col;

      }

    }

    // Internal helper to generate geometry

    // (this could be optimised to keep the

    // object we're generating open, rather

    // than having to reopen it each time)

    private void GenerateSegment(

      Point3d oldPos, Point3d newPos)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

      Autodesk.AutoCAD.ApplicationServices.

      TransactionManager tm =

        doc.TransactionManager;

      // Create the current object, if there is none

      if (m_poly == null)

      {

        BlockTable bt =

          (BlockTable)m_trans.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord ms =

          (BlockTableRecord)m_trans.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );

        // Create the polyline

        m_poly = new Polyline3d();

        m_poly.Color = m_pen.Color;

        // Add the polyline to the database

        ms.AppendEntity(m_poly);

        m_trans.AddNewlyCreatedDBObject(m_poly, true);

        // Add the first vertex

        PolylineVertex3d vert =

          new PolylineVertex3d(oldPos);

        m_poly.AppendVertex(vert);

        m_trans.AddNewlyCreatedDBObject(vert, true);

      }

      // Add the new vertex

      PolylineVertex3d vert2 =

        new PolylineVertex3d(newPos);

      m_poly.AppendVertex(vert2);

      m_trans.AddNewlyCreatedDBObject(vert2, true);

      // Display the graphics, to avoid long,

      // black-box operations

      if (m_updateGraphics)

      {

        tm.QueueForGraphicsFlush();

        tm.FlushGraphics();

        ed.UpdateScreen();

      }

    }

  }

  public class Commands

  {

    [CommandMethod("CB")]

    static public void Cube()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        TurtleEngine te = new TurtleEngine(tr);

        // Draw a simple 3D cube

        te.PenDown();

        for (int i=0; i < 4; i++)

        {

          for (int j=0; j < 4; j++)

          {

            te.Move(100);

            te.Turn(Math.PI / 2);

          }

          te.Move(100);

          te.Pitch(Math.PI / -2);

        }

        tr.Commit();

      }

    }

    static private int CubesPerLevel(int level)

    {

      if (level == 0)

        return 0;

      else

        return 2 * CubesPerLevel(level - 1) + 1;

    }

    static public bool GetHilbertInfo(

      out Point3d position,

      out double size,

      out int level

    )

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      size = 0;

      level = 0;

      position = Point3d.Origin;

      PromptPointOptions ppo =

        new PromptPointOptions(

          "\nSelect base point of Hilbert cube: "

        );

      PromptPointResult ppr =

        ed.GetPoint(ppo);

      if (ppr.Status != PromptStatus.OK)

        return false;

      position = ppr.Value;

      PromptDoubleOptions pdo =

        new PromptDoubleOptions(

          "\nEnter size <100>: "

        );

      pdo.AllowNone = true;

      PromptDoubleResult pdr =

        ed.GetDouble(pdo);

      if (pdr.Status != PromptStatus.None &&

          pdr.Status != PromptStatus.OK)

        return false;

      if (pdr.Status == PromptStatus.OK)

        size = pdr.Value;

      else

        size = 100;

      PromptIntegerOptions pio =

        new PromptIntegerOptions(

          "\nEnter level <5>: "

        );

      pio.AllowNone = true;

      pio.LowerLimit = 1;

      pio.UpperLimit = 10;

      PromptIntegerResult pir =

        ed.GetInteger(pio);

      if (pir.Status != PromptStatus.None &&

          pir.Status != PromptStatus.OK)

        return false;

      if (pir.Status == PromptStatus.OK)

        level = pir.Value;

      else

        level = 5;

      return true;

    }

    private static void Hilbert(

      TurtleEngine te, double size, int level)

    {

      if (level > 0)

      {

        int newLevel = level - 1;

        te.Pitch(Math.PI / -2);       // Down Pitch 90

        te.Roll(Math.PI / -2);        // Left Roll 90

        Hilbert(te, size, newLevel);  // Recurse

        te.Move(size);                // Forward Size

        te.Pitch(Math.PI / -2);       // Down Pitch 90

        te.Roll(Math.PI / -2);        // Left Roll 90

        Hilbert(te, size, newLevel);  // Recurse

        te.Move(size);                // Forward Size

        Hilbert(te, size, newLevel);  // Recurse

        te.Turn(Math.PI / -2);        // Left Turn 90

        te.Move(size);                // Forward Size

        te.Pitch(Math.PI / -2);       // Down Pitch 90

        te.Roll(Math.PI / 2);         // Right Roll 90

        te.Roll(Math.PI / 2);         // Right Roll 90

        Hilbert(te, size, newLevel);  // Recurse

        te.Move(size);                // Forward Size

        Hilbert(te, size, newLevel);  // Recurse

        te.Pitch(Math.PI / 2);        // Up Pitch 90

        te.Move(size);                // Forward Size

        te.Turn(Math.PI / 2);         // Right Turn 90

        te.Roll(Math.PI / 2);         // Right Roll 90

        te.Roll(Math.PI / 2);         // Right Roll 90

        Hilbert(te, size, newLevel);  // Recurse

        te.Move(size);                // Forward Size

        Hilbert(te, size, newLevel);  // Recurse

        te.Turn(Math.PI / -2);        // Left Turn 90

        te.Move(size);                // Forward Size

        te.Roll(Math.PI / 2);         // Right Roll 90

        Hilbert(te, size, newLevel);  // Recurse

        te.Turn(Math.PI / -2);        // Left Turn 90

        te.Roll(Math.PI / 2);         // Right Roll 90

      }

    }

    [CommandMethod("DH")]

    static public void DrawHilbert()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      double size;

      int level;

      Point3d position;

      if (!GetHilbertInfo(out position, out size, out level))

        return;

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        TurtleEngine te = new TurtleEngine(tr);

        // Draw a Hilbert cube

        te.Position = position;

        te.PenDown();

        Hilbert(te, size / CubesPerLevel(level), level);

        tr.Commit();

      }

    }

  }

}

To try out this new engine, I implemented a few commands: one draws a simply cube (the CB command), while the other does something much more interesting - it draws a  Hilbert curve in 3D (I've called it a Hilbert cube, although that's not really the official terminology). Check out the DH command above and its results, below.

Here are the results of running the DH command for levels 1-6. Level 7 is as close to a solid black cube as you can get without zooming very, very closely, so that's where I stopped.

First the plan view:

Now for 3D:

For fun, I took the level 4 cube and drew a circle at one its end-points:

Here's what happens when we EXTRUDE the circle along the Polyline3d path, setting the Visual Style to conceptual:

A final note, to close out today's topic: a very useful (and fascinating) reference for me during this implementation has been  The Algorithmic Beauty of Plants, a volume by Przemyslaw Prusinkiewicz and Aristid Lindenmayer. While it is unfortunately no longer in print, it is thankfully available as a free download.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-13 15:13:00 | 显示全部楼层
July 15, 2008
Turtle fractals in AutoCAD using .NET - Part 4
I just couldn't resist coming back to this fun (at least for me :-) series... for reference here are parts 1, 2 and 3, while the series really started here.
There are two main places I wanted to take this implementation: firstly it really needed to be made 3D, which is the focus of this post, and I still then want to take it further by implementing a turtle graphics-oriented language (one could probably call it a Domain Specific Language, the domain being turtle graphics), which is likely to be a subset or variant of Logo. This is likely to be done using F#, but we'll see when I get around to it... :-)
Firstly, some concepts: with a 2D turtle graphics system we only really need two operations, Move and Turn. As we update our implementation to cope with that pesky third dimension we need to extend the set of operations to include Pitch and Roll. So our "turtle" needs to have more than just a position and a direction, it needs its own positioning matrix (essentially its own coordinate system). Think of the turtle as having its own little UCS icon travelling around with it (the direction being the X axis): we can then implement Turn as a rotation around its current Z axis, Pitch as a rotation around its Y axis and Roll as a rotation around its X axis. Each of these operations will, of course, update the coordinate system so that it's pointing somewhere different.
The below implementation maintains the Direction property, but it's now read-only: the underlying implementation is now via a CoordinateSystem3d object member variable (m_ecs). Each of the Move, Turn, Pitch and Roll operations adjusts the coordinate system, as does setting the Position property.
As for the geometry that we create via the turtle's movements: Polyline objects are inherently 2D, so the new implementation makes use of Polyline3d objects instead. Polyline3d objects contain PolylineVertex3d objects, and have slightly different requirements about database residency as we're adding our vertices, but the fundamental approach is not really any different. A couple of points to note... as pen thickness doesn't really make sense in 3D (at least for Polyline3d objects - we could, of course, get clever with extruding profiles along our paths, if we really wanted to), I've decided to ignore it, for now. The same is true of pen colour. These are both left as enhancements for the future.
Here's the updated C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine
  46.   {
  47.     // Private members
  48.     private Transaction m_trans;
  49.     private Polyline3d m_poly;
  50.     private Pen m_pen;
  51.     private CoordinateSystem3d m_ecs;
  52.     private bool m_updateGraphics;
  53.     // Public properties
  54.     public Point3d Position
  55.     {
  56.       get { return m_ecs.Origin; }
  57.       set {
  58.         m_ecs =
  59.           new CoordinateSystem3d(
  60.             value,
  61.             m_ecs.Xaxis,
  62.             m_ecs.Yaxis
  63.           );
  64.       }
  65.     }
  66.     public Vector3d Direction
  67.     {
  68.       get { return m_ecs.Xaxis; }
  69.     }
  70.     // Constructor
  71.     public TurtleEngine(Transaction tr)
  72.     {
  73.       m_pen = new Pen();
  74.       m_trans = tr;
  75.       m_poly = null;
  76.       m_ecs =
  77.         new CoordinateSystem3d(
  78.           Point3d.Origin,
  79.           Vector3d.XAxis,
  80.           Vector3d.YAxis
  81.         );
  82.       m_updateGraphics = false;
  83.     }
  84.     // Public methods
  85.     public void Turn(double angle)
  86.     {
  87.       // Rotate our direction by the
  88.       // specified angle
  89.       Matrix3d mat =
  90.         Matrix3d.Rotation(
  91.           angle,
  92.           m_ecs.Zaxis,
  93.           Position
  94.         );
  95.       m_ecs =
  96.         new CoordinateSystem3d(
  97.           m_ecs.Origin,
  98.           m_ecs.Xaxis.TransformBy(mat),
  99.           m_ecs.Yaxis.TransformBy(mat)
  100.         );
  101.     }
  102.     public void Pitch(double angle)
  103.     {
  104.       // Pitch in our direction by the
  105.       // specified angle
  106.       Matrix3d mat =
  107.         Matrix3d.Rotation(
  108.           angle,
  109.           m_ecs.Yaxis,
  110.           m_ecs.Origin
  111.         );
  112.       m_ecs =
  113.         new CoordinateSystem3d(
  114.           m_ecs.Origin,
  115.           m_ecs.Xaxis.TransformBy(mat),
  116.           m_ecs.Yaxis
  117.         );
  118.     }
  119.     public void Roll(double angle)
  120.     {
  121.       // Roll along our direction by the
  122.       // specified angle
  123.       Matrix3d mat =
  124.         Matrix3d.Rotation(
  125.           angle,
  126.           m_ecs.Xaxis,
  127.           m_ecs.Origin
  128.         );
  129.       m_ecs =
  130.         new CoordinateSystem3d(
  131.           m_ecs.Origin,
  132.           m_ecs.Xaxis,
  133.           m_ecs.Yaxis.TransformBy(mat)
  134.         );
  135.     }
  136.     public void Move(double distance)
  137.     {
  138.       // Move the cursor by a specified
  139.       // distance in the direction in
  140.       // which we're pointing
  141.       Point3d oldPos = m_ecs.Origin;
  142.       Point3d newPos = oldPos + m_ecs.Xaxis * distance;
  143.       m_ecs =
  144.         new CoordinateSystem3d(
  145.           newPos,
  146.           m_ecs.Xaxis,
  147.           m_ecs.Yaxis
  148.         );
  149.       // If the pen is down, we draw something
  150.       if (m_pen.Down)
  151.         GenerateSegment(oldPos, newPos);
  152.     }
  153.     public void PenDown()
  154.     {
  155.       m_pen.Down = true;
  156.     }
  157.     public void PenUp()
  158.     {
  159.       m_pen.Down = false;
  160.       // We'll start a new entity with the next
  161.       // use of the pen
  162.       m_poly = null;
  163.     }
  164.     public void SetPenWidth(double width)
  165.     {
  166.       // Pen width is not currently implemented in 3D
  167.       //m_pen.Width = width;
  168.     }
  169.     public void SetPenColor(int idx)
  170.     {
  171.       // Right now we just use an ACI,
  172.       // to make the code simpler
  173.       Color col =
  174.         Color.FromColorIndex(
  175.           ColorMethod.ByAci,
  176.           (short)idx
  177.         );
  178.       // If we have to change the color,
  179.       // we'll start a new entity
  180.       // (if the entity type we're creating
  181.       // supports per-segment colors, we
  182.       // don't need to do this)
  183.       if (col != m_pen.Color)
  184.       {
  185.         m_poly = null;
  186.         m_pen.Color = col;
  187.       }
  188.     }
  189.     // Internal helper to generate geometry
  190.     // (this could be optimised to keep the
  191.     // object we're generating open, rather
  192.     // than having to reopen it each time)
  193.     private void GenerateSegment(
  194.       Point3d oldPos, Point3d newPos)
  195.     {
  196.       Document doc =
  197.         Application.DocumentManager.MdiActiveDocument;
  198.       Database db = doc.Database;
  199.       Editor ed = doc.Editor;
  200.       Autodesk.AutoCAD.ApplicationServices.
  201.       TransactionManager tm =
  202.         doc.TransactionManager;
  203.       // Create the current object, if there is none
  204.       if (m_poly == null)
  205.       {
  206.         BlockTable bt =
  207.           (BlockTable)m_trans.GetObject(
  208.             db.BlockTableId,
  209.             OpenMode.ForRead
  210.           );
  211.         BlockTableRecord ms =
  212.           (BlockTableRecord)m_trans.GetObject(
  213.             bt[BlockTableRecord.ModelSpace],
  214.             OpenMode.ForWrite
  215.           );
  216.         // Create the polyline
  217.         m_poly = new Polyline3d();
  218.         m_poly.Color = m_pen.Color;
  219.         // Add the polyline to the database
  220.         ms.AppendEntity(m_poly);
  221.         m_trans.AddNewlyCreatedDBObject(m_poly, true);
  222.         // Add the first vertex
  223.         PolylineVertex3d vert =
  224.           new PolylineVertex3d(oldPos);
  225.         m_poly.AppendVertex(vert);
  226.         m_trans.AddNewlyCreatedDBObject(vert, true);
  227.       }
  228.       // Add the new vertex
  229.       PolylineVertex3d vert2 =
  230.         new PolylineVertex3d(newPos);
  231.       m_poly.AppendVertex(vert2);
  232.       m_trans.AddNewlyCreatedDBObject(vert2, true);
  233.       // Display the graphics, to avoid long,
  234.       // black-box operations
  235.       if (m_updateGraphics)
  236.       {
  237.         tm.QueueForGraphicsFlush();
  238.         tm.FlushGraphics();
  239.         ed.UpdateScreen();
  240.       }
  241.     }
  242.   }
  243.   public class Commands
  244.   {
  245.     [CommandMethod("CB")]
  246.     static public void Cube()
  247.     {
  248.       Document doc =
  249.         Application.DocumentManager.MdiActiveDocument;
  250.       Transaction tr =
  251.         doc.TransactionManager.StartTransaction();
  252.       using (tr)
  253.       {
  254.         TurtleEngine te = new TurtleEngine(tr);
  255.         // Draw a simple 3D cube
  256.         te.PenDown();
  257.         for (int i=0; i < 4; i++)
  258.         {
  259.           for (int j=0; j < 4; j++)
  260.           {
  261.             te.Move(100);
  262.             te.Turn(Math.PI / 2);
  263.           }
  264.           te.Move(100);
  265.           te.Pitch(Math.PI / -2);
  266.         }
  267.         tr.Commit();
  268.       }
  269.     }
  270.     static private int CubesPerLevel(int level)
  271.     {
  272.       if (level == 0)
  273.         return 0;
  274.       else
  275.         return 2 * CubesPerLevel(level - 1) + 1;
  276.     }
  277.     static public bool GetHilbertInfo(
  278.       out Point3d position,
  279.       out double size,
  280.       out int level
  281.     )
  282.     {
  283.       Document doc =
  284.         Application.DocumentManager.MdiActiveDocument;
  285.       Editor ed = doc.Editor;
  286.       size = 0;
  287.       level = 0;
  288.       position = Point3d.Origin;
  289.       PromptPointOptions ppo =
  290.         new PromptPointOptions(
  291.           "\nSelect base point of Hilbert cube: "
  292.         );
  293.       PromptPointResult ppr =
  294.         ed.GetPoint(ppo);
  295.       if (ppr.Status != PromptStatus.OK)
  296.         return false;
  297.       position = ppr.Value;
  298.       PromptDoubleOptions pdo =
  299.         new PromptDoubleOptions(
  300.           "\nEnter size <100>: "
  301.         );
  302.       pdo.AllowNone = true;
  303.       PromptDoubleResult pdr =
  304.         ed.GetDouble(pdo);
  305.       if (pdr.Status != PromptStatus.None &&
  306.           pdr.Status != PromptStatus.OK)
  307.         return false;
  308.       if (pdr.Status == PromptStatus.OK)
  309.         size = pdr.Value;
  310.       else
  311.         size = 100;
  312.       PromptIntegerOptions pio =
  313.         new PromptIntegerOptions(
  314.           "\nEnter level <5>: "
  315.         );
  316.       pio.AllowNone = true;
  317.       pio.LowerLimit = 1;
  318.       pio.UpperLimit = 10;
  319.       PromptIntegerResult pir =
  320.         ed.GetInteger(pio);
  321.       if (pir.Status != PromptStatus.None &&
  322.           pir.Status != PromptStatus.OK)
  323.         return false;
  324.       if (pir.Status == PromptStatus.OK)
  325.         level = pir.Value;
  326.       else
  327.         level = 5;
  328.       return true;
  329.     }
  330.     private static void Hilbert(
  331.       TurtleEngine te, double size, int level)
  332.     {
  333.       if (level > 0)
  334.       {
  335.         int newLevel = level - 1;
  336.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  337.         te.Roll(Math.PI / -2);        // Left Roll 90
  338.         Hilbert(te, size, newLevel);  // Recurse
  339.         te.Move(size);                // Forward Size
  340.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  341.         te.Roll(Math.PI / -2);        // Left Roll 90
  342.         Hilbert(te, size, newLevel);  // Recurse
  343.         te.Move(size);                // Forward Size
  344.         Hilbert(te, size, newLevel);  // Recurse
  345.         te.Turn(Math.PI / -2);        // Left Turn 90
  346.         te.Move(size);                // Forward Size
  347.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  348.         te.Roll(Math.PI / 2);         // Right Roll 90
  349.         te.Roll(Math.PI / 2);         // Right Roll 90
  350.         Hilbert(te, size, newLevel);  // Recurse
  351.         te.Move(size);                // Forward Size
  352.         Hilbert(te, size, newLevel);  // Recurse
  353.         te.Pitch(Math.PI / 2);        // Up Pitch 90
  354.         te.Move(size);                // Forward Size
  355.         te.Turn(Math.PI / 2);         // Right Turn 90
  356.         te.Roll(Math.PI / 2);         // Right Roll 90
  357.         te.Roll(Math.PI / 2);         // Right Roll 90
  358.         Hilbert(te, size, newLevel);  // Recurse
  359.         te.Move(size);                // Forward Size
  360.         Hilbert(te, size, newLevel);  // Recurse
  361.         te.Turn(Math.PI / -2);        // Left Turn 90
  362.         te.Move(size);                // Forward Size
  363.         te.Roll(Math.PI / 2);         // Right Roll 90
  364.         Hilbert(te, size, newLevel);  // Recurse
  365.         te.Turn(Math.PI / -2);        // Left Turn 90
  366.         te.Roll(Math.PI / 2);         // Right Roll 90
  367.       }
  368.     }
  369.     [CommandMethod("DH")]
  370.     static public void DrawHilbert()
  371.     {
  372.       Document doc =
  373.         Application.DocumentManager.MdiActiveDocument;
  374.       double size;
  375.       int level;
  376.       Point3d position;
  377.       if (!GetHilbertInfo(out position, out size, out level))
  378.         return;
  379.       Transaction tr =
  380.         doc.TransactionManager.StartTransaction();
  381.       using (tr)
  382.       {
  383.         TurtleEngine te = new TurtleEngine(tr);
  384.         // Draw a Hilbert cube
  385.         te.Position = position;
  386.         te.PenDown();
  387.         Hilbert(te, size / CubesPerLevel(level), level);
  388.         tr.Commit();
  389.       }
  390.     }
  391.   }
  392. }
To try out this new engine, I implemented a few commands: one draws a simply cube (the CB command), while the other does something much more interesting - it draws a  Hilbert curve in 3D (I've called it a Hilbert cube, although that's not really the official terminology). Check out the DH command above and its results, below.
Here are the results of running the DH command for levels 1-6. Level 7 is as close to a solid black cube as you can get without zooming very, very closely, so that's where I stopped.
First the plan view:

Now for 3D:

For fun, I took the level 4 cube and drew a circle at one its end-points:

Here's what happens when we EXTRUDE the circle along the Polyline3d path, setting the Visual Style to conceptual:

A final note, to close out today's topic: a very useful (and fascinating) reference for me during this implementation has been  The Algorithmic Beauty of Plants, a volume by Przemyslaw Prusinkiewicz and Aristid Lindenmayer. While it is unfortunately no longer in print, it is thankfully available as a free download.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-7-16 10:08:00 | 显示全部楼层
July 17, 2008
Turtle fractals in AutoCAD using .NET - Part 5
Once again I've ended up extending this series in a way I didn't originally expect to (and yes, that's a good thing :-). Here are parts 1, 2, 3 and 4, as well as the post that started it all.
After thinking about my initial 3D implementation in Part 4, I realised that implementing pen colours and widths would actually be relatively easy. Here's the idea:
Each section of a different width and/or pen colour is actually a separate extruded solid
Whenever we start a new section we start off by creating a circular profile of the current pen width at the start
When we terminate that section - by changing the pen colour or width - we extrude the profile along the Polyline3d defining the section's path
This extruded Solid3d will be the colour of the pen, of course
We then erase the original polyline
In order to achieve this, we now have a TerminateCurrentSection() helper function, which we call whenever the pen width or colour changes, and when we are done with the TurtleEngine, of course. For this last part we've changed the TurtleEngine to implement IDisposable: this gives us the handy Dispose() method to implement (which simply calls TerminateCurrentSection()), and we can the add the using() statement to control the TurtleEngine's lifetime. One important point: we need to Dispose of the TurtleEngine before we commit the transaction, otherwise it won't work properly.
Here's the modified C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Colors;
  7. using System;
  8. namespace TurtleGraphics
  9. {
  10.   // This class encapsulates pen
  11.   // information and will be
  12.   // used by our TurtleEngine
  13.   class Pen
  14.   {
  15.     // Private members
  16.     private Color m_color;
  17.     private double m_width;
  18.     private bool m_down;
  19.     // Public properties
  20.     public Color Color
  21.     {
  22.       get { return m_color; }
  23.       set { m_color = value; }
  24.     }
  25.     public double Width
  26.     {
  27.       get { return m_width; }
  28.       set { m_width = value; }
  29.     }
  30.     public bool Down
  31.     {
  32.       get { return m_down; }
  33.       set { m_down = value; }
  34.     }
  35.     // Constructor
  36.     public Pen()
  37.     {
  38.       m_color =
  39.         Color.FromColorIndex(ColorMethod.ByAci, 0);
  40.       m_width = 0.0;
  41.       m_down = false;
  42.     }
  43.   }
  44.   // The main Turtle Graphics engine
  45.   class TurtleEngine : IDisposable
  46.   {
  47.     // Private members
  48.     private Transaction m_trans;
  49.     private Polyline3d m_poly;
  50.     private Circle m_profile;
  51.     private Pen m_pen;
  52.     private CoordinateSystem3d m_ecs;
  53.     private bool m_updateGraphics;
  54.     // Public properties
  55.     public Point3d Position
  56.     {
  57.       get { return m_ecs.Origin; }
  58.       set {
  59.         m_ecs =
  60.           new CoordinateSystem3d(
  61.             value,
  62.             m_ecs.Xaxis,
  63.             m_ecs.Yaxis
  64.           );
  65.       }
  66.     }
  67.     public Vector3d Direction
  68.     {
  69.       get { return m_ecs.Xaxis; }
  70.     }
  71.     // Constructor
  72.     public TurtleEngine(Transaction tr)
  73.     {
  74.       m_pen = new Pen();
  75.       m_trans = tr;
  76.       m_poly = null;
  77.       m_profile = null;
  78.       m_ecs =
  79.         new CoordinateSystem3d(
  80.           Point3d.Origin,
  81.           Vector3d.XAxis,
  82.           Vector3d.YAxis
  83.         );
  84.       m_updateGraphics = false;
  85.     }
  86.     public void Dispose()
  87.     {
  88.       TerminateCurrentSection();
  89.     }
  90.     // Public methods
  91.     public void Turn(double angle)
  92.     {
  93.       // Rotate our direction by the
  94.       // specified angle
  95.       Matrix3d mat =
  96.         Matrix3d.Rotation(
  97.           angle,
  98.           m_ecs.Zaxis,
  99.           Position
  100.         );
  101.       m_ecs =
  102.         new CoordinateSystem3d(
  103.           m_ecs.Origin,
  104.           m_ecs.Xaxis.TransformBy(mat),
  105.           m_ecs.Yaxis.TransformBy(mat)
  106.         );
  107.     }
  108.     public void Pitch(double angle)
  109.     {
  110.       // Pitch in our direction by the
  111.       // specified angle
  112.       Matrix3d mat =
  113.         Matrix3d.Rotation(
  114.           angle,
  115.           m_ecs.Yaxis,
  116.           m_ecs.Origin
  117.         );
  118.       m_ecs =
  119.         new CoordinateSystem3d(
  120.           m_ecs.Origin,
  121.           m_ecs.Xaxis.TransformBy(mat),
  122.           m_ecs.Yaxis
  123.         );
  124.     }
  125.     public void Roll(double angle)
  126.     {
  127.       // Roll along our direction by the
  128.       // specified angle
  129.       Matrix3d mat =
  130.         Matrix3d.Rotation(
  131.           angle,
  132.           m_ecs.Xaxis,
  133.           m_ecs.Origin
  134.         );
  135.       m_ecs =
  136.         new CoordinateSystem3d(
  137.           m_ecs.Origin,
  138.           m_ecs.Xaxis,
  139.           m_ecs.Yaxis.TransformBy(mat)
  140.         );
  141.     }
  142.     public void Move(double distance)
  143.     {
  144.       // Move the cursor by a specified
  145.       // distance in the direction in
  146.       // which we're pointing
  147.       Point3d oldPos = m_ecs.Origin;
  148.       Point3d newPos = oldPos + m_ecs.Xaxis * distance;
  149.       m_ecs =
  150.         new CoordinateSystem3d(
  151.           newPos,
  152.           m_ecs.Xaxis,
  153.           m_ecs.Yaxis
  154.         );
  155.       // If the pen is down, we draw something
  156.       if (m_pen.Down)
  157.         GenerateSegment(oldPos, newPos);
  158.     }
  159.     public void PenDown()
  160.     {
  161.       m_pen.Down = true;
  162.     }
  163.     public void PenUp()
  164.     {
  165.       m_pen.Down = false;
  166.       // We'll start a new entity with the next
  167.       // use of the pen
  168.       TerminateCurrentSection();
  169.     }
  170.     public void SetPenWidth(double width)
  171.     {
  172.       m_pen.Width = width;
  173.       TerminateCurrentSection();
  174.     }
  175.     public void SetPenColor(int idx)
  176.     {
  177.       // Right now we just use an ACI,
  178.       // to make the code simpler
  179.       Color col =
  180.         Color.FromColorIndex(
  181.           ColorMethod.ByAci,
  182.           (short)idx
  183.         );
  184.       // If we have to change the color,
  185.       // we'll start a new entity
  186.       // (if the entity type we're creating
  187.       // supports per-segment colors, we
  188.       // don't need to do this)
  189.       if (col != m_pen.Color)
  190.       {
  191.         TerminateCurrentSection();
  192.         m_pen.Color = col;
  193.       }
  194.     }
  195.     // Internal helper to generate geometry
  196.     private void GenerateSegment(
  197.       Point3d oldPos, Point3d newPos)
  198.     {
  199.       Document doc =
  200.         Application.DocumentManager.MdiActiveDocument;
  201.       Database db = doc.Database;
  202.       Editor ed = doc.Editor;
  203.       Autodesk.AutoCAD.ApplicationServices.
  204.       TransactionManager tm =
  205.         doc.TransactionManager;
  206.       // Create the current object, if there is none
  207.       if (m_poly == null)
  208.       {
  209.         BlockTable bt =
  210.           (BlockTable)m_trans.GetObject(
  211.             db.BlockTableId,
  212.             OpenMode.ForRead
  213.           );
  214.         BlockTableRecord ms =
  215.           (BlockTableRecord)m_trans.GetObject(
  216.             bt[BlockTableRecord.ModelSpace],
  217.             OpenMode.ForWrite
  218.           );
  219.         // Create the polyline
  220.         m_poly = new Polyline3d();
  221.         m_poly.Color = m_pen.Color;
  222.         // Add the polyline to the database
  223.         ms.AppendEntity(m_poly);
  224.         m_trans.AddNewlyCreatedDBObject(m_poly, true);
  225.         // Add the first vertex
  226.         PolylineVertex3d vert =
  227.           new PolylineVertex3d(oldPos);
  228.         m_poly.AppendVertex(vert);
  229.         m_trans.AddNewlyCreatedDBObject(vert, true);
  230.         m_profile =
  231.           new Circle(oldPos, Direction, m_pen.Width);
  232.         ms.AppendEntity(m_profile);
  233.         m_trans.AddNewlyCreatedDBObject(m_profile, true);
  234.         m_profile.DowngradeOpen();
  235.       }
  236.       // Add the new vertex
  237.       PolylineVertex3d vert2 =
  238.         new PolylineVertex3d(newPos);
  239.       m_poly.AppendVertex(vert2);
  240.       m_trans.AddNewlyCreatedDBObject(vert2, true);
  241.       // Display the graphics, to avoid long,
  242.       // black-box operations
  243.       if (m_updateGraphics)
  244.       {
  245.         tm.QueueForGraphicsFlush();
  246.         tm.FlushGraphics();
  247.         ed.UpdateScreen();
  248.       }
  249.     }
  250.     // Internal helper to generate 3D geometry
  251.     private void TerminateCurrentSection()
  252.     {
  253.       if (m_profile != null && m_poly != null)
  254.       {
  255.         Document doc =
  256.           Application.DocumentManager.MdiActiveDocument;
  257.         Database db = doc.Database;
  258.         Editor ed = doc.Editor;
  259.         try
  260.         {
  261.           // Generate a Region from our circular profile
  262.           DBObjectCollection col =
  263.             new DBObjectCollection();
  264.           col.Add(m_profile);
  265.           DBObjectCollection res =
  266.             Region.CreateFromCurves(col);
  267.           Region reg =
  268.             res[0] as Region;
  269.           if (reg != null)
  270.           {
  271.             BlockTable bt =
  272.               (BlockTable)m_trans.GetObject(
  273.                 db.BlockTableId,
  274.                 OpenMode.ForRead
  275.               );
  276.             BlockTableRecord ms =
  277.               (BlockTableRecord)m_trans.GetObject(
  278.                 bt[BlockTableRecord.ModelSpace],
  279.                 OpenMode.ForWrite
  280.               );
  281.             // Extrude our Region along the Polyline3d path
  282.             Solid3d sol = new Solid3d();
  283.             sol.ExtrudeAlongPath(reg, m_poly, 0.0);
  284.             sol.Color = m_pen.Color;
  285.             // Add the generated Solid3d to the database
  286.             ms.AppendEntity(sol);
  287.             m_trans.AddNewlyCreatedDBObject(sol, true);
  288.             // Get rid of the Region, profile and path
  289.             reg.Dispose();
  290.             m_profile.UpgradeOpen();
  291.             m_profile.Erase();
  292.             m_poly.Erase();
  293.           }
  294.         }
  295.         catch (System.Exception ex)
  296.         {
  297.           ed.WriteMessage(
  298.             "\nException: {0}",
  299.             ex.Message
  300.           );
  301.         }
  302.       }
  303.       m_profile = null;
  304.       m_poly = null;
  305.     }
  306.   }
  307.   public class Commands
  308.   {
  309.     [CommandMethod("CB")]
  310.     static public void Cube()
  311.     {
  312.       Document doc =
  313.         Application.DocumentManager.MdiActiveDocument;
  314.       Transaction tr =
  315.         doc.TransactionManager.StartTransaction();
  316.       using (tr)
  317.       {
  318.         TurtleEngine te = new TurtleEngine(tr);
  319.         using (te)
  320.         {
  321.           // Draw a simple 3D cube
  322.           te.SetPenWidth(5.0);
  323.           te.PenDown();
  324.           for (int i = 0; i < 4; i++)
  325.           {
  326.             for (int j = 0; j < 4; j++)
  327.             {
  328.               // Only draw some of the segments
  329.               // (this stops overlap)
  330.               if (i % 2 == 0 || j % 2 == 0)
  331.                 te.PenDown();
  332.               else
  333.                 te.PenUp();
  334.               te.SetPenColor(i+j+1);
  335.               te.Move(100);
  336.               te.Turn(Math.PI / 2);
  337.             }
  338.             te.PenUp();
  339.             te.Move(100);
  340.             te.Pitch(Math.PI / -2);
  341.           }
  342.         }
  343.         tr.Commit();
  344.       }
  345.     }
  346.     static private int CubesPerLevel(int level)
  347.     {
  348.       if (level == 0)
  349.         return 0;
  350.       else
  351.         return 2 * CubesPerLevel(level - 1) + 1;
  352.     }
  353.     static public bool GetHilbertInfo(
  354.       out Point3d position,
  355.       out double size,
  356.       out int level
  357.     )
  358.     {
  359.       Document doc =
  360.         Application.DocumentManager.MdiActiveDocument;
  361.       Editor ed = doc.Editor;
  362.       size = 0;
  363.       level = 0;
  364.       position = Point3d.Origin;
  365.       PromptPointOptions ppo =
  366.         new PromptPointOptions(
  367.           "\nSelect base point of Hilbert cube: "
  368.         );
  369.       PromptPointResult ppr =
  370.         ed.GetPoint(ppo);
  371.       if (ppr.Status != PromptStatus.OK)
  372.         return false;
  373.       position = ppr.Value;
  374.       PromptDoubleOptions pdo =
  375.         new PromptDoubleOptions(
  376.           "\nEnter size <100>: "
  377.         );
  378.       pdo.AllowNone = true;
  379.       PromptDoubleResult pdr =
  380.         ed.GetDouble(pdo);
  381.       if (pdr.Status != PromptStatus.None &&
  382.           pdr.Status != PromptStatus.OK)
  383.         return false;
  384.       if (pdr.Status == PromptStatus.OK)
  385.         size = pdr.Value;
  386.       else
  387.         size = 100;
  388.       PromptIntegerOptions pio =
  389.         new PromptIntegerOptions(
  390.           "\nEnter level <5>: "
  391.         );
  392.       pio.AllowNone = true;
  393.       pio.LowerLimit = 1;
  394.       pio.UpperLimit = 10;
  395.       PromptIntegerResult pir =
  396.         ed.GetInteger(pio);
  397.       if (pir.Status != PromptStatus.None &&
  398.           pir.Status != PromptStatus.OK)
  399.         return false;
  400.       if (pir.Status == PromptStatus.OK)
  401.         level = pir.Value;
  402.       else
  403.         level = 5;
  404.       return true;
  405.     }
  406.     private static void Hilbert(
  407.       TurtleEngine te, double size, int level)
  408.     {
  409.       if (level > 0)
  410.       {
  411.         te.SetPenColor(level);
  412.         int newLevel = level - 1;
  413.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  414.         te.Roll(Math.PI / -2);        // Left Roll 90
  415.         Hilbert(te, size, newLevel);  // Recurse
  416.         te.SetPenColor(level);
  417.         te.Move(size);                // Forward Size
  418.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  419.         te.Roll(Math.PI / -2);        // Left Roll 90
  420.         Hilbert(te, size, newLevel);  // Recurse
  421.         te.SetPenColor(level);
  422.         te.Move(size);                // Forward Size
  423.         Hilbert(te, size, newLevel);  // Recurse
  424.         te.Turn(Math.PI / -2);        // Left Turn 90
  425.         te.Move(size);                // Forward Size
  426.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  427.         te.Roll(Math.PI / 2);         // Right Roll 90
  428.         te.Roll(Math.PI / 2);         // Right Roll 90
  429.         Hilbert(te, size, newLevel);  // Recurse
  430.         te.SetPenColor(level);
  431.         te.Move(size);                // Forward Size
  432.         Hilbert(te, size, newLevel);  // Recurse
  433.         te.SetPenColor(level);
  434.         te.Pitch(Math.PI / 2);        // Up Pitch 90
  435.         te.Move(size);                // Forward Size
  436.         te.Turn(Math.PI / 2);         // Right Turn 90
  437.         te.Roll(Math.PI / 2);         // Right Roll 90
  438.         te.Roll(Math.PI / 2);         // Right Roll 90
  439.         Hilbert(te, size, newLevel);  // Recurse
  440.         te.SetPenColor(level);
  441.         te.Move(size);                // Forward Size
  442.         Hilbert(te, size, newLevel);  // Recurse
  443.         te.SetPenColor(level);
  444.         te.Turn(Math.PI / -2);        // Left Turn 90
  445.         te.Move(size);                // Forward Size
  446.         te.Roll(Math.PI / 2);         // Right Roll 90
  447.         Hilbert(te, size, newLevel);  // Recurse
  448.         te.SetPenColor(level);
  449.         te.Turn(Math.PI / -2);        // Left Turn 90
  450.         te.Roll(Math.PI / 2);         // Right Roll 90
  451.       }
  452.     }
  453.     [CommandMethod("DH")]
  454.     static public void DrawHilbert()
  455.     {
  456.       Document doc =
  457.         Application.DocumentManager.MdiActiveDocument;
  458.       double size;
  459.       int level;
  460.       Point3d position;
  461.       if (!GetHilbertInfo(out position, out size, out level))
  462.         return;
  463.       int cbl = CubesPerLevel(level);
  464.       Transaction tr =
  465.         doc.TransactionManager.StartTransaction();
  466.       using (tr)
  467.       {
  468.         TurtleEngine te = new TurtleEngine(tr);
  469.         using (te)
  470.         {
  471.           // Draw a Hilbert cube
  472.           te.Position = position;
  473.           te.SetPenWidth(10.0 / cbl);
  474.           te.PenDown();
  475.           Hilbert(te, size / cbl, level);
  476.         }
  477.         tr.Commit();
  478.       }
  479.     }
  480.   }
  481. }
Here are the results of the modified CB command, which now has coloured segments with a width:

Here's what we get from the DH command. This command now runs pretty slowly for the higher levels - it is doing a lot of work, after all - and only runs at all because we're using separate sections by changing the colour regularly. You'll notice that the pen width is set according to the level, as the finer the detail, the finer the pen width needed.
First the plan view:

Then the full 3D view:

Here's a close-up of the level 5 cube:

A word of caution: some of these higher levels are extremely resource-intensive. Please do not attempt to play around with something like this while working on something you don't want to lose: there is always a slim chance of the application (and even the system, if you're really unlucky) being brought down when system resources become scarce.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-26 00:44 , Processed in 0.257076 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表