Using an AutoCAD 2010 overrule to control the copying of XData using .NET

It's quite common for AutoCAD developers to use Extended Entity Data (XData) to tag objects their application cares about. This is certainly the approach taken in the recent series showing how to overrule the graphics display for an object – we store a "pipe radius" in XData attached to the Line or Circle we want to have a 3D profile.

That's fine, but what if we're storing a unique identifier with an object in XData that we do not want copied with the object? The Overrule API in AutoCAD 2010 allows you to hook into the DeepClone of an object and control what data gets copied with the object, whether via the COPY command or some other process that chooses to copy your object.

Here's an example CopyOverrule class that makes use of the XData functions we previously defined as part of the PipeDrawOverrule class:

public class CopyOverrule : ObjectOverrule

{

  static public CopyOverrule theOverrule =

    new CopyOverrule();

 

  public override DBObject DeepClone(

    DBObject dbObject, DBObject ownerObject,

    IdMapping idMap, bool isPrimary

  )

  {

    // First we deep clone the object via the parent

 

    DBObject res =

      base.DeepClone(dbObject, ownerObject, idMap, isPrimary);

 

    // Then we check for our XData

 

    if (PipeDrawOverrule.PipeRadiusForObject(res) > 0.0)

    {

      // A transaction is needed by the set function to access

      // the RegApp table - we could also assume the app name

      // is registered and have a separate implementation

      // not taking the transaction...       

      // Just as we might also have chosen to remove the XData

 

      Transaction tr =

        dbObject.Database.TransactionManager.StartTransaction();

      using (tr)

      {

        PipeDrawOverrule.SetPipeRadiusOnObject(tr, res, 0.0);

        tr.Commit();

      }

    }

    return res;

  }

}

In this code we call the DeepClone implementation in the parent class and set the result – the copy of our object – to the res variable. If there any additional
objects were copied during the DeepClone process, there should be IdPair entries in the idMap variable referring to both the originator and the copy. In this code we just check the XData on the returned object, but we might also iterate through and check the contents of the idMap.

Also, rather than removing the XData – which some people might prefer to do, depending on the behaviour of their application – we just set it to 0. Which is enough to make sure the object's graphic display doesn't make use of a pipe radius.

Here's the complete code, including the code to register and unregister the overrule in the OVERRULE0 and OVERRULE1 commands:

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 DrawOverrules

{

  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."

 &
#160;            );

            }

            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 CopyOverrule : ObjectOverrule

  {

    static public CopyOverrule theOverrule =

      new CopyOverrule();

 

    public override DBObject DeepClone(

      DBObject dbObject, DBObject ownerObject,

      IdMapping idMap, bool isPrimary

    )

    {

      // First we deep clone the object via the parent

 

      DBObject res =

        base.DeepClone(dbObject, ownerObject, idMap, isPrimary);

 

      // Then we check for our XData

 

      if (PipeDrawOverrule.PipeRadiusForObject(res) > 0.0)

      {

        // A transaction is needed by the set function to access

        // the RegApp table - we could also assume the app name

        // is registered and have a separate
implementation

        // not taking the transaction...       

        // Just as we might also have chosen to remove the XData

 

        Transaction tr =

          dbObject.Database.TransactionManager.StartTransaction();

        using (tr)

        {

          PipeDrawOverrule.SetPipeRadiusOnObject(tr, res, 0.0);

          tr.Commit();

        }

      }

      return res;

    }

  }

 

  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(

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

      );

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Entity)),

        CopyOverrule.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

      );

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Entity)),

        CopyOverrule.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)

      {

        // Loop through the selected objects

 

        foreach (SelectedObject o in ss)

        {

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

      }

    }

  }

}

Let's see what happens when we attempt to copy a circular pipe using the standard COPY command (after having first created it using MP and have it display using OVERRULE1). We can see that as we select the location, the circle is displayed with a pipe radius:

Copying an overruled circle

But once we specify the location, the circle's radius is removed:

Overruled copy of the overruled object

9 responses to “Using an AutoCAD 2010 overrule to control the copying of XData using .NET”

  1. Hi,Kean
    How to access object-specific data through AutoCAD’s Properties Palette?

  2. Kean Walmsley Avatar
    Kean Walmsley

    Hi csharpbird,

    I've finally got around to implementing this (thanks for the reminder), so I'll go ahead and post it in the next few days.

    Regards,

    Kean

  3. Kean,

    Good stuff mate - keep it coming! Properties palette will be interesting...looking forward to it.

    Cheers,
    Glenn.

  4. HI,
    This method of deepclonning works in the same database.
    But how make a deepcloning betwen different database?

  5. Copying betwen databases is best done via wblock (usually Database.WblockCloneObjects()) and insert (Database.Insert()).

    If you have follow-up questions on the topic, please submit them to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Kean

  6. Kean,
    You mention iterating through the contents of the IDmap.
    I'd like to check the contents of the idMap when cloning to see what objectID's it contains and if they should be cloned.

    idMap.Item(Key as objectID)
    idMap.Contains(Key as objectID)

    Both ask for the objectIDs which is what I want to check for.

  7. Sorry..I did try the idmap.Contains(ojbID) but it didn't seem to accept that it contained an objectID when it should have. I can't access the contents of the idmap to find out why.

  8. Hi Sam,

    From what I recall you can iterate through an idMap using this approach:

    foreach (KeyValuePair kvp in idMap)
    {
    ...
    }

    Kean

  9. Thank you for this code.

Leave a Reply

Your email address will not be published. Required fields are marked *