DXFを解析していると、LINEやCIRCLEの次に難しく感じやすい図形の一つがSPLINEです。
LINEなら始点と終点、CIRCLEなら中心と半径があれば形状をほぼ理解できます。
一方、SPLINEでは、
- 次数
- ノット
- 制御点
- フィット点
- 重み
- 閉曲線か
- 周期的か
- 有理スプラインか
といった複数の情報が組み合わさって曲線を定義します。
AutodeskのDXF仕様では、SPLINEエンティティにこれらを表す専用グループコードが定義されています。
この記事では、DXFのSPLINEを「何となく曲線として読む」のではなく、再利用可能な幾何データとして分解して理解することを目標にします。
SPLINEは点を順番につないだ線ではない
まず重要なのは、
SPLINEは制御点を直線で順番につないだ図形ではない
ということです。
制御点は曲線の形を制御しますが、一般に曲線そのものがすべての制御点を通過するわけではありません。
概念的には、
制御点列
+
ノット列
+
次数
+
必要なら重み
↓
B-spline / NURBS曲線
という関係です。
SciPyの公式ドキュメントでも、B-splineは基底関数の線形結合として表され、ノット列と係数、次数によって定義されます。
1次元表現では、S(x)=j=0∑n−1cjBj,k;t(x)
と表されます。
ここで、
- cj:係数
- Bj,k;t:次数k、ノット列tに基づくB-spline基底関数
です。
CADのSPLINEでは、この考え方を2次元・3次元の制御点へ拡張して曲線形状を表現します。
DXFで最初に見るべきコード
Autodesk公式仕様でSPLINEに定義される主なグループコードは次の通りです。
| コード | 意味 |
|---|---|
70 | スプラインフラグ |
71 | 曲線の次数 |
72 | ノット数 |
73 | 制御点数 |
74 | フィット点数 |
40 | ノット値 |
41 | 重み |
10,20,30 | 制御点X,Y,Z |
11,21,31 | フィット点X,Y,Z |
12,22,32 | 開始接線 |
13,23,33 | 終了接線 |
210,220,230 | 法線ベクトル |
SPLINEを読むときは、まず、
70
71
72
73
74
の5項目を見ると全体構造を把握しやすくなります。
コード70はビットフラグ
グループコード70は単純な種類番号ではなく、ビットコードです。
Autodesk仕様では次の意味があります。
| 値 | 意味 |
|---|---|
| 1 | Closed |
| 2 | Periodic |
| 4 | Rational |
| 8 | Planar |
| 16 | Linear |
複数の条件が同時に成立する場合は、値が加算されます。
たとえば、
70
13
なら、13=1+4+8
なので、
- Closed
- Rational
- Planar
のビットが立っていることになります。
Pythonでは、
flags = 13
is_closed = bool(flags & 1)
is_periodic = bool(flags & 2)
is_rational = bool(flags & 4)
is_planar = bool(flags & 8)
is_linear = bool(flags & 16)
のように判定できます。
flags == 1のような比較だけでは複合状態を扱えないため、ビット演算を使う方が適切です。
コード71は次数
グループコード71はスプライン曲線のdegree、次数です。
たとえば、
71
3
なら3次スプラインです。
3次B-splineはCADでよく使われる代表的な形式ですが、DXFリーダーを作る場合は、
if degree == 3:
と固定せず、入力データとして保持する方が安全です。
内部データでは、
{
"degree": 3
}
のように保存できます。
コード72はノット数
グループコード72はノットの個数です。
実際のノット値はコード40として繰り返し現れます。
たとえば、
72
8
40
0.0
40
0.0
40
0.0
40
0.0
40
1.0
40
1.0
40
1.0
40
1.0
なら、ノット数は8です。
ノットは曲線を構成する区間や基底関数の作用範囲を決める重要なパラメータです。
SciPyのBSplineでも、B-splineはノット列、係数、次数を基本パラメータとして持ちます。
ノットは座標値ではない
ここは誤解しやすい部分です。
ノット値は、
X座標
Y座標
のような空間上の位置そのものではありません。
ノットは曲線を評価するパラメータ空間上の値です。
つまり、
制御点 → 幾何空間
ノット → パラメータ空間
と分けて考える必要があります。
ノットを座標として描画しようとすると、SPLINEの意味を取り違えてしまいます。
コード73は制御点数
グループコード73は制御点の数です。
制御点は、
10 → X
20 → Y
30 → Z
の組で繰り返されます。
たとえば、
73
4
なら、その後に4個の制御点が存在することを期待できます。
内部表現なら、
control_points = [
(0.0, 0.0, 0.0),
(20.0, 40.0, 0.0),
(60.0, 40.0, 0.0),
(80.0, 0.0, 0.0),
]
のように構造化できます。
制御点とフィット点は別物
SPLINEにはコード74でフィット点数も定義できます。
フィット点の座標は、
11
21
31
です。
つまり、
10/20/30 → 制御点
11/21/31 → フィット点
です。
この2つを混同してはいけません。
制御点は曲線形状を制御する点であり、フィット点は曲線を作成・フィッティングするときに使われる点です。
DXFパーサーでは、
control_points = []
fit_points = []
と別配列で保持する方が安全です。
コード41は重み
有理スプラインの場合、各制御点にweight、重みを持たせることができます。
DXFではコード41がその値です。Autodesk仕様では、重みが1でない場合に現れます。
通常のB-splineに重みを導入したものがNURBSです。
概念的には、C(u)=∑iNi,p(u)wi∑iNi,p(u)wiPi
と表せます。
ここで、
- Pi:制御点
- wi:重み
- Ni,p(u):B-spline基底関数
です。
重みを変えると、その制御点が曲線へ与える影響度が変わります。
したがってDXFを独自レンダリングするときにコード41を無視すると、Rationalフラグが立ったSPLINEを正しく再現できない可能性があります。
「コード41がない=データ欠損」ではない
重みがDXFに現れない場合、それだけで異常とは限りません。
Autodesk仕様では、重みが1でない場合にコード41が現れるとされています。
したがってパーサー側では、
weights = [1.0] * control_point_count
を初期値として持ち、コード41が存在する場合に上書きする設計が考えられます。
ただし実際の対応では、対象DXFバージョンや生成元CADの出力をfixtureで確認することが重要です。
コード72・73と実データ数を検証する
SPLINEを解析するときは、宣言された個数と実際に読めた個数を照合すると堅牢になります。
たとえば、
if len(knots) != declared_knot_count:
raise ValueError("ノット数がDXF宣言値と一致しません")
if len(control_points) != declared_control_point_count:
raise ValueError("制御点数がDXF宣言値と一致しません")
とできます。
これは単なる読み込み処理ではなく、DXFデータ検証ルールとして再利用できます。
グループコードの順番へ依存しすぎない
DXF処理では、
degree = values[5]
のように「何番目に出るか」へ依存する実装は避けた方がよいでしょう。
SPLINEでは特に、
- ノット値が複数回出現する
- 制御点が複数回出現する
- フィット点は存在しないことがある
- 重みも省略される場合がある
ためです。
DXFを読み込んだ後、
entity_type
group_code
value
の形で逐次処理し、必要なコードごとに収集する方が拡張しやすくなります。
Pythonの内部モデルに変換する
DXFの生グループコードをそのままアプリケーション全体で使うと、処理がDXFへ強く依存します。
そこでSPLINE読み込み後に、次のような内部モデルへ変換します。
from dataclasses import dataclass
@dataclass
class SplineData:
degree: int
flags: int
knots: list[float]
control_points: list[tuple[float, float, float]]
weights: list[float]
fit_points: list[tuple[float, float, float]]
さらに、
@property
def is_closed(self) -> bool:
return bool(self.flags & 1)
のように意味へ変換できます。
これなら、
DXF parser
↓
SplineData
↓
renderer / validator / converter
という責務分離が可能です。
SciPyへ接続する場合
非有理B-splineを数値的に扱う場合、SciPyのBSplineは有力な選択肢です。
SciPyでは、
BSpline(t, c, k)
という構造で、
t:ノット列c:係数k:次数
を指定します。
ただし、CADの3次元制御点データをそのまま1本の1次元Splineへ渡すわけではありません。
2DならX成分とY成分、3DならさらにZ成分について同じパラメータuで評価し、x(u),y(u),z(u)
を組み合わせて曲線を作る考え方になります。
DXFビューアでは近似ポリライン化も有効
簡易DXFビューアを作る場合、最初から完全なNURBSレンダラーを作らなくても構いません。
SPLINEを一定のパラメータ間隔で評価し、
SPLINE
↓
多数の評価点
↓
折れ線
として描画できます。
たとえば、
points = []
for u in parameters:
x = spline_x(u)
y = spline_y(u)
points.append((x, y))
として、その点列をCanvasやSVGへ渡します。
ただし評価間隔が粗すぎると曲率の大きい場所で形状誤差が大きくなります。
将来的には、
- 曲率
- 弦誤差
- 最大セグメント長
などを基準に適応分割する方が高品質です。
SPLINEをデータ資産として扱う
SPLINEを単なる「CAD上の曲線」と見るだけではなく、
degree
knots
control_points
weights
flags
という構造化データとして保持すると用途が広がります。
たとえば、
{
"type": "spline",
"degree": 3,
"closed": false,
"periodic": false,
"rational": false,
"knots": [],
"control_points": [],
"weights": []
}
のようにすれば、
- DXF解析
- SVG変換
- Web表示
- CAD形状比較
- 曲線長計算
- 幾何検証
- 別CAD形式への変換
へ再利用できます。
実装時の主な検証項目
SPLINEを扱うパーサーでは、最低限次を検証するとよいでしょう。
次数は正の整数か
ノット数と実データ数は一致するか
制御点数と実データ数は一致するか
フィット点数と実データ数は一致するか
Rationalフラグ時に重みを正しく扱えるか
Closed / Periodicを区別しているか
2D前提なのか3Dまで対応するのか
ここまで明文化すると、SPLINE対応をテスト可能な仕様にできます。
まとめ
DXFのSPLINEは、単純な点列ではありません。
中心となる構造は、
次数
+
ノット
+
制御点
+
必要なら重み
+
状態フラグ
です。
AutodeskのDXF仕様では、
70:フラグ71:次数72:ノット数73:制御点数74:フィット点数40:ノット41:重み10/20/30:制御点11/21/31:フィット点
として明確に分けられています。
SPLINEを正しく扱うには、DXFのコードを直接描画処理へ渡すのではなく、
DXF → 構造化されたSplineモデル → 曲線評価 → 描画
という段階へ分離するのが有効です。
LINEやARCのような基本図形から一歩進み、SPLINEを構造として扱えるようになると、DXFビューア、CAD変換、図形比較、Web描画といった処理へつなげやすくなります。
参考情報
- Autodesk公式 DXF Reference — SPLINEエンティティのグループコード。
- Autodesk公式 DXF Reference — ENTITIESセクションと各図形エンティティ。
- SciPy公式
BSpline— B-splineのノット、係数、次数、および基底関数。

