To continue my investigations into IronPython and the fun I’m having with overrules, I decided to to port my most recent C# overrule implementation across to IronPython. I’ve also been trying to do the same for IronRuby, but – so far – without luck.
Please refer back to one of the previous IronPython posts for the PYLOAD command implementation needed to load our Python script into AutoCAD.
Here are the contents of our .py file:
import clr
path = 'C:\\Program Files\\Autodesk\\AutoCAD 2010\\'
clr.AddReferenceToFileAndPath(path + 'acdbmgd.dll')
clr.AddReferenceToFileAndPath(path + 'acmgd.dll')
import Autodesk
import Autodesk.AutoCAD.Runtime as ar
import Autodesk.AutoCAD.ApplicationServices as aas
import Autodesk.AutoCAD.DatabaseServices as ads
import Autodesk.AutoCAD.EditorInput as aei
import Autodesk.AutoCAD.GraphicsInterface as agi
import Autodesk.AutoCAD.Geometry as ag
import Autodesk.AutoCAD.Colors as ac
import Autodesk.AutoCAD.Internal as ai
from Autodesk.AutoCAD.Internal import Utils
def autocad_command(function):
"""
Function to register AutoCAD commands
To be used via a function decorator
"""
# First query the function name
n = function.__name__
# Create the callback and add the command
cc = ai.CommandCallback(function)
Utils.AddCommand('pycmds', n, n, ar.CommandFlags.Modal, cc)
# Let's now write a message to the command-line
doc = aas.Application.DocumentManager.MdiActiveDocument
ed = doc.Editor
ed.WriteMessage("\nRegistered Python command: {0}", n)
appName = "TTIF_PIPE"
appCode = 1001
radCode = 1040
def PipeRadiusForObject(obj):
"""
Get the XData for a particular object
and return the "pipe radius" if it exists
"""
res = 0.0
try:
rb = obj.XData
if rb is None:
return res
foundStart = False
for tv in rb:
if tv.TypeCode == appCode and tv.Value == appName:
foundStart = True
else:
if foundStart:
if tv.TypeCode == radCode:
res = tv.Value
break
rb.Dispose()
except:
return 0.0
return res
def SetPipeRadiusOnObject(tr, obj, radius):
"""
Set the pipe radius as XData on a particular object
"""
db = obj.Database
# Make sure the application is registered
# (we could separate this out to be called
# only once for a set of operations)
rat = tr.GetObject(db.RegAppTableId, ads.OpenMode.ForRead)
if not rat.Has(appName):
rat.UpgradeOpen()
ratr = ads.RegAppTableRecord()
ratr.Name = appName
rat.Add(ratr)
tr.AddNewlyCreatedDBObject(ratr, True)
# Create the XData and set it on the object
rb = ads.ResultBuffer(
ads.TypedValue(appCode, appName),
ads.TypedValue(radCode, radius)
)
obj.XData = rb
rb.Dispose()
class PipeDrawOverrule(agi.DrawableOverrule):
"""
The base class for our draw overrules specifying the
registered application name for the XData upon which
to filter
"""
def __init__(self):
# Tell AutoCAD to filter on our application name
# (this means our overrule will only be called
# on objects possessing XData with this name)
self.SetXDataFilter(appName)
class LinePipeDrawOverrule(PipeDrawOverrule):
"""
An overrule to make a pipe out of a line
"""
def __init__(self):
self._sweepOpts = ads.SweepOptions()
def WorldDraw(self, d, wd):
radius = PipeRadiusForObject(d)
if radius > 0.0:
# Draw the line as is, with overruled attributes
PipeDrawOverrule.WorldDraw(self, d, wd)
if not d.Id.IsNull and d.Length > 0.0:
# Draw a pipe around the line
c = wd.SubEntityTraits.TrueColor
wd.SubEntityTraits.TrueColor = ac.EntityColor(0x00AFAFFF)
wd.SubEntityTraits.LineWeight = ads.LineWeight.LineWeight000
start = d.StartPoint
end = d.EndPoint
norm = ag.Vector3d(
end.X - start.X,
end.Y - start.Y,
end.Z - start.Z
)
clr = ads.Circle(start, norm, radius)
pipe = ads.ExtrudedSurface()
try:
pipe.CreateExtrudedSurface(clr, norm, self._sweepOpts)
except:
doc = aas.Application.DocumentManager.MdiActiveDocument
doc.Editor.WriteMessage(
"\nFailed with CreateExtrudedSurface."
)
clr.Dispose()
pipe.WorldDraw(wd)
pipe.Dispose()
wd.SubEntityTraits.TrueColor = c
return True
return PipeDrawOverrule.WorldDraw(self, d, wd)
def SetAttributes(self, d, t):
b = PipeDrawOverrule.SetAttributes(self, d, t)
radius = PipeRadiusForObject(d)
if radius > 0.0:
# Set color to magenta
t.Color = 6
# and lineweight to .40 mm
t.LineWeight = ads.LineWeight.LineWeight040
return b
class CirclePipeDrawOverrule(PipeDrawOverrule):
"""
An overrule to make a pipe out of a circle
"""
def __init__(self):
self._sweepOpts = ads.SweepOptions()
def WorldDraw(self, d, wd):
radius = PipeRadiusForObject(d)
if radius > 0.0:
# Draw the circle as is, with overruled attributes
PipeDrawOverrule.WorldDraw(self, d, wd)
# Needed to avoid ill-formed swept surface
if d.Radius > radius:
# Draw a pipe around the circle
c = wd.SubEntityTraits.TrueColor
wd.SubEntityTraits.TrueColor = ac.EntityColor(0x3FFFE0E0)
wd.SubEntityTraits.LineWeight = ads.LineWeight.LineWeight000
start = d.StartPoint
cen = d.Center
norm = ag.Vector3d(
cen.X - start.X,
cen.Y - start.Y,
cen.Z - start.Z
)
clr = ads.Circle(start, norm.CrossProduct(d.Normal), radius)
pipe = ads.SweptSurface()
pipe.CreateSweptSurface(clr, d, self._sweepOpts)
clr.Dispose()
pipe.WorldDraw(wd)
pipe.Dispose()
wd.SubEntityTraits.TrueColor = c
return True
return PipeDrawOverrule.WorldDraw(self, d, wd)
def SetAttributes(self, d, t):
b = PipeDrawOverrule.SetAttributes(self, d, t)
radius = PipeRadiusForObject(d)
if radius > 0.0:
# Set color to yellow
t.Color = 2
# and lineweight to .60 mm
t.LineWeight = ads.LineWeight.LineWeight060
return b
class LinePipeTransformOverrule(ads.TransformOverrule):
"""
An overrule to explode a linear pipe into Solid3d objects
"""
def __init__(self):
self._sweepOpts = ads.SweepOptions()
def Explode(self, e, objs):
radius = PipeRadiusForObject(e)
if radius > 0.0:
if not e.Id.IsNull and e.Length > 0.0:
# Draw a pipe around the line
start = e.StartPoint
end = e.EndPoint
norm = ag.Vector3d(
end.X - start.X,
end.Y - start.Y,
end.Z - start.Z
)
clr = ads.Circle(start, norm, radius)
pipe = ads.ExtrudedSurface()
try:
pipe.CreateExtrudedSurface(clr, norm, self._sweepOpts)
except:
doc = aas.Application.DocumentManager.MdiActiveDocument
doc.Editor.WriteMessage(
"\nFailed with CreateExtrudedSurface."
)
clr.Dispose()
objs.Add(pipe)
return
PipeDrawOverrule.Explode(self, e, objs)
class CirclePipeTransformOverrule(ads.TransformOverrule):
"""
An overrule to explode a circular pipe into Solid3d objects
"""
def __init__(self):
self._sweepOpts = ads.SweepOptions()
def Explode(self, e, objs):
radius = PipeRadiusForObject(e)
if radius > 0.0:
if e.Radius > radius:
start = e.StartPoint
cen = e.Center
norm = ag.Vector3d(
cen.X - start.X,
cen.Y - start.Y,
cen.Z - start.Z
)
clr = ads.Circle(start, norm.CrossProduct(e.Normal), radius)
pipe = ads.SweptSurface()
pipe.CreateSweptSurface(clr, e, self._sweepOpts)
clr.Dispose()
objs.Add(pipe)
return
PipeDrawOverrule.Explode(self, e, objs)
def Overrule(enable):
# Regen to see the effect
# (turn on/off Overruling and LWDISPLAY)
ar.Overrule.Overruling = enable
if enable:
aas.Application.SetSystemVariable("LWDISPLAY", 1)
else:
aas.Application.SetSystemVariable("LWDISPLAY", 0)
doc = aas.Application.DocumentManager.MdiActiveDocument
doc.SendStringToExecute("REGEN3\n", True, False, False)
doc.Editor.Regen()
# Set some global variables
radius = 0.0
overruling = False
lpdo = LinePipeDrawOverrule()
cpdo = CirclePipeDrawOverrule()
lpto = LinePipeTransformOverrule()
cpto = CirclePipeTransformOverrule()
class Commands:
@autocad_command
def Overrule1():
# Only add the overrule if not currently attached
global overruling
if not overruling:
# Create a temporary line to get its class
# (this may not be the best way)
ln = ads.Line()
ads.ObjectOverrule.AddOverrule(
ar.RXClass.GetClass(ln.GetType()),
lpdo,
True
)
ads.ObjectOverrule.AddOverrule(
ar.RXClass.GetClass(ln.GetType()),
lpto,
True
)
ln.Dispose()
# Create a temporary circle to get its class
# (this may not be the best way)
c = ads.Circle()
ads.ObjectOverrule.AddOverrule(
ar.RXClass.GetClass(c.GetType()),
cpdo,
True
)
ads.ObjectOverrule.AddOverrule(
ar.RXClass.GetClass(c.GetType()),
cpto,
True
)
c.Dispose()
overruling = True
Overrule(True)
@autocad_command
def Overrule0():
# Only remove the overrule if previously added
global overruling
if overruling:
# Create a temporary line to get its class
# (this may not be the best way)
ln = ads.Line()
ads.ObjectOverrule.RemoveOverrule(
ar.RXClass.GetClass(ln.GetType()),
lpdo
)
ads.ObjectOverrule.RemoveOverrule(
ar.RXClass.GetClass(ln.GetType()),
lpto
)
ln.Dispose()
# Create a temporary circle to get its class
# (this may not be the best way)
c = ads.Circle()
ads.ObjectOverrule.RemoveOverrule(
ar.RXClass.GetClass(c.GetType()),
cpdo
)
ads.ObjectOverrule.RemoveOverrule(
ar.RXClass.GetClass(c.GetType()),
cpto
)
c.Dispose()
overruling = False
Overrule(False)
@autocad_command
def MakePipe():
doc = aas.Application.DocumentManager.MdiActiveDocument
db = doc.Database
ed = doc.Editor
# Pick up the radius from the global scope
global radius
# Ask the user to select the entities to make into pipes
pso = aei.PromptSelectionOptions()
pso.AllowDuplicates = False
pso.MessageForAdding = "\nSelect objects to turn into pipes: "
selRes = ed.GetSelection(pso)
# If the user didn't make valid selection, we return
if selRes.Status <> aei.PromptStatus.OK:
return
ss = selRes.Value
# Ask the user for the pipe radius to set
pdo = aei.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
pdr = ed.GetDouble(pdo)
# Return if something went wrong
if pdr.Status <> aei.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
tr = db.TransactionManager.StartTransaction()
# Loop through the selected objects
for o in ss:
# We could choose only to add XData to the objects
# we know will use it (Lines and Circles, for now)
obj = tr.GetObject(o.ObjectId, ads.OpenMode.ForWrite)
SetPipeRadiusOnObject(tr, obj, radius)
tr.Commit()
tr.Dispose()
Once you have built your loader application and used PYLOAD to load your .py script, you should be able to use MAKEPIPE and OVERRULE1 to create pipes:
And EXPLODE to reduce them to Solid3d objects:
The logic in the code is basically same as that used in C#, although I have added some checks to make sure nothing bad happens if you call OVERRULE1 or OVERRULE0 repeatedly (previously I did not check for this, which could lead to an exception).