December 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      










« Using an AutoCAD 2010 overrule to control the copying of XData using .NET | Main | Last day of voting for AU 2009 classes »

May 06, 2009

Modifying an AutoCAD object’s state via a dynamic property defined using .NET

I’ve been meaning to get to this one for a while. This post takes the OPM .NET implementation and shows how to use it to allow modification of data persisted with an object: in this case we’re going to use the XData in which we store the “pipe radius” for the AutoCAD 2010 overrule sample we’ve recently been developing.

To start with, I needed to migrate the OPM .NET module to work with AutoCAD 2010, which meant installing Visual Studio 2008 SP1. Other than that the code migrated very easily, and the project (with the built asdkOPMNetExt.dll assembly) can be found here. I recommend placing the module in AutoCAD’s main program folder and having it demand-load on AutoCAD startup (if you choose to use it).

[A quick comment on that, as I know some people dislike doing this... it's highly recommended to place your .NET assemblies in AutoCAD's main program folder: you will avoid a whole category of subtle problems by doing so. You needn't feel it's dangerous as long as you prefix the filename of each of your modules with your Registered Developer Symbol (RDS).]

Here’s the C# code to add to our overrule application:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows.OPM;

using Autodesk.AutoCAD.Interop.Common;

using System.Runtime.InteropServices;

using System.Reflection;

using System;

using DrawOverrules;

 

namespace PropertyEditing

{

  #region Our Custom Property

  [

    Guid("E64CAA14-EA92-46ea-82D6-420FA873F16F"),

    ProgId("OverruleSample.PipeRadius.1"),

    ClassInterface(ClassInterfaceType.None),

    ComDefaultInterface(typeof(IDynamicProperty2)),

    ComVisible(true)

  ]

  public class CustomProp : IDynamicProperty2

  {

    private IDynamicPropertyNotify2 m_pSink = null;

 

    // Unique property ID

 

    public void GetGUID(out Guid propGUID)

    {

      propGUID =

        new Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB");

    }

 

    // Property display name

 

    public void GetDisplayName(out string szName)

    {

      szName = "Pipe radius";

    }

 

    // Show/Hide property in the OPM, for this object instance

 

    public void IsPropertyEnabled(object pUnk, out int bEnabled)

    {

      bEnabled = 1;

    }

 

    // Is property showing but disabled

 

    public void IsPropertyReadOnly(out int bReadonly)

    {

      bReadonly = 0;

    }

 

    // Get the property description string

 

    public void GetDescription(out string szName)

    {

      szName =

        "Radius of the pipe profile applied to this linear entity.";

    }

 

    // OPM will typically display these in an edit field

    // optional: meta data representing property type name,

    // ex. ACAD_ANGLE

 

    public void GetCurrentValueName(out string szName)

    {

      throw new System.NotImplementedException();

    }

 

    // What is the property type, ex. VT_R8

 

    public void GetCurrentValueType(out ushort varType)

    {

      // The Property Inspector supports the following data

      // types for dynamic properties:

      // VT_I2, VT_I4, VT_R4, VT_R8,VT_BSTR, VT_BOOL

      // and VT_USERDEFINED.

 

      varType = 4; // VT_R4?

    }

 

    // Get the property value, passes the specific object

    // we need the property value for.

 

    public void GetCurrentValueData(object pUnk, ref object pVarData)

    {

      // Get the value and return it to AutoCAD

 

      AcadObject obj = pUnk as AcadObject;

      if (obj != null)

      {

        Document doc =

          Application.DocumentManager.MdiActiveDocument;

        Transaction tr =

          doc.TransactionManager.StartTransaction();

        using (tr)

        {

          DBObject o =

            tr.GetObject(

              new ObjectId((IntPtr)obj.ObjectID),

              OpenMode.ForRead

            );

          pVarData =

            PipeDrawOverrule.PipeRadiusForObject(o);

        }

      }

      else

        pVarData = 0.0;

    }

 

    // Set the property value, passes the specific object we

    // want to set the property value for

 

    public void SetCurrentValueData(object pUnk, object varData)

    {

      // Save the value returned to you

 

      AcadObject obj = pUnk as AcadObject;

      if (obj != null)

      {

        Document doc =

          Application.DocumentManager.MdiActiveDocument;

        DocumentLock dl =

          doc.LockDocument(

            DocumentLockMode.ProtectedAutoWrite,

            null, null, true

          );       

        using (dl)

        {

          Transaction tr =

            doc.TransactionManager.StartTransaction();

          using (tr)

          {

            DBObject o =

              tr.GetObject(

                new ObjectId((IntPtr)obj.ObjectID),

                OpenMode.ForWrite

              );

            PipeDrawOverrule.SetPipeRadiusOnObject(

              tr, o, (float)varData

            );

            tr.Commit();

          }

        }

      }

    }

 

    // OPM passes its implementation of IDynamicPropertyNotify, you

    // cache it and call it to inform OPM your property has changed

 

    public void Connect(object pSink)

    {

      m_pSink = (IDynamicPropertyNotify2)pSink;

    }

 

    public void Disconnect()

    {

      m_pSink = null;

    }

  }

  #endregion

 

  #region Application Entry Point

  public class MyEntryPoint : IExtensionApplication

  {

    protected internal CustomProp custProp = null;

 

    public void Initialize()

    {

      Assembly.LoadFrom("asdkOPMNetExt.dll");

 

      // Add the Dynamic Property to Lines and Circles

      // (might add it at the Entity level, instead)

 

      Dictionary classDict = SystemObjects.ClassDictionary;

      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");

      RXClass cirDesc = (RXClass)classDict.At("AcDbCircle");

      custProp = new CustomProp();

      IPropertyManager2 pPropMan =

        (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);

      pPropMan.AddProperty((object)custProp);

      pPropMan =

        (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(cirDesc);

      pPropMan.AddProperty((object)custProp);

    }

 

    public void Terminate()

    {

      // Remove the Dynamic Property

 

      Dictionary classDict = SystemObjects.ClassDictionary;

      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");

      RXClass cirDesc = (RXClass)classDict.At("AcDbCircle");

      IPropertyManager2 pPropMan =

        (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);

      pPropMan.RemoveProperty((object)custProp);

      pPropMan =

        (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(cirDesc);

      pPropMan.RemoveProperty((object)custProp);

      custProp = null;

    }

  }

  #endregion

}

Some comments on the implementation:

  • GetCurrentValueData() and SetCurrentValueData() both have to open the object to access it’s .NET protocol
    • We might also have used COM to access the XData, but this approach reuses previously-developed code
    • To modify the object we need to lock the current document
      • We use the ProtectedAutoWrite locking mode for this, so that all our property edits are grouped into a single undo group
        • We use the “protected” version of the locking mode as there’s a lock needed elsewhere, probably in the drawing code. If we use the standard AutoWrite lock we get an eLockViolation message
    • We’re using a new transaction for each read/modification
      • This feels like overkill, but then as we’re in an UI-bound operation it’s unlikely to have a perceived performance impact
  • We’re also using the static protocol from the DrawOverrule class for the XData retrieval/setting
    • With hindsight this probably should live in its own helper class, which is the original way I had it :-S :-)

Here’s a model we’re going to modify using the Properties Palette:

Our overruled objects with different radii

Now we select one of our overruled objects – a circle – and see it's new dynamic property:

Dynamic property of one overruled object

When we select all our objects, we see the property varies:

Dynamic property of our varying overruled objects

Now we modify the property to be the same for all our objects:

Modifying the dynamic property on our varying overruled objects

And we can see the result of our modification:

The result of our dynamic property modification

You can see that the property currently isn’t categorised: as mentioned previously, we would have to implement ICategorizedProperty in our OPM .NET module for this to be possible. Which I will attempt, one day.

TrackBack

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

Listed below are links to weblogs that reference Modifying an AutoCAD object’s state via a dynamic property defined using .NET:

blog comments powered by Disqus

Feed/Share

10 Random Posts