凡庸なループを捨て、堅牢な「バッチ処理」を設計せよ:SolidWorks図面一括印刷の極意
業務自動化の現場において、多くの開発者が陥る罠がある。それは、「動けばいい」という思考で書かれた、脆弱なループ処理だ。
「フォルダ内のファイルを順に開いて、PrintOutメソッドを叩く」
これだけなら、コードは5行で済むだろう。しかし、対象が数百枚の図面(.slddrw)になった瞬間、そのコードは牙を剥く。メモリリークによるSolidWorksのクラッシュ、プリンタドライバとの競合によるスプーリングの失敗、そして、たった一つのファイル破損によって全体の処理が止まってしまう絶望。
我々エンジニアに求められるのは、単なるコードの記述ではない。「例外が発生しても、システムを死なせない設計」だ。
本稿では、数百枚の図面を確実に、かつ最適な設定で印刷するための、プロダクションレベルの設計思想と実装コードを伝授する。
1. なぜ、あなたの「一括印刷」は失敗するのか
中級者への階段を登るために、まずは失敗のメカニズムを理解しなければならない。
オブジェクトのライフサイクル管理不足
SolidWorks APIにおいて、`swApp.OpenDoc6` で開いたドキュメントを、適切に `swApp.CloseDoc` し、かつオブジェクト変数に `Nothing` を代入しない場合、メモリ上に「ゴースト」が残る。これが数百回繰り返されると、SolidWorksは物理メモリを食いつぶし、強制終了(Crash)を招く。
プリンタドライバの「待ち」を無視している
`PrintOut` メソッドは命令を投げた瞬間に制御を戻そうとするが、物理プリンタやPDFドライバのスプーリングには時間がかかる。高速なループで次々と印刷命令を投げると、ドライバ側がオーバーフローを起こし、印刷データが破損するか、スプールが止まる。
エラーハンドリングの欠如
「図面が破損している」「ファイルが読み取り専用である」といった、実務で必ず遭遇するイレギュラーに対し、`On Error Resume Next` で誤魔化してはならない。それは、エラーを「隠蔽」しているだけであり、問題の「解決」ではない。
2. 堅牢な設計:アーキテクチャの三原則
プロフェッショナルなツールを構築する際、私は常に以下の三原則を設計に組み込む。
1. 隔離(Isolation): 各ファイルの処理を独立させ、一つの失敗が全体に波及しないようにする。
2. 記録(Logging): どのファイルが成功し、どのファイルが失敗したかを、後から検証可能な形で出力する。
3. リソース解放(Clean-up): 成功・失敗に関わらず、必ずオブジェクトを破棄し、メモリをクリーンにする。
3. 実装:プロダクション・クオリティのVBAコード
以下に、これらの思想を具現化したコードを示す。このコードは単なるサンプルではない。そのまま実務の基盤として使える「骨組み」だ。
' ==============================================================================
' Module: BatchDrawingPrinter
' Description: 指定フォルダ内の図面を一括印刷する堅牢なバッチ処理モジュール
' Author: Chief Architect
' ==============================================================================
Option Explicit
' 定数定義:マジックナンバーを排除し、保守性を高める
Private Const SUCCESS_MARKER As String = "SUCCESS"
Private Const ERROR_MARKER As String = "ERROR"
Sub ExecuteBatchPrint()
Dim swApp As SldWorks.SldWorks
Dim fso As Object
Dim targetFolder As Object
Dim fileItem As Object
Dim folderPath As String
Dim logFilePath As String
Dim printerName As String
' --- 設定領域 ---
folderPath = "C:\Work\Drawings\" ' 対象フォルダ
printerName = "Adobe PDF" ' 使用するプリンタ名(正確に指定)
logFilePath = folderPath & "print_log.txt"
' ----------------
On Error GoTo ErrorHandler
Set swApp = Application.SldWorks
Set fso = CreateObject("Scripting.FileSystemObject")
' フォルダの存在確認
If Not fso.FolderExists(folderPath) Then
MsgBox "指定されたフォルダが存在しません。", vbCritical
Exit Sub
End If
Set targetFolder = fso.GetFolder(folderPath)
' ログファイルの初期化
Dim logFile As Object
Set logFile = fso.CreateTextFile(logFilePath, True)
logFile.WriteLine "Batch Print Job Started: " & Now
logFile.WriteLine "Printer: " & printerName
logFile.WriteLine "------------------------------------------"
' メインループ:ファイル単位の隔離処理
For Each fileItem In targetFolder.Files
' 拡張子チェック (.slddrw)
If LCase(fso.GetExtensionName(fileItem.Path)) = "slddrw" Then
' 個別ファイルの処理(ここでエラーが発生してもメインループは継続する)
ProcessSingleFile swApp, fileItem.Path, printerName, logFile
End If
Next fileItem
logFile.WriteLine "------------------------------------------"
logFile.WriteLine "Batch Print Job Finished: " & Now
logFile.Close
MsgBox "バッチ処理が完了しました。詳細はログを確認してください。" & vbCrLf & logFilePath, vbInformation
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
If Not logFile Is Nothing Then logFile.Close
End Sub
' ------------------------------------------------------------------------------
' Function: ProcessSingleFile
' Purpose: 単一ファイルのオープン、設定、印刷、クローズを制御する
' ------------------------------------------------------------------------------
Private Sub ProcessSingleFile(ByRef swApp As SldWorks.SldWorks, _
ByVal filePath As String, _
ByVal printerName As String, _
ByRef logFile As Object)
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
Dim fileName As String
fileName = Mid(filePath, InStrRev(filePath, "\") + 1)
On Error GoTo FileError
' 1. ファイルのオープン
' 第3引数 swOpenDocOptions_Silent を使用し、ダイアログを抑制
Set swModel = swApp.OpenDoc6(filePath, swDocDRAWING, swOpenDocOptions_Silent, "", 0, 0)
If swModel Is Nothing Then
Err.Raise vbObjectError + 513, "OpenDoc", "ファイルを開けませんでした。"
End If
Set swDraw = swModel
' 2. プリンタ設定の割り当て
' 注意: プリンタ名はOSに登録されている正確な名称である必要がある
swApp.SetUserPreferenceStringValue swUserPreferenceStringValue_e.swPrintPrinterName, printerName
' 3. 印刷実行
' PrintOut メソッドの引数は、プリンタドライバの仕様に依存する
' 複雑なページ設定が必要な場合は、swModel.SetupPage を検討すること
swModel.PrintOut2
' 4. スプーリング待ちの微小な遅延(重要:ドライバのクラッシュ防止)
' 本来はWindows APIのSleepを使うのが理想的だが、VBAではDoEventsで制御
DoEvents
' 成功ログ
logFile.WriteLine "[" & SUCCESS_MARKER & "] " & fileName
' 5. クリーンアップ(リソース解放の鉄則)
CleanUp:
If Not swModel Is Nothing Then
swApp.CloseDoc swModel.GetTitle
Set swModel = Nothing
End If
Exit Sub
FileError:
' 個別エラーの記録
logFile.WriteLine "[" & ERROR_MARKER & "] " & fileName & " : " & Err.Description
Resume CleanUp
End Sub
4. エンジニアとしての高度なアドバイス
このコードをベースに、さらに実務の要求レベルを引き上げるための「次の一手」を伝授する。
用紙サイズの自動判定について
上記コードでは `PrintOut2` を使用しているが、もし「図面サイズ(A3ならA3、A4ならA4)に合わせてプリンタの用紙設定を動的に変えたい」という要求がある場合、`swModel.PageSetup` オブジェクトを操作する必要がある。
しかし、ここで注意すべきは、「SolidWorksのPageSetup設定」と「プリンタドライバの用紙設定」は必ずしも同期しないということだ。確実に制御するには、プリンタドライバが提供するAPI(もしあれば)か、Windows APIを介した高度な制御が必要になるケースが多い。
パフォーマンスの最適化
数百枚の処理を行う場合、`swApp.Visible = False` にしてバックグラウンドで実行させるのが定石だが、これには副作用がある。GUIが非表示だと、エラー発生時にダイアログが背後で止まり、プロセスがゾンビ化することがある。
実務では、「処理中はVisible = Trueにし、特定のタイミングで画面更新を止める」という、ユーザーへの視認性と速度のバランスを考慮した設計が求められる。
データベースとの連携
実務では「どの図面が、どのプロジェクトに属しているか」という情報をExcelやSQL Serverから取得し、印刷順序を制御することが求められる。その場合、`FileSystemObject` でファイルを探すのではなく、「データベースから取得したパスのリストを配列に格納し、その配列を回す」設計に変更せよ。これが「ファイル中心の思考」から「データ中心の思考」への転換だ。
結論
自動化ツールにおける「堅牢さ」とは、コードの短さではなく、「想定外の事態に対する準備の厚さ」である。
エラーを無視せず、リソースを確実に解放し、結果をログに残す。この当たり前のような作法を徹底できるかどうかが、単なるプログラマーと、信頼されるエンジニアを分ける境界線となる。
さあ、あなたのコードに、その魂を込めろ。
