乐筑天下

搜索
欢迎各位开发者和用户入驻本平台 尊重版权,从我做起,拒绝盗版,拒绝倒卖 签到、发布资源、邀请好友注册,可以获得银币 请注意保管好自己的密码,避免账户资金被盗
查看: 153|回复: 9

Kean专题(13)—Geometry

[复制链接]

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-7-4 20:27:00 | 显示全部楼层 |阅读模式
一、生成Koch分形
July 30, 2007
Generating Koch fractals in  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...

s4fgjyz0aga.png

s4fgjyz0aga.png


... becomes four lines...

nk12wjwjok2.png

nk12wjwjok2.png


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

efta2142rjx.png

efta2142rjx.png


... etc. ...

zlnmwpt2zhw.png

zlnmwpt2zhw.png


... etc. ...

u4xtzn1dpee.png

u4xtzn1dpee.png


... etc. ...

cpjdwoc4zzj.png

cpjdwoc4zzj.png


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

4a4njfoxmhj.png

4a4njfoxmhj.png


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:
[code]
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
using System;
namespace Kochizer
{
  public class Commands
  {
    // We generate 4 new entities for every old entity
    // (unless a complex entity such as a polyline)
    const int newEntsPerOldEnt = 4;
    [CommandMethod("KA")]
    public void KochizeAll()
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // Acquire user input - whether to create the
      // new geometry to the left or the right...
      PromptKeywordOptions pko =
        new PromptKeywordOptions(
          "\nCreate fractal to side (Left/): "
        );
      pko.Keywords.Add("Left");
      pko.Keywords.Add("Right");
      PromptResult pr =
        ed.GetKeywords(pko);
      bool bLeft = false;
      if (pr.Status != PromptStatus.None &&
          pr.Status != PromptStatus.OK)
        return;
      if ((string)pr.StringResult == "Left")
        bLeft = true;
      // ... and the recursion depth for the command.
      PromptIntegerOptions pio =
        new PromptIntegerOptions(
          "\nEnter recursion level : "
        );
      pio.AllowZero = false;
      pio.AllowNegative = false;
      pio.AllowNone = true;
      PromptIntegerResult pir =
        ed.GetInteger(pio);
      int recursionLevel = 1;
      if (pir.Status != PromptStatus.None &&
          pir.Status != PromptStatus.OK)
        return;
      if (pir.Status == PromptStatus.OK)
        recursionLevel = pir.Value;
      // Note: strictly speaking we're not recursing,
      // we're iterating, but the effect to the user
      // is the same.
      Transaction tr =
        doc.TransactionManager.StartTransaction();
      using (tr)
      {
        BlockTable bt =
          (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
          );
        using (bt)
        {
          // No need to open the block table record
          // for write, as we're just reading data
          // for now
          BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
              bt[BlockTableRecord.ModelSpace],
              OpenMode.ForRead
            );
          using (btr)
          {
            // List of changed entities
            // (will contain complex entities, such as
            // polylines"
            ObjectIdCollection modified =
              new ObjectIdCollection();
            // List of entities to erase
            // (will contain replaced entities)
            ObjectIdCollection toErase =
              new ObjectIdCollection();
            // List of new entitites to add
            // (will be processed recursively or
            // assed to the open block table record)
            List newEntities =
              new List(
                db.ApproxNumObjects * newEntsPerOldEnt
              );
            // Kochize each entity in the open block
            // table record
            foreach (ObjectId objId in btr)
            {
              Entity ent =
                (Entity)tr.GetObject(
                  objId,
                  OpenMode.ForRead
                );
              Kochize(
                ent,
                modified,
                toErase,
                newEntities,
                bLeft
              );
            }
            // If we need to loop,
            // work on the returned entities
            while (--recursionLevel > 0)
            {
              // Create an output array
              List newerEntities =
                new List(
                  newEntities.Count * newEntsPerOldEnt
                );
              // Kochize all the modified (complex) entities
              foreach (ObjectId objId in modified)
              {
                Entity ent =
                  (Entity)tr.GetObject(
                    objId,
                    OpenMode.ForRead
                  );
                Kochize(
                  ent,
                  modified,
                  toErase,
                  newerEntities,
                  bLeft
                );
              }
              // Kochize all the non-db resident entities
              foreach (Entity ent in newEntities)
              {
                Kochize(
                  ent,
                  modified,
                  toErase,
                  newerEntities,
                  bLeft
                );
              }
              // We now longer need the intermediate entities
              // previously output for the level above,
              // we replace them with the latest output
              newEntities.Clear();
              newEntities = newerEntities;
            }
            // Erase each of the replaced db-resident entities
            foreach (ObjectId objId in toErase)
            {
              Entity ent =
                (Entity)tr.GetObject(
                  objId,
                  OpenMode.ForWrite
                );
              ent.Erase();
            }
            // Add the new entities
            btr.UpgradeOpen();
            foreach (Entity ent in newEntities)
            {
              btr.AppendEntity(ent);
              tr.AddNewlyCreatedDBObject(ent, true);
            }
            tr.Commit();
          }
        }
      }
    }
    // Dispatch function to call through to various per-type
    // functions
    private void Kochize(
      Entity ent,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      Line ln = ent as Line;
      if (ln != null)
      {
        Kochize(ln, modified, toErase, toAdd, bLeft);
        return;
      }
      Arc arc = ent as Arc;
      if (arc != null)
      {
        Kochize(arc, modified, toErase, toAdd, bLeft);
        return;
      }
    }
    // Create 4 new lines from a line passed in
    private void Kochize(
      Line ln,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      // Get general info about the line
      // and calculate the main 5 points
      Point3d pt1 = ln.StartPoint,
              pt5 = ln.EndPoint;
      Vector3d vec1 = pt5 - pt1,
              norm1 = vec1.GetNormal();
      double d_3 = vec1.Length / 3;
      Point3d pt2 = pt1 + (norm1 * d_3),
              pt4 = pt1 + (2 * norm1 * d_3);
      Vector3d vec2 = pt4 - pt2;
      if (bLeft)
        vec2 =
          vec2.RotateBy(
            Math.PI / 3, new Vector3d(0, 0, 1)
          );
      else
        vec2 =
          vec2.RotateBy(
            5 * Math.PI / 3, new Vector3d(0, 0, 1)
          );
      Point3d pt3 = pt2 + vec2;
      // Mark the original to be erased
      if (ln.ObjectId != ObjectId.Null)
        toErase.Add(ln.ObjectId);
      // Create the first line
      Line ln1 = new Line(pt1, pt2);
      ln1.SetPropertiesFrom(ln);
      ln1.Thickness = ln.Thickness;
      toAdd.Add(ln1);
      // Create the second line
      Line ln2 = new Line(pt2, pt3);
      ln2.SetPropertiesFrom(ln);
      ln2.Thickness = ln.Thickness;
      toAdd.Add(ln2);
      // Create the third line
      Line ln3 = new Line(pt3, pt4);
      ln3.SetPropertiesFrom(ln);
      ln3.Thickness = ln.Thickness;
      toAdd.Add(ln3);
      // Create the fourth line
      Line ln4 = new Line(pt4, pt5);
      ln4.SetPropertiesFrom(ln);
      ln4.Thickness = ln.Thickness;
      toAdd.Add(ln4);
    }
    // Create 4 new arcs from an arc passed in
    private void Kochize(
      Arc arc,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      // Get general info about the arc
      // and calculate the main 5 points
      Point3d pt1 = arc.StartPoint,
              pt5 = arc.EndPoint;
      double length = arc.GetDistAtPoint(pt5),
            angle = arc.StartAngle;
      //bool bLocalLeft = false;
      Vector3d full = pt5 - pt1;
      //if (full.GetAngleTo(Vector3d.XAxis) > angle)
        //bLocalLeft = true;
      Point3d pt2 = arc.GetPointAtDist(length / 3),
              pt4 = arc.GetPointAtDist(2 * length / 3);
      // Mark the original to be erased
      if (arc.ObjectId != ObjectId.Null)
        toErase.Add(arc.ObjectId);
      // Create the first arc
      Point3d mid = arc.GetPointAtDist(length / 6);
      CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);
      Arc arc1 = circArc2Arc(tmpArc);
      arc1.SetPropertiesFrom(arc);
      arc1.Thickness = arc.Thickness;
      toAdd.Add(arc1);
      // Create the second arc
      mid = arc.GetPointAtDist(length / 2);
      tmpArc.Set(pt2, mid, pt4);
      if (bLeft)
        tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt2);
      else
        tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt2);
      Arc arc2 = circArc2Arc(tmpArc);
      arc2.SetPropertiesFrom(arc);
      arc2.Thickness = arc.Thickness;
      toAdd.Add(arc2);
      // Create the third arc
      mid = arc.GetPointAtDist(length / 2);
      tmpArc.Set(pt2, mid, pt4);
      if (bLeft)
        tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt4);
      else
        tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt4);
      Arc arc3 = circArc2Arc(tmpArc);
      arc3.SetPropertiesFrom(arc);
      arc3.Thickness = arc.Thickness;
      toAdd.Add(arc3);
      // Create the fourth arc
      mid = arc.GetPointAtDist(5 * length / 6);
      Arc arc4 =
        circArc2Arc(new CircularArc3d(pt4, mid, pt5));
      arc4.SetPropertiesFrom(arc);
      arc4.Thickness = arc.Thickness;
      toAdd.Add(arc4);
    }
    Arc circArc2Arc(CircularArc3d circArc)
    {
      double ang, start, end;
      ang =
        circArc.ReferenceVector.GetAngleTo(Vector3d.XAxis);
      ang =
        (circArc.ReferenceVector.Y

5kfo1ryg0yb.png

5kfo1ryg0yb.png


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.

本帖以下内容被隐藏保护;需要你回复后,才能看到!

游客,如果您要查看本帖隐藏内容请回复
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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:[code]
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
using System;
namespace Kochizer
{
  public class Commands
  {
    // We generate 4 new entities for every old entity
    // (unless a complex entity such as a polyline)
    const int newEntsPerOldEnt = 4;
    [CommandMethod("KA")]
    public void KochizeAll()
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // Acquire user input - whether to create the
      // new geometry to the left or the right...
      PromptKeywordOptions pko =
        new PromptKeywordOptions(
          "\nCreate fractal to side (Left/): "
        );
      pko.Keywords.Add("Left");
      pko.Keywords.Add("Right");
      PromptResult pr =
        ed.GetKeywords(pko);
      bool bLeft = false;
      if (pr.Status != PromptStatus.None &&
          pr.Status != PromptStatus.OK)
        return;
      if ((string)pr.StringResult == "Left")
        bLeft = true;
      // ... and the recursion depth for the command.
      PromptIntegerOptions pio =
        new PromptIntegerOptions(
          "\nEnter recursion level : "
        );
      pio.AllowZero = false;
      pio.AllowNegative = false;
      pio.AllowNone = true;
      PromptIntegerResult pir =
        ed.GetInteger(pio);
      int recursionLevel = 1;
      if (pir.Status != PromptStatus.None &&
          pir.Status != PromptStatus.OK)
        return;
      if (pir.Status == PromptStatus.OK)
        recursionLevel = pir.Value;
      // Note: strictly speaking we're not recursing,
      // we're iterating, but the effect to the user
      // is the same.
      Transaction tr =
        doc.TransactionManager.StartTransaction();
      using (tr)
      {
        BlockTable bt =
          (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
          );
        using (bt)
        {
          // No need to open the block table record
          // for write, as we're just reading data
          // for now
          BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
              bt[BlockTableRecord.ModelSpace],
              OpenMode.ForRead
            );
          using (btr)
          {
            // List of changed entities
            // (will contain complex entities, such as
            // polylines"
            ObjectIdCollection modified =
              new ObjectIdCollection();
            // List of entities to erase
            // (will contain replaced entities)
            ObjectIdCollection toErase =
              new ObjectIdCollection();
            // List of new entitites to add
            // (will be processed recursively or
            // assed to the open block table record)
            List newEntities =
              new List(
                db.ApproxNumObjects * newEntsPerOldEnt
              );
            // Kochize each entity in the open block
            // table record
            foreach (ObjectId objId in btr)
            {
              Entity ent =
                (Entity)tr.GetObject(
                  objId,
                  OpenMode.ForRead
                );
              Kochize(
                ent,
                modified,
                toErase,
                newEntities,
                bLeft
              );
            }
            // If we need to loop,
            // work on the returned entities
            while (--recursionLevel > 0)
            {
              // Create an output array
              List newerEntities =
                new List(
                  newEntities.Count * newEntsPerOldEnt
                );
              // Kochize all the modified (complex) entities
              foreach (ObjectId objId in modified)
              {
                Entity ent =
                  (Entity)tr.GetObject(
                    objId,
                    OpenMode.ForRead
                  );
                Kochize(
                  ent,
                  modified,
                  toErase,
                  newerEntities,
                  bLeft
                );
              }
              // Kochize all the non-db resident entities
              foreach (Entity ent in newEntities)
              {
                Kochize(
                  ent,
                  modified,
                  toErase,
                  newerEntities,
                  bLeft
                );
              }
              // We now longer need the intermediate entities
              // previously output for the level above,
              // we replace them with the latest output
              newEntities.Clear();
              newEntities = newerEntities;
            }
            // Erase each of the replaced db-resident entities
            foreach (ObjectId objId in toErase)
            {
              Entity ent =
                (Entity)tr.GetObject(
                  objId,
                  OpenMode.ForWrite
                );
              ent.Erase();
            }
            // Add the new entities
            btr.UpgradeOpen();
            foreach (Entity ent in newEntities)
            {
              btr.AppendEntity(ent);
              tr.AddNewlyCreatedDBObject(ent, true);
            }
            tr.Commit();
          }
        }
      }
    }
    // Dispatch function to call through to various per-type
    // functions
    private void Kochize(
      Entity ent,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      Line ln = ent as Line;
      if (ln != null)
      {
        Kochize(ln, modified, toErase, toAdd, bLeft);
        return;
      }
      Arc arc = ent as Arc;
      if (arc != null)
      {
        Kochize(arc, modified, toErase, toAdd, bLeft);
        return;
      }
      Polyline pl = ent as Polyline;
      if (pl != null)
      {
        Kochize(pl, modified, toErase, toAdd, bLeft);
        return;
      }
    }
    // Create 4 new lines from a line passed in
    private void Kochize(
      Line ln,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      // Get general info about the line
      // and calculate the main 5 points
      Point3d pt1 = ln.StartPoint,
              pt5 = ln.EndPoint;
      Vector3d vec1 = pt5 - pt1,
               norm1 = vec1.GetNormal();
      double d_3 = vec1.Length / 3;
      Point3d pt2 = pt1 + (norm1 * d_3),
              pt4 = pt1 + (2 * norm1 * d_3);
      Vector3d vec2 = pt4 - pt2;
      if (bLeft)
        vec2 =
          vec2.RotateBy(
            Math.PI / 3, new Vector3d(0, 0, 1)
          );
      else
        vec2 =
          vec2.RotateBy(
            5 * Math.PI / 3, new Vector3d(0, 0, 1)
          );
      Point3d pt3 = pt2 + vec2;
      // Mark the original to be erased
      if (ln.ObjectId != ObjectId.Null)
        toErase.Add(ln.ObjectId);
      // Create the first line
      Line ln1 = new Line(pt1, pt2);
      ln1.SetPropertiesFrom(ln);
      ln1.Thickness = ln.Thickness;
      toAdd.Add(ln1);
      // Create the second line
      Line ln2 = new Line(pt2, pt3);
      ln2.SetPropertiesFrom(ln);
      ln2.Thickness = ln.Thickness;
      toAdd.Add(ln2);
      // Create the third line
      Line ln3 = new Line(pt3, pt4);
      ln3.SetPropertiesFrom(ln);
      ln3.Thickness = ln.Thickness;
      toAdd.Add(ln3);
      // Create the fourth line
      Line ln4 = new Line(pt4, pt5);
      ln4.SetPropertiesFrom(ln);
      ln4.Thickness = ln.Thickness;
      toAdd.Add(ln4);
    }
    // Create 4 new arcs from an arc passed in
    private void Kochize(
      Arc arc,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      // Get general info about the arc
      // and calculate the main 5 points
      Point3d pt1 = arc.StartPoint,
              pt5 = arc.EndPoint;
      double length = arc.GetDistAtPoint(pt5),
             angle = arc.StartAngle;
      Vector3d full = pt5 - pt1;
      Point3d pt2 = arc.GetPointAtDist(length / 3),
              pt4 = arc.GetPointAtDist(2 * length / 3);
      // Mark the original to be erased
      if (arc.ObjectId != ObjectId.Null)
        toErase.Add(arc.ObjectId);
      // Create the first arc
      Point3d mid = arc.GetPointAtDist(length / 6);
      CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);
      Arc arc1 = circArc2Arc(tmpArc);
      arc1.SetPropertiesFrom(arc);
      arc1.Thickness = arc.Thickness;
      toAdd.Add(arc1);
      // Create the second arc
      mid = arc.GetPointAtDist(length / 2);
      tmpArc.Set(pt2, mid, pt4);
      if (bLeft)
        tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt2);
      else
        tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt2);
      Arc arc2 = circArc2Arc(tmpArc);
      arc2.SetPropertiesFrom(arc);
      arc2.Thickness = arc.Thickness;
      toAdd.Add(arc2);
      // Create the third arc
      tmpArc.Set(pt2, mid, pt4);
      if (bLeft)
        tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
      else
        tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
      Arc arc3 = circArc2Arc(tmpArc);
      arc3.SetPropertiesFrom(arc);
      arc3.Thickness = arc.Thickness;
      toAdd.Add(arc3);
      // Create the fourth arc
      mid = arc.GetPointAtDist(5 * length / 6);
      Arc arc4 =
        circArc2Arc(new CircularArc3d(pt4, mid, pt5));
      arc4.SetPropertiesFrom(arc);
      arc4.Thickness = arc.Thickness;
      toAdd.Add(arc4);
    }
    Arc circArc2Arc(CircularArc3d circArc)
    {
      Point3d center = circArc.Center;
      Vector3d normal = circArc.Normal;
      Vector3d refVec = circArc.ReferenceVector;
      Plane plane = new Plane(center, normal);
      double ang = refVec.AngleOnPlane(plane);
      return new Arc(
        center,
        normal,
        circArc.Radius,
        circArc.StartAngle + ang,
        circArc.EndAngle + ang
      );
    }
    private void Kochize(
      Polyline pl,
      ObjectIdCollection modified,
      ObjectIdCollection toErase,
      List toAdd,
      bool bLeft
    )
    {
      pl.UpgradeOpen();
      if (pl.ObjectId != ObjectId.Null &&
          !modified.Contains(pl.ObjectId))
      {
        modified.Add(pl.ObjectId);
      }
      for(int vn = 0; vn

3gr1yhh1f21.png

3gr1yhh1f21.png


回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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/): "
  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 : "
  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 newEntities =
  96.                 new List(
  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 newerEntities =
  135.                   new List(
  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 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 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 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 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  progressMeterIncrements ?
  398.             totalOps / progressMeterIncrements :
  399.             totalOps
  400.           );
  401.       }
  402.       // This function is called whenever an operation
  403.       // is performed
  404.       public bool Tick()
  405.       {
  406.         if (++currentInc == updateIncrement)
  407.         {
  408.           pm.MeterProgress();
  409.           currentInc = 0;
  410.           System.Windows.Forms.Application.DoEvents();
  411.         }
  412.         // Check whether the filter has set the flag
  413.         if (cancelled)
  414.           pm.Stop();
  415.         return !cancelled;
  416.       }
  417.       // The message filter callback
  418.       public bool PreFilterMessage(
  419.         ref System.Windows.Forms.Message m
  420.       )
  421.       {
  422.         if (m.Msg == WM_KEYDOWN)
  423.         {
  424.           // Check for the Escape keypress
  425.           System.Windows.Forms.Keys kc =
  426.             (System.Windows.Forms.Keys)(int)m.WParam &
  427.             System.Windows.Forms.Keys.KeyCode;
  428.           if (m.Msg == WM_KEYDOWN &&
  429.               kc == System.Windows.Forms.Keys.Escape)
  430.           {
  431.             cancelled = true;
  432.           }
  433.           // Return true to filter all keypresses
  434.           return true;
  435.         }
  436.         // Return false to let other messages through
  437.         return false;
  438.       }
  439.     }
  440.   }
  441. }

回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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:[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 ObjectId m_currentObject;
    private Pen m_pen;
    private Point3d m_position;
    private Vector3d m_direction;
    // Public properties
    public Point3d Position
    {
      get { return m_position; }
      set { m_position = value; }
    }
    public Vector3d Direction
    {
      get { return m_direction; }
      set { m_direction = value; }
    }
    // Constructor
    public TurtleEngine()
    {
      m_pen = new Pen();
      m_currentObject = ObjectId.Null;
      m_position = Point3d.Origin;
      m_direction = new Vector3d(1.0, 0.0, 0.0);
    }
    // Public methods
    public void Turn(double angle)
    {
      // Rotate our direction by the
      // specified angle
      Matrix3d mat =
        Matrix3d.Rotation(
          angle,
          Vector3d.ZAxis,
          Position
        );
      Direction =
        Direction.TransformBy(mat);
    }
    public void Move(double distance)
    {
      // Move the cursor by a specified
      // distance in the direction in
      // which we're pointing
      Point3d oldPos = Position;
      Position += Direction * distance;
      // If the pen is down, we draw something
      if (m_pen.Down)
        GenerateSegment(oldPos, Position);
    }
    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_currentObject =
        ObjectId.Null;
    }
    public void SetPenWidth(double width)
    {
      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_currentObject =
          ObjectId.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;
      Transaction tr =
        tm.StartTransaction();
      using (tr)
      {
        Polyline pl;
        Plane plane;
        // Create the current object, if there is none
        if (m_currentObject == ObjectId.Null)
        {
          BlockTable bt =
            (BlockTable)tr.GetObject(
              db.BlockTableId,
              OpenMode.ForRead
            );
          BlockTableRecord ms =
            (BlockTableRecord)tr.GetObject(
              bt[BlockTableRecord.ModelSpace],
              OpenMode.ForWrite
            );
          // Create the polyline
          pl = new Polyline();
          pl.Color = m_pen.Color;
          // Define its plane
          plane = new Plane(
            pl.Ecs.CoordinateSystem3d.Origin,
            pl.Ecs.CoordinateSystem3d.Zaxis
          );
          // Add the first vertex
          pl.AddVertexAt(
            0, oldPos.Convert2d(plane),
            0.0, m_pen.Width, m_pen.Width
          );
          // Add the polyline to the database
          m_currentObject =
            ms.AppendEntity(pl);
          tr.AddNewlyCreatedDBObject(pl, true);
        }
        else
        {
          // Get the current object, if there is one
          pl =
            (Polyline)tr.GetObject(
              m_currentObject,
              OpenMode.ForWrite
            );
          // Calculate its plane
          plane = new Plane(
            pl.Ecs.CoordinateSystem3d.Origin,
            pl.Ecs.CoordinateSystem3d.Zaxis
          );
        }
        // Now we have our current object open,
        // add the new vertex
        pl.AddVertexAt(
          pl.NumberOfVertices,
          newPos.Convert2d(plane),
          0.0, m_pen.Width, m_pen.Width
        );
        // Display the graphics, to avoid long,
        // black-box operations
        tm.QueueForGraphicsFlush();
        tm.FlushGraphics();
        ed.UpdateScreen();
        tr.Commit();
      }
    }
  }
  public class Commands
  {
    // A command to create some simple geometry
    [CommandMethod("DTG")]
    static public void DrawTurtleGraphics()
    {
      TurtleEngine te = new TurtleEngine();
      // Draw a red circle
      te.SetPenColor(1);
      te.SetPenWidth(7);
      te.PenDown();
      for (int i = 0; i

                               
登录/注册后可看大图


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.
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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:

bkzmj1xyf03.png

bkzmj1xyf03.png


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 : "
  214.         );
  215.       pio.AllowNone = true;
  216.       pio.LowerLimit = 0;
  217.       pio.UpperLimit = 8;
  218.       PromptIntegerResult pir =
  219.         ed.GetInteger(pio);
  220.       if (pir.Status != PromptStatus.OK &&
  221.           pir.Status != PromptStatus.None)
  222.         return;
  223.       if (pir.Status == PromptStatus.OK)
  224.         level = pir.Value;
  225.       Transaction tr =
  226.         doc.TransactionManager.StartTransaction();
  227.       using (tr)
  228.       {
  229.         TurtleEngine te = new TurtleEngine(tr);
  230.         // Draw a Koch snowflake
  231.         te.SetPenColor(0);
  232.         te.SetPenWidth(0);
  233.         te.PenDown();
  234.         // 100 is an arbitrary value:
  235.         // you could also prompt the user for this
  236.         KochIsland(te, 100, level);
  237.         tr.Commit();
  238.       }
  239.     }
  240.   }
  241. }
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. :-)

df0b4oghbyr.png

df0b4oghbyr.png


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

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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 :-)复制代码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:

clcuv0tgt5z.png

clcuv0tgt5z.png


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:

yhyk1ezsmzd.png

yhyk1ezsmzd.png


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 : "
  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 : "
  260.         );
  261.       pio.AllowNone = true;
  262.       PromptIntegerResult pir =
  263.         ed.GetInteger(pio);
  264.       if (pir.Status != PromptStatus.None &&
  265.           pir.Status != PromptStatus.OK)
  266.         return;
  267.       if (pir.Status == PromptStatus.OK)
  268.         variability = pir.Value;
  269.       Transaction tr =
  270.         doc.TransactionManager.StartTransaction();
  271.       using (tr)
  272.       {
  273.         TurtleEngine te = new TurtleEngine(tr);
  274.         // Draw a random fractal tree
  275.         te.Position = position;
  276.         te.SetPenColor(0);
  277.         te.SetPenWidth(0);
  278.         te.Turn(Math.PI / 2);
  279.         te.PenDown();
  280.         RandomTree(te, treeLength, variability);
  281.         tr.Commit();
  282.       }
  283.     }
  284.   }
  285. }
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:

abnbq0t5rfp.png

abnbq0t5rfp.png


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.
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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:复制代码This - when translated to use our turtle engine inside AutoCAD - creates a somewhat straight, unnatural-looking fern:

pfgi24cw21z.png

pfgi24cw21z.png


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 : "
  232.         );
  233.       pdo.AllowNone = true;
  234.       PromptDoubleResult pdr =
  235.         ed.GetDouble(pdo);
  236.       if (pdr.Status != PromptStatus.None &&
  237.           pdr.Status != PromptStatus.OK)
  238.         return false;
  239.       if (pdr.Status == PromptStatus.OK)
  240.         treeLength = pdr.Value;
  241.       else
  242.         treeLength = 100;
  243.       return true;
  244.     }
  245.     [CommandMethod("FF")]
  246.     static public void FractalFern()
  247.     {
  248.       Document doc =
  249.         Application.DocumentManager.MdiActiveDocument;
  250.       double fernLength;
  251.       Point3d position;
  252.       if (!GetFernInfo(out position, out fernLength))
  253.         return;
  254.       Transaction tr =
  255.         doc.TransactionManager.StartTransaction();
  256.       using (tr)
  257.       {
  258.         TurtleEngine te = new TurtleEngine(tr);
  259.         // Draw a fractal fern
  260.         te.Position = position;
  261.         te.SetPenColor(92);
  262.         te.SetPenWidth(0);
  263.         te.Turn(Math.PI / 2);
  264.         te.PenDown();
  265.         Fern(te, fernLength);
  266.         tr.Commit();
  267.       }
  268.     }
  269.   }
  270. }
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:

vvdgebykt1t.png

vvdgebykt1t.png


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

tngbfpqk13s.png

tngbfpqk13s.png


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).
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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
        {
          for (int j=0; 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 : "
        );
      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 : "
        );
      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:

gf2dqdl4sxe.png

gf2dqdl4sxe.png


Now for 3D:

araqsxyujdg.png

araqsxyujdg.png


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

pcnyy3to2jt.png

pcnyy3to2jt.png


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.
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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 : "
  258.         );
  259.       pdo.AllowNone = true;
  260.       PromptDoubleResult pdr =
  261.         ed.GetDouble(pdo);
  262.       if (pdr.Status != PromptStatus.None &&
  263.           pdr.Status != PromptStatus.OK)
  264.         return false;
  265.       if (pdr.Status == PromptStatus.OK)
  266.         size = pdr.Value;
  267.       else
  268.         size = 100;
  269.       PromptIntegerOptions pio =
  270.         new PromptIntegerOptions(
  271.           "\nEnter level : "
  272.         );
  273.       pio.AllowNone = true;
  274.       pio.LowerLimit = 1;
  275.       pio.UpperLimit = 10;
  276.       PromptIntegerResult pir =
  277.         ed.GetInteger(pio);
  278.       if (pir.Status != PromptStatus.None &&
  279.           pir.Status != PromptStatus.OK)
  280.         return false;
  281.       if (pir.Status == PromptStatus.OK)
  282.         level = pir.Value;
  283.       else
  284.         level = 5;
  285.       return true;
  286.     }
  287.     private static void Hilbert(
  288.       TurtleEngine te, double size, int level)
  289.     {
  290.       if (level > 0)
  291.       {
  292.         int newLevel = level - 1;
  293.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  294.         te.Roll(Math.PI / -2);        // Left Roll 90
  295.         Hilbert(te, size, newLevel);  // Recurse
  296.         te.Move(size);                // Forward Size
  297.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  298.         te.Roll(Math.PI / -2);        // Left Roll 90
  299.         Hilbert(te, size, newLevel);  // Recurse
  300.         te.Move(size);                // Forward Size
  301.         Hilbert(te, size, newLevel);  // Recurse
  302.         te.Turn(Math.PI / -2);        // Left Turn 90
  303.         te.Move(size);                // Forward Size
  304.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  305.         te.Roll(Math.PI / 2);         // Right Roll 90
  306.         te.Roll(Math.PI / 2);         // Right Roll 90
  307.         Hilbert(te, size, newLevel);  // Recurse
  308.         te.Move(size);                // Forward Size
  309.         Hilbert(te, size, newLevel);  // Recurse
  310.         te.Pitch(Math.PI / 2);        // Up Pitch 90
  311.         te.Move(size);                // Forward Size
  312.         te.Turn(Math.PI / 2);         // Right Turn 90
  313.         te.Roll(Math.PI / 2);         // Right Roll 90
  314.         te.Roll(Math.PI / 2);         // Right Roll 90
  315.         Hilbert(te, size, newLevel);  // Recurse
  316.         te.Move(size);                // Forward Size
  317.         Hilbert(te, size, newLevel);  // Recurse
  318.         te.Turn(Math.PI / -2);        // Left Turn 90
  319.         te.Move(size);                // Forward Size
  320.         te.Roll(Math.PI / 2);         // Right Roll 90
  321.         Hilbert(te, size, newLevel);  // Recurse
  322.         te.Turn(Math.PI / -2);        // Left Turn 90
  323.         te.Roll(Math.PI / 2);         // Right Roll 90
  324.       }
  325.     }
  326.     [CommandMethod("DH")]
  327.     static public void DrawHilbert()
  328.     {
  329.       Document doc =
  330.         Application.DocumentManager.MdiActiveDocument;
  331.       double size;
  332.       int level;
  333.       Point3d position;
  334.       if (!GetHilbertInfo(out position, out size, out level))
  335.         return;
  336.       Transaction tr =
  337.         doc.TransactionManager.StartTransaction();
  338.       using (tr)
  339.       {
  340.         TurtleEngine te = new TurtleEngine(tr);
  341.         // Draw a Hilbert cube
  342.         te.Position = position;
  343.         te.PenDown();
  344.         Hilbert(te, size / CubesPerLevel(level), level);
  345.         tr.Commit();
  346.       }
  347.     }
  348.   }
  349. }
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.
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 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 : "
  325.         );
  326.       pdo.AllowNone = true;
  327.       PromptDoubleResult pdr =
  328.         ed.GetDouble(pdo);
  329.       if (pdr.Status != PromptStatus.None &&
  330.           pdr.Status != PromptStatus.OK)
  331.         return false;
  332.       if (pdr.Status == PromptStatus.OK)
  333.         size = pdr.Value;
  334.       else
  335.         size = 100;
  336.       PromptIntegerOptions pio =
  337.         new PromptIntegerOptions(
  338.           "\nEnter level : "
  339.         );
  340.       pio.AllowNone = true;
  341.       pio.LowerLimit = 1;
  342.       pio.UpperLimit = 10;
  343.       PromptIntegerResult pir =
  344.         ed.GetInteger(pio);
  345.       if (pir.Status != PromptStatus.None &&
  346.           pir.Status != PromptStatus.OK)
  347.         return false;
  348.       if (pir.Status == PromptStatus.OK)
  349.         level = pir.Value;
  350.       else
  351.         level = 5;
  352.       return true;
  353.     }
  354.     private static void Hilbert(
  355.       TurtleEngine te, double size, int level)
  356.     {
  357.       if (level > 0)
  358.       {
  359.         te.SetPenColor(level);
  360.         int newLevel = level - 1;
  361.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  362.         te.Roll(Math.PI / -2);        // Left Roll 90
  363.         Hilbert(te, size, newLevel);  // Recurse
  364.         te.SetPenColor(level);
  365.         te.Move(size);                // Forward Size
  366.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  367.         te.Roll(Math.PI / -2);        // Left Roll 90
  368.         Hilbert(te, size, newLevel);  // Recurse
  369.         te.SetPenColor(level);
  370.         te.Move(size);                // Forward Size
  371.         Hilbert(te, size, newLevel);  // Recurse
  372.         te.Turn(Math.PI / -2);        // Left Turn 90
  373.         te.Move(size);                // Forward Size
  374.         te.Pitch(Math.PI / -2);       // Down Pitch 90
  375.         te.Roll(Math.PI / 2);         // Right Roll 90
  376.         te.Roll(Math.PI / 2);         // Right Roll 90
  377.         Hilbert(te, size, newLevel);  // Recurse
  378.         te.SetPenColor(level);
  379.         te.Move(size);                // Forward Size
  380.         Hilbert(te, size, newLevel);  // Recurse
  381.         te.SetPenColor(level);
  382.         te.Pitch(Math.PI / 2);        // Up Pitch 90
  383.         te.Move(size);                // Forward Size
  384.         te.Turn(Math.PI / 2);         // Right Turn 90
  385.         te.Roll(Math.PI / 2);         // Right Roll 90
  386.         te.Roll(Math.PI / 2);         // Right Roll 90
  387.         Hilbert(te, size, newLevel);  // Recurse
  388.         te.SetPenColor(level);
  389.         te.Move(size);                // Forward Size
  390.         Hilbert(te, size, newLevel);  // Recurse
  391.         te.SetPenColor(level);
  392.         te.Turn(Math.PI / -2);        // Left Turn 90
  393.         te.Move(size);                // Forward Size
  394.         te.Roll(Math.PI / 2);         // Right Roll 90
  395.         Hilbert(te, size, newLevel);  // Recurse
  396.         te.SetPenColor(level);
  397.         te.Turn(Math.PI / -2);        // Left Turn 90
  398.         te.Roll(Math.PI / 2);         // Right Roll 90
  399.       }
  400.     }
  401.     [CommandMethod("DH")]
  402.     static public void DrawHilbert()
  403.     {
  404.       Document doc =
  405.         Application.DocumentManager.MdiActiveDocument;
  406.       double size;
  407.       int level;
  408.       Point3d position;
  409.       if (!GetHilbertInfo(out position, out size, out level))
  410.         return;
  411.       int cbl = CubesPerLevel(level);
  412.       Transaction tr =
  413.         doc.TransactionManager.StartTransaction();
  414.       using (tr)
  415.       {
  416.         TurtleEngine te = new TurtleEngine(tr);
  417.         using (te)
  418.         {
  419.           // Draw a Hilbert cube
  420.           te.Position = position;
  421.           te.SetPenWidth(10.0 / cbl);
  422.           te.PenDown();
  423.           Hilbert(te, size / cbl, level);
  424.         }
  425.         tr.Commit();
  426.       }
  427.     }
  428.   }
  429. }
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.
回复

使用道具 举报

发表回复

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

本版积分规则

  • 微信公众平台

  • 扫描访问手机版

  • 点击图片下载手机App

QQ|关于我们|小黑屋|乐筑天下 繁体中文

GMT+8, 2025-6-28 19:09 , Processed in 0.420988 second(s), 75 queries .

© 2020-2025 乐筑天下

联系客服 关注微信 帮助中心 下载APP 返回顶部 返回列表