- 积分
- 24557
- 明经币
- 个
- 注册时间
- 2004-3-17
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
楼主 |
发表于 2009-9-12 20:27:00
|
显示全部楼层
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:- using Autodesk.AutoCAD.ApplicationServices;
- using Autodesk.AutoCAD.DatabaseServices;
- using Autodesk.AutoCAD.EditorInput;
- using Autodesk.AutoCAD.Geometry;
- using Autodesk.AutoCAD.Runtime;
-
- namespace PointOnCurveTest
- {
- public class LinkApplication
- {
- static string regAppName = "TTIF_PTS";
-
- public class LinkTransOverrule : TransformOverrule
- {
- static bool _transforming = false;
-
- // A static pointer to our overrule instance
-
- static public LinkTransOverrule theOverrule =
- new LinkTransOverrule();
-
- // A flag to indicate whether we're overruling
-
- static private bool overruling = false;
-
- // Our overrule should only be applied to objects
- // carrying our XData
-
- public LinkTransOverrule()
- {
- SetXDataFilter(regAppName);
- }
-
- // Turn on the transform overrule if it isn't already
-
- public static void TurnOn()
- {
- if (!overruling)
- {
- ObjectOverrule.AddOverrule(
- RXClass.GetClass(typeof(Entity)),
- LinkTransOverrule.theOverrule,
- true
- );
- overruling = true;
- TransformOverrule.Overruling = true;
- }
- }
-
- // Turn off the transform overrule if it's on
-
- public static void TurnOff()
- {
- if (overruling)
- {
- ObjectOverrule.RemoveOverrule(
- RXClass.GetClass(typeof(Entity)),
- LinkTransOverrule.theOverrule
- );
- overruling = false;
- TransformOverrule.Overruling = false;
- }
- }
-
- // Our primary overruled function
-
- public override void TransformBy(Entity e, Matrix3d mat)
- {
- // If we're already transforming, don't re-enter, just
- // super-message to the base
-
- if (_transforming)
- base.TransformBy(e, mat);
- else
- {
- Database db = HostApplicationServices.WorkingDatabase;
-
- // Otherwise get our linked objects: check whether
- // there are any
-
- ObjectIdCollection ids = GetLinkedObjects(e);
- if (ids.Count > 0)
- {
- // If we're dealing with a point...
-
- DBPoint pt = e as DBPoint;
- if (pt != null)
- {
- // Work through the curves to find the closest to our
- // transformed point
-
- double min = 0.0;
- Point3d bestPt = Point3d.Origin;
- bool first = true;
- ObjectId bestId = ObjectId.Null;
-
- // We're using an Open/Close transaction, to avoid
- // problems with us using transactions in an event
- // handler
-
- OpenCloseTransaction tr =
- db.TransactionManager.StartOpenCloseTransaction();
- using (tr)
- {
- // For now linked objects can be curves, else
- // they'll be ignored
-
- Curve cur;
- foreach (ObjectId curId in ids)
- {
- DBObject obj =
- tr.GetObject(curId, OpenMode.ForRead, true);
-
- // If our linked object was erased, remove it
- // from this object's links
-
- if (obj.IsErased)
- {
- RemoveLinkedObject(e, curId);
- }
- else
- {
- // Otherwise we get the closest point on it
- // to our point, to compare with others
-
- cur = obj as Curve;
- if (cur != null)
- {
- Point3d ptLoc =
- pt.Position.TransformBy(mat);
- Point3d ptOnCurve =
- cur.GetClosestPointTo(ptLoc, false);
- Vector3d dist = ptOnCurve - ptLoc;
-
- if (first || dist.Length < min)
- {
- first = false;
- min = dist.Length;
- bestPt = ptOnCurve;
- bestId = curId;
- }
- }
- }
- }
-
- // If we didn't find a point, super-message
- // (will transform the point as normal)
-
- if (first)
- base.TransformBy(e, mat);
- else
- pt.Position = bestPt;
- }
- }
-
- if (e is Curve)
- {
- // Automatically super-message: we're not changing the
- // transform behaviour of the curve, only of the
- // linked objects
-
- base.TransformBy(e, mat);
-
- // Get each linked object and transform it along with
- // the "parent"
-
- OpenCloseTransaction tr =
- db.TransactionManager.StartOpenCloseTransaction();
- using (tr)
- {
- foreach (ObjectId id in ids)
- {
- DBObject obj = tr.GetObject(id, OpenMode.ForWrite);
- Entity ent = obj as Entity;
-
- if (ent != null)
- {
- _transforming = true;
- ent.TransformBy(mat);
- _transforming = false;
- }
- }
- tr.Commit();
- }
- }
- }
- }
- }
- }
-
- // Function to add a reference between one object and another
- // (stored in an object's XData)
-
- public static void AddLinkedObject(DBObject obj, ObjectId id)
- {
- Database db =
- HostApplicationServices.WorkingDatabase;
-
- // First we need to make sure our application name is
- // in the Registered Application Table
-
- OpenCloseTransaction tr =
- db.TransactionManager.StartOpenCloseTransaction();
- using (tr)
- {
- RegAppTable rat =
- (RegAppTable)tr.GetObject(
- db.RegAppTableId, OpenMode.ForRead
- );
- if (!rat.Has(regAppName))
- {
- rat.UpgradeOpen();
- RegAppTableRecord ratr =
- new RegAppTableRecord();
- ratr.Name = regAppName;
- tr.AddNewlyCreatedDBObject(ratr, true);
- rat.Add(ratr);
- }
- tr.Commit();
- }
-
- // Get our object's current XData
-
- ResultBuffer rb = obj.XData;
-
- // Check the XData, to see whether our link already exists
-
- bool foundLink = false;
-
- if (rb != null)
- {
- bool foundStart = false;
-
- foreach (TypedValue tv in rb)
- {
- // The first TypedValue is our application name
-
- if (tv.TypeCode ==
- (int)DxfCode.ExtendedDataRegAppName &&
- tv.Value.ToString() == regAppName)
- foundStart = true;
- else
- {
- if (foundStart)
- {
- // Our links will all have the same code (1005)
-
- if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
- {
- if (id.Handle.ToString() == tv.Value.ToString())
- foundLink = true;
- }
- }
- }
- }
- }
-
- // If we didn't find the link, add it
-
- if (!foundLink)
- {
- // This is our link
-
- TypedValue tv2 =
- new TypedValue(
- (int)DxfCode.ExtendedDataHandle, id.Handle
- );
-
- // If there was no previous XData, create the ResultBuffer
-
- if (rb == null)
- {
- rb =
- new ResultBuffer(
- new TypedValue(
- (int)DxfCode.ExtendedDataRegAppName,
- regAppName
- ),
- tv2
- );
- }
- else
- {
- // Add our TypeValue to an existing ResultBuffer
-
- rb.Add(tv2);
- }
-
- // Set the XData on our object
-
- obj.XData = rb;
- }
- rb.Dispose();
- }
-
- // Function to remove a reference from one object to another
- // (stored in an object's XData)
-
- public static void RemoveLinkedObject(DBObject obj, ObjectId id)
- {
- // Get the existing XData
-
- ResultBuffer oldRb = obj.XData;
-
- // Create a ResultBuffer for the "filtered" XData
- // (will contain all the old links, except the one we want
- // to remove)
-
- ResultBuffer newRb =
- new ResultBuffer(
- new TypedValue(
- (int)DxfCode.ExtendedDataRegAppName,
- regAppName)
- );
-
- bool foundLink = false;
-
- if (oldRb != null)
- {
- bool foundStart = false;
-
- // Loop through the XData
-
- foreach (TypedValue tv in oldRb)
- {
- // The first TypedValue is our application name
-
- if (tv.TypeCode ==
- (int)DxfCode.ExtendedDataRegAppName &&
- tv.Value.ToString() == regAppName)
- foundStart = true;
- else
- {
- if (foundStart)
- {
- // Our links will all have the same code (1005)
-
- if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
- {
- // If the link is not the one we want to remove,
- // add it to our list of XData to retain,
- // otherwise set a flag to say we found it
-
- if (id.Handle.ToString() != tv.Value.ToString())
- newRb.Add(tv);
- else
- foundLink = true;
- }
- }
- }
- }
- }
-
- // If we found the link (and therefore have XData to set)
- // change the object's XData
-
- if (foundLink)
- obj.XData = newRb;
-
- // Clean-up after ourselves
-
- oldRb.Dispose();
- newRb.Dispose();
- }
-
- // Function to retrieve the references from one object to another
- // (stored in an object's XData)
-
- public static ObjectIdCollection GetLinkedObjects(DBObject obj)
- {
- Database db =
- HostApplicationServices.WorkingDatabase;
-
- // Will return an ObjectIdCollection
-
- ObjectIdCollection ids = new ObjectIdCollection();
-
- // Get the object's XData
-
- using (ResultBuffer rb = obj.XData)
- {
- if (rb != null)
- {
- bool foundStart = false;
-
- foreach (TypedValue tv in rb)
- {
- // The first TypedValue is our application name
-
- if (tv.TypeCode ==
- (int)DxfCode.ExtendedDataRegAppName &&
- tv.Value.ToString() == regAppName)
- foundStart = true;
- else
- {
- if (foundStart)
- {
- // Our links will all have the same code (1005)
-
- if (tv.TypeCode == (int)DxfCode.ExtendedDataHandle)
- {
- // Get the long integer from the string stored in
- // our XData, and the Handle from the long
-
- long lg =
- System.Convert.ToInt64(tv.Value.ToString(), 16);
- Handle hd = new Handle(lg);
-
- // And then the ObjectId from the Handle
-
- ObjectId id = db.GetObjectId(false, hd, -1);
-
- // Add it to our list, if it isn't already in it
-
- if (!ids.Contains(id))
- ids.Add(id);
- }
- }
- }
- }
- }
- }
- return ids;
- }
-
- [CommandMethod("POC")]
- public void CreatePointOnCurve()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- Editor ed = doc.Editor;
-
- // Ask the user to select a curve
-
- PromptEntityOptions opts =
- new PromptEntityOptions(
- "\nSelect curve at the point to create: "
- );
- opts.SetRejectMessage(
- "\nEntity must be a curve."
- );
- opts.AddAllowedClass(typeof(Curve), false);
-
- PromptEntityResult per = ed.GetEntity(opts);
-
- ObjectId curId = per.ObjectId;
- if (curId != ObjectId.Null)
- {
- // Let's make sure we'll be able to see our point
-
- db.Pdmode = 97; // square with a circle
- db.Pdsize = -10; // relative to the viewport size
-
- Transaction tr =
- doc.TransactionManager.StartTransaction();
- using (tr)
- {
- DBObject obj =
- tr.GetObject(curId, OpenMode.ForRead);
- Curve cur = obj as Curve;
- if (cur != null)
- {
- // Our initial point should be the closest point
- // on the curve to the one picked
-
- Point3d pos =
- cur.GetClosestPointTo(per.PickedPoint, false);
- DBPoint pt = new DBPoint(pos);
-
- // Add it to the same space as the curve
-
- BlockTableRecord btr =
- (BlockTableRecord)tr.GetObject(
- cur.BlockId,
- OpenMode.ForWrite
- );
- ObjectId ptId = btr.AppendEntity(pt);
- AddLinkedObject(pt, curId);
- cur.UpgradeOpen();
- AddLinkedObject(cur, ptId);
- tr.AddNewlyCreatedDBObject(pt, true);
- }
- tr.Commit();
-
- LinkTransOverrule.TurnOn();
- }
- }
- }
- }
- }
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
|