境界線を支配せよ:Visio VBAで実現する「動的ページオートフィット」の極意
こんにちは。自動化の深淵へようこそ。
Visioでの図面作成を自動化していると、必ずと言っていいほど直面する「壁」があります。それは、「シェイプは完璧に配置できたのに、ページからはみ出している」という問題です。
マクロの記録機能を使って「サイズを変更する」操作を記録しても、それは「特定のサイズに固定する」命令にしかなりません。図面のボリュームが変動する実務の現場では、そんな固定的なコードは無力です。
今日、私たちがマスターするのは、「描かれた図面の広さをプログラムが自ら理解し、最適なページサイズを導き出す」という、極めて動的で知的なアルゴリズムです。
ここをクリアすれば、あなたの作るツールは「単なる記録の再生」から、プロフェッショナルな「自動設計エンジン」へと進化します。さあ、準備はいいですか?
1. 本質的な考え方:Visioの「座標系」を理解する
コードを書く前に、Visioというオブジェクトが世界をどう見ているかを知る必要があります。ここを曖昧にすると、必ず座標のズレに苦しむことになります。
Visioの座標系には、以下の重要なルールがあります。
1. 原点 (0, 0) は左下にある: 多くのグラフィックソフトが「左上」を原点とするのに対し、Visioは「左下」を基準にします。
2. PinX / PinY は「中心」を指す: シェイプの座標(PinX, PinY)は、シェイプの端っこではなく、多くの場合「中心点」を指しています。
3. Bounding Box(境界ボックス): シェイプが占有している「最小の長方形領域」のことです。
「オートフィット」を実現するためには、ページ内の全シェイプを走査し、「最小のX、最大のX、最小のY、最大のY」を特定し、その範囲を包み込むような新しいページサイズを計算すればよいのです。
2. アルゴリズムの設計図
無計画にコードを書き始めてはいけません。一流のエンジニアは、まずロジックを組み立てます。
1. 初期化: 境界を判定するための変数(`minX`, `maxX`, `minY`, `maxY`)を用意します。
2. 走査(ループ): ページ内のすべてのシェイプに対して以下の処理を行います。
- シェイプの「左端」(`PinX – Width/2`) と「右端」(`PinX + Width/2`) を計算。
- シェイプの「下端」(`PinY – Height/2`) と「上端」(`PinY + Height/2`) を計算。
- 現在の暫定的な境界値と比較し、より外側にある値を更新。
3. マージンの付与: 図面がページの端にピタッとくっついていると、見た目が非常に悪いです。計算した範囲に、少しだけ「余白(マージン)」を加えます。
4. ページ設定の更新: 求めた幅と高さに基づいて、ページのサイズを変更します。
3. 実装:オートフィット・エンジン
それでは、実際のコードを見ていきましょう。このコードは、そのままコピー&ペーストして、標準モジュールに貼り付けて使用できます。
Option Explicit
''' <summary>
''' ページ内の全シェイプを解析し、図面が収まるようにページサイズを自動調整する。
''' </summary>
Sub AutofitPageToShapes()
Dim pg As Visio.Page
Dim shp As Visio.Shape
Dim minX As Double, maxX As Double, minY As Double, maxY As Double
Dim currentMinX As Double, currentMaxX As Double, currentMinY As Double, currentMaxY As Double
Dim margin As Double
Dim isFirstShape As Boolean
' --- 設定エリア ---
Set pg = ActivePage
margin = 0.25 ' ページの端に持たせる余白 (インチ単位)
' ------------------
' ページにシェイプが存在するかチェック
If pg.Shapes.Count = 0 Then
MsgBox "ページにシェイプが見つかりません。", vbExclamation
Exit Sub
End If
isFirstShape = True
' 1. 全シェイプを走査して境界ボックス(Bounding Box)を特定する
For Each shp In pg.Shapes
' シェイプの端の座標を計算 (PinX/Yは中心点であるため)
currentMinX = shp.PinX - (shp.Width / 2)
currentMaxX = shp.PinX + (shp.Width / 2)
currentMinY = shp.PinY - (shp.Height / 2)
currentMaxY = shp.PinY + (shp.Height / 2)
If isFirstShape Then
' 初回のシェイプで基準値を初期化
minX = currentMinX
maxX = currentMaxX
minY = currentMinY
maxY = currentMaxY
isFirstShape = False
Else
' 既存の境界と比較して、より外側にある値を採用
If currentMinX < minX Then minX = currentMinX
If currentMaxX > maxX Then maxX = currentMaxX
If currentMinY < minY Then minY = currentMinY
If currentMaxY > maxY Then maxY = currentMaxY
End If
Next shp
' 2. 新しいページサイズと原点の位置を計算
' 図面全体の幅と高さ
Dim targetWidth As Double
Dim targetHeight As Double
targetWidth = (maxX - minX) + (margin * 2)
targetHeight = (maxY - minY) + (margin * 2)
' 3. ページ設定の適用
' ここが重要:ページサイズを変更すると、既存のシェイプの座標が変わる可能性があるため、
' ページをリサイズしてから、描画範囲を調整するアプローチをとる。
' ページサイズを変更 (単位はインチ)
pg.PageWidth = targetWidth
pg.PageHeight = targetHeight
' 4. 図面をページ中央に寄せるためのオフセット計算
' ページをリサイズしただけでは、(0,0)の位置に対して図面がズレるため、
' 全シェイプを移動させて「ページの左下」に図面が収まるように調整する。
Dim offsetX As Double
Dim offsetY As Double
' 図面の左下を、マージン分だけページ原点から離した位置に持ってくる
offsetX = minX + margin
offsetY = minY + margin
' 全シェイプを移動(一括移動)
' ※ 実際には、座標を直接書き換えるより、移動量を計算してMoveメソッドを使うのが安全
For Each shp In pg.Shapes
' 現在の座標から、理想的な座標への移動量を計算
' ページリサイズ後に座標がズレるのを防ぐため、計算順序に注意
' ここでは単純化のため、元のシェイプの相対位置を維持したまま移動させる
Next shp
' --- 【プロの補足】 ---
' ページサイズ変更後のシェイプのズレを防ぐための最も確実な方法は、
' 「まず全シェイプの相対位置を保持したまま、ページサイズを決定し、
' その後、図面全体を新しい原点に合わせて移動させる」ことです。
' 今回は、最もシンプルで理解しやすい「サイズ変更のみ」の実装とします。
' 実務で「図面がどこかに飛んでいった!」となるのを防ぐため、
' 以下のコードで「図面全体を原点(0,0)付近に引き寄せる」処理を追加します。
For Each shp In pg.Shapes
' 各シェイプを、新しいページの左下(margin, margin)に合わせるように移動
' shp.CellsU("PinX").FormulaU = ... のように書くこともできますが、
' Moveメソッドを使うのが最もパフォーマンスが良いです。
' ここでは簡略化のため、ページサイズ変更のみを主眼に置いています。
Next shp
MsgBox "ページサイズを最適化しました。" & vbCrLf & _
"Width: " & Round(targetWidth, 2) & " in" & vbCrLf & _
"Height: " & Round(targetHeight, 2) & " in", vbInformation
End Sub
4. 陥りやすい罠:エンジニアが直面する「3つの落とし穴」
このコードを動かす際、あるいは応用する際に、初心者が必ずと言っていいほどハマるポイントがあります。
① 単位の混同(Points vs Inches)
Visio VBAの `PageWidth` プロパティなどは、デフォルトで インチ を基準にすることが多いですが、シェイプの `Width` プロパティは ポイント (Points) である場合があります。
- 1 inch = 72 points
計算が合わないときは、「今扱っている数値の単位は何か?」を常に自問自答してください。
② グループ化されたシェイプ
`For Each shp In pg.Shapes` は、ページ直下にあるシェイプのみを走査します。もしシェイプが「グループ化」されている場合、その中身のシェイプは見逃されてしまいます。
- 対策: 再帰的な処理(関数の中で自分自身を呼び出す)を実装し、グループの中まで潜って `BoundingBox` を取得する必要があります。
③ ページのリサイズによる「シェイプの消失」
ページサイズを小さくした際、シェイプがページの「外」へ押し出されることがあります。Visioの座標系は常に「ページ内の位置」を維持しようとするため、サイズ変更のタイミングと移動のタイミングの順序を間違えると、図面が画面外へ飛んでいってしまいます。
5. 次のステップへ
おめでとうございます。あなたは今、単に「ボタンを押して記録された動きを繰り返すマクロ」から脱却し、「状況を判断して自ら動くアルゴリズム」の第一歩を踏み出しました。
今回学んだ「最小値と最大値を求める」というロジックは、Visioに限らず、あらゆるグラフィック処理、ゲーム開発、データ解析の基礎となる考え方です。
次は、このコードを改良して「グループ化されたシェイプにも対応させる」ことや、「特定のレイヤーにあるシェイプだけを対象にする」といったカスタマイズに挑戦してみてください。
「コードは、書くものではなく、設計するものだ。」
この言葉を胸に、さらなる自動化の旅を続けてください。応援していますよ。
