AutoCAD VBAを掌握する極限の知見
【実務中級】SendCommandメソッドの光と影:API未対応コマンドをVBAから実行する際の同期制御と注意点
開発プロジェクトの現場において、AutoCAD VBAは今なお強力な自動化の武器だ。`AcadDocument`の各オブジェクトを直接操作し、画層、ブロック、図形を意のままに生成する――。これこそがVBAの醍醐味である。
しかし、実務で複雑な図面を扱うエンジニアなら、一度はこう壁にぶつかったはずだ。
「このオブジェクトモデル、肝心のあのコマンドを叩くメソッドがないぞ…?」
寸法拘束の付与、一部のサードパーティ製アドオンの独自コマンド、あるいは極めて特殊なジオメトリ編集。APIが用意されていない領域に直面したとき、多くの開発者が安易に手を伸ばすのが、禁断の果実――`SendCommand`メソッドである。
今回は、この`SendCommand`の光と影を徹底的に解剖する。単なる「コマンドラインへの文字列流し込み」という甘い認識は、やがて図面の破損や制御のロストという致命的なバグを産む。現場のプロとして、なぜこのメソッドが危険を孕み、どう制御すればプロダクション品質に昇華できるのかを伝授しよう。
—
1. なぜ `SendCommand` は「影」を持つのか?(非同期の罠)
まず、AutoCAD VBAのオブジェクトモデルと、AutoCADのコアエンジン(C++層)の関係を理解しなければならない。
VBAから通常のメソッド(例: `AddLine`など)を呼ぶ場合、COMインターフェースを介して処理が同期的に行われるか、あるいは比較的安全にキューが処理される。
しかし、`SendCommand` は文字通り 「AutoCADのコマンドラインにキーボード入力をエミュレートして文字列を送り込む」 だけの機能だ。
ここで発生するのが、完全な非同期実行(Asynchronous Execution)という問題である。
- VBA側で `SendCommand` を実行すると、AutoCAD側へコマンドが「投入」された瞬間、VBAのマクロは次の行へ進んでしまう。
- しかし、AutoCAD側ではまだそのコマンドが処理中(あるいは選択待ち)である。
- 結果として、VBAが勝手に先に進み、まだ存在しないオブジェクトを操作しようとしてエラーになる、あるいはコマンドが競合してフリーズするという現象が起きる。
この「タイミングのズレ」を制圧しない限り、実務で使える堅牢な自動化ツールを作ることは不可能だ。
—
2. 同期制御の極意:VBAを「待たせる」技術
`SendCommand` を安全に扱うための鉄則、それは 「AutoCADがアイドル状態になるまでVBA側で適切に待機(同期)させること」 だ。
単純な `Application.Wait` や `DoEvents` の乱用は、CPUを無駄に食いつぶすだけでなく、タイミング依存の脆弱なコードを生む。現場で採用すべきアプローチは、AutoCADのドキュメントステータスや、LISPとの連携による確実な同期だ。
プロダクションコード例:安全な `SendCommand` ラッパー
以下のコードは、実務でそのまま使える堅牢な `SendCommand` のラッパー関数を含むモジュールである。単にコマンドを送るだけでなく、エラーハンドリングと処理待ちの概念を組み込んでいる。
Option Explicit
‘ —————————————————————–
‘ 模範的なコマンド実行モジュール
‘ —————————————————————–
Public Sub ExecuteCommandSafely()
On Error GoTo ErrorHandler
Dim acadApp As AcadApplication
Set acadApp = ThisDrawing.Application
‘ 1. 実行前のドキュメント状態の確認
If ThisDrawing.ActiveSpace = acModelSpace Then
‘ 例として、API未対応の特殊なストレッチや、
‘ 外部LISPルーチンを呼び出すシチュエーションを想定
Dim cmdString As String
‘ 注意: SendCommandの文字列の末尾には、Enterキーを意味する半角スペースや改行(vbCr)が必須
cmdString = “_STRETCH C 0,0 100,100 ; 50,50 ; ”
‘ 2. コマンドの送信
Call SendCommandAndWait(ThisDrawing, cmdString)
Else
MsgBox “モデル空間アクティブ時にのみ実行可能です。”, vbExclamation, “処理中断”
End If
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ —————————————————————–
‘ AutoCADのコマンド完了を擬似的に同期させるラッパー関数
‘ —————————————————————–
Private Sub SendCommandAndWait(ByRef targetDoc As AcadDocument, ByVal commandLine As String)
‘ AutoCADがコマンド実行中(CmdActiveシステム変数など)でないことを確認してから送るのが理想だが、
‘ VBA単体では限界があるため、直前の処理が完了していることを保証するウェイトを入れる
targetDoc.SendCommand commandLine
‘ AutoCADの内部処理キューをフラッシュし、VBAの実行権を一時的にOS/AutoCADに渡す
DoEvents
‘ 大規模な図形処理を伴う場合は、ここで一定時間のアイドルを挟むか、
‘ 編集されたオブジェクトのカウント変化を監視するロジックを挟む
Dim i As Long
For i = 1 To 100000: DoEvents: Next i ‘ 簡易ウェイト(実務では必要に応じて調整)
End Sub
—
3. さらに高みを目指す:AutoLISPとのハイブリッド戦略
VBAの `SendCommand` の限界を突破する究極のテクニックが、AutoLISPルーチンとの組み合わせ(ハイブリッド戦略)だ。
複雑な条件分岐、図面データベース(DXFグループコード)の高度な操作、あるいは複数のコマンド連続実行は、VBAだけで `SendCommand` を連打すると確実に破綻する。
その代わり、「VBAから一発のLISP式(セックス表达式)を `SendCommand` で流し込み、実際の重い処理はLISP側で同期処理させる」というアーキテクチャをとるのが最も美しい。
LISP連携のメリット
1. 処理の原子性(アトミズム): LISP側で `(progn …)` を使えば、一連の処理が途中で途切れることなく確実に実行される。
2. システム変数の安全な退避・復元: VBAでゴリゴリ書くより、LISPの `getvar` / `setvar` の方が記述がスマートで確実。
Public Sub RunLispViaVBA()
Dim lispExpression As String
‘ AutoCAD上で動作するLISP式を組み立てて一括送信する
‘ 例:画層「UNITS_TEMP」を作成し、現在の画層に設定して線分を一本引くLISP
lispExpression = “(progn ” & _
“(command “”_-LAYER”” “”_M”” “”UNITS_TEMP”” “”””) ” & _
“(command “”_LINE”” “”0,0″” “”100,100″” “”””) ” & _
“(princ “”\nLISP処理が正常に完了しました。””)) ” & vbCr
‘ 送信
ThisDrawing.SendCommand lispExpression
DoEvents
MsgBox “LISPスクリプトの投入が完了しました。”, vbInformation
End Sub
—
4. まとめ:プロダクションコードにおける心得
1. `SendCommand` は「最後の手段」と心得よ
まずは標準オブジェクトモデル(`ModelSpace.Add…` や `AcadBlock` など)で実現できないかを最優先で検討すること。APIがあるならAPIを使うべきだ。
2. 文字列の末尾(Enter)に細心の注意を払え
コマンドラインは人間のキーボード入力を模倣している。スペース(` `)や改行(`vbCr`)の抜けが、致命的なコマンドのハンギングを引き起こす。
3. `DoEvents` とエラーハンドリングを怠るな
非同期の壁を超えるためには、OSとAutoCADへの処理権の譲渡(`DoEvents`)と、万が一のクラッシュに備えた堅牢な `On Error` 制御が不可欠である。
API未対応の領域を攻略できたとき、あなたのVBAスキルは「初級者のマクロ記録」から「プロフェッショナルなCAD自動化エンジニア」へと確実にシフトする。
コードの美しさと、背後にある挙動への深い理解――その両立を忘れないでほしい。
