【上級】AcadDocument.Database.CopyObjectsの戻り値「IdPair」を解析し、オブジェクトIDの対応関係を厳密に管理する
AutoCAD VBAによる大規模な図面自動化において、最もプリミティブでありながら、多くの開発者が挫折する壁が「図面間(またはデータベース間)のオブジェクト複製と、それに伴うIDの断絶」だ。
`AcadDocument.Database.CopyObjects` メソッドは、複数のオブジェクトを一括して別データベースへ転送するための強力なエンジンである。しかし、このメソッドが返すVariant配列(中身は`IdPair`オブジェクトの配列)を正しくハンドリングできなければ、コピーされた図形と元の属性データ、外部データベース(RDB)上のレコード、あるいは図面内のXData(拡張データ)やDICTIONARYとのリンクは完全に破綻する。
本稿では、`IdPair`の内部構造とオブジェクトのライフサイクルを完全に掌握し、極限までパフォーマンスを高めた厳密なID管理・同期処理の実装パターンを解説する。
—
1. `CopyObjects` と `IdPair` の本質
`CopyObjects` は、ソースデータベースからデスティネーションデータベースへオブジェクトのメモリイメージを複製する。この時、AutoCADの内部アーキテクチャ(ObjectARX層)では、ハンドル(Handle)およびオブジェクトID(ObjectId / AcadObjectId)が新しく採番される。
単純に図形をコピーするだけならばこれで十分だが、実務の現場では以下のような要件が必ず発生する。
- コピー元が持っていたカスタム属性(ブロックの属性やXData)の関連付けを維持したい。
- 外部のSQLデータベースにあるメタデータと、新しく生成された図形のIDを紐付け直してデータベースを更新したい。
- 複雑な複合オブジェクト(ブロック定義、グループ、ディクショナリ等)の内部構造の整合性を保ちたい。
ここで鍵を握るのが、`CopyObjects` の戻り値である `IdPair`(アイディ・ペア)である。
`IdPair` オブジェクトの主要プロパティ
- `Key`: コピー元のオブジェクトID(またはAcadObject)
- `Value`: コピー先のオブジェクトID(またはAcadObject)
- `IsCloned`: オブジェクトが実際にクローン(複製)されたかどうかを示すブール値
- `IsPrimary`: 呼び出し時に直接指定されたプライマリ(根源的)なオブジェクトであるか、あるいはそれに伴って自動追従してコピーされた依存オブジェクト(サブオブジェクト)であるかを示すブール値
この `IsPrimary` と `IsCloned` のフラグを無視してコードを書くプログラマは、大規模図面を扱った瞬間にメモリリークや意図しない関連付けエラーに直面することになる。
—
2. 実装:厳密なID対応関係管理エンジン
以下に、ソースデータベースからアクティブデータベースへオブジェクト群をコピーし、`IdPair` を完全に解析して、新旧のIDマッピングをDictionary(連想配列)として構築する実用的なVBAコードを示す。
このコードでは、VBAの弱い型付けによるパフォーマンス劣化を防ぎ、オブジェクトの参照を適切に管理・解放する設計を取り入れている。
Option Explicit
‘ —————————————————————————–
ريخ: AutoCAD VBA Architectural Series
テーマ: CopyObjectsとIdPairによる厳密なIDマッピング管理
—————————————————————————–
Public Sub ExecuteAdvancedCopy()
Dim srcDoc As AcadDocument
Dim destDoc As AcadDocument
Set destDoc = ThisDrawing ‘ コピー先を現在図面とする
‘ ※注意: 実運用では適切なエラーハンドリングとファイル存在チェックを入れること
Dim targetFilePath As String
targetFilePath = “C:\AutoCAD\SourceTemplate.dwg”
‘ ソース図面をバックグラウンド(不可視)で開くのが実務の定石
Set srcDoc = Documents.Open(targetFilePath, True)
On Error GoTo ErrorHandler
‘ 1. コピー対象のオブジェクト群(SelectionSetまたは特定のCollection)を準備
Dim sset As AcadSelectionSet
Set sset = CreateSelectionSet(srcDoc, “CopyTargetSet”)
If sset.Count = 0 Then
MsgBox “コピー対象のオブジェクトが存在しません。”, vbExclamation
GoTo Cleanup
End If
‘ AcadSelectionSetをObject配列(Variant)に変換
Dim objs() As Object
ReDim objs(0 To sset.Count – 1)
Dim i As Long
For i = 0 To sset.Count – 1
Set objs(i) = sset.Item(i)
Next i
‘ 2. オーナーとなるディクショナリやモデルスペースの取得
Dim destOwner As AcadBlock
Set destOwner = destDoc.ModelSpace
‘ 3. CopyObjectsの実行
‘ 構文: object.CopyObjects(Objects, Owner [, IdPairs])
Dim idPairs() As Variant
‘ AutoCADの内部データベース間でオブジェクトを移送
srcDoc.Database.CopyObjects objs, destOwner, idPairs
‘ 4. IdPairの解析とマッピングの構築
Dim idMap As Object
Set idMap = CreateObject(“Scripting.Dictionary”)
Call AnalyzeIdPairs(idPairs, idMap)
‘ 5. マッピング結果を利用した事後処理(例:ログ出力や外部DB連携)
Call ProcessMappedObjects(idMap, destDoc)
MsgBox “オブジェクトのコピーとIDマッピング解析が正常に完了しました。”, vbInformation
Cleanup:
‘ 6. 厳密なメモリ解放とリソースのクリーンアップ
On Error Resume Next
If Not sset Is Nothing Then
srcDoc.SelectionSets.Item(sset.Name).Delete
End If
If Not srcDoc Is Nothing Then
srcDoc.Close False
End If
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Resume Cleanup
End Sub
‘ —————————————————————————–
‘ IdPair配列を解析し、Scripting.Dictionaryへマッピングを格納する核心関数
‘ —————————————————————————–
Private Sub AnalyzeIdPairs(ByRef idPairs() As Variant, ByRef outMap As Object)
Dim p As Variant
Dim srcId As String
Dim destId As String
For Each p In idPairs
‘ IdPairオブジェクトのプロパティを評価
‘ ※AutoCADのバージョンによりIdPairはCOMラッパー経由で扱うため、
‘ Late Binding(遅延バインディング)の安全なプロパティアクセスを行う。
Dim isCloned As Boolean
Dim isPrimary As Boolean
isCloned = p.IsCloned
isPrimary = p.IsPrimary
If isCloned Then
‘ Key (コピー元ID) と Value (コピー先ID) を取得
‘ 注: .Key および .Value は AcadObjectId または オブジェクト本体を返す場合があるため、
‘ 文字列としてのHandleまたはObjectIDに正規化する。
Dim srcObj As Object
Dim destObj As Object
Set srcObj = p.Key
Set destObj = p.Value
If Not srcObj Is Nothing And Not destObj Is Nothing Then
srcId = CStr(srcObj.Handle)
destId = CStr(destObj.Handle)
‘ カスタム構造体またはカンマ区切りのメタデータを格納
Dim mappingInfo As String
mappingInfo = “DestHandle:” & destId & “|Primary:” & CStr(isPrimary)
If Not outMap.Exists(srcId) Then
outMap.Add srcId, mappingInfo
End If
End If
‘ オブジェクト参照の明示的破棄
Set srcObj = Nothing
Set destObj = Nothing
End If
Next p
End Sub
‘ —————————————————————————–
‘ ユーティリティ: 選択セットの安全な生成
‘ —————————————————————————–
Private Function CreateSelectionSet(ByVal targetDoc As AcadDocument, ByVal ssName As String) As AcadSelectionSet
Dim ss As AcadSelectionSet
On Error Resume Next
Set ss = targetDoc.SelectionSets.Item(ssName)
If Not ss Is Nothing Then
ss.Delete
End If
On Error GoTo 0
Set ss = targetDoc.SelectionSets.Add(ssName)
‘ 例として、すべてのレイヤー「0」以外の図形、あるいは特定の条件でフィルタリング
Dim filterType(0) As Integer
Dim filterData(0) As Variant
filterType(0) = 0 ‘ 例: INSERT (ブロック参照) のみを対象にする場合
filterData(0) = “INSERT”
‘ 全選択の場合はフィルタなしでも可。今回は全取得の例とするため引数なし。
ss.Select acSelectionSetAll
Set CreateSelectionSet = ss
End Function
‘ —————————————————————————–
‘ マッピング済みのオブジェクトを使った業務ロジックのプレースホルダー
‘ —————————————————————————–
Private Sub ProcessMappedObjects(ByVal idMap As Object, ByVal doc As AcadDocument)
Dim key As Variant
For Each key In idMap.Keys
‘ シニアエンジニア向け知見:
‘ ここで idMap.Item(key) をパースし、
‘ 外部SQL Serverへの書き戻しや、拡張データ(XData)の動的書き換えを行う。
Debug.Print “Src Handle: ” & key & ” -> ” & idMap.Item(key)
Next key
End Sub
—
3. チーフアーキテクトが教える「現場の罠」と最適化の極意
実務において上記のコード、あるいは類似の処理を実装する際、以下の「現場の罠」に必ず直面する。ここを突破できるかどうかが、プロのエンジニアとアマチュアの分かれ道となる。
① COMラッパーのメモリリークとガベージコレクションの遅延
VBAはCOM(Component Object Model)の参照カウント方式で動作している。`For Each p In idPairs` のループ内で取得する `p.Key` や `p.Value` は、内部で新しいCOMラッパーインスタンスを生成している場合がある。
これを適切に `Set srcObj = Nothing` でループごとに解放しないと、数千・数万のオブジェクトを扱う大規模図面(プラント図面やインフラ図面)でVBAのメモリ空間が枯渇し、「メモリ不足 (Out of memory)」 エラーでAutoCADごとクラッシュする。
② ハンドル(Handle)と オブジェクトID(ObjectId)の混同
AutoCADには「ハンドル(16進数の文字列。図面ファイル間で不変なことが多い)」と「オブジェクトID(セッション中のメモリポインタに依存する整数値)」が存在する。
`IdPair` が返すのは厳密にはオブジェクトの参照またはIDであるが、異なるデータベース間(`srcDoc.Database` と `destDoc.Database`)を行き来する場合、オブジェクトIDはその生存期間が異なるため、永続的なキーとしては絶対にハンドル(Handle)を使用すべきである。上記のコードで `srcObj.Handle` をキーに採用しているのはそのためだ。
③ トランザクションとパフォーマンスのトレードオフ
もし数十万オブジェクト規模のバッチ処理を行う場合、標準のVBAコレクション操作やドキュメント操作は極端に重くなる。さらに速度を追求する場合、AutoCAD VBAのレイヤーを脱却し、.NET (C# / VB.NET) によるManaged ObjectARX (AcadApp.Application.DocumentManager) へ移行することを強く推奨する。
しかし、レガシーな社内資産や、VBAしか許されないセキュリティ環境下においては、上記のコードのように「選択セットの確実な破棄」「遅延バインディングの回避」「適切なエラーハンドリング」を徹底することで、VBAであってもビクともしない堅牢な自動化システムを構築できる。
—
結び
`AcadDocument.Database.CopyObjects` と `IdPair` の解析は、単なる「図面のコピペ」を超えた、図面間データ統合の心臓部である。この挙動を完全に理解し、メモリのライフサイクルまでコントロール下に置いた時、あなたのAutoCAD VBAスキルは確実に次のステージへと到達するだろう。
