明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
12
返回列表 发新帖

[Kean专集] Kean专题(11)—Overrules

   关闭 [复制链接]
 楼主| 发表于 2009-9-12 20:27 | 显示全部楼层
August 31, 2009
Gluing a point to an AutoCAD curve using overrules from .NET – Part 3
In the most recent part of this series, we looked at one possible mechanism to allow points to be moved along a network of curves, extending the first part in this series, which focused on the case of a point on a single curve.
This post is going to focus on something slightly different: it’s going to look at making the points added to a particular curve be associative to that curve – i.e. travel along with it as the curve is moved – and in the process we’re going to adjust the way we link between our objects, by moving the information into an object’s Extended Entity Data (XData). This does a few things: firstly it breaks our network-related technique (never fear – we’ll be able to get it back, in due course :-) by removing the idea of a central list of curves. It also allows us to have points that are connected to curves, and points that are not (which is clearly desirable). It also lays a more solid foundation for other operations, such as erasing the points on a curve when the curve itself is erased. All good stuff.
Just to be clear: there are lots of ways ways to approach this problem – this is just another possible mechanism – so you might be interested in checking out this previous series, which addressed a similar problem a little differently.
For now let’s see this basic implementation of our XData-related code. Here’s the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.Runtime;
  6. namespace PointOnCurveTest
  7. {
  8.   public class LinkApplication
  9.   {
  10.     static string regAppName = "TTIF_PTS";
  11.     public class LinkTransOverrule : TransformOverrule
  12.     {
  13.       static bool _transforming = false;
  14.       // A static pointer to our overrule instance
  15.       static public LinkTransOverrule theOverrule =
  16.         new LinkTransOverrule();
  17.       // A flag to indicate whether we're overruling
  18.       static private bool overruling = false;
  19.       // Our overrule should only be applied to objects
  20.       // carrying our XData
  21.       public LinkTransOverrule()
  22.       {
  23.         SetXDataFilter(regAppName);
  24.       }
  25.       // Turn on the transform overrule if it isn't already
  26.       public static void TurnOn()
  27.       {
  28.         if (!overruling)
  29.         {
  30.           ObjectOverrule.AddOverrule(
  31.             RXClass.GetClass(typeof(Entity)),
  32.             LinkTransOverrule.theOverrule,
  33.             true
  34.           );
  35.           overruling = true;
  36.           TransformOverrule.Overruling = true;
  37.         }
  38.       }
  39.       // Turn off the transform overrule if it's on
  40.       public static void TurnOff()
  41.       {
  42.         if (overruling)
  43.         {
  44.           ObjectOverrule.RemoveOverrule(
  45.             RXClass.GetClass(typeof(Entity)),
  46.             LinkTransOverrule.theOverrule
  47.           );
  48.           overruling = false;
  49.           TransformOverrule.Overruling = false;
  50.         }
  51.       }
  52.       // Our primary overruled function
  53.       public override void TransformBy(Entity e, Matrix3d mat)
  54.       {
  55.         // If we're already transforming, don't re-enter, just
  56.         // super-message to the base
  57.         if (_transforming)
  58.           base.TransformBy(e, mat);
  59.         else
  60.         {
  61.           Database db = HostApplicationServices.WorkingDatabase;
  62.           // Otherwise get our linked objects: check whether
  63.           // there are any
  64.           ObjectIdCollection ids = GetLinkedObjects(e);
  65.           if (ids.Count > 0)
  66.           {
  67.             // If we're dealing with a point...
  68.             DBPoint pt = e as DBPoint;
  69.             if (pt != null)
  70.             {
  71.               // Work through the curves to find the closest to our
  72.               // transformed point
  73.               double min = 0.0;
  74.               Point3d bestPt = Point3d.Origin;
  75.               bool first = true;
  76.               ObjectId bestId = ObjectId.Null;
  77.               // We're using an Open/Close transaction, to avoid
  78.               // problems with us using transactions in an event
  79.               // handler
  80.               OpenCloseTransaction tr =
  81.                 db.TransactionManager.StartOpenCloseTransaction();
  82.               using (tr)
  83.               {
  84.                 // For now linked objects can be curves, else
  85.                 // they'll be ignored
  86.                 Curve cur;
  87.                 foreach (ObjectId curId in ids)
  88.                 {
  89.                   DBObject obj =
  90.                     tr.GetObject(curId, OpenMode.ForRead, true);
  91.                   // If our linked object was erased, remove it
  92.                   // from this object's links
  93.                   if (obj.IsErased)
  94.                   {
  95.                     RemoveLinkedObject(e, curId);
  96.                   }
  97.                   else
  98.                   {
  99.                     // Otherwise we get the closest point on it
  100.                     // to our point, to compare with others
  101.                     cur = obj as Curve;
  102.                     if (cur != null)
  103.                     {
  104.                       Point3d ptLoc =
  105.                         pt.Position.TransformBy(mat);
  106.                       Point3d ptOnCurve =
  107.                         cur.GetClosestPointTo(ptLoc, false);
  108.                       Vector3d dist = ptOnCurve - ptLoc;
  109.                       if (first || dist.Length < min)
  110.                       {
  111.                         first = false;
  112.                         min = dist.Length;
  113.                         bestPt = ptOnCurve;
  114.                         bestId = curId;
  115.                       }
  116.                     }
  117.                   }
  118.                 }
  119.                 // If we didn't find a point, super-message
  120.                 // (will transform the point as normal)
  121.                 if (first)
  122.                   base.TransformBy(e, mat);
  123.                 else
  124.                   pt.Position = bestPt;
  125.               }
  126.             }
  127.             if (e is Curve)
  128.             {
  129.               // Automatically super-message: we're not changing the
  130.               // transform behaviour of the curve, only of the
  131.               // linked objects
  132.               base.TransformBy(e, mat);
  133.               // Get each linked object and transform it along with
  134.               // the "parent"
  135.               OpenCloseTransaction tr =
  136.                 db.TransactionManager.StartOpenCloseTransaction();
  137.               using (tr)
  138.               {
  139.                 foreach (ObjectId id in ids)
  140.                 {
  141.                   DBObject obj = tr.GetObject(id, OpenMode.ForWrite);
  142.                   Entity ent = obj as Entity;
  143.                   if (ent != null)
  144.                   {
  145.                     _transforming = true;
  146.                     ent.TransformBy(mat);
  147.                     _transforming = false;
  148.                   }
  149.                 }
  150.                 tr.Commit();
  151.               }
  152.             }
  153.           }
  154.         }
  155.       }
  156.     }
  157.     // Function to add a reference between one object and another
  158.     // (stored in an object's XData)
  159.     public static void AddLinkedObject(DBObject obj, ObjectId id)
  160.     {
  161.       Database db =
  162.         HostApplicationServices.WorkingDatabase;
  163.       // First we need to make sure our application name is
  164.       // in the Registered Application Table
  165.       OpenCloseTransaction tr =
  166.         db.TransactionManager.StartOpenCloseTransaction();
  167.       using (tr)
  168.       {
  169.         RegAppTable rat =
  170.           (RegAppTable)tr.GetObject(
  171.             db.RegAppTableId, OpenMode.ForRead
  172.           );
  173.         if (!rat.Has(regAppName))
  174.         {
  175.           rat.UpgradeOpen();
  176.           RegAppTableRecord ratr =
  177.             new RegAppTableRecord();
  178.           ratr.Name = regAppName;
  179.           tr.AddNewlyCreatedDBObject(ratr, true);
  180.           rat.Add(ratr);
  181.         }
  182.         tr.Commit();
  183.       }
  184.       // Get our object's current XData
  185.       ResultBuffer rb = obj.XData;
  186.       // Check the XData, to see whether our link already exists
  187.       bool foundLink = false;
  188.       if (rb != null)
  189.       {
  190.         bool foundStart = false;
  191.         foreach (TypedValue tv in rb)
  192.         {
  193.           // The first TypedValue is our application name
  194.           if (tv.TypeCode ==
  195.               (int)DxfCode.ExtendedDataRegAppName &&
  196.               tv.Value.ToString() == regAppName)
  197.             foundStart = true;
  198.           else
  199.           {
  200.             if (foundStart)
  201.             {
  202.               // Our links will all have the same code (1005)
  203.               if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
  204.               {
  205.                 if (id.Handle.ToString() == tv.Value.ToString())
  206.                   foundLink = true;
  207.               }
  208.             }
  209.           }
  210.         }
  211.       }
  212.       // If we didn't find the link, add it
  213.       if (!foundLink)
  214.       {
  215.         // This is our link
  216.         TypedValue tv2 =
  217.           new TypedValue(
  218.             (int)DxfCode.ExtendedDataHandle, id.Handle
  219.           );
  220.         // If there was no previous XData, create the ResultBuffer
  221.         if (rb == null)
  222.         {
  223.           rb =
  224.             new ResultBuffer(
  225.               new TypedValue(
  226.                 (int)DxfCode.ExtendedDataRegAppName,
  227.                 regAppName
  228.               ),
  229.               tv2
  230.             );
  231.         }
  232.         else
  233.         {
  234.           // Add our TypeValue to an existing ResultBuffer
  235.           rb.Add(tv2);
  236.         }
  237.         // Set the XData on our object
  238.         obj.XData = rb;
  239.       }
  240.       rb.Dispose();
  241.     }
  242.     // Function to remove a reference from one object to another
  243.     // (stored in an object's XData)
  244.     public static void RemoveLinkedObject(DBObject obj, ObjectId id)
  245.     {
  246.       // Get the existing XData
  247.       ResultBuffer oldRb = obj.XData;
  248.       // Create a ResultBuffer for the "filtered" XData
  249.       // (will contain all the old links, except the one we want
  250.       // to remove)
  251.       ResultBuffer newRb =
  252.         new ResultBuffer(
  253.           new TypedValue(
  254.             (int)DxfCode.ExtendedDataRegAppName,
  255.             regAppName)
  256.           );
  257.       bool foundLink = false;
  258.       if (oldRb != null)
  259.       {
  260.         bool foundStart = false;
  261.         // Loop through the XData
  262.         foreach (TypedValue tv in oldRb)
  263.         {
  264.           // The first TypedValue is our application name
  265.           if (tv.TypeCode ==
  266.               (int)DxfCode.ExtendedDataRegAppName &&
  267.               tv.Value.ToString() == regAppName)
  268.             foundStart = true;
  269.           else
  270.           {
  271.             if (foundStart)
  272.             {
  273.               // Our links will all have the same code (1005)
  274.               if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
  275.               {
  276.                 // If the link is not the one we want to remove,
  277.                 // add it to our list of XData to retain,
  278.                 // otherwise set a flag to say we found it
  279.                 if (id.Handle.ToString() != tv.Value.ToString())
  280.                   newRb.Add(tv);
  281.                 else
  282.                   foundLink = true;
  283.               }
  284.             }
  285.           }
  286.         }
  287.       }
  288.       // If we found the link (and therefore have XData to set)
  289.       // change the object's XData
  290.       if (foundLink)
  291.         obj.XData = newRb;
  292.       // Clean-up after ourselves
  293.       oldRb.Dispose();
  294.       newRb.Dispose();
  295.     }
  296.     // Function to retrieve the references from one object to another
  297.     // (stored in an object's XData)
  298.     public static ObjectIdCollection GetLinkedObjects(DBObject obj)
  299.     {
  300.       Database db =
  301.         HostApplicationServices.WorkingDatabase;
  302.       // Will return an ObjectIdCollection
  303.       ObjectIdCollection ids = new ObjectIdCollection();
  304.       // Get the object's XData
  305.       using (ResultBuffer rb = obj.XData)
  306.       {
  307.         if (rb != null)
  308.         {
  309.           bool foundStart = false;
  310.           foreach (TypedValue tv in rb)
  311.           {
  312.             // The first TypedValue is our application name
  313.             if (tv.TypeCode ==
  314.                 (int)DxfCode.ExtendedDataRegAppName &&
  315.                 tv.Value.ToString() == regAppName)
  316.               foundStart = true;
  317.             else
  318.             {
  319.               if (foundStart)
  320.               {
  321.                 // Our links will all have the same code (1005)
  322.                 if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
  323.                 {
  324.                   // Get the long integer from the string stored in
  325.                   // our XData, and the Handle from the long
  326.                   long lg =
  327.                     System.Convert.ToInt64(tv.Value.ToString(), 16);
  328.                   Handle hd = new Handle(lg);
  329.                   // And then the ObjectId from the Handle
  330.                   ObjectId id = db.GetObjectId(false, hd, -1);
  331.                   // Add it to our list, if it isn't already in it
  332.                   if (!ids.Contains(id))
  333.                     ids.Add(id);
  334.                 }
  335.               }
  336.             }
  337.           }
  338.         }
  339.       }
  340.       return ids;
  341.     }
  342.     [CommandMethod("POC")]
  343.     public void CreatePointOnCurve()
  344.     {
  345.       Document doc =
  346.         Application.DocumentManager.MdiActiveDocument;
  347.       Database db = doc.Database;
  348.       Editor ed = doc.Editor;
  349.       // Ask the user to select a curve
  350.       PromptEntityOptions opts =
  351.       new PromptEntityOptions(
  352.         "\nSelect curve at the point to create: "
  353.       );
  354.       opts.SetRejectMessage(
  355.         "\nEntity must be a curve."
  356.       );
  357.       opts.AddAllowedClass(typeof(Curve), false);
  358.       PromptEntityResult per = ed.GetEntity(opts);
  359.       ObjectId curId = per.ObjectId;
  360.       if (curId != ObjectId.Null)
  361.       {
  362.         // Let's make sure we'll be able to see our point
  363.         db.Pdmode = 97;  // square with a circle
  364.         db.Pdsize = -10; // relative to the viewport size
  365.         Transaction tr =
  366.           doc.TransactionManager.StartTransaction();
  367.         using (tr)
  368.         {
  369.           DBObject obj =
  370.             tr.GetObject(curId, OpenMode.ForRead);
  371.           Curve cur = obj as Curve;
  372.           if (cur != null)
  373.           {
  374.             // Our initial point should be the closest point
  375.             // on the curve to the one picked
  376.             Point3d pos =
  377.               cur.GetClosestPointTo(per.PickedPoint, false);
  378.             DBPoint pt = new DBPoint(pos);
  379.             // Add it to the same space as the curve
  380.             BlockTableRecord btr =
  381.               (BlockTableRecord)tr.GetObject(
  382.                 cur.BlockId,
  383.                 OpenMode.ForWrite
  384.               );
  385.             ObjectId ptId = btr.AppendEntity(pt);
  386.             AddLinkedObject(pt, curId);
  387.             cur.UpgradeOpen();
  388.             AddLinkedObject(cur, ptId);
  389.             tr.AddNewlyCreatedDBObject(pt, true);
  390.           }
  391.           tr.Commit();
  392.           LinkTransOverrule.TurnOn();
  393.         }
  394.       }
  395.     }
  396.   }
  397. }
Some points to note about this implementation:
    * We now register the overrule to work for all entity types, but at the same time we tell AutoCAD to filter on our Register Application Name
          o Only objects with our XData will cause the overrule to be called
    * Our TransformBy() function will therefore apply to all entities with our XData, although we’re only (currently) interested in DBPoints and Curves
          o We do need to stop the function re-entering for the points we transform along with the parent curve, but that’s a simple matter of setting/checking a boolean flag
    * We have kept the technique of checking a number of curves, to see which is closest, but as we’re only adding the XData during the POC command – which currently only allows selection of a single curve – the behaviour is now once again similar to the first post in the series (although this is very easy to change)
Now let’s see what happens when our code is executed. We’ll take the same drawing as last time – which contains a Spline and a Polyline – and use the POC command to add a few points on each:

When we move the Spline, we see the points on it now move along with it:

What’s interesting is that when we do the same for a Polyline, the points don’t appear during the drag…

… but they do, indeed, show up in the right place once the Polyline has completed the move to its destination:

This is apparently due to optimization inside AutoCAD: during a recent discussion with our Engineering team I learned that during certain operations – such as when a modification to an object or set of objects can be represented by a standard transformation (such as scale, rotate or move) – AutoCAD captures the graphics of those objects and then performs a “sprite” optimization to move the object(s), rather than continually asking the object(s) to participate in the movement by drawing their own graphics or by transforming their internal state.
It’s not fully clear to me why this is happening in this particular situation – and with the Polyline rather than the Spline – but it does appear that this optimization has been turned on during the movement of the Polyline. This only occurs in very specific situations – such as in 3D rather than 2D – but it does seem something that would be valuable for applications to have more explicit control over.
That’s it for today’s post. I’m actually on vacation this week, but I do have something to share later in the week related to our “Plugin on the Month” initiative…

本帖子中包含更多资源

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

x
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-3-29 13:00 , Processed in 0.211759 second(s), 17 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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