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
- GripOverrule
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:
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:
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.