Kean Walmsley


  • About the Author
    Kean on Google+

July 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    








« DevTV: AutoCAD VBA to .NET Migration Basics | Main | Triangulating an AutoCAD polyface mesh from a set of points using .NET »

April 23, 2009

Overruling AutoCAD 2010’s entity display and explode using IronPython

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:

Overruled lines and circles

And EXPLODE to reduce them to Solid3d objects:

Exploded overruled lines and circles

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

TrackBack

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

Listed below are links to weblogs that reference Overruling AutoCAD 2010’s entity display and explode using IronPython:

blog comments powered by Disqus

10 Random Posts