【上級】AcadDocument.Database.CopyObjectsを用いた「図面間高速オブジェクトコピー」:クリップボードを介さずメモリ上で図形を転送する安定実装
開発プロジェクトの現場において、図面間でのオブジェクトのコピーは日常茶飯事だ。しかし、ここで素人が書いたコードや、安易にUI操作をマクロ化しただけのスクリプトを導入すると、たちまち大規模なパフォーマンス低下や予期せぬクラッシュの温床となる。
「図面Aから図面Bへ、大量のブロックや線分を移植したい」
そう考えたとき、大半の初学者は `Copy` メソッドでクリップボード経由の操作を模倣するか、画面をアクティブに切り替えて `SendCommand` で `_COPYCLIP` や `_PASTECLIP` を叩くという愚行に走る。
断言しよう。実務の現場でクリップボードを介した図面間コピーを行うプログラマブルなコードは、即座にリファクタリング対象だ。
OSのクリップボードは外部プロセスとの共有資源であり、AutoCADの非同期処理やマルチドキュメント環境(MDI)において、致命的な競合(クリップボードロック)やメモリリークを引き起こす「爆弾」に他ならない。
今回は、AutoCAD VBAの底力を限界まで引き出し、OSをバイパスしてメモリ空間内で完結させる究極の高速転送手法、`Database.CopyObjects` を用いた堅牢な実装アーキテクチャを伝授する。
—
なぜ「クリップボード経由」は実務で破綻するのか?
実務でAutoCADを酷使するエンジニアなら痛感しているはずだ。
`SendCommand` や `CopyClipboard` を用いたコードは、以下のような構造的欠陥を抱えている。
1. UI依存の脆弱性: 画面のちらつき(ScreenUpdating)が発生し、処理中にユーザーがマウスやキーボードに触れるとコマンドが中断する。
2. パフォーマンスの絶望的な低下: 図形データを一度OSのクリップボードというシリアライズ空間に書き出し、それを別ウィンドウが読み取るという無駄なI/Oが発生する。数千個のオブジェクトを扱えば、秒単位ではなく「分単位」のロスになる。
3. トランザクションの不整合: エラーハンドリングが極めて難しく、途中で失敗した場合に中途半端なデータが貼り付けられた残骸図面が残る。
これに対し、今回解説する `Database.CopyObjects` は、AutoCADのコアエンジン(ObjectARXの思想をVBA層に色濃く残したAPI)を直接叩く。図面データ(Database)から別の図面データ(Database)へ、メモリ上で直接オブジェクトのポインタ群をごっそり複製・マッピングするため、描画エンジンをバイパスして圧倒的な速度と安定性を誇る。
—
アーキテクチャ設計:CopyObjectsのメカニズム
`Database.CopyObjects` を使いこなすためには、AutoCADのデータベース構造(Database / Dictionary / ObjectId)を正しく理解する必要がある。
メソッドのシグネチャは概ね以下の通りだ。
object.CopyObjects(Objects, Owner, IdMap)
- Objects: コピー元オブジェクトの `Variant` 配列(`ObjectId` の1次元配列)。
- Owner: コピー先の所有者(通常はコピー先図面の `ModelSpace` や `BlockTableRecord`)。
- IdMap: `IdPair` のコレクション。コピーに伴いIDが新規採番されるため、その新旧のマッピング関係が格納される(リファレンスの再配線に極めて重要)。
ここで最も重要な注意点がある。
「異なるデータベース間(DrawingA.Database から DrawingB.Database)でオブジェクトをコピーする場合、Ownerには必ずコピー先データベース側にあるオブジェクトを指定しなければならない。」
異なるDatabase間でオブジェクトの所有権を混同すると、即座に致命的なメモリ違反(Fatal Error)を引き起こす。この境界線を厳格に管理することが、堅牢なコードの要諦となる。
—
【プロダクションコード】図面間高速オブジェクトコピー実装
以下のコードは、開いている別図面(または指定した図面ファイル)から、特定の画層(Layer)に属するオブジェクト、あるいは選択されたオブジェクト群を、現在の図面のモデル空間へメモリ上で一瞬にして転送する実用モジュールである。
エラーハンドリング、トランザクションの概念、そしてオブジェクトIDの安全な配列変換を網羅したプロフェッショナル仕様となっている。
Option Explicit
” ==============================================================================
” 模範的プロフェッショナル実装:図面間メモリ上オブジェクト高速コピー
” ==============================================================================
Public Sub ExecuteHighSpeedCrossDrawingCopy()
Dim sourceDoc As AcadDocument
Dim targetDoc As AcadDocument
Dim sourceDb As AcadDatabase
Dim targetDb As AcadDatabase
‘ 1. ターゲット(現在アクティブな図面)の定義
Set targetDoc = ThisDrawing
Set targetDb = targetDoc.Database
‘ 2. コピー元図面の取得(※ここでは例として、すでに開いている2番目の図面を想定)
‘ 実務では GetOpenDocument などのラッパー関数で安全に取得すること
On Error GoTo ErrorHandler
If Application.Documents.Count < 2 {
MsgBox "コピー元の図面が他に開かれていません。", vbCritical, "致命的エラー"
Exit Sub
End If
' 例として、アクティブ以外の最初のドキュメントをコピー元とする
Dim doc As AcadDocument
For Each doc In Application.Documents
If doc.FullName <> targetDoc.FullName Then
Set sourceDoc = doc
Exit For
End If
Next doc
If sourceDoc Is Nothing Then
Err.Raise vbObjectError + 1000, “CopyProcess”, “有効なコピー元図面が見つかりません。”
End If
Set sourceDb = sourceDoc.Database
‘ 3. コピー対象のオブジェクト群(ObjectId)を収集する
‘ ここでは「コピー元のモデル空間にある全エンティティ」を対象とするが、
% フィルタリング条件(画層名や画素タイプ)をここに挟むのが実務的
Dim sourceEnts() As AcadEntity
Dim objIDArray() As AcadObjectId
Dim entCount As Long
entCount = sourceDoc.ModelSpace.Count
If entCount = 0 {
MsgBox “コピー元図面のモデル空間にオブジェクトが存在しません。”, vbExclamation, “情報”
Exit Sub
End If
ReDim objIDArray(0 To entCount – 1)
Dim i As Long
For i = 0 To entCount – 1
Set sourceEnts(i) = sourceDoc.ModelSpace.Item(i)
objIDArray(i) = sourceEnts(i).ObjectId
Next i
‘ 4. 配列を Variant 型にキャスト(CopyObjectsの仕様要件)
Dim varSourceIds As Variant
varSourceIds = objIDArray
‘ 5. コピー先オーナー(ターゲット図面のモデル空間)の取得
Dim targetOwner As AcadBlockTableRecord
Set targetOwner = targetDb.ModelSpace
‘ 6. データベース間CopyObjectsの実行(クリップボードを完全にバイパス)
Dim idMap As AcadIdPair
‘ AutoCADの内部トランザクション的コンテキストで保護
targetDb.CopyObjects varSourceIds, targetOwner, idMap
‘ 7. 完了フィードバック
targetDoc.Regen acActiveViewport
MsgBox “図面間のメモリ上高速コピーが正常に完了しました。” & vbCrLf & _
“転送オブジェクト数: ” & entCount, vbInformation, “処理成功”
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “例外トラップ”
‘ 実務ではここにログ出力や詳細なトランザクションロールバック処理を記述する
End Sub
—
現場で絶対に外せない「3つの設計的注意点」
このコードを実際の業務システムや自動化ツールに組み込む際、以下の3点を見落とすと必ず痛い目を見る。
1. 画層(Layer)や線種(Linetype)の依存関係
オブジェクト単体をコピーしても、それが依存している「画層」や「文字スタイル」「ブロック定義」がコピー先図面に存在しない場合、AutoCADは自動的にデフォルト値にフォールバックするか、最悪の場合未定義ステートを引き起こす。
大規模なデータ移行を行う場合は、`CopyObjects` を実行する前に、あらかじめ必要な画層やスタイルオブジェクトの存在確認・事前コピー(依存関係の解決)を行うルーチンを前置するのがプロの作法だ。
2. メモリとオブジェクトのライフサイクル
VBAのオブジェクト参照 (`AcadEntity` や `AcadDatabase`) は、処理が終わったら確実に `Nothing` を代入してメモリ解放のヒントを明示すべきである。特に多数の図面をループ処理でバッチ処理するツールの場合、オブジェクトの解放漏れが累積すると、VBAホストプロセス(Acad.exe)のメモリ使用量が跳ね上がり、メモリリークによる強制終了を引き起こす。
3. 同名ブロック(意図しない上書き)のコンフリクト
`CopyObjects` は賢いがゆえに、コピー元とコピー先で同名のブロック定義が存在する場合、IDマップ上で競合が起きる可能性がある。実務ツールでは、ブロック名にプレフィックスを付与するサニタイズ処理を挟むか、事前に名前空間の衝突を防ぐバリデーションを設計に組み込むべきだ。
—
総括
AutoCAD VBAにおける真のパフォーマンスチューニングとは、小手先のテクニックではなく、「AutoCADが内部でどのようにメモリとオブジェクトを管理しているか」というアーキテクチャの本質を理解し、それに逆らわないコードを書くことに他ならない。
今回解説した `Database.CopyObjects` によるメモリ上転送は、その最たる例だ。
クリップボードという「遅く、不安定で、時代遅れのインターフェース」を捨て去り、ダイレクトなデータベース操作をマスターしたあなたなら、どんなに巨大で複雑な図面間連携タスクであっても、ビクともしない堅牢な業務自動化システムを構築できるはずだ。
現場のエンジニアよ、非効率なコードを駆逐し、真に洗練された自動化の世界を実装せよ。
