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 use the ProtectedAutoWrite locking mode for this, so that all our property edits are grouped into a single undo group
- 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:
Now we select one of our overruled objects – a circle – and see it's new dynamic property:
When we select all our objects, we see the property varies:
Now we modify the property to be the same for all our objects:
And we can see the result of our 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.