【テクニカル・上級編】Page.DrawNURBSを用いた高精度曲線の動的生成:制御点と重み(Weights)の数学的配置によるCAD連携基礎 – Visio VBA解析バイブル

スポンサーリンク

Page.DrawNURBSを用いた高精度曲線の動的生成:CAD連携の極限領域

Visio VBAにおける描画系のAPIは、一見するとお絵描きツールの延長線上に捉えられがちだ。しかし、`Page.DrawLine`や`Page.DrawOval`といったプリミティブなメソッドの背後にあるオブジェクトモデルの挙動、そしてCOMのメモリ管理機構を熟知していれば、Visioは単なるダイアグラム作成ソフトではなく、外部CADエンジンと直結する高精度なベクターグラフィックス・レンダラーに変貌する。

今回は、その最たる例である `Page.DrawNURBS` メソッドを取り上げる。
非一様有理Bスプライン(NURBS: Non-Uniform Rational B-Spline)は、工業用CADデータの交換フォーマット(STEPやIGES、あるいはDXF)における曲線の標準表現である。これをVisioのVBAから正確に制御し、数式から導き出された制御点(Control Points)と重み(Weights)、そしてノットベクトル(Knot Vector)を流し込んで寸分違わぬ曲線を動的生成する手法を解説する。

1. Visio NURBSエンジンの内部構造と数学的要件

Visioの `Page.DrawNURBS` メソッドのシグネチャを正確に理解している開発者は少ない。
公式ドキュメントの記述は断片的だが、COMコンポーネントとしての内部実装を見据えたとき、このメソッドは以下のパラメータを要求する。

retShape = Page.DrawNURBS(xyArray, weightArray, knotArray, degree)

1. `xyArray` (Double() または Variant): 制御点の2次元座標を平坦化した配列($x_0, y_0, x_1, y_1, \dots$)。
2. `weightArray` (Double() または Variant): 各制御点に対する重み(Weights)。有理Bスプラインの「有理」を担保する要素であり、各点の形状への影響度を制御する。
3. `knotArray` (Double() または Variant): ノットベクトル。基底関数の区間を定義する。
4. `degree` (Long): 次数(Bスプラインの次数 $p$)。通常、三次曲線であれば $p = 3$。

ここで重要なのは、VBAの `Variant` 配列(特に `SafeArray`)とCOM間でのメモリコピーのコスト、そして型の厳密性である。Variant型に放り込む際、暗黙の型変換が発生すると、VisioのC++コアエンジン側でパースエラー(不意の `Error 9: インデックスが有効ではありません。` や `Error -2147352567 (80020009)`)を引き起こす。配列は必ず `Double` 型として明示的に初期化し、メモリ連続性を担保しなければならない。

2. 実装:CAD連携を見据えたNURBS動的生成エンジン

以下のコードは、外部システム(CSVやJSON、あるいは直接のCADパーサー)から渡された数理データを想定し、Visio上で高精度なNURBS曲線を描画するプロダクションコードである。

メモリリークを完全に防ぐため、オブジェクトのライフサイクル管理と、Variant配列の安全な構築に細心の注意を払っている。

Option Explicit

‘ =========================================================================
‘ 模範的CAD連携モジュール:NURBS曲線動的生成
‘ アーキテクト: チーフシステムアーキテクト
‘ =========================================================================

Public Sub RenderCADNURBSCurve()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage

If vsoPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical
Exit Sub
Endif

‘ 1. 制御点データ(X, Yのペア)の定義
‘ 実際のシステム連携では、ここで外部ファイルやDBからストリーム読み込みを行う
Dim rawPoints As Variant
rawPoints = Array( _
1.0, 1.0, _
2.0, 4.0, _
5.0, 5.0, _
8.0, 2.0, _
10.0, 6.0 _
)

Dim numPoints As Long
numPoints = (UBound(rawPoints) – LBound(rawPoints) + 1) / 2

‘ 2. 重み(Weights)の定義
‘ すべての制御点の重みを 1.0(標準)とする場合でも、配列のサイズ一致が必須
Dim rawWeights() As Double
ReDim rawWeights(numPoints – 1)
Dim i As Long
For i = 0 To numPoints – 1
rawWeights(i) = 1.0
Next i
‘ 例として特定の制御点の重みを変更し、曲線の引き込みを調整する場合:
‘ rawWeights(2) = 1.5

‘ 3. ノットベクトル(Knot Vector)の生成
‘ 次数 (Degree) = 3 の場合、ノットの数は (制御点数 + 次数 + 1) となる
Dim degree As Long
degree = 3

Dim numKnots As Long
numKnots = numPoints + degree + 1

Dim rawKnots() As Double
ReDim rawKnots(numKnots – 1)

‘ クラップト・ノットベクトル(Clamped Knot Vector両端で制御点を通る)の自動生成
For i = 0 to numKnots – 1
If i <= degree Then rawKnots(i) = 0.0 ElseIf i >= numPoints Then
rawKnots(i) = CDbl(numPoints – degree)
Else
rawKnots(i) = CDbl(i – degree)
End If
Next i

‘ 4. Visio APIへの引き渡しとエラーハンドリング
Dim vsoShape As Visio.Shape
On Error GoTo ErrorHandler

‘ VBAの配列をVisioのCOM境界を越えて渡すためのVariantラップ
Dim xyArrayVar As Variant
Dim weightArrayVar As Variant
Dim knotArrayVar As Variant

xyArrayVar = rawPoints
weightArrayVar = rawWeights
knotArrayVar = rawKnots

‘ Page.DrawNURBSの実行
‘ 座標単位はデフォルトでインチ。必要に応じてミリメートル換算(Visio.VisUnitCodes)を適用
Set vsoShape = vsoPage.DrawNURBS(xyArrayVar, weightArrayVar, knotArrayVar, degree)

‘ 5. 生成されたシェイプの後処理(メタデータの付与とスタイリング)
If Not vsoShape Is Nothing Then
vsoShape.Cells(“LineColor”).FormulaU = “RGB(0, 102, 204)”
vsoShape.Cells(“LineWeight”).FormulaU = “1.5 pt”
vsoShape.NameU = “CAD_Imported_NURBS”

‘ カスタムプロパティ(ShapeData)の付与によるシステム間トレーサビリティの確保
Call AttachMetadata(vsoShape, “CAD_SRC_ID_99281”, degree)
End If

GoTo Cleanup

ErrorHandler:
MsgBox “NURBS描画中に致命的なエラーが発生しました: ” & Err.Description & ” (Code: ” & Err.Number & “)”, vbCritical

Cleanup:
‘ 6. 厳格なオブジェクト解放(メモリリークの根絶)
Set vsoShape = Nothing
Set vsoPage = Nothing
Erase rawWeights
Erase rawKnots
End Sub

Private Sub AttachMetadata(ByRef shp As Visio.Shape, ByVal srcId As String, ByVal deg As Long)
‘ ShapeSheetにカスタムプロパティセクションを動的追加し、CAD連携のメタデータを保持する
Const visSectionProp As Integer = 24
Dim rowIdx As Long

rowIdx = shp.AddRow(visSectionProp, Visio.VisRowIndices.visRowLast, Visio.VisExistsFlags.visExistsAnywhere)
shp.CellsSRC(visSectionProp, rowIdx, Visio.VisCellIndices.visCustPropsLabel).FormulaU = “””CAD Source ID”””
shp.CellsSRC(visSectionProp, rowIdx, Visio.VisCellIndices.visCustPropsValue).FormulaU = “””” & srcId & “”””

rowIdx = shp.AddRow(visSectionProp, Visio.VisRowIndices.visRowLast, Visio.VisExistsFlags.visExistsAnywhere)
shp.CellsSRC(visSectionProp, rowIdx, Visio.VisCellIndices.visCustPropsLabel).FormulaU = “””Spline Degree”””
shp.CellsSRC(visSectionProp, rowIdx, Visio.VisCellIndices.visCustPropsValue).FormulaU = CStr(deg)
End Sub

3. チーフアーキテクトが指摘する「罠」と最適化の知見

実務レベルの大規模なCAD連携システムをVisio上で構築する際、上記のコードだけでは不十分なケースに直面する。現場のエンジニアが陥りが数々の「罠」と、その回避策を共有する。

A. 座標系(Units)の罠とインチ・ミリ問題

Visioの内部データ構造は、常にインチ(Inches)を基本単位として保持している。一方、機械・建築CADから出力される数値データはミリメートル(mm)やメートル(m)であることが多い。
`Page.DrawNURBS` に生のミリメートル数値をそのまま渡すと、キャンバスからはみ出る巨大なシェイプが生成されるか、あるいは描画エンジンがオーバーフローを起こす。
必ず以下のような換算ロジックをバイナリデータの前段に挟むべきだ。

‘ ミリメートルからVisio内部インチへ変換する係数
Const MM_TO_INCH As Double = 1.0 / 25.4
‘ 配列内の全座標をあらかじめスケーリングしておくこと

B. バッチ処理時のパフォーマンス劣化と画面描画の抑制

数千本のCAD由来のNURBS曲線を一括描画する場合、シェイプが1つ生成されるたびにVisioのGUIが再描画(Redraw)走ると、パフォーマンスが劇的に低下する。
コードの実行前後に以下のアプリケーションレベルの最適化を施すことは、プロフェッショナルとしての必須要件である。

‘ 描画の凍結とイベントの無効化
Application.ScreenUpdating = False
Application.EventsEnabled = False

‘ — 処理本体 —

‘ 復元
Application.EventsEnabled = True
Application.ScreenUpdating = True
ActiveWindow.Redraw

C. レガシー環境とメモリの断片化

Visio VBAは32bitプロセスとして動作することが多く(Visio 2013以降64bit版も存在するが、アドインやCOMコンポーネントの依存関係で32bit動作を強いられる現場は未だに多い)、大量の `Variant` 配列や複雑な幾何計算をループ内で回すと、ヒープ領域の断片化(Heap Fragmentation)による「メモリ不足」エラーが突如発生する。
配列の使い回しや、`Erase` による即時メモリ解放、および `Set Object = Nothing` の徹底は、24時間稼働する自動帳票生成サーバーやバッチシステムにおいて生命線となる。

総括

`Page.DrawNURBS` を使いこなすことは、Visio VBAプログラミングにおける一つの到達点である。単なる図形の配置ではなく、背後にある数学モデル(Bスプライン基底関数)と、VisioのCOMアーキテクチャの仕様を完全に同期させることで、レガシーとみなされがちなVBA環境であっても、最先端のCAD/CAM連携基盤と同等の高精度なレンダリングパイプラインを構築することが可能となる。

技術の限界を決めるのはツールではない。それを操るエンジニアの「解像度」に他ならない。

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