【テクニカル・上級編】【実務中級】AcadDocument.Database.ClonedObjectsを用いた「図面間オブジェクトコピー」後のハンドル番号追跡 – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:`CopyObjects`と`IDPair`による図面間オブジェクトコピー&高精度ハンドル追跡

複数図面間でオブジェクト(エンティティ)を複製・同期させるシステムを設計する際、避けて通れない最大の壁がある。それは「オブジェクト・アイデンティティ(永続識別子:ハンドル)の喪失」である。

AutoCADにおける「ハンドル(Handle)」は、同一図面(Database)内においてのみ永続性とユニークネスが保証された16進数の文字列(最大16文字)である。別図面へとオブジェクトがコピーされた瞬間、そのコピー先オブジェクトには、コピー先データベースのシーケンスに基づいた全く新しいハンドルが再割り当てされる。

図面外部のRDB(生産管理システム、設備管理台帳、BOMなど)と図形オブジェクトをハンドル値で紐付けているエンタープライズシステムにおいて、この「ハンドルの不連続性」はデータリンクの切断を意味する。

多くの開発者は、コピー後にコピー先図面の `ModelSpace` を全走査(セーブ直後のタイムスタンプ比較や、幾何情報の照合など)するという、極めて脆弱かつ低速な力技でこれを解決しようとする。しかし、オブジェクト数が数万を超える大規模図面や、ネットワーク経由でのバッチ処理において、そのようなアプローチはパフォーマンス破綻とデータ不整合を引き起こす。

本稿では、AutoCAD ActiveX APIが提供する `CopyObjects` メソッド、およびその裏で生成される `IDPair`(`AcadIDPair`)マッピングテーブルを完全に制御し、コピー元とコピー先のハンドル変換マップをミリ秒単位で決定論的に構築する極限の技術を解説する。

—

1. `CopyObjects` と `IDPair`(マッピングテーブル)の深層

AutoCAD内における図形コピーの真髄は、単なるクローン生成ではない。データベース間(あるいは同一データベース内)での構造のディープコピー(Deep Clone)である。

ActiveX APIにおける `CopyObjects` のプロトタイプは以下の通り定義されている。

RetVal = object.CopyObjects(ObjectsArray [, Owner] [, IDPairs])

  • `ObjectsArray`: コピー元オブジェクトのオブジェクト配列(`Variant`)
  • `Owner`: コピー先となるコンテナ(通常はターゲット図面の `ModelSpace` や `PaperSpace`、または `Block`)
  • `IDPairs`: [出力引数] コピー処理の結果として生成されるマッピング情報の配列(`Variant`)

この第3引数 `IDPairs`(実体は `AcadIDPair` オブジェクトの配列)こそが、オブジェクト追跡の鍵を握る。

`AcadIDPair` オブジェクトの構造

`AcadIDPair` は、コピー処理のトランスレーション(翻訳)テーブルを構成する単一レコードである。以下の重要プロパティを持つ。

| プロパティ名 | 型 | 説明 |
| :— | :— | :— |
| `Key` | Long (32bit) / LongPtr (64bit) | コピー元オブジェクトの ObjectID |
| `Value` | Long (32bit) / LongPtr (64bit) | コピー先オブジェクトの ObjectID |
| `IsCloned` | Boolean | オブジェクトが正常にクローン(コピー)されたかを示す |
| `IsPrimary` | Boolean | コピー対象として明示的に指定されたプライマリオブジェクトか、随伴コピー(依存関係による自動コピー)かを示す |

`Key` と `Value` に格納されるのは「ハンドル」ではなく「ObjectID(実行メモリ上でのオブジェクト識別ポインタ)」である。ObjectIDは図面セッションを閉じると消失する一時的な値であるため、外部DBとの連携には、このObjectIDを経由して、永続的な「ハンドル番号」へと変換する処理が不可欠となる。

—

2. メモリ空間の最適化とWin32 APIによる実行パフォーマンス計測

図面間コピーは、AutoCADのメモリ空間(特に複数文書インターフェース:MDI環境)を激しく消費する。特にOut-of-Process(外部EXEからVBA/COM経由で操作)や、大容量のDWGをバックグラウンドで処理する場合、徹底したリソース解放を行わなければ、容易に `Out of Memory` やGDIリソース枯渇による強制終了(ACAD.EXEのクラッシュ)を招く。

本実装では、実行速度をマイクロ秒単位で精密にプロファイリングするため、Windows APIの `QueryPerformanceCounter` を使用する。また、処理完了後にはCOMオブジェクトの参照カウントを明示的に減少させ、GC(ガーベジコレクション)を待たずにメモリを即時解放する設計を徹底する。

—

3. 実戦コード:図面間コピー&高精度ハンドル追跡システム

以下に、実務でそのまま使用できる頑健なVBAコードを示す。
このコードは、開いているアクティブ図面(ソース)から選択したオブジェクトを、バックグラウンドで新規作成(またはオープン)したターゲット図面へ複製し、「コピー元ハンドル ⇔ コピー先ハンドル」の完全なマッピングリストを瞬時に構築する。

Option Explicit

‘ ==============================================================================
‘ 高精度プロファイリング用 Win32 API 宣言
‘ ==============================================================================
If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Else
Private Declare Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
End If

‘ ==============================================================================
‘ システム間連携:図面間オブジェクトコピー&ハンドル追跡実行コア
‘ ==============================================================================
Public Sub ExecuteCrossDrawingCopyAndTrack()
Dim tStart As Currency, tEnd As Currency, tFreq As Currency
QueryPerformanceFrequency tFreq
QueryPerformanceCounter tStart

Dim srcDoc As AcadDocument
Dim tgtDoc As AcadDocument
Set srcDoc = ThisDrawing ‘ コピー元:現在のアクティブ図面

‘ 1. コピー対象の選定(例としてモデル空間内の全オブジェクトを選択)
Dim entityCount As Long
entityCount = srcDoc.ModelSpace.Count
If entityCount = 0 Then
MsgBox “ソース図面のモデル空間にオブジェクトが存在しません。”, vbExclamation
Exit Sub
End If

‘ コピー対象のオブジェクト配列を構築
Dim objArray() As Object
ReDim objArray(0 To entityCount – 1)

Dim i As Long
For i = 0 To entityCount – 1
Set objArray(i) = srcDoc.ModelSpace.Item(i)
Next i

‘ 2. ターゲット図面の作成(またはバックグラウンドオープン)
‘ ※ここではデモンストレーションのため、新規図面を作成
On Error Resume Next
Set tgtDoc = ThisDrawing.Application.Documents.Add
If Err.Number <> 0 Then
MsgBox “ターゲット図面の作成に失敗しました: ” & Err.Description, vbCritical
Exit Sub
End If
On Error GoTo 0

‘ 3. コピー実行用の変数定義
Dim varObjArray As Variant
varObjArray = objArray ‘ Variant型配列にキャスト

Dim idPairs As Variant ‘ コピー後にAcadIDPairの配列が格納される
Dim retObjects As Variant

‘ 4. コピー処理の実行
‘ CopyObjects(コピー対象配列, コピー先コンテナ, IDPairs)
On Error GoTo Err_Copy
retObjects = srcDoc.CopyObjects(varObjArray, tgtDoc.ModelSpace, idPairs)
On Error GoTo 0

‘ 5. IDPairsを走査し、ハンドル対応マップを構築
Dim idPair As AcadIDPair
Dim srcObjID As LongPtr ‘ 64bit環境対応
Dim tgtObjID As LongPtr

Dim srcObj As AcadObject
Dim tgtObj As AcadObject

Dim srcHandle As String
Dim tgtHandle As String

‘ CSV出力または外部DB書き込みを想定したログ出力
Debug.Print “=== [START] ハンドル追跡マッピングテーブル ===”
Debug.Print “Source_Handle,Target_Handle,IsPrimary,IsCloned”

‘ LBoundからUBoundまでIDPairを高速スキャン
Dim pairIndex As Long
For pairIndex = LBound(idPairs) To UBound(idPairs)
Set idPair = idPairs(pairIndex)

‘ クローン処理が正常に行われたオブジェクトのみを追跡
If idPair.IsCloned Then
‘ IDPairからメモリ上のObjectIDを取得
srcObjID = idPair.Key
tgtObjID = idPair.Value

‘ ObjectIDから実オブジェクトを参照し、永続識別子「ハンドル」を抽出
‘ ※異なる図面のデータベースにアクセスするため、各々のDocumentオブジェクトから解決する
Set srcObj = srcDoc.ObjectIdToObject(srcObjID)
Set tgtObj = tgtDoc.ObjectIdToObject(tgtObjID)

srcHandle = srcObj.Handle
tgtHandle = tgtObj.Handle

‘ 結果を出力(カンマ区切り形式)
Debug.Print srcHandle & “,” & tgtHandle & “,” & idPair.IsPrimary & “,” & idPair.IsCloned

‘ 参照の明示的破棄(メモリリーク防止)
Set srcObj = Nothing
Set tgtObj = Nothing
End If
Set idPair = Nothing
Next pairIndex
Debug.Print “=== [END] ハンドル追跡マッピングテーブル ===”

‘ 6. パフォーマンス計測結果の表示
QueryPerformanceCounter tEnd
Dim elapsedSec As Double
elapsedSec = CDbl(tEnd – tStart) / CDbl(tFreq)

Debug.Print “処理完了時間: ” & Format(elapsedSec, “0.0000”) & ” 秒”
MsgBox “オブジェクト追跡コピー完了。” & vbCrLf & _
“コピー数: ” & (UBound(idPairs) – LBound(idPairs) + 1) & ” 件” & vbCrLf & _
“処理時間: ” & Format(elapsedSec, “0.000”) & ” 秒”, vbInformation

Exit Sub

Err_Copy:
MsgBox “コピー処理中に致命的なエラーが発生しました: ” & Err.Description, vbCritical
‘ リソース解放
Set srcDoc = Nothing
Set tgtDoc = Nothing
End Sub

—

4. アーキテクチャの急所とレガシー保守の現実

このシステムをエンタープライズ環境で本番運用するにあたり、チーフアーキテクトとして指摘しておくべき「3つの致命的な地雷」とその処方箋を明記する。

急所1:64bit環境における `ObjectID` のオーバーフロー問題

AutoCAD 2014以降、VBA環境は64bit(VBA7)に対応している。32bit環境では `ObjectID` は `Long`(4バイト)であったが、64bit環境ではメモリポインタが拡張されているため、`LongPtr`(あるいはVBA内部で `Double` や `Variant/LongLong`)としてキャストしなければ、`Type Mismatch`(型不一致エラー)でプロセスが停止する。
上記コードでは、`srcObjID` および `tgtObjID` の宣言に `LongPtr`(VBA7環境)を使用し、動的なメモリ空間の拡張に対応している。

急所2:随伴コピー(Dependency Copy)の罠

`IDPairs` の走査ログを見ると、明示的にコピー対象(`ObjectsArray`)に指定した数よりも遥かに多いペアが検出されることがある。
これは、画層(Layer)、線種(Linetype)、寸法スタイル(DimStyle)、またはブロック定義(BlockTableRecord)など、オブジェクトが依存している非グラフィカルなシンボルテーブルが「随伴コピー」されるためである。
外部システムとの紐付けを純粋な「図形オブジェクトのみ」に限定したい場合は、ループ内で必ず以下のフィルタリングを実行すること。

‘ プライマリオブジェクト(ユーザーが直接指定した図形)のみを対象とする
If idPair.IsPrimary = True Then
‘ ここでハンドル変換・DB書き込みを行う
End If

急所3:トランザクションと図面の自動保存(AutoSave)の衝突

大量のオブジェクトを `CopyObjects` で別図面に流し込んでいる最中にAutoCADの「自動保存(AutoSave)」タイマーが介入すると、データベースのロック競合が発生し、エラー `E_ACCESSDENIED` やサイレントクラッシュを引き起こす。
バッチ処理の開始時には、一時的に自動保存のインターバルを `0`(オフ)にし、処理完了後に元の設定に戻すのが、レガシー環境における極めて実践的な防衛策である。

Dim originalAutoSave As Variant
originalAutoSave = ThisDrawing.GetVariable(“SAVETIME”)
ThisDrawing.SetVariable “SAVETIME”, 0 ‘ 自動保存を一時停止

‘ === コピー処理実行 ===

ThisDrawing.SetVariable “SAVETIME”, originalAutoSave ‘ 設定を復元

—

5. まとめ:データ駆動型CADシステムの基盤として

`AcadDocument.Database.ClonedObjects`(ActiveXにおいては `CopyObjects` の `IDPairs` 戻り値)を掌握することは、単に「図面間で絵をコピーする」レベルのVBA開発から脱却し、「CAD図面をリレーショナルデータベースのグラフィカルフロントエンドとして定義する」ための必須条件である。

この手法を用いれば、以下のシステム連携が完全な決定論(Deterministic)に基づいて動作する。

1. PLM/BOM連携: 図面Aから図面Bへ部品ブロックをコピーした際、PLM側の構成ツリーのキー(ハンドル)を、何ら幾何学的な探索を行うことなく一瞬で再マッピングする。
2. GIS/設備管理: 施設図面間でネットワークトポロジー(配管・配線の接続関係)をコピーする際、接続ノードのハンドル関係性をコピー先でも完全に再構築する。

レガシーVBAと言えども、その極限のAPI仕様を理解し、メモリ空間とハードウェアパフォーマンスを緻密にマネジメントすれば、現代の最新言語によるシステムに比肩する、極めて頑健で高速なシステムを構築・維持できるのである。

タイトルとURLをコピーしました