找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1907|回复: 0

[分享] Optimized overruling in AutoCAD 2010 using .NET

[复制链接]

已领礼包: 6个

财富等级: 恭喜发财

发表于 2013-5-27 01:41:35 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

In the last post we looked at some code that attaches additional data to individual entities, allowing them to be drawn independently with different visual properties via the new Overrule API in AutoCAD 2010.

A couple of comments – one from Qun, who provided the original F# sample, and one from Tony Tanzillo – have prompted me to optimize the code somewhat. Tony pointed out, very validly, that as the previous code registers its overrule against Drawable objects it will get called for every entity (and various objects besides) which could clearly impact performance. Qun pointed me to some interesting framework capabilities that allow you to let AutoCAD decide whether to call your overrule based on the existence of certain XData or some other criterion (such as the existence of a particular entry in the object’s extension dictionary).

So here’s what I’ve done…

I decided to define separate overrule classes for each object type we want to handle and have these classes derive from a common abstract base class. This allows us to encapsulate all the common XData-related stuff inside the base class, and leave the child classes to worry about the type-specific display. We will then register our overrules for the specific classes they care about, which will lead them to only being called for objects of those types.

A nice feature is the ability to let AutoCAD filter on the existence of specific XData prior to invoking our overrule: check out the constructor of the PipeOverrule class – it calls SetXDataFilter() on the base DrawableOverrule class, telling AutoCAD the registered application name for which to look. An interesting test: draw some lines and/or circles, and use the MP command in the below sample to attach radii to some of them. If you set a breakpoint in your WorldDraw() functions and run the code as it stands, you’ll see that they only get called for the circles/lines that have been modified by the MP command. If you comment that one line, you should see WorldDraw() being called for all circles and lines, irrespective of whether they’ve been given a radius or not.

Before we get into the code, here’s the protocol for the DrawableOverrule class, as provided by Visual Studio:
  1. namespace Autodesk.AutoCAD.GraphicsInterface
  2. {
  3.   [Wrapper("AcGiDrawableOverrule")]
  4.   public abstract class DrawableOverrule : Overrule
  5.   {
  6.     protected internal DrawableOverrule();

  7.     public virtual int SetAttributes(
  8.       Drawable drawable, DrawableTraits traits);
  9.     public override sealed void SetCustomFilter();
  10.     public override sealed void SetExtensionDictionaryEntryFilter(
  11.       string entryName);
  12.     public override sealed void SetIdFilter(ObjectId[] ids);
  13.     public override sealed void SetNoFilter();
  14.     public override sealed void SetXDataFilter(
  15.       string registeredApplicationName);
  16.     public virtual void ViewportDraw(
  17.       Drawable drawable, ViewportDraw vd);
  18.     public virtual int ViewportDrawLogicalFlags(
  19.       Drawable drawable, ViewportDraw vd);
  20.     public virtual bool WorldDraw(Drawable drawable, WorldDraw wd);
  21.   }
  22. }

Aside from filtering on XData, we can see that we can also filter on specific ObjectIds (good for overruling objects very specifically), extension dictionary entries (good if you’re working with lots of data per object and XData is no longer viable) or something more custom.

Here’s the protocol for the Overrule class, the base class for DrawableOverrule:
  1. namespace Autodesk.AutoCAD.Runtime
  2. {
  3.   [Wrapper("AcRxOverrule")]
  4.   public abstract class Overrule : RXObject
  5.   {
  6.     public static bool Overruling { get; set; }

  7.     public static void AddOverrule(
  8.       RXClass targetClass, Overrule overrule, bool bAtLast);
  9.     public static bool HasOverrule(
  10.       RXObject overruledSubject, RXClass targetClass);
  11.     public virtual bool IsApplicable(RXObject overruledSubject);
  12.     public static void RemoveOverrule(
  13.       RXClass targetClass, Overrule overrule);
  14.     public abstract void SetCustomFilter();
  15.     public abstract void SetExtensionDictionaryEntryFilter(
  16.       string entryName);
  17.     public abstract void SetIdFilter(ObjectId[] ids);
  18.     public abstract void SetNoFilter();
  19.     public abstract void SetXDataFilter(
  20.       string registeredApplicationName);
  21.   }
  22. }

The protocol in which we’re interested has been declared as abstract (== pure virtual) at this level, so we need to use DrawableOverrule as a base class unless we want to implement the protocol ourselves. One additional function to be overridden at this level is IsApplicable(), which is the fundamental way the overrule tells AutoCAD whether it can work with a specific object, or not. This is the function we would override to implement a custom filter, and is the only part of the filter protocol available from ObjectARX: more work has been done on the .NET side of things, which thankfully makes our lives much simpler. :-)

One open question in my mind: while asking AutoCAD to filter on XData is simpler and means our code is called less often, it does mean that both AutoCAD and our code will be checking for XData (when we retrieve the radius information). Presumably AutoCAD is going to check very efficiently, which means the code should be quicker in the cases where we have lots of objects that are not overruled, but in the cases where we are overruling a lot of objects there does appear to be some potential for this making the application run marginally slower. Ultimately – as mentioned earlier - there’s just a single line of code to comment out if you want to see the difference (and presumably profile the performance in some way). Just something to watch for, in case – I doubt this is something that’s going to have a significant – or even measurable – performance impact, but anyway.

Here’s the modified C# code:

  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.GraphicsInterface;
  7. using Autodesk.AutoCAD.Colors;

  8. namespace DrawOverrule
  9. {
  10.   public abstract class PipeOverrule : DrawableOverrule
  11.   {
  12.     const string regAppName = "TTIF_PIPE";

  13.     public PipeOverrule()
  14.     {
  15.       // Tell AutoCAD to filter on our application name
  16.       // (this means our overrule will only be called
  17.       // on objects possessing XData with this name)

  18.       SetXDataFilter(regAppName);
  19.     }

  20.     // Get the XData for a particular object
  21.     // and return the "pipe radius" if it exists

  22.     public static double PipeRadiusForObject(DBObject obj)
  23.     {
  24.       double res = 0.0;

  25.       ResultBuffer rb = obj.XData;
  26.       if (rb != null)
  27.       {
  28.         bool foundStart = false;

  29.         foreach (TypedValue tv in rb)
  30.         {
  31.           if (tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName &&
  32.               tv.Value.ToString() == regAppName)
  33.             foundStart = true;
  34.           else
  35.           {
  36.             if (foundStart == true)
  37.             {
  38.               if (tv.TypeCode == (int)DxfCode.ExtendedDataReal)
  39.               {
  40.                 res = (double)tv.Value;
  41.                 break;
  42.               }
  43.             }
  44.           }
  45.         }
  46.         rb.Dispose();
  47.       }
  48.       return res;
  49.     }

  50.     // Set the "pipe radius" in the XData of a particular object

  51.     public static void SetPipeRadiusOnObject(
  52.       Transaction tr, DBObject obj, double radius
  53.     )
  54.     {
  55.       Database db = obj.Database;

  56.       // Make sure the application is registered
  57.       // (we could separate this out to be called
  58.       // only once for a set of operations)

  59.       RegAppTable rat =
  60.         (RegAppTable)tr.GetObject(
  61.           db.RegAppTableId,
  62.           OpenMode.ForRead
  63.         );

  64.       if (!rat.Has(regAppName))
  65.       {
  66.         rat.UpgradeOpen();
  67.         RegAppTableRecord ratr = new RegAppTableRecord();
  68.         ratr.Name = regAppName;
  69.         rat.Add(ratr);
  70.         tr.AddNewlyCreatedDBObject(ratr, true);
  71.       }

  72.       // Create the XData and set it on the object

  73.       ResultBuffer rb =
  74.         new ResultBuffer(
  75.           new TypedValue(
  76.             (int)DxfCode.ExtendedDataRegAppName, regAppName
  77.           ),
  78.           new TypedValue(
  79.             (int)DxfCode.ExtendedDataReal, radius
  80.           )
  81.         );
  82.       obj.XData = rb;
  83.       rb.Dispose();
  84.     }
  85.   }

  86.   // An overrule to make a pipe out of line

  87.   public class LinePipeOverrule : PipeOverrule
  88.   {
  89.     static public LinePipeOverrule theOverrule =
  90.       new LinePipeOverrule();

  91.     private SweepOptions sweepOpts = new SweepOptions();

  92.     public override bool WorldDraw(Drawable d, WorldDraw wd)
  93.     {
  94.       double radius = 0.0;

  95.       if (d is DBObject)
  96.         radius = PipeRadiusForObject((DBObject)d);

  97.       if (radius > 0.0)
  98.       {
  99.         Line line = d as Line;

  100.         if (line != null)
  101.         {
  102.           // Draw the line as is, with overruled attributes

  103.           base.WorldDraw(line, wd);
  104.           if (!line.Id.IsNull && line.Length > 0.0)
  105.           {
  106.             // Draw a pipe around the line

  107.             EntityColor c =
  108.               wd.SubEntityTraits.TrueColor;
  109.             wd.SubEntityTraits.TrueColor =
  110.               new EntityColor(0x00AfAfff);
  111.             wd.SubEntityTraits.LineWeight =
  112.               LineWeight.LineWeight000;
  113.             Circle clr =
  114.               new Circle(
  115.                 line.StartPoint,
  116.                 line.EndPoint - line.StartPoint,
  117.                 radius
  118.               );
  119.             ExtrudedSurface pipe = new ExtrudedSurface();
  120.             try
  121.             {
  122.               pipe.CreateExtrudedSurface(
  123.                 clr, line.EndPoint - line.StartPoint, sweepOpts
  124.               );
  125.             }
  126.             catch
  127.             {
  128.               Document doc =
  129.                 Application.DocumentManager.MdiActiveDocument;
  130.               doc.Editor.WriteMessage(
  131.                 "\nFailed with CreateExtrudedSurface."
  132.               );
  133.             }
  134.             clr.Dispose();
  135.             pipe.WorldDraw(wd);
  136.             pipe.Dispose();
  137.             wd.SubEntityTraits.TrueColor = c;
  138.           }
  139.           return true;
  140.         }
  141.       }
  142.       return base.WorldDraw(d, wd);
  143.     }

  144.     public override int SetAttributes(Drawable d, DrawableTraits t)
  145.     {
  146.       int b = base.SetAttributes(d, t);

  147.       double radius = 0.0;

  148.       if (d is DBObject)
  149.         radius = PipeRadiusForObject((DBObject)d);

  150.       if (radius > 0.0)
  151.       {
  152.         // Set color to index 6

  153.         t.Color = 6;

  154.         // and lineweight to .40 mm

  155.         t.LineWeight = LineWeight.LineWeight040;
  156.       }
  157.       return b;
  158.     }
  159.   }

  160.   // An overrule to make a pipe out of circle

  161.   public class CirclePipeOverrule : PipeOverrule
  162.   {
  163.     static public CirclePipeOverrule theOverrule =
  164.       new CirclePipeOverrule();

  165.     private SweepOptions sweepOpts = new SweepOptions();

  166.     public override bool WorldDraw(Drawable d, WorldDraw wd)
  167.     {
  168.       double radius = 0.0;

  169.       if (d is DBObject)
  170.         radius = PipeRadiusForObject((DBObject)d);

  171.       if (radius > 0.0)
  172.       {
  173.         Circle circle = d as Circle;

  174.         if (circle != null)
  175.         {
  176.           // Draw the circle as is, with overruled attributes

  177.           base.WorldDraw(circle, wd);

  178.           // Needed to avoid ill-formed swept surface

  179.           if (circle.Radius > radius)
  180.           {
  181.             // Draw a pipe around the cirle

  182.             EntityColor c = wd.SubEntityTraits.TrueColor;
  183.             wd.SubEntityTraits.TrueColor =
  184.               new EntityColor(0x3fffe0e0);
  185.             wd.SubEntityTraits.LineWeight =
  186.               LineWeight.LineWeight000;
  187.             Vector3d normal =
  188.               (circle.Center - circle.StartPoint).
  189.                 CrossProduct(circle.Normal);
  190.             Circle clr =
  191.               new Circle(
  192.                 circle.StartPoint, normal, radius
  193.               );
  194.             SweptSurface pipe = new SweptSurface();
  195.             pipe.CreateSweptSurface(clr, circle, sweepOpts);
  196.             clr.Dispose();
  197.             pipe.WorldDraw(wd);
  198.             pipe.Dispose();
  199.             wd.SubEntityTraits.TrueColor = c;
  200.           }
  201.           return true;
  202.         }
  203.       }
  204.       return base.WorldDraw(d, wd);
  205.     }

  206.     public override int SetAttributes(Drawable d, DrawableTraits t)
  207.     {
  208.       int b = base.SetAttributes(d, t);

  209.       double radius = 0.0;

  210.       if (d is DBObject)
  211.         radius = PipeRadiusForObject((DBObject)d);

  212.       if (radius > 0.0)
  213.       {
  214.         // Set color to index 2

  215.         t.Color = 2;

  216.         // and lineweight to .60 mm

  217.         t.LineWeight = LineWeight.LineWeight060;
  218.       }
  219.       return b;
  220.     }
  221.   }

  222.   public class Commands
  223.   {
  224.     private double _radius = 0.0;

  225.     public void Overrule(bool enable)
  226.     {
  227.       // Regen to see the effect
  228.       // (turn on/off Overruling and LWDISPLAY)

  229.       DrawableOverrule.Overruling = enable;
  230.       if (enable)
  231.         Application.SetSystemVariable("LWDISPLAY", 1);
  232.       else
  233.         Application.SetSystemVariable("LWDISPLAY", 0);

  234.       Document doc =
  235.         Application.DocumentManager.MdiActiveDocument;
  236.       doc.SendStringToExecute("REGEN3\n", true, false, false);
  237.       doc.Editor.Regen();
  238.     }

  239.     [CommandMethod("OVERRULE1")]
  240.     public void OverruleStart()
  241.     {
  242.       ObjectOverrule.AddOverrule(
  243.         RXClass.GetClass(typeof(Line)),
  244.         LinePipeOverrule.theOverrule,
  245.         true
  246.       );
  247.       ObjectOverrule.AddOverrule(
  248.         RXClass.GetClass(typeof(Circle)),
  249.         CirclePipeOverrule.theOverrule,
  250.         true
  251.       );
  252.       Overrule(true);
  253.     }

  254.     [CommandMethod("OVERRULE0")]
  255.     public void OverruleEnd()
  256.     {
  257.       ObjectOverrule.RemoveOverrule(
  258.         RXClass.GetClass(typeof(Line)),
  259.         LinePipeOverrule.theOverrule
  260.       );
  261.       ObjectOverrule.RemoveOverrule(
  262.         RXClass.GetClass(typeof(Circle)),
  263.         CirclePipeOverrule.theOverrule
  264.       );
  265.       Overrule(false);
  266.     }

  267.     [CommandMethod("MP", CommandFlags.UsePickSet)]
  268.     public void MakePipe()
  269.     {
  270.       Document doc =
  271.         Application.DocumentManager.MdiActiveDocument;
  272.       Database db = doc.Database;
  273.       Editor ed = doc.Editor;

  274.       // Ask the user to select the entities to make into pipes

  275.       PromptSelectionOptions pso =
  276.         new PromptSelectionOptions();
  277.       pso.AllowDuplicates = false;
  278.       pso.MessageForAdding =
  279.         "\nSelect objects to turn into pipes: ";

  280.       PromptSelectionResult selRes =
  281.         doc.Editor.GetSelection(pso);

  282.       // If the user didn't make valid selection, we return

  283.       if (selRes.Status != PromptStatus.OK)
  284.         return;

  285.       SelectionSet ss = selRes.Value;

  286.       // Ask the user for the pipe radius to set

  287.       PromptDoubleOptions pdo =
  288.         new PromptDoubleOptions(
  289.           "\nSpecify pipe radius:"
  290.         );

  291.       // Use the previous value, if if already called

  292.       if (_radius > 0.0)
  293.       {
  294.         pdo.DefaultValue = _radius;
  295.         pdo.UseDefaultValue = true;
  296.       }
  297.       pdo.AllowNegative = false;
  298.       pdo.AllowZero = false;

  299.       PromptDoubleResult pdr =
  300.         ed.GetDouble(pdo);

  301.       // Return if something went wrong

  302.       if (pdr.Status != PromptStatus.OK)
  303.         return;

  304.       // Set the "last radius" value for when
  305.       // the command is called next

  306.       _radius = pdr.Value;

  307.       // Use a transaction to edit our various objects

  308.       Transaction tr =
  309.         db.TransactionManager.StartTransaction();
  310.       using (tr)
  311.       {
  312.         // Lop through the selected objects

  313.         foreach (SelectedObject o in ss)
  314.         {
  315.           // We could choose only to add XData to the objects
  316.           // we know will use it (Lines and Circles, for now)

  317.           DBObject obj =
  318.             tr.GetObject(o.ObjectId, OpenMode.ForWrite);
  319.           PipeOverrule.SetPipeRadiusOnObject(tr, obj, _radius);
  320.         }
  321.         tr.Commit();
  322.       }
  323.     }
  324.   }
  325. }

In terms of what happens when we run the code, here are the results from the previous post (as it should function identically).

First some simple geometry for which we’re going to overrule the display:

0.jpg

And here’s some geometry for which we’ve attached radii using the MP command, once we’ve turned our overrules on via the OVERRULE1 command:

1.jpg

论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-11-17 22:39 , Processed in 0.325607 second(s), 30 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表