Kean Walmsley


  • About the Author
    Kean on Google+

July 2014

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








« Customizing the display of standard AutoCAD objects using .NET | Main | Optimized overruling in AutoCAD 2010 using .NET »

April 10, 2009

Let’s get specific: customizing the display of individual AutoCAD objects using .NET

In the last post we looked at some C# code to customize the display of all Lines and Circles within AutoCAD, adding a thickness (or a diameter) to make them look more like pipes. This was, in turn, based on this F# post.

The previous code implemented an overrule that allowed us to insert our own graphics for every instance of the types of object we cared about.

[A quick note on the previous implementation: we actually register the overrule to be called for all “drawable” objects: the inheritance tree for the Circle class is: Drawable –> DBObject –> Entity –> Curve –> Circle. Drawable belongs to the Autodesk.AutoCAD.GraphicsInterface namespace while all the others are db-resident and therefore belong to Autodesk.AutoCAD.DatabaseServices. Within our “drawable overrule” we check the type of the object before proceeding: right now we only care about Lines and Circles, but this could clearly be extended to handle other types of object, also.]

In this post we take this a step further. Rather than apply a single, arbitrary radius to every Line and Circle in the drawing, we’re going to attach the radius as extended entity data (XData) to the individual objects we care about and then during the WorldDraw() of our overrule we will attempt to retrieve that data and – if it exists – use it to apply an object—specific radius. Because XData is part of an object’s persistent data, this will also work across drawing sessions (assuming our application is re-loaded and the overrule is turned on).

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

using Autodesk.AutoCAD.Colors;

 

namespace DrawOverrule

{

  public class XDataHelpers

  {

    const string regAppName = "TTIF_PIPE";

 

    // Get the XData for a particular object

    // and return the "pipe radius" if it exists

 

    public static double PipeRadiusForObject(DBObject obj)

    {

      double res = 0.0;

 

      ResultBuffer rb = obj.XData;

      if (rb != null)

      {

        bool foundStart = false;

 

        foreach (TypedValue tv in rb)

        {

          if (tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName &&

              tv.Value.ToString() == regAppName)

            foundStart = true;

          else

          {

            if (foundStart == true)

            {

              if (tv.TypeCode == (int)DxfCode.ExtendedDataReal)

              {

                res = (double)tv.Value;

                break;

              }

            }

          }

        }

        rb.Dispose();

      }

      return res;

    }

 

    // Set the "pipe radius" in the XData of a particular object

 

    public static void SetPipeRadiusOnObject(

      Transaction tr, DBObject obj, double radius

    )

    {

      Database db = obj.Database;

 

      // Make sure the application is registered

      // (we could separate this out to be called

      // only once for a set of operations)

 

      RegAppTable rat =

        (RegAppTable)tr.GetObject(

          db.RegAppTableId,

          OpenMode.ForRead

        );

 

      if (!rat.Has(regAppName))

      {

        rat.UpgradeOpen();

        RegAppTableRecord ratr = new RegAppTableRecord();

        ratr.Name = regAppName;

        rat.Add(ratr);

        tr.AddNewlyCreatedDBObject(ratr, true);

      }

 

      // Create the XData and set it on the object

 

      ResultBuffer rb =

        new ResultBuffer(

          new TypedValue(

            (int)DxfCode.ExtendedDataRegAppName, regAppName

          ),

          new TypedValue(

            (int)DxfCode.ExtendedDataReal, radius

          )

        );

      obj.XData = rb;

      rb.Dispose();

    }

  }

 

  public class DrawOverrule : DrawableOverrule

  {

    static public DrawOverrule theOverrule =

      new DrawOverrule();

 

    private SweepOptions sweepOpts = new SweepOptions();

 

    public override bool WorldDraw (Drawable d, WorldDraw wd)

    {

      double radius = 0.0;

 

      if (d is DBObject)

        radius = XDataHelpers.PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        if (d is Line)

        {

          Line line = (Line)d;

 

          // Draw the line as is, with overruled attributes

 

          base.WorldDraw(line, wd);

          if (!line.Id.IsNull && line.Length > 0.0)

          {

            // Draw a pipe around the line

 

            EntityColor c =

              wd.SubEntityTraits.TrueColor;

            wd.SubEntityTraits.TrueColor =

              new EntityColor(0x00AfAfff);

            wd.SubEntityTraits.LineWeight =

              LineWeight.LineWeight000;

            Circle clr =

              new Circle(

                line.StartPoint,

                line.EndPoint - line.StartPoint,

                radius

              );

            ExtrudedSurface pipe = new ExtrudedSurface();

            try

            {

              pipe.CreateExtrudedSurface(

                clr, line.EndPoint - line.StartPoint, sweepOpts

              );

            }

            catch

            {

              Document doc =

                Application.DocumentManager.MdiActiveDocument;

              doc.Editor.WriteMessage(

                "\nFailed with CreateExtrudedSurface."

              );

            }

            clr.Dispose();

            pipe.WorldDraw(wd);

            pipe.Dispose();

            wd.SubEntityTraits.TrueColor = c;

          }

          return true;

        }

        else if (d is Circle)

        {

          Circle circle = (Circle)d;

 

          // Draw the circle as is, with overruled attributes

 

          base.WorldDraw(circle, wd);

 

          // Needed to avoid ill-formed swept surface

 

          if (circle.Radius > radius)

          {

            // Draw a pipe around the cirle

 

            EntityColor c = wd.SubEntityTraits.TrueColor;

            wd.SubEntityTraits.TrueColor =

              new EntityColor(0x3fffe0e0);

            wd.SubEntityTraits.LineWeight =

              LineWeight.LineWeight000;

            Vector3d normal =

              (circle.Center - circle.StartPoint).

                CrossProduct(circle.Normal);

            Circle clr =

              new Circle(

                circle.StartPoint, normal, radius

              );

            SweptSurface pipe = new SweptSurface();

            pipe.CreateSweptSurface(clr, circle, sweepOpts);

            clr.Dispose();

            pipe.WorldDraw(wd);

            pipe.Dispose();

            wd.SubEntityTraits.TrueColor = c;

          }

          return true;

        }

      }

      return base.WorldDraw(d, wd);

    }

 

    public override int SetAttributes(Drawable d, DrawableTraits t)

    {

      int b = base.SetAttributes(d, t);

 

      double radius = 0.0;

 

      if (d is DBObject)

        radius = XDataHelpers.PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        if (d is Line)

        {

          // If d is LINE, set color to index 6

 

          t.Color = 6;

 

          // and lineweight to .40 mm

 

          t.LineWeight = LineWeight.LineWeight040;

        }

        else if (d is Circle)

        {

          // If d is CIRCLE, set color to index 2

 

          t.Color = 2;

 

          // and lineweight to .60 mm

 

          t.LineWeight = LineWeight.LineWeight060;

        }

      }

      return b;

    }

  }

 

  public class Commands

  {

    private double _radius = 0.0;

 

    public void Overrule(bool enable)

    {

      // Regen to see the effect

      // (turn on/off Overruling and LWDISPLAY)

 

      DrawableOverrule.Overruling = enable;

      if (enable)

        Application.SetSystemVariable("LWDISPLAY", 1);

      else

        Application.SetSystemVariable("LWDISPLAY", 0);

 

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      doc.SendStringToExecute("REGEN3\n", true, false, false);

      doc.Editor.Regen();

    }

 

    [CommandMethod("OVERRULE1")]

    public void OverruleStart()

    {

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Drawable)),

        DrawOverrule.theOverrule,

        true

      );

      Overrule(true);

    }

 

    [CommandMethod("OVERRULE0")]

    public void OverruleEnd()

    {

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Drawable)),

        DrawOverrule.theOverrule

      );

      Overrule(false);

    }

 

    [CommandMethod("MP", CommandFlags.UsePickSet)]

    public void MakePipe()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Ask the user to select the entities to make into pipes

 

      PromptSelectionOptions pso =

        new PromptSelectionOptions();

      pso.AllowDuplicates = false;

      pso.MessageForAdding =

        "\nSelect objects to turn into pipes: ";

 

      PromptSelectionResult selRes =

        doc.Editor.GetSelection(pso);

 

      // If the user didn't make valid selection, we return

 

      if (selRes.Status != PromptStatus.OK)

        return;

 

      SelectionSet ss = selRes.Value;

 

      // Ask the user for the pipe radius to set

 

      PromptDoubleOptions pdo =

        new PromptDoubleOptions(

          "\nSpecify pipe radius:"

        );

 

      // Use the previous value, if if already called

 

      if (_radius > 0.0)

      {

        pdo.DefaultValue = _radius;

        pdo.UseDefaultValue = true;

      }

      pdo.AllowNegative = false;

      pdo.AllowZero = false;

 

      PromptDoubleResult pdr =

        ed.GetDouble(pdo);

 

      // Return if something went wrong

 

      if (pdr.Status != PromptStatus.OK)

        return;

 

      // Set the "last radius" value for when

      // the command is called next

 

      _radius = pdr.Value;

 

      // Use a transaction to edit our various objects

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Lop through the selected objects

 

        foreach (SelectedObject o in ss)

        {

          // We could choose only to add XData to the objects

          // we know will use it (Lines and Circles, for now)

 

          DBObject obj =

            tr.GetObject(o.ObjectId, OpenMode.ForWrite);

          XDataHelpers.SetPipeRadiusOnObject(tr, obj, _radius);

        }

        tr.Commit();

      }

    }

  }

}

The core overrule has changed very little – we’ve just added calls to our helper functions to retrieve XData that has been added through the use of the new MP command – but the OverruleEnd() function (the implementation of the OVERRULE0 command) has been modified to properly remove the overrule when called. This allows the overrule to be re-added without a “duplicate key” error.

Here’s some simple geometry for which we’re going to overrule the display:

2D wireframe display of individual lines and circles

When we run the OVERRULE1 command to add our overrule and then use the MP command to add XData to the entities we want to turn into pipes (we can do this in the reverse order, also), we can see that we can selectively apply radii to individual objects in the AutoCAD drawing (displayed here using the conceptual visual style):

3D conceptual display of overruled individual lines and circles

A comment came in on a previous post about accessing object-specific data through AutoCAD’s Properties Palette: what I’ll probably do in one of the coming posts is to show how to connect a dynamic property in the Properties Palette with our “pipe radius” XData, to show how this is possible.

Update:

A more efficient version of the code provided in this post can be found here.

TrackBack

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

Listed below are links to weblogs that reference Let’s get specific: customizing the display of individual AutoCAD objects using .NET:

blog comments powered by Disqus

10 Random Posts