October 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  










« Optimized overruling in AutoCAD 2010 using .NET | Main | SDI, the end is nigh »

April 16, 2009

Overruling explode in AutoCAD 2010 using .NET

I am really starting to love the new Overrule API in AutoCAD 2010, and I still feel as though I’m just scratching the surface. This question came in overnight from Danny Polkinhorn (thanks, Danny! :-) :

It's exciting to see a very usable implementation of 'custom' objects in .NET. Obviously, this implementation protects what could be proprietary business intelligence from being sent around, but it brings up a question. What process would you use to 'explode' these elements so that you could send the drawing to someone without your code, but with the custom elements in it?

My first thought was to just see what happened when the EXPLODE command encountered the overruled lines & circles created with the code in the last post. At first I was a little discouraged:

Command: EXPLODE

Select objects: Specify opposite corner: 9 found

9 were not able to be exploded.

Select objects: *Cancel*

None found.

My worry, at this stage, was that the EXPLODE command was excluding “simple” objects such as lines and circles during its selection process, which would mean that even if we were able to overrule the explode behaviour of our special lines & circles we would have difficulty getting our code called. Thankfully this is not the case, but I only found this out as I implemented a TransformOverrule and overruled Explode for my lines and circles.

Before looking into the specifics, here’s a quick list of the various Overrules available in AutoCAD 2010 with their overrideable methods:

  • Overrule (the base class)
    • GripOverrule
      • GetGripPoints
      • GetStretchPoints
      • MoveGripPointsAt
      • MoveStretchPointsAt
      • OnGripStatusChanged
    • OsnapOverrule
      • GetObjectSnapPoints
      • IsContentSnappable
    • SubentityOverrule
      • AddSubentPaths
      • DeleteSubentPaths
      • GetCompoundObjectTransform
      • GetGripPointsAtSubentPath
      • GetGsMarkersAtSubentPath
        GetSubentClassId
      • GetSubentPathGeomExtents
      • GetSubentPathsAtGsMarker
      • MoveGripPointsAtSubentPaths
      • OnSubentGripStatusChanged
      • SubentPtr
      • TransformSubentPathsBy
    • TransformOverrule
      • CloneMeForDragging
      • Explode
      • GetTransformedCopy
      • HideMeForDragging
      • TransformBy
    • GeometryOverrule
      • GetGeomExtents
      • IntersectWith
    • PropertiesOverrule
      • GetClassID
      • List
    • ObjectOverrule
      • Cancel
      • Close
      • DeepClone
      • Erase
      • Open
      • WblockClone
    • DrawableOverrule
      • SetAttributes
      • ViewportDraw
      • ViewportDrawLogicalFlags
      • WorldDraw

So far we’ve only actually used the DrawableOverrule, implementing SetAttributes() and WorldDraw().

[Aside: as I compiled the above list I had one of those “woah” moments. It was bit like the feeling I remember as a child while reading “The Elfstones of Shannara”, the – much better – sequel to “The Sword of Shannara” (Terry Brooks’ first fantasy novel). In “The Sword of Shannara” the main character used some elfstones for protection, but it’s only in the sequel that you find out just how powerful they are, and that they’re actually only one of several different varieties of elfstones. Anyone who hasn’t been a fantasy/sci-fi geek in their time is almost certainly rolling their eyes (if they’re not being physically sick) at this point, so I’d better get back on track. I still enjoy a good sci-fi novel, by the way, but these days I find I can’t do fantasy that isn’t Tolkien.]

It’s clear that to customize the explode for our lines and circles we’re going to need a TransformOverrule.

Here’s the C# code from the last post, with our additional TransformOverrules. I’ve changed a few of the class names to reflect the fact they are overrule draw behaviour vs. transform behaviour, but otherwise the code is basically the same as last time, apart from the addition of the LinePipeTransformOverrule and CirclePipeTransformOverrule classes (and the code to register/unregister them).

The Explode functions take pretty much the same geometry we used to draw the “enhanced” objects, but this time it gets returned to the system and is used to replace the original entities. This code doesn’t do anything fancy about the entity colour, etc., but we could certainly do so, if we chose.

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 Overrules

{

  public abstract class PipeDrawOverrule : DrawableOverrule

  {

    const string regAppName = "TTIF_PIPE";

 

    public PipeDrawOverrule()

    {

      // Tell AutoCAD to filter on our application name

      // (this means our overrule will only be called

      // on objects possessing XData with this name)

 

      SetXDataFilter(regAppName);

    }

 

    // 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();

    }

  }

 

  // An overrule to make a pipe out of line

 

  public class LinePipeDrawOverrule : PipeDrawOverrule

  {

    static public LinePipeDrawOverrule theOverrule =

      new LinePipeDrawOverrule();

 

    private SweepOptions sweepOpts = new SweepOptions();

 

    public override bool WorldDraw(Drawable d, WorldDraw wd)

    {

      double radius = 0.0;

 

      if (d is DBObject)

        radius = PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        Line line = d as Line;

 

        if (line != null)

        {

          // 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;

        }

      }

      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 = PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        // Set color to index 6

 

        t.Color = 6;

 

        // and lineweight to .40 mm

 

        t.LineWeight = LineWeight.LineWeight040;

      }

      return b;

    }

  }

 

  // An overrule to make a pipe out of circle

 

  public class CirclePipeDrawOverrule : PipeDrawOverrule

  {

    static public CirclePipeDrawOverrule theOverrule =

      new CirclePipeDrawOverrule();

 

    private SweepOptions sweepOpts = new SweepOptions();

 

    public override bool WorldDraw(Drawable d, WorldDraw wd)

    {

      double radius = 0.0;

 

      if (d is DBObject)

        radius = PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        Circle circle = d as Circle;

 

        if (circle != null)

        {

          // 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 = PipeRadiusForObject((DBObject)d);

 

      if (radius > 0.0)

      {

        // Set color to index 2

 

        t.Color = 2;

 

        // and lineweight to .60 mm

 

        t.LineWeight = LineWeight.LineWeight060;

      }

      return b;

    }

  }

 

  public class LinePipeTransformOverrule : TransformOverrule

  {

    static public LinePipeTransformOverrule theOverrule =

      new LinePipeTransformOverrule();

 

    private SweepOptions sweepOpts = new SweepOptions();

 

    public override void Explode(Entity e, DBObjectCollection objs)

    {

      double radius = 0.0;

 

      if (e is DBObject)

        radius = PipeDrawOverrule.PipeRadiusForObject(e);

 

      if (radius > 0.0)

      {

        Line line = e as Line;

 

        if (line != null)

        {

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

          {

            // Draw a pipe around the line

 

            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();

            objs.Add(pipe);

          }

          return;

        }

      }

      base.Explode(e, objs);

    }

  }

 

  public class CirclePipeTransformOverrule : TransformOverrule

  {

    static public CirclePipeTransformOverrule theOverrule =

      new CirclePipeTransformOverrule();

 

    private SweepOptions sweepOpts = new SweepOptions();

 

    public override void Explode(Entity e, DBObjectCollection objs)

    {

      double radius = 0.0;

 

      if (e is DBObject)

        radius = PipeDrawOverrule.PipeRadiusForObject(e);

 

      if (radius > 0.0)

      {

        Circle circle = e as Circle;

 

        if (circle != null)

        {

          // Needed to avoid ill-formed swept surface

 

          if (circle.Radius > radius)

          {

            // Draw a pipe around the cirle

 

            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();

            objs.Add(pipe);

          }

          return;

        }

      }

      base.Explode(e, objs);

    }

  }

 

  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(Line)),

        LinePipeDrawOverrule.theOverrule,

        true

      );

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeDrawOverrule.theOverrule,

        true

      );

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeTransformOverrule.theOverrule,

        true

      );

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeTransformOverrule.theOverrule,

        true

      );

      Overrule(true);

    }

 

    [CommandMethod("OVERRULE0")]

    public void OverruleEnd()

    {

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeDrawOverrule.theOverrule

      );

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeDrawOverrule.theOverrule

      );

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeTransformOverrule.theOverrule

      );

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeTransformOverrule.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);

          PipeDrawOverrule.SetPipeRadiusOnObject(tr, obj, _radius);

        }

        tr.Commit();

      }

    }

  }

}

As before we can create a bunch of lines and circles, assign them radii using the MP command, and then display them using the OVERRULE1 command:

Overruled lines and circles

Now when we perform our EXPLODE, selecting the various objects, and instead of a rejection message we get a set of Solid3D objects replacing our lines and circles:

Exploded overruled lines and circles

Too cool! :-)

As you can hopefully see, the more you look at the Overrule API in AutoCAD 2010, the more power you uncover. Keep the comments & questions coming – it’s fun to see how this little application is evolving based on your feedback.

TrackBack

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

Listed below are links to weblogs that reference Overruling explode in AutoCAD 2010 using .NET:

blog comments powered by Disqus

Feed/Share

10 Random Posts