【実務・中級編】【中級者向け】複雑にネストされたグループ構造とシンボルインスタンスを再帰関数で完全に分解し、フラットなレイヤー階層へ再構築する処理 – CorelDRAW VBA解析バイブル

スポンサーリンク

【CorelDRAW VBA極限の知見】ネスト地獄からの脱却:複雑なグループとシンボルインスタンスを再帰的にフラット化する設計思想

開発現場で最も頭を抱える瞬間、それは「前任者が遺した、無限にネストされたグループ構造とシンボルインスタンスの山」を渡された時だ。

グループを開き、さらにその中のグループを開き、ロックされたレイヤーを解除し、シンボル(Symbol)のマスターを汚さないようにインスタンスを解除(Break Apart)し……。これを手作業で行うなど、エンジニアの恥である。

CorelDRAW VBAにおいて、オブジェクトの階層構造をプログラムで制御することは、一見すると単純なループ処理に見える。だが、「コレクションの走査中に構造を破壊することの危険性」や「シンボルインスタンスの特殊なライフサイクル」を理解していない者が書いたコードは、決まって「無限ループ」「メモリリーク」「予期せぬオブジェクトの消失」という致命的なバグを引き起こす。

今回は、CorelDRAWの内部構造(DOM)を完全に掌握し、地獄のようなネスト構造を安全かつ一瞬でフラットなレイヤー階層へと再構築する、プロダクション品質のアルゴリズムを伝授する。

—

1. なぜ「通常のForループ」ではネストを破壊できないのか?

初心者がやりがちな最大のミスが、以下のようなコードだ。

‘ 【アンチパターン】絶対にやってはいけない書き方
Dim sh As Shape
For Each sh in ActivePage.Layers(1).Shapes
If sh.Type = cdrGroupShape Then
‘ ループ中にグループをバラすと、インデックスが狂い、処理漏れやエラーが起きる
sh.Ungroup
End If
Next sh

コレクション破壊の罠

VBAの `For Each` は、背後でコレクションのポインタをインクリメントしている。ループの最中に `Ungroup` や `BreakApart` を実行してオブジェクトの生存状態(ライフサイクル)を変化させると、インデックスの整合性が崩れ、一部のシェイプがスキップされるか、メモリアクセス違反でCorelDRAWごと強制終了する。

シンボルインスタンスという異物

さらに厄介なのが「シンボル(Symbol)」だ。シンボルインスタンスは、通常のグループとは異なり、マスター定義への参照を持っている。これを単に `Ungroup` しようものなら、エラーを吐くか、意図しないマスターデータの破壊を誘発する。シンボルは必ず「独立したシェイプ(Normal Shape)への変換(`BreakApart` または `CreateFromSymbol` の逆操作)」という手順を踏まなければならない。

—

2. 堅牢なフラット化を実現するアーキテクチャ

この問題を解決するため、我々は以下の設計原則を導入する。

1. 再帰関数(Recursive Function)による深さ優先探索(DFS)
ネストの深さに依存しないよう、関数自身を呼び出して最下層のプリミティブまで到達する。
2. 逆順ループ(Backward Loop)による安全な分解
コレクションを操作・破壊する場合は、必ず末尾から先頭に向かって(`For i = Count To 1 Step -1`)処理する。これによりインデックスのズレを完全に排除する。
3. シンボルインスタンスの事前ハンドリング
`cdrSymbolShape` に遭遇した場合は、即座に実体化(Break)してから子要素の解析へ移行する。

—

3. プロダクションコード:完全フラット化エンジン

以下のコードは、アクティブページの全シェイプを走査し、すべてのグループおよびシンボルを強制的に解除した上で、指定したターゲットレイヤーへ完全にフラットに再配置する実用的なVBAモジュールである。

‘ =========================================================================
‘ Module: ModFlattenEngine
‘ Description: 複雑なネスト構造・シンボルを再帰的に完全フラット化するエンジン
‘ =========================================================================

Option Explicit

Public Sub ExecuteFlattenHierarchy()
Dim targetLayer As Layer
Dim startTime As Double
startTime = Timer

‘ 画面描画を停止し、処理速度を極限まで引き上げる(パフォーマンスの鉄則)
EventsEnabled = False
ActiveDocument.BeginCommandGroup “Flatten All Hierarchies”
On Error GoTo ErrorHandler

‘ 処理結果を出力する新規レイヤーを作成(または既存の “Flattened_Output” を利用)
Set targetLayer = GetOrCreateLayer(“Flattened_Output”)

‘ ルートレイヤー群を走査
Dim lyr As Layer
For Each lyr In ActivePage.Layers
‘ 処理対象レイヤー以外(出力先など)はスキップ
If lyr.Name <> targetLayer.Name And Not lyr.Locked And lyr.Visible Then
‘ 再帰関数の呼び出し
Call ProcessShapesRecursive(lyr.Shapes, targetLayer)
End If
Next lyr

ActiveDocument.EndCommandGroup
EventsEnabled = True

MsgBox “フラット化が完了しました。処理時間: ” & Format(Timer – startTime, “0.00”) & “秒”, vbInformation, “完了”
Exit Sub

ErrorHandler:
EventsEnabled = True
ActiveDocument.AbortCommandGroup
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub

‘ ————————————————————————-
‘ 再帰的にシェイプを走査し、グループ/シンボルを分解してターゲットに移動する核心関数
‘ ————————————————————————-
Private Sub ProcessShapesRecursive(ByVal shs As Shapes, ByVal destLayer As Layer)
Dim i As Long
Dim sh As Shape

‘ 【重要】逆順ループでコレクション操作の安全性を担保
For i = shs.Count To 1 Step -1
Set sh = shs(i)

‘ 1. シンボルインスタンスのハンドリング
If sh.Type = cdrSymbolShape Then
‘ シンボルを通常のシェイプ群に変換
Dim ungroupedShs As Shapes
Set ungroupedShs = sh.BreakApart()
‘ 変換されたシェイプ群に対して再帰処理を継続
Call ProcessShapesRecursive(ungroupedShs, destLayer)

‘ 2. グループシェイプのハンドリング
ElseIf sh.Type = cdrGroupShape Then
‘ グループ内の子シェイプ群を退避させてからグループを解除
Dim innerShs As Shapes
Set innerShs = sh.UngroupEx

If Not innerShs Is Nothing Then
‘ 解除して露出した子シェイプ群をさらに再帰処理
Call ProcessShapesRecursive(innerShs, destLayer)
End If

‘ 3. プリミティブ(単体オブジェクト:パス、テキスト、ビットマップ等)
Else
‘ 階層の縛りを外し、目的のフラットレイヤーへ移動
sh.MoveToLayer destLayer
End If
Next i
End Sub

‘ ————————————————————————-
‘ ユーティリティ: 出力用レイヤーの取得または作成
‘ ————————————————————————-
Private Function GetOrCreateLayer(ByVal layerName As String) As Layer
Dim lyr As Layer
On Error Resume Next
Set lyr = ActivePage.Layers(layerName)
On Error GoTo 0

If lyr Is Nothing Then
Set lyr = ActivePage.CreateLayer(layerName)
End If

‘ 確実に操作可能状態にする
lyr.Locked = False
lyr.Visible = True
Set GetOrCreateLayer = lyr
End Function

—

4. チーフアーキテクトが解説する実装の急所

このコードが実務で耐えうる堅牢性を持つ理由は、以下の3点に集約される。

① `EventsEnabled = False` によるUIスレッドの完全隔離

CorelDRAW VBAにおいて、オブジェクトを1つ動かしたりレイヤーを移動したりするたびに、GUIの再描画(画面の書き換え)が発生する。これが数千個のオブジェクトで繰り返されると、処理時間の9割が無駄な描画コストに消える。処理の冒頭でイベントと描画を殺し、最後に復元する手法は、大規模データを扱うVBAでは義務である。

② `UngroupEx` の採用

標準の `Ungroup` メソッドは、しばしば予期せぬ挙動や取りこぼしを起こす。CorelDRAWオブジェクトモデルにおいて、より確実にグループを解体し、かつ内包されていたシェイプのコレクション(`Shapes` オブジェクト)を直接返却する `UngroupEx` を選択するのがプロの選択だ。

③ トランザクション制御 (`BeginCommandGroup` / `EndCommandGroup`)

万が一、処理の途中でメモリ不足や不正なデータ構造にヒットした場合でも、`AbortCommandGroup` を通じてドキュメントの状態を「処理実行前の安全なスナップショット」にロールバックできる。業務自動化ツールにおいて、データを破壊しない設計は何よりも優先される。

—

5. おわりに:真の自動化へ向けて

他社データや古いバージョンのCorelDRAW資産のクレンジングは、デザイナーにとって最も苦痛な作業の一つだ。しかし、今回解説したような「DOMのライフサイクルを理解した再帰アルゴリズム」を一度構築してしまえば、数時間かかる手作業を「0.5秒のワンクリック」へと昇華させることができる。

コードは単に動くだけでは不十分だ。「安全で、速く、予測可能であること」。プロフェッショナルとして、この水準に到達したVBAコードをあなたの現場にも導入してほしい。

タイトルとURLをコピーしました