Object Enablerなしで動作するAutoCAD用の「カスタム」カスタムオブジェクトの作成

こんにちは

AutoCADとそのサードパーティソリューションを扱う人は、確かにプロキシオブジェクトの問題に直面しています。プロキシオブジェクトを表示または移動するには、これらのオブジェクトまたはいわゆるObject Enablerが作成されたライブラリを同じ開発者からインストールする必要があります。 これは非常に不便です。 たとえば、顧客/下請業者からドキュメントを受け取り、正方形のみが表示されます。



AutoCADの「非伝統的な」オブジェクトを作成した私の個人的な経験を共有したいと思います。 基礎は匿名ブロックです。 オブジェクトプロパティは、 BlockReference :: ExtensionDictionaryに個別に保存されます。 これにより、サードパーティのアプリケーションまたはスクリプトがそれらにアクセスして読み取り、必要に応じて、元のライブラリがなくてもそれらを変更できます。 ブロック内のプリミティブは、常にその状態に従ってレンダリングされます。 とにかく、AutoCADの安定性ははるかに高いです。 横から見ると、すべてがシンプルに見えます。 しかし、このメカニズムを実装しようとすると、さまざまな「落とし穴」が特定されました。 これについて順番に。





最初に、 EntityJigから継承したJigを実装する必要があります-オブジェクトの作成に必要な入力順序を設定します。 その中で、必要な状態の数を決定する必要があります。私の場合は次のようになります。

public enum MyJigState { EnteringBasePoint, EnteringEndPoint, Done }
      
      





このクラスの主なメソッドはUpdate()Sampler()であり 、その結果、このクラスを取得しました。

 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; } } }
      
      







EntityOverrideからオブジェクトのメインクラスを継承し、エンティティを再定義しました。



 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; } }
      
      







これにより、ブロックに含まれるオブジェクトのレンダリングに浸らないようにすることができます。 また、このクラスでは、ブロック内のプリミティブの位置と分布をオンデマンドで再カウントする関数を実装しました。



クラスのインスタンスを作成した後、 Erase(true)によってすぐに削除されるBlockReferenceを作成する関数が呼び出されます。これにより、入力が保存されたときにファイルが中断されたときにオブジェクトを自動的に削除できます。



  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; }
      
      







途中で、この関数はXDataに 、これが私たちのオブジェクトであるという情報を入れます-他のブロック間でのその後の検索のために。



オブジェクト自体の作成では、すべてが単純であるように思われます(これまでの途中で、実装へのアプローチを変更する必要がありました)。 最初の問題は、グリップ(ペン)をブロックに追加して管理するとき、および変更をキャンセルするときに発生しました。

オートデスクのドキュメントに従ってすべてを実行し、GripOverruleグリップクラスを継承したため、困難に直面しました。 それらは、オブジェクトに加えられた変更を元に戻す機能を失ったという事実から成り立っていました。 したがって、元のデータに基づいて2番目のインスタンスを作成し、それを変更する必要がありました。結果が正の場合、すべての変更を親オブジェクトに転送します。 また、TransientManagerを使用する必要がありました。 すべては問題ありませんが、オブジェクト( Entity )のインスタンスがオーバーライドされた関数MoveGripPointsAtにnullとして渡されオブジェクトのIdを格納するカスタムクラスGripDataの作成を伴うことが判明しました。



次の石は、多数のオブジェクトを同時に変更することでした。 以前彼について推測しました。 しかし、そのような規模ではありません。 オブジェクトのプロパティパネルは標準のメカニズムによって実装され、Comを介した作業(AutoCAD 2013ではARXで削除されたようです)に加えて、オブジェクトの変更後にトランザクションが非常に長くなることが判明しました。 また、標準ツールを使用してオブジェクトを再描画する定数呼び出しは、速度の点で不十分であることが判明しました。 これらの理由により、はるかに高速に動作するLISP関数を使用することが決定されました。 しかし、これにより、それらを見つけて通常の外観にまとめる作業が行われました。 それらのいくつかはプラットフォームに依存していることに注意してください。



  [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
      
      







さらに、これらの関数を使用しXRecordの読み取り/編集を実装する必要がありました。

生産性が大幅に向上しました。 現時点では、たとえば1000個のオブジェクトを作成した場合、それらすべてのプロパティを同時に変更すると、標準的な方法を使用して作成された1000個のカスタムオブジェクトを操作する場合よりも高速になります。

原則は非常に簡潔に述べられていますが、誰かが興味を持っている場合は、スカイプ経由で連絡することができます:vsegodvadcatsimvolovまたはここ。

誰かが役に立つことを願っています。



既製のライブラリの例は、レベルマークの作業を実装します



All Articles