📜 ⬆️ ⬇️

Creating a "non-standard" custom object for AutoCAD that works without Object Enabler

Good day.
Those who deal with AutoCAD and third-party solutions for it, for sure, faced with the problem of proxy objects, to display or move which you want to install the libraries with which these objects were created or the so-called Objects Enablers , from the same developers. This is quite inconvenient. For example, you received a document from a customer / subcontractor and you see only squares.

I want to share with you my personal experience in creating an “unconventional” object for AutoCAD. Based on an anonymous block. Object properties are stored separately in BlockReference :: ExtensionDictionary. This allows a third-party application or script to access and read them, and, if desired, change them, without the presence of original libraries. Primitives inside the block are always drawn according to their state. Anyway, the stability of the work of AutoCAD is much higher. From the side it looks simple. But when trying to implement this mechanism, various “pitfalls” were identified. About this in order.


First you need to implement Jig , inherited from EntityJig - to set the order of data entry necessary to create an object. In it we must determine how many states we need, in my case it looks like this:
public enum MyJigState { EnteringBasePoint, EnteringEndPoint, Done } 

The main methods of this class will be Update () and Sampler () in the end I got this class:
 public class MyJig : EntityJig { public enum MyJigState { EnteringBasePoint, EnteringEndPoint, Done } MyJigState _state = MyJigState.EnteringBasePoint; PointSampler _basePoint = new PointSampler(AcGe.Point3d.Origin); PointSampler _endPoint = new PointSampler(new AcGe.Point3d(10, 10, 0)); LevelMark _levelMark; public MyJig(LevelMark levelmark, BlockReference reference) : base(reference) { this._levelMark = levelmark; } protected override bool Update() { try { Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument; using (DocumentLock dl = doc.LockDocument(DocumentLockMode.ProtectedAutoWrite, null, null, true)) { using (var transaction = Acad.TransactionManager.StartTransaction()) { BlockReference reference = (BlockReference)transaction.GetObject(this.Entity.Id, OpenMode.ForWrite, true); reference.Erase(false); reference.Position = this._levelMark.InsertionPoint; reference.BlockUnit = Acad.Database.Insunits; transaction.Commit(); } this._levelMark.UpdateEntities(); this._levelMark.BlockRecord.UpdateAnonymousBlocks(); } } catch (System.Exception ex) { return false; } return true; } public MyJigState State { get { return this._state; } set { this._state = value; } } protected override SamplerStatus Sampler(JigPrompts prompts) { try { switch (_state) { case MyJigState.EnteringBasePoint: return _basePoint.Acquire(prompts, "\n  :", value => { Matrix3d ucs = Acad.Editor.CurrentUserCoordinateSystem; this._levelMark.InsertionPoint = value }); case MyJigState.EnteringEndPoint: return _endPoint.Acquire(prompts, "\n  :", value => { Matrix3d ucs = Acad.Editor.CurrentUserCoordinateSystem; this._levelMark.EndPoint = value }); default: return SamplerStatus.NoChange; } } catch { return SamplerStatus.NoChange; } } } 

')
I inherited the main object class from EntityOverride and redefined Entities:

 private Lazy<AcDb.Line> line = new Lazy<AcDb.Line>(() => new AcDb.Line(Point3d.Origin, new Point3d(10, 0, 0))); private Lazy<AcDb.Polyline> simbolPoly = new Lazy<AcDb.Polyline>(() => new AcDb.Polyline()); private Lazy<AcDb.Polyline> arrowPoly = new Lazy<AcDb.Polyline>(() => new AcDb.Polyline()); private Lazy<AcDb.MText> text = new Lazy<AcDb.MText>(() => new AcDb.MText()); private Lazy<AcDb.MText> note = new Lazy<AcDb.MText>(() => new AcDb.MText()); public override IEnumerable<AcDb.Entity> Entities { get { yield return line.Value; yield return simbolPoly.Value; yield return arrowPoly.Value; yield return text.Value; yield return note.Value; } } 


What makes it possible not to sweat about drawing objects included in the block. Also in this class, I implemented a function that, upon request, recalculates the position and distribution of primitives inside the block.

After creating an instance of our class, a function is called to create a BlockReference , which is immediately deleted. Erase (true) - this makes it possible, when the input is interrupted, to automatically delete an object while saving the file.

  static BlockReference CreateBlock(ref LevelMark lm, ObjectContextCollection occ, ObjectId layerId) { ObjectId id; BlockReference reference; using (AcAp.Application.DocumentManager.MdiActiveDocument.LockDocument()) { using (var transaction = Acad.TransactionManager.StartTransaction()) { using (var blockTable = Acad.Database.BlockTableId.Write<AcDb.BlockTable>()) { var blockId = blockTable.Add(lm.BlockRecord); reference = new AcDb.BlockReference(lm.InsertionPoint, blockId); using (var modelSpace = Acad.Database.CurrentSpaceId/*modelSpaceId*/.Write<AcDb.BlockTableRecord>()) { Matrix3d ucs = Acad.Editor.CurrentUserCoordinateSystem; reference.TransformBy(ucs); id = modelSpace.AppendEntity(reference); ResultBuffer xData = new ResultBuffer(new TypedValue[] { new TypedValue((int)DxfCode.ExtendedDataRegAppName, MyPlugin.CurrentDictionaryName), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "This is a " + MyPlugin.CurrentDictionaryName) }); reference.XData = xData; reference.LayerId = layerId; xData.Dispose(); } transaction.AddNewlyCreatedDBObject(reference, true); reference.Erase(true); transaction.AddNewlyCreatedDBObject(lm.BlockRecord, true); } transaction.Commit(); } if (id != null) { lm.BlockId = id; lm.UpdateParameters(id); } } return reference; } 


Along the way, this function places in XData the information that this is our object - for later searching among other blocks.

It seems that with the creation of the object itself everything is simple (although on the way to this a couple of times I had to change the approach to the implementation). The first difficulties arose with the addition of their grip to the block (pens) and their management, as well as with the abolition of changes.
Having done everything according to the Autodesk documentation, having inherited the GripOverrule grip class, I had difficulties. They consisted in the fact that I lost the opportunity to undo the changes made to the object. Therefore, I had to create a second copy based on the data from the original and carry out changes with it, and if a positive result is achieved, transfer all changes to the parent object. And also had to use TransientManager. Everything would be fine, but it turned out that the object instance ( Entity ) is passed as null to the overridden function MoveGripPointsAt , which resulted in the creation of a custom class GripData , which stores the object Id in it.

The next stone was the simultaneous change of a large number of objects at the same time. I guessed it before. But not on such a scale. The object property panel is implemented through a standard mechanism, and it assumes work through Com (in AutoCAD 2013, it seemed to have been carried to ARX), plus, closing transactions after changing an object turned out to be insanely long. Also, the constant challenge of redrawing an object with standard tools turned out to be unsatisfying to needs, in terms of speed. For these reasons, it was decided to use LISP functions that work much faster. But this resulted in the work of finding and wrapping them in a normal form. It is worth noting here that some of them are platform dependent.

  [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr acedNEntSelP(string p1, long[] name, Point2d p3, bool p, Matrix3d p4, IntPtr p5); public static ResultBuffer NEntSelP(string p1, ObjectId id, Point2d p3, bool p, Matrix3d p4, ref ResultBuffer p5) { long[] adsName = new long[2]; if (acdbGetAdsName(adsName, id) != 0) return null; IntPtr ip = acedNEntSelP(p1, adsName, p3, p, p4, p5.UnmanagedObject); if (ip != IntPtr.Zero) return ResultBuffer.Create(ip, false); return null; } [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr acdbEntGet(long[] name); public static ResultBuffer EntGet(ObjectId id) { long[] adsName = new long[2]; if (acdbGetAdsName(adsName, id) != 0) return null; IntPtr ip = acdbEntGet(adsName); if (ip != IntPtr.Zero) return ResultBuffer.Create(ip, false); return null; } [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int acdbGetObjectId(ref ObjectId objId, long[] name); [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern int acdbEntMakeX(IntPtr resBuf, long[] adsName); public static ObjectId EntMakeX(ResultBuffer resBuf) { long[] adsName = new long[2]; int ip = acdbEntMakeX(resBuf.UnmanagedObject, adsName); if (ip == RTNORM) { ObjectId objId = new ObjectId(); acdbGetObjectId(ref objId, adsName); return objId; } else { return ObjectId.Null; } } [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern int acdbEntDel(long[] name); public static int EntDel(ObjectId id) { long[] adsName = new long[2]; if (acdbGetAdsName(adsName, id) != 0) return RTERROR; return acdbEntDel(adsName); } [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern int acdbDictAdd(long[] dictName, string symName, long[] objName); public static int DictAdd(ObjectId dictNameId, string symName, ObjectId objNameId) { long[] dictName = new long[2]; if (acdbGetAdsName(dictName, dictNameId) != 0) return RTERROR; long[] objName = new long[2]; if (acdbGetAdsName(objName, objNameId) != 0) return RTERROR; byte[] srcb = System.Text.UnicodeEncoding.Unicode.GetBytes(symName); System.Text.ASCIIEncoding ue = new System.Text.ASCIIEncoding(); string dst = ue.GetString(srcb); return acdbDictAdd(dictName, dst, objName); } const short RTNORM = 5100; const short RTERROR = -5001; #if WIN32 [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acdbGetAdsName@@YA?AW4ErrorStatus@Acad@@AAY01JVAcDbObjectId@@@Z")] public static extern int acdbGetAdsName(long[] objName, ObjectId objId); //public static extern Autodesk.AutoCAD.Runtime.ErrorStatus acdbGetAdsName(out long adsName, ObjectId id); #else [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acdbGetAdsName@@YA?AW4ErrorStatus@Acad@@AEAY01_JVAcDbObjectId@@@Z")] public static extern int acdbGetAdsName(long[] objName, ObjectId objId); #endif [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] public static extern int acdbEntUpd(long[] ent); public static long[] GetAdsName(ObjectId id) { long[] adsName = new long[1]; acdbGetAdsName(adsName, id); return adsName; } public static bool EntityUpdate(long[] adsName) { return (acdbEntUpd(adsName) == RTNORM); } public static bool EntityUpdate(ObjectId id) { long[] adsName = new long[1]; if (acdbGetAdsName(adsName, id) != 0) return false; return (acdbEntUpd(adsName) == RTNORM); } [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)] private static extern int acdbEntMod(System.IntPtr resbuf); public static int EntMod(ResultBuffer resultBuffer) { return acdbEntMod(resultBuffer.UnmanagedObject); } #if WIN32 [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedSetStatusBarProgressMeter@@YAHPB_WHH@Z")] public static extern int acedSetStatusBarProgressMeter(string label, int minPos, int maxPos); [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedSetStatusBarProgressMeterPos@@YAHH@Z")] public static extern int acedSetStatusBarProgressMeterPos(int pos); [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedRestoreStatusBar@@YAXXZ")] public static extern int acedRestoreStatusBar(); #else [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedSetStatusBarProgressMeter@@YAHPEB_WHH@Z")] public static extern int acedSetStatusBarProgressMeter(string label, int minPos, int maxPos); [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedSetStatusBarProgressMeterPos@@YAHH@Z")] public static extern int acedSetStatusBarProgressMeterPos(int pos); [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedRestoreStatusBar@@YAXXZ")] public static extern int acedRestoreStatusBar(); #endif 


Additionally, I had to implement XRecord reading / editing through these functions.
That greatly improved performance. At the moment, if you create, for example, 1000 objects, then when you change properties at the same time for all of them, the speed is higher than when working with 1000 custom objects created by standard methods.
The principle is stated very briefly, but if someone is interested, you can contact via Skype: vsegodvadcatsimvolov or here.
I hope someone will be useful.

An example of a ready-made library that implements the work level mark

Source: https://habr.com/ru/post/154591/


All Articles