【AutoCAD VBA】図面間オブジェクトコピーで「ハンドル番号」を完全追跡する極限のデータ連携術(IdMap / AcadIdPair 徹底攻略)
CAD図面と外部データベース(SQL Server、Oracle、PostgreSQL、あるいはAccessやExcelなど)を連携させたシステムを構築する際、避けて通れない超重要課題があります。
それは、「オブジェクトのユニークキー(主キー)をいかにして維持・追跡するか」という問題です。
AutoCAD内のオブジェクトを特定する唯一の不変の識別子は「ハンドル(Handle)番号」です。しかし、図面間でオブジェクトをコピー(`CopyObjects`)した瞬間、コピー先では新しいハンドル番号が再割り当てされます。このとき、コピー元とコピー先のハンドルの「一対一の対応関係」を正確に把握できなければ、外部データベースとの紐付けは一瞬で崩壊します。
「コピー後にオブジェクトの座標やプロパティで検索して強引に紐付ける」といった素人極まりない実装は、実務では絶対に通用しません。同座標に同一プロパティの図形が重なっていただけでシステムは誤動作し、図面が大きくなれば検索処理(O(N^2)の破滅的ループ)によって処理速度は著しく低下します。
本記事では、AutoCAD VBAに用意されている強力ながらも光の当たらない隠れた仕様、`CopyObjects` メソッドの `IDMap`(AcadIdPair)引数を駆使し、64bit環境でも絶対に破綻しない「図面間オブジェクトコピー&高精度ハンドル追跡エンジン」の極意を伝授します。
—
1. なぜ単純なアプローチは破綻するのか?
実務の過酷な環境を知らない開発者は、以下のような「動けばラッキー」レベルの設計をしてしまいがちです。
✕ 敗北のアンチパターン1:コピー前後の「要素数」から末尾を取得する
「コピー前の図面要素数が100で、5個コピーしたから、コピー後の末尾101〜105がコピーされたオブジェクトだ」というロジックです。
これは、マルチタスク環境やリアクター(イベント監視)が動作しているCAD環境、またはコピー処理に伴って自動生成される随伴オブジェクト(スタイル、レイアウト、辞書オブジェクト等)が存在する場合に、インデックスがズレて一瞬で崩壊します。
✕ 敗北のアンチパターン2:コピー後に「座標や属性」で走査・マッチングする
「コピー元のX座標が100だから、コピー先でX座標が100の図形を探す」というロジックです。
図面内に同じ形状・配置の図形が複数存在した瞬間に誤判定を起こします。また、コピー先で座標変換(位置ズレや回転)を伴うコピーを行った場合には追跡不能になります。何より、数万のオブジェクトをループで走査する処理はパフォーマンスの墓場です。
◯ 勝利の設計:`AcadIdPair` による完全なる「IDマッピング」
AutoCADの内部カーネル(ObjectARX)は、ディープクローン(深層コピー)を行う際、コピー元とコピー先のオブジェクトIDの対応表をメモリ内に生成しています。
VBAの `CopyObjects` メソッドには、この対応表を `AcadIdPair` のコレクションとして取得する仕組みが標準で備わっています。これを利用することこそが、最速かつ唯一の正攻法です。
—
2. AutoCADにおける「ハンドル」と「オブジェクトID」の決定的な違い
VBAで実装に入る前に、この2つの識別子の違いを脳に叩き込んでください。ここを混同していると、64bit化された現代のAutoCAD VBAで必ずバグ(型オーバーフローやメモリアクセス違反)を誘発します。
| 識別子 | 性質 | 有効範囲と永続性 | VBAでのデータ型 |
| :— | :— | :— | :— |
| ハンドル (Handle) | 16進数の文字列(例: `”1F4″`) | 図面ファイル内で永続的に不変。図面を保存して閉じても変わらない。 | `String` |
| オブジェクトID (ObjectID) | メモリ上のポインタ(アドレス) | メモリ展開中のみ有効。図面を開き直すたびに変動する。 | `Long` (32bit) / `LongPtr` または `Double` (64bit) |
外部データベースに保存すべきは、永続性が保証された「ハンドル(Handle)」の一択です。
しかし、VBAの `IDMap` が返してくるのはメモリ上の識別子である `ObjectID` です。
したがって、我々が構築すべきロジックは以下のフローになります。
[コピー元オブジェクト]
│ (Handleを保持)
▼
[コピー元 ObjectID] ──(IDMap / AcadIdPair)──► [コピー先 ObjectID]
│
▼
[コピー先オブジェクト]
│ (Handleを取得)
▼
[コピー先 Handle] (DBへ保存)
—
3. 実務に耐える堅牢なプロダクションコード
それでは、具体的な実装を示します。
このコードは、開いている「ソース図面(アクティブ図面)」から、特定のオブジェクトを選択し、別の「ターゲット図面」へとコピーした上で、新旧のハンドル対応表を完全にマッピングして出力・管理するものです。
32bit/64bitのAutoCAD環境双方で安全に動作し、エラー発生時には確実にトランザクションをロールバック(コピー先オブジェクトを消去してメモリ解放)する堅牢な設計となっています。
参照設定の追加
このコードを実行する前に、VBAエディタの `ツール` -> `参照設定` で以下にチェックが入っていることを確認してください。
- `AutoCAD
Type Library` - `Microsoft Scripting Runtime` (対応表の高速処理に `Dictionary` を使用するため)
Option Explicit
‘ ==============================================================================
‘ 構造化された図面間オブジェクトコピー&ハンドル追跡エンジン
‘ ==============================================================================
Public Sub CopyObjectsWithHandleTracking()
On Error GoTo ErrorHandler
Dim acApp As AcadApplication
Set acApp = ThisDrawing.Application
‘ 1. コピー元(Source)とコピー先(Target)の図面を定義
Dim srcDoc As AcadDocument
Set srcDoc = ThisDrawing ‘ 現在アクティブな図面をソースとする
If acApp.Documents.Count < 2 Then
MsgBox "ターゲットとなる別の図面を開いた状態で実行してください。", vbExclamation, "実行エラー"
Exit Sub
End If
' 簡易的に、2番目の図面をターゲットとする
Dim tgtDoc As AcadDocument
Dim doc As AcadDocument
For Each doc In acApp.Documents
If doc.FullName <> srcDoc.FullName Then
Set tgtDoc = doc
Exit For
End If
Next doc
If tgtDoc Is Nothing Then
MsgBox “適切なターゲット図面が見つかりません。”, vbCritical, “エラー”
Exit Sub
End If
‘ ユーザーへのプロセス提示
Debug.Print “==================================================”
Debug.Print “コピー元: ” & srcDoc.Name
Debug.Print “コピー先: ” & tgtDoc.Name
Debug.Print “==================================================”
‘ 2. コピー対象のオブジェクト(Entity)を選択(ここでは簡易的にモデル空間の全図形を対象)
‘ 実務では、選択セット(SelectionSet)フィルタを用いて、特定のDB連携対象図形のみを抽出すること。
Dim entityCount As Long
entityCount = srcDoc.ModelSpace.Count
If entityCount = 0 Then
MsgBox “コピー元のモデル空間にオブジェクトが存在しません。”, vbInformation, “処理中断”
Exit Sub
End If
‘ コピー対象オブジェクトをオブジェクト配列に格納
Dim objArray() As Object
ReDim objArray(0 To entityCount – 1)
Dim i As Long
Dim ent As AcadEntity
i = 0
For Each ent In srcDoc.ModelSpace
Set objArray(i) = ent
i = i + 1
Next ent
‘ 3. CopyObjectsの鍵となる「IDMap(IdPair配列)」の宣言
‘ ※Variant型として宣言する必要がある(AutoCAD APIの制約)
Dim idMap As Variant
Dim copiedObjects As Variant
‘ 4. コピー処理の実行
‘ 第1引数: コピー対象オブジェクト配列
‘ 第2引数: コピー先オーナー(ターゲット図面のモデル空間)
‘ 第3引数: IDMap(空のVariantを渡すと、コピー後にAcadIdPairの配列が格納されて戻る)
copiedObjects = srcDoc.CopyObjects(objArray, tgtDoc.ModelSpace, idMap)
‘ 5. マッピングデータの解析とハンドル追跡
‘ Scripting.Dictionary を用いて「元ハンドル ➔ 新ハンドル」の高速検索マップを構築
Dim handleMap As Scripting.Dictionary
Set handleMap = New Scripting.Dictionary
‘ AcadIdPairコレクションの解析
Dim pairIndex As Long
Dim idPair As AcadIdPair
Dim srcObj As AcadObject
Dim tgtObj As AcadObject
Dim srcHandle As String
Dim tgtHandle As String
‘ エラーハンドリング用のロールバックフラグ
Dim isMappingSuccess As Boolean
isMappingSuccess = False
‘ idMap配列をループ処理
For pairIndex = LBound(idMap) To UBound(idMap)
Set idPair = idMap(pairIndex)
‘ IsClonedプロパティで、実際にコピーが成功したオブジェクトのみを対象にする
‘ (依存関係によりコピーがスキップされたオブジェクトを排除するため)
If idPair.IsCloned Then
‘ ソース(コピー元)のObjectIDからオブジェクトを取得
‘ ※64bit環境におけるObjectIDの内部表現の差異を吸収するため、
‘ 直接ObjectIDをキャストせず、ObjectIdToObjectメソッドを使用する。
Set srcObj = srcDoc.ObjectIdToObject(idPair.Key)
‘ ターゲット(コピー先)のObjectIDからオブジェクトを取得
Set tgtObj = tgtDoc.ObjectIdToObject(idPair.Value)
‘ それぞれのオブジェクトから「永続的なキー」であるハンドルを取得
srcHandle = srcObj.Handle
tgtHandle = tgtObj.Handle
‘ Dictionaryに格納(キー: コピー元ハンドル, 値: コピー先ハンドル)
If Not handleMap.Exists(srcHandle) Then
handleMap.Add srcHandle, tgtHandle
Debug.Print “追跡成功 [元ハンドル]: ” & srcHandle & ” ➔ [新ハンドル]: ” & tgtHandle
End If
End If
Next pairIndex
isMappingSuccess = True
‘ 6. 外部データベース更新処理への橋渡し(シミュレーション)
If isMappingSuccess Then
Debug.Print “————————————————–”
Debug.Print “データ連携開始: 総数 ” & handleMap.Count & ” 件のハンドル対応を検出。”
‘ ここで外部DB更新クラスや関数を呼び出す
‘ Call UpdateExternalDatabase(handleMap)
MsgBox “図面間コピーおよびハンドル追跡が正常に完了しました。” & vbCrLf & _
“追跡オブジェクト数: ” & handleMap.Count & ” 件”, vbInformation, “成功”
End If
Exit Sub
ErrorHandler:
‘ 堅牢なエラーハンドリング
‘ 万が一途中で失敗した場合は、コピー先に中途半端に生成されたオブジェクト群を削除(ロールバック)
Dim errDesc As String
errDesc = Err.Description
On Error Resume Next ‘ ロールバック処理中の2重エラーを防ぐ
If Not IsEmpty(copiedObjects) Then
Debug.Print “エラー発生のため、ロールバックを実行します…”
Dim delObj As AcadObject
For Each delObj In copiedObjects
delObj.Delete
Next delObj
End If
MsgBox “致命的なエラーが発生したため、処理をロールバックしました。” & vbCrLf & _
“エラー内容: ” & errDesc, vbCritical, “システムエラー”
End Sub
—
4. 外部データベース連携設計における「実務上の3大落とし穴」
このハンドル追跡ロジックを実際の業務システムに組み込む際、アーキテクトとして必ず考慮しなければならない「落とし穴」と、その対策を提示します。
落とし穴①:コピー先図面での「Undo(元に戻す)」による不整合
VBAツールが走り、データベースのレコードが「新ハンドル」に更新された後、設計者がCAD上で `Ctrl + Z`(Undo)を押してコピー処理を取り消した場合、「CAD上には存在しないが、DBには登録されている」というゴーストレコード(不整合)が発生します。
- 対策:
- CAD図面の保存(`BeginSave`)イベント時に、DBに登録されているハンドルが図面内に実在するかを検証する「整合性チェッカー」をバックグラウンドで走らせる。
- または、VBAでのコピー完了時に、DB側のステータスを「仮登録(Pending)」とし、設計者が図面を保存した、あるいは明示的に「確定」ボタンを押したタイミングで「本登録」に昇格させるトランザクション設計にする。
落とし穴②:ネストされたオブジェクト(ブロック内図形)のコピー
`CopyObjects` は強力ですが、ブロック定義(`AcadBlock`)の中にある個々の図形をコピー対象にすると、マッピングが複雑化します。ブロック参照(`AcadBlockReference`)をコピーした場合は、ブロック参照自体のハンドルは追跡できますが、ブロック内部の個々の構成要素のハンドルはコピーされず、同一の定義を参照します。
- 対策:
- DBに紐付けるべきは、「ブロック内の子図形」ではなく、常にレイアウト空間・モデル空間に配置された「親であるブロック参照(あるいは個々の独立したエンティティ)」にするというルール(データモデル設計)を徹底すること。
落とし穴③:64bit版AutoCADにおける `ObjectID` の数値オーバーフロー
AutoCAD 2014以降、完全に64bit化された環境において、VBAの内部では `ObjectID` が通常の `Long` 型(32bit整数)の許容範囲を超えるケースが多発します。
`idPair.Key` や `idPair.Value` を、直接 `Long` 型の変数に代入して比較しようとすると、`オーバーフロー (Error 6)` でプログラムが即死します。
- 対策:
- 上記のプロダクションコードに示した通り、IDの値自体を数値演算にかけたり変数に代入したりしてはなりません。取得した `idPair.Key` を直接 `ObjectIdToObject` メソッドの引数に引き渡し、オブジェクト実体を介して安全に `Handle`(String型)へと変換して処理してください。
—
5. まとめ
実務で使える「プロのコード」と「素人のスパゲッティコード」の境界線は、「不測の事態(エラー、ユーザーの予期せぬ操作、CADのプラットフォーム仕様)をどこまで予見して設計されているか」にあります。
今回解説した `CopyObjects` と `AcadIdPair`(IDMap)を駆使したハンドル追跡手法は、図面間コピーにおけるデータ連携の「唯一の正解」です。
座標検索や要素数比較といった不安定な実装を排除し、AutoCADのカーネルが保証するマッピング情報をダイレクトにハッキングすることで、極めて高速で、100%バグの起きない堅牢なCAD・DB連携システムを構築してください。
