Kean Walmsley


  • About the Author
    Kean on Google+

April 2014

Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      







« Generating Koch fractals in AutoCAD using .NET - Part 1 | Main | "Custom Objects Masterclass" webcast recording available for download »

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

    9 namespace Kochizer

   10 {

   11   public class Commands

   12   {

   13     // We generate 4 new entities for every old entity

   14     // (unless a complex entity such as a polyline)

   15

   16     const int newEntsPerOldEnt = 4;

   17

   18     [CommandMethod("KA")]

   19     public void KochizeAll()

   20     {

   21       Document doc =

   22         Application.DocumentManager.MdiActiveDocument;

   23       Database db = doc.Database;

   24       Editor ed = doc.Editor;

   25

   26       // Acquire user input - whether to create the

   27       // new geometry to the left or the right...

   28

   29       PromptKeywordOptions pko =

   30         new PromptKeywordOptions(

   31           "\nCreate fractal to side (Left/<Right>): "

   32         );

   33       pko.Keywords.Add("Left");

   34       pko.Keywords.Add("Right");

   35

   36       PromptResult pr =

   37         ed.GetKeywords(pko);

   38       bool bLeft = false;

   39

   40       if (pr.Status != PromptStatus.None &&

   41           pr.Status != PromptStatus.OK)

   42         return;

   43

   44       if ((string)pr.StringResult == "Left")

   45         bLeft = true;

   46

   47       // ... and the recursion depth for the command.

   48

   49       PromptIntegerOptions pio =

   50         new PromptIntegerOptions(

   51           "\nEnter recursion level <1>: "

   52         );

   53       pio.AllowZero = false;

   54       pio.AllowNegative = false;

   55       pio.AllowNone = true;

   56

   57       PromptIntegerResult pir =

   58         ed.GetInteger(pio);

   59       int recursionLevel = 1;

   60

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

   62           pir.Status != PromptStatus.OK)

   63         return;

   64

   65       if (pir.Status == PromptStatus.OK)

   66         recursionLevel = pir.Value;

   67

   68       // Note: strictly speaking we're not recursing,

   69       // we're iterating, but the effect to the user

   70       // is the same.

   71

   72       Transaction tr =

   73         doc.TransactionManager.StartTransaction();

   74       using (tr)

   75       {

   76         BlockTable bt =

   77           (BlockTable)tr.GetObject(

   78             db.BlockTableId,

   79             OpenMode.ForRead

   80           );

   81         using (bt)

   82         {

   83           // No need to open the block table record

   84           // for write, as we're just reading data

   85           // for now

   86

   87           BlockTableRecord btr =

   88             (BlockTableRecord)tr.GetObject(

   89               bt[BlockTableRecord.ModelSpace],

   90               OpenMode.ForRead

   91             );

   92           using (btr)

   93           {

   94             // List of changed entities

   95             // (will contain complex entities, such as

   96             // polylines"

   97

   98             ObjectIdCollection modified =

   99               new ObjectIdCollection();

  100

  101             // List of entities to erase

  102             // (will contain replaced entities)

  103

  104             ObjectIdCollection toErase =

  105               new ObjectIdCollection();

  106

  107             // List of new entitites to add

  108             // (will be processed recursively or

  109             // assed to the open block table record)

  110

  111             List<Entity> newEntities =

  112               new List<Entity>(

  113                 db.ApproxNumObjects * newEntsPerOldEnt

  114               );

  115

  116             // Kochize each entity in the open block

  117             // table record

  118

  119             foreach (ObjectId objId in btr)

  120             {

  121               Entity ent =

  122                 (Entity)tr.GetObject(

  123                   objId,

  124                   OpenMode.ForRead

  125                 );

  126               Kochize(

  127                 ent,

  128                 modified,

  129                 toErase,

  130                 newEntities,

  131                 bLeft

  132               );

  133             }

  134

  135             // If we need to loop,

  136             // work on the returned entities

  137

  138             while (--recursionLevel > 0)

  139             {

  140               // Create an output array

  141

  142               List<Entity> newerEntities =

  143                 new List<Entity>(

  144                   newEntities.Count * newEntsPerOldEnt

  145                 );

  146

  147               // Kochize all the modified (complex) entities

  148

  149               foreach (ObjectId objId in modified)

  150               {

  151                 Entity ent =

  152                   (Entity)tr.GetObject(

  153                     objId,

  154                     OpenMode.ForRead

  155                   );

  156                 Kochize(

  157                   ent,

  158                   modified,

  159                   toErase,

  160                   newerEntities,

  161                   bLeft

  162                 );

  163               }

  164

  165               // Kochize all the non-db resident entities

  166

  167               foreach (Entity ent in newEntities)

  168               {

  169                 Kochize(

  170                   ent,

  171                   modified,

  172                   toErase,

  173                   newerEntities,

  174                   bLeft

  175                 );

  176               }

  177

  178               // We now longer need the intermediate entities

  179               // previously output for the level above,

  180               // we replace them with the latest output

  181

  182               newEntities.Clear();

  183               newEntities = newerEntities;

  184             }

  185

  186             // Erase each of the replaced db-resident entities

  187

  188             foreach (ObjectId objId in toErase)

  189             {

  190               Entity ent =

  191                 (Entity)tr.GetObject(

  192                   objId,

  193                   OpenMode.ForWrite

  194                 );

  195               ent.Erase();

  196             }

  197

  198             // Add the new entities

  199

  200             btr.UpgradeOpen();

  201             foreach (Entity ent in newEntities)

  202             {

  203               btr.AppendEntity(ent);

  204               tr.AddNewlyCreatedDBObject(ent, true);

  205             }

  206             tr.Commit();

  207           }

  208         }

  209       }

  210     }

  211

  212     // Dispatch function to call through to various per-type

  213     // functions

  214

  215     private void Kochize(

  216       Entity ent,

  217       ObjectIdCollection modified,

  218       ObjectIdCollection toErase,

  219       List<Entity> toAdd,

  220       bool bLeft

  221     )

  222     {

  223       Line ln = ent as Line;

  224       if (ln != null)

  225       {

  226         Kochize(ln, modified, toErase, toAdd, bLeft);

  227         return;

  228       }

  229       Arc arc = ent as Arc;

  230       if (arc != null)

  231       {

  232         Kochize(arc, modified, toErase, toAdd, bLeft);

  233         return;

  234       }

  235       Polyline pl = ent as Polyline;

  236       if (pl != null)

  237       {

  238         Kochize(pl, modified, toErase, toAdd, bLeft);

  239         return;

  240       }

  241     }

  242

  243     // Create 4 new lines from a line passed in

  244

  245     private void Kochize(

  246       Line ln,

  247       ObjectIdCollection modified,

  248       ObjectIdCollection toErase,

  249       List<Entity> toAdd,

  250       bool bLeft

  251     )

  252     {

  253       // Get general info about the line

  254       // and calculate the main 5 points

  255

  256       Point3d pt1 = ln.StartPoint,

  257               pt5 = ln.EndPoint;

  258       Vector3d vec1 = pt5 - pt1,

  259               norm1 = vec1.GetNormal();

  260       double d_3 = vec1.Length / 3;

  261       Point3d pt2 = pt1 + (norm1 * d_3),

  262               pt4 = pt1 + (2 * norm1 * d_3);

  263       Vector3d vec2 = pt4 - pt2;

  264

  265       if (bLeft)

  266         vec2 =

  267           vec2.RotateBy(

  268             Math.PI / 3, new Vector3d(0, 0, 1)

  269           );

  270       else

  271         vec2 =

  272           vec2.RotateBy(

  273             5 * Math.PI / 3, new Vector3d(0, 0, 1)

  274           );

  275       Point3d pt3 = pt2 + vec2;

  276

  277       // Mark the original to be erased

  278

  279       if (ln.ObjectId != ObjectId.Null)

  280         toErase.Add(ln.ObjectId);

  281

  282       // Create the first line

  283

  284       Line ln1 = new Line(pt1, pt2);

  285       ln1.SetPropertiesFrom(ln);

  286       ln1.Thickness = ln.Thickness;

  287       toAdd.Add(ln1);

  288

  289       // Create the second line

  290

  291       Line ln2 = new Line(pt2, pt3);

  292       ln2.SetPropertiesFrom(ln);

  293       ln2.Thickness = ln.Thickness;

  294       toAdd.Add(ln2);

  295

  296       // Create the third line

  297

  298       Line ln3 = new Line(pt3, pt4);

  299       ln3.SetPropertiesFrom(ln);

  300       ln3.Thickness = ln.Thickness;

  301       toAdd.Add(ln3);

  302

  303       // Create the fourth line

  304

  305       Line ln4 = new Line(pt4, pt5);

  306       ln4.SetPropertiesFrom(ln);

  307       ln4.Thickness = ln.Thickness;

  308       toAdd.Add(ln4);

  309     }

  310

  311     // Create 4 new arcs from an arc passed in

  312

  313     private void Kochize(

  314       Arc arc,

  315       ObjectIdCollection modified,

  316       ObjectIdCollection toErase,

  317       List<Entity> toAdd,

  318       bool bLeft

  319     )

  320     {

  321       // Get general info about the arc

  322       // and calculate the main 5 points

  323

  324       Point3d pt1 = arc.StartPoint,

  325               pt5 = arc.EndPoint;

  326       double length = arc.GetDistAtPoint(pt5),

  327             angle = arc.StartAngle;

  328       Vector3d full = pt5 - pt1;

  329

  330       Point3d pt2 = arc.GetPointAtDist(length / 3),

  331               pt4 = arc.GetPointAtDist(2 * length / 3);

  332

  333       // Mark the original to be erased

  334

  335       if (arc.ObjectId != ObjectId.Null)

  336         toErase.Add(arc.ObjectId);

  337

  338       // Create the first arc

  339

  340       Point3d mid = arc.GetPointAtDist(length / 6);

  341       CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);

  342       Arc arc1 = circArc2Arc(tmpArc);

  343       arc1.SetPropertiesFrom(arc);

  344       arc1.Thickness = arc.Thickness;

  345       toAdd.Add(arc1);

  346

  347       // Create the second arc

  348

  349       mid = arc.GetPointAtDist(length / 2);

  350       tmpArc.Set(pt2, mid, pt4);

  351       if (bLeft)

  352         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt2);

  353       else

  354         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt2);

  355       Arc arc2 = circArc2Arc(tmpArc);

  356       arc2.SetPropertiesFrom(arc);

  357       arc2.Thickness = arc.Thickness;

  358       toAdd.Add(arc2);

  359

  360       // Create the third arc

  361

  362       tmpArc.Set(pt2, mid, pt4);

  363       if (bLeft)

  364         tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);

  365       else

  366         tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);

  367       Arc arc3 = circArc2Arc(tmpArc);

  368       arc3.SetPropertiesFrom(arc);

  369       arc3.Thickness = arc.Thickness;

  370       toAdd.Add(arc3);

  371

  372       // Create the fourth arc

  373

  374       mid = arc.GetPointAtDist(5 * length / 6);

  375       Arc arc4 =

  376         circArc2Arc(new CircularArc3d(pt4, mid, pt5));

  377       arc4.SetPropertiesFrom(arc);

  378       arc4.Thickness = arc.Thickness;

  379       toAdd.Add(arc4);

  380     }

  381

  382     Arc circArc2Arc(CircularArc3d circArc)

  383     {

  384       Point3d center = circArc.Center;

  385       Vector3d normal = circArc.Normal;

  386       Vector3d refVec = circArc.ReferenceVector;

  387       Plane plane = new Plane(center, normal);

  388       double ang = refVec.AngleOnPlane(plane);

  389       return new Arc(

  390         center,

  391         normal,

  392         circArc.Radius,

  393         circArc.StartAngle + ang,

  394         circArc.EndAngle + ang

  395       );

  396     }

  397

  398     private void Kochize(

  399       Polyline pl,

  400       ObjectIdCollection modified,

  401       ObjectIdCollection toErase,

  402       List<Entity> toAdd,

  403       bool bLeft

  404     )

  405     {

  406       pl.UpgradeOpen();

  407

  408       if (pl.ObjectId != ObjectId.Null &&

  409           !modified.Contains(pl.ObjectId))

  410       {

  411         modified.Add(pl.ObjectId);

  412       }

  413

  414       for(int vn = 0; vn < pl.NumberOfVertices; vn++)

  415       {

  416         SegmentType st = pl.GetSegmentType(vn);

  417         if (st != SegmentType.Line && st != SegmentType.Arc)

  418           continue;

  419

  420         double sw = pl.GetStartWidthAt(vn),

  421               ew = pl.GetEndWidthAt(vn);

  422

  423         if (st == SegmentType.Line)

  424         {

  425           if (vn + 1 == pl.NumberOfVertices)

  426             continue;

  427

  428           LineSegment2d ls = pl.GetLineSegment2dAt(vn);

  429           Point2d pt1 = ls.StartPoint,

  430                   pt5 = ls.EndPoint;

  431           Vector2d vec = pt5 - pt1;

  432           double d_3 = vec.Length / 3;

  433           Point2d pt2 = pt1 + (vec.GetNormal() * d_3),

  434                   pt4 = pt1 + (vec.GetNormal() * 2 * d_3);

  435           Vector2d vec2 = pt4 - pt2;

  436

  437           if (bLeft)

  438             vec2 = vec2.RotateBy(Math.PI / 3);

  439           else

  440             vec2 = vec2.RotateBy(5 * Math.PI / 3);

  441

  442           Point2d pt3 = pt2 + vec2;

  443

  444           pl.AddVertexAt(++vn, pt2, 0, sw, ew);

  445           pl.AddVertexAt(++vn, pt3, 0, sw, ew);

  446           pl.AddVertexAt(++vn, pt4, 0, sw, ew);

  447         }

  448         else if (st == SegmentType.Arc)

  449         {

  450           CircularArc3d ca = pl.GetArcSegmentAt(vn);

  451           double oldBulge = pl.GetBulgeAt(vn);

  452

  453           // Build a standard arc and use that for the calcs

  454

  455           Arc arc = circArc2Arc(ca);

  456

  457           // Get the main 5 points

  458

  459           Point3d pt1 = arc.StartPoint,

  460                   pt5 = arc.EndPoint;

  461

  462           double ln = arc.GetDistAtPoint(pt5);

  463           Point3d pt2 = arc.GetPointAtDist(ln / 3),

  464                   pt4 = arc.GetPointAtDist(2 * ln / 3);

  465

  466           Point3d mid = arc.GetPointAtDist(ln / 2);

  467

  468           CircularArc3d tmpArc = new CircularArc3d(pt2, mid, pt4);

  469           if (bLeft)

  470             tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);

  471           else

  472             tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);

  473

  474           Point3d pt3 = tmpArc.StartPoint;

  475

  476           // Now add the new segments, setting the bulge

  477           // for the existing one and the new ones to a third

  478           // (as the segments are a third as big as the old one)

  479

  480           CoordinateSystem3d ecs = pl.Ecs.CoordinateSystem3d;

  481           Plane pn = new Plane(ecs.Origin, pl.Normal);

  482           double bu = oldBulge / 3;

  483

  484           pl.SetBulgeAt(vn, bu);

  485           pl.AddVertexAt(++vn, pt2.Convert2d(pn), bu, sw, ew);

  486           pl.AddVertexAt(++vn, pt3.Convert2d(pn), bu, sw, ew);

  487           pl.AddVertexAt(++vn, pt4.Convert2d(pn), bu, sw, ew);

  488         }

  489       }

  490       pl.DowngradeOpen();

  491     }

  492   }

  493 }

  494

And here's the source file for download.

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:

Koch_9

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200e3982158d68833

Listed below are links to weblogs that reference Generating Koch fractals in AutoCAD using .NET - Part 2:

blog comments powered by Disqus

10 Random Posts