DXFのOCSとWCSを理解する|extrusion・任意軸アルゴリズム・座標変換

「DXFのOCSとWCSを理解する|extrusion・任意軸アルゴリズム・座標変換」の内容を表す技術イラスト

DXFの座標を読み取ったとき、ファイルに記録された数値がそのままワールド座標になるとは限りません。

たとえばCIRCLEの中心として、(0, 0, 2.828)(0,\ 0,\ 2.828)

が記録されていても、実際のWCS上の中心が、(0, 2, 2)(0,\ 2,\ 2)

である可能性があります。

これは、CIRCLEなどの平面的なエンティティが、WCSではなく**OCS(Object Coordinate System)**で座標を保持することがあるためです。

DXFパーサーや独自ビューアでOCSを無視すると、

  • 円や円弧が違う位置に表示される
  • LWPOLYLINEが意図しない平面に現れる
  • TEXTやINSERTの回転方向が変わる
  • 3次元DXFを2次元として誤解する
  • エンティティごとに座標の整合性が崩れる

といった問題が起こります。

この記事では、WCS、UCS、OCSの違いから、extrusionベクトル、任意軸アルゴリズム、OCSとWCSの座標変換までを整理します。

目次

WCSは図面全体の基準座標系

WCSはWorld Coordinate Systemの略です。

図面全体に対する固定の基準座標系であり、X軸、Y軸、Z軸は変化しません。

WCSの基底ベクトルを、Wx=(1,0,0)\mathbf{W_x}=(1,0,0)Wy=(0,1,0)\mathbf{W_y}=(0,1,0)Wz=(0,0,1)\mathbf{W_z}=(0,0,1)

とします。

WCS上の点、P=(100,50,20)P=(100,50,20)

は、世界座標の原点からX方向へ100、Y方向へ50、Z方向へ20進んだ位置です。

LINE、POINT、3DFACE、3Dポリラインなど、特定の平面へ限定されないエンティティでは、主要な点がWCSで記録されます。

UCSはユーザーが操作するときの座標系

UCSはUser Coordinate Systemの略です。

CAD利用者が作図しやすいように、原点や軸方向を変更できる作業用座標系です。

たとえば斜面上に穴を配置するとき、その斜面に合わせてUCSを設定すれば、通常のXY平面と同じ感覚で作図できます。

ただし、現在のUCSで入力した座標が、そのままDXFエンティティの座標として保存されるわけではありません。図形データは、エンティティの仕様に応じてWCSまたはOCSへ変換されます。

UCSの定義自体を保持するテーブルレコードは存在しますが、DXFの図形座標を読むときに「作図時のUCSが分からないから復元できない」と考える必要はありません。

必要なのは、そのエンティティがWCSとOCSのどちらを使うか判断することです。

OCSは平面図形を3次元空間へ配置する座標系

OCSはObject Coordinate Systemの略です。

主な目的は、円、円弧、文字、2Dポリラインなどの平面的な図形を、3次元空間内の任意の平面へ配置することです。

円を例にすると、円そのものはOCSのXY平面上に定義できます。

OCSのXY平面
    ↓
中心・半径・角度を定義
    ↓
OCSのZ軸方向を指定
    ↓
3次元空間内の平面へ配置

OCSのZ軸方向を定めるベクトルが、extrusion vectorです。

日本語では押し出し方向と呼ばれることがありますが、OCSを理解するときは、まず「図形平面の法線ベクトル」と考えると分かりやすくなります。

extrusionを表すグループコード

DXFでは、extrusion方向を一般に次のグループコードで表します。

グループコード意味
210extrusion方向のX成分
220extrusion方向のY成分
230extrusion方向のZ成分

たとえば、

210
0.0
220
0.0
230
1.0

なら、extrusionベクトルは、N=(0,0,1)\mathbf{N}=(0,0,1)

です。

これはWCSのZ軸と同じ方向なので、OCSとWCSの各軸は一致します。

この場合、OCS座標とWCS座標の数値も同じです。

一方、

210
0.0
220
0.70710678
230
0.70710678

なら、N=(0,12,12)\mathbf{N} = \left( 0,\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right)

です。

図形平面はWCSのXY平面から傾き、OCS座標をWCS座標として直接使用できなくなります。

extrusionだけではX軸方向が決まらない

extrusionベクトルから分かるのはOCSのZ軸方向です。

しかし、Z軸だけを指定しても、X軸とY軸の方向は一意に決まりません。同じZ軸のまわりに、座標系を自由に回転できるためです。

そこでAutoCADは、**任意軸アルゴリズム(Arbitrary Axis Algorithm)**を使用してOCSのX軸とY軸を決定します。

ここでいう「任意」は、毎回異なる方向を選ぶという意味ではありません。

同じextrusionベクトルからは、常に同じX軸とY軸が得られる決定的なアルゴリズムです。

任意軸アルゴリズム

正規化したextrusionベクトルを、OCSのZ軸、Az\mathbf{A_z}

とします。

まず、Z軸がWCSのZ軸にほぼ平行かどうかを判定します。∣Azx∣<164|A_{zx}|<\frac{1}{64}

かつ、∣Azy∣<164|A_{zy}|<\frac{1}{64}

なら、Ax=Wy×Az∣Wy×Az∣\mathbf{A_x} = \frac{ \mathbf{W_y}\times\mathbf{A_z} }{ \left| \mathbf{W_y}\times\mathbf{A_z} \right| }

とします。

それ以外では、Ax=Wz×Az∣Wz×Az∣\mathbf{A_x} = \frac{ \mathbf{W_z}\times\mathbf{A_z} }{ \left| \mathbf{W_z}\times\mathbf{A_z} \right| }

です。

最後に右手系となるY軸を、Ay=Az×Ax\mathbf{A_y} = \mathbf{A_z}\times\mathbf{A_x}

で求めます。

この処理により、Ax, Ay, Az\mathbf{A_x},\ \mathbf{A_y},\ \mathbf{A_z}

という、互いに直交するOCSの基底ベクトルが得られます。

判定値 1/641/64 を使うのは、基準軸とextrusion方向がほぼ平行な場合に、外積がゼロに近づくことを避けるためです。

extrusionがWCSのZ軸ならOCSとWCSは一致する

extrusionベクトルが、Az=(0,0,1)\mathbf{A_z}=(0,0,1)

の場合を考えます。

X成分とY成分はどちらも 1/641/64 未満なので、Ax=Wy×Az\mathbf{A_x} = \mathbf{W_y}\times\mathbf{A_z}

です。(0,1,0)×(0,0,1)=(1,0,0)(0,1,0)\times(0,0,1) = (1,0,0)

となります。

したがって、Ax=(1,0,0)\mathbf{A_x}=(1,0,0)

です。

続いて、Ay=Az×Ax\mathbf{A_y} = \mathbf{A_z}\times\mathbf{A_x}

なので、(0,0,1)×(1,0,0)=(0,1,0)(0,0,1)\times(1,0,0) = (0,1,0)

となります。

結果は、Ax=Wx\mathbf{A_x}=\mathbf{W_x}Ay=Wy\mathbf{A_y}=\mathbf{W_y}Az=Wz\mathbf{A_z}=\mathbf{W_z}

です。

一般的な2次元DXFでOCSを意識しなくても図形が正しく表示されることが多いのは、extrusionの既定方向がWCSのZ軸と一致しているためです。

傾いたOCSの軸を求める

次のextrusionベクトルを考えます。N=(0,1,1)\mathbf{N}=(0,1,1)

正規化すると、Az=(0,12,12)\mathbf{A_z} = \left( 0,\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right)

です。

Y成分は 1/641/64 より大きいため、WCSのZ軸との外積を使います。Ax=Wz×Az\mathbf{A_x} = \mathbf{W_z}\times\mathbf{A_z}

正規化後の結果は、Ax=(−1,0,0)\mathbf{A_x}=(-1,0,0)

です。

Y軸は、Ay=Az×Ax\mathbf{A_y} = \mathbf{A_z}\times\mathbf{A_x}

より、Ay=(0,−12,12)\mathbf{A_y} = \left( 0,-\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right)

となります。

これで、傾いた平面を表すOCSの3軸が確定しました。

WCSからOCSへ変換する

WCS上の点を、Pw\mathbf{P_w}

とします。

OCS座標は、WCS上の点をOCSの各基底ベクトルへ射影して求めます。xo=Pw⋅Ax\boxed{ x_o=\mathbf{P_w}\cdot\mathbf{A_x} }yo=Pw⋅Ay\boxed{ y_o=\mathbf{P_w}\cdot\mathbf{A_y} }zo=Pw⋅Az\boxed{ z_o=\mathbf{P_w}\cdot\mathbf{A_z} }

ここで ⋅\cdot は内積です。

先ほどのOCSに対し、WCS上の点、Pw=(0,2,2)\mathbf{P_w}=(0,2,2)

を変換します。

X座標は、xo=(0,2,2)⋅(−1,0,0)=0x_o = (0,2,2)\cdot(-1,0,0) = 0

です。

Y座標は、yo=(0,2,2)⋅(0,−12,12)=0y_o = (0,2,2)\cdot \left( 0,-\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right) = 0

です。

Z座標は、zo=(0,2,2)⋅(0,12,12)=22z_o = (0,2,2)\cdot \left( 0,\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right) = 2\sqrt{2}

なので、zo≈2.828z_o\approx2.828

です。

したがって、Po=(0,0,2.828)\boxed{ \mathbf{P_o}=(0,0,2.828) }

となります。

OCSからWCSへ変換する

OCS上の点を、Po=(xo,yo,zo)\mathbf{P_o}=(x_o,y_o,z_o)

とすると、WCS座標はOCSの基底ベクトルを使って、Pw=xoAx+yoAy+zoAz\boxed{ \mathbf{P_w} = x_o\mathbf{A_x} + y_o\mathbf{A_y} + z_o\mathbf{A_z} }

で求められます。

先ほどの、Po=(0,0,2.828)\mathbf{P_o}=(0,0,2.828)

を変換すると、Pw=2.828(0,12,12)\mathbf{P_w} = 2.828 \left( 0,\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right)

なので、Pw≈(0,2,2)\mathbf{P_w}\approx(0,2,2)

へ戻ります。

傾いたCIRCLEのDXF例

WCS上の中心が、(0,2,2)(0,2,2)

半径が1、extrusion方向が、(0,12,12)\left( 0,\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}} \right)

である円は、概念的に次のように記録できます。

0
CIRCLE
10
0.0
20
0.0
30
2.82842712
40
1.0
210
0.0
220
0.70710678
230
0.70710678

コード10、20、30で記録された中心はOCS座標です。

これをWCS座標として、(0,0,2.828)(0,0,2.828)

に円を配置すると誤りになります。

コード210、220、230からOCSを構築し、中心をWCSへ変換することで、(0,2,2)(0,2,2)

という本来の位置が得られます。

OCSを使用する代表的なエンティティ

OCSで座標を扱う代表的な平面エンティティには、次のものがあります。

  • CIRCLE
  • ARC
  • SOLID
  • TRACE
  • TEXT
  • ATTRIB
  • ATTDEF
  • SHAPE
  • INSERT
  • 2DのPOLYLINEとVERTEX
  • LWPOLYLINE
  • HATCH
  • IMAGE

一方、代表的なWCSエンティティには、

  • LINE
  • POINT
  • 3DFACE
  • 3DのPOLYLINEとVERTEX
  • POLYMESH
  • POLYFACE

などがあります。

DIMENSIONのように、同じエンティティ内でWCSの点とOCSの点が混在するものもあります。

また、extrusion属性を持つからといって、すべての座標が必ずOCSとは限りません。ELLIPSEのように、extrusion方向を持ちながら主要座標をWCSで扱うエンティティもあります。

したがって実装では、コード210が存在するかどうかだけで判断せず、エンティティ型ごとのDXF仕様を確認します。

Pythonで任意軸アルゴリズムを実装する

任意軸アルゴリズムと座標変換は、次のように実装できます。

from math import sqrt


Vec3 = tuple[float, float, float]


def dot(a: Vec3, b: Vec3) -> float:
    return sum(x * y for x, y in zip(a, b))


def cross(a: Vec3, b: Vec3) -> Vec3:
    return (
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0],
    )


def normalize(v: Vec3) -> Vec3:
    length = sqrt(dot(v, v))
    if length == 0:
        raise ValueError("zero-length vector")
    return tuple(value / length for value in v)


def arbitrary_axes(
    extrusion: Vec3,
) -> tuple[Vec3, Vec3, Vec3]:
    az = normalize(extrusion)

    if abs(az[0]) < 1 / 64 and abs(az[1]) < 1 / 64:
        reference = (0.0, 1.0, 0.0)
    else:
        reference = (0.0, 0.0, 1.0)

    ax = normalize(cross(reference, az))
    ay = normalize(cross(az, ax))

    return ax, ay, az


def wcs_to_ocs(point: Vec3, extrusion: Vec3) -> Vec3:
    ax, ay, az = arbitrary_axes(extrusion)
    return (
        dot(point, ax),
        dot(point, ay),
        dot(point, az),
    )


def ocs_to_wcs(point: Vec3, extrusion: Vec3) -> Vec3:
    ax, ay, az = arbitrary_axes(extrusion)
    x, y, z = point

    return (
        x * ax[0] + y * ay[0] + z * az[0],
        x * ax[1] + y * ay[1] + z * az[1],
        x * ax[2] + y * ay[2] + z * az[2],
    )


extrusion = (0.0, 1.0, 1.0)
point_wcs = (0.0, 2.0, 2.0)

point_ocs = wcs_to_ocs(point_wcs, extrusion)
restored_wcs = ocs_to_wcs(point_ocs, extrusion)

print(point_ocs)
print(restored_wcs)

計算誤差を丸めると、次の結果になります。

(0.0, 0.0, 2.828427...)
(0.0, 2.0, 2.0)

実装では、extrusionベクトルを必ず正規化してから座標系を構築します。

ezdxfで変換する

ezdxfにはOCS変換用のクラスが用意されています。

from ezdxf.math import OCS


ocs = OCS((0, 1, 1))

center_wcs = (0, 2, 2)
center_ocs = ocs.from_wcs(center_wcs)

restored_wcs = ocs.to_wcs(center_ocs)

print(center_ocs)
print(restored_wcs)

DXFエンティティからOCSを取得する場合は、グラフィカルエンティティのocs()メソッドも使用できます。

circle_ocs = circle.ocs()
center_wcs = circle_ocs.to_wcs(circle.dxf.center)

ただし、ライブラリがエンティティのすべての座標を自動的にWCSへ統一して返すとは限りません。

読み取った属性がどの座標系に属するか理解したうえで変換します。

実装で起こりやすい失敗

すべてのコード10をWCSとして扱う。
グループコードの意味はエンティティ型によって異なります。CIRCLEの中心とLINEの始点では座標系が同じとは限りません。

extrusionを単なる押し出し量と考える。
extrusionベクトルは方向を示します。厚みや押し出し距離を表すthicknessとは分けて扱います。

extrusionからZ軸だけを作る。
座標変換にはX軸とY軸も必要です。AutoCADと互換性のある任意軸アルゴリズムで決定します。

OCSの原点を図形中心へ移動する。
OCSの原点はWCSの原点と一致します。図形平面までの距離はOCSのZ座標、つまりelevationによって表現されます。

UCS座標をそのままDXFへ保存する。
作業用UCSで得た座標は、対象エンティティに応じてWCSまたはOCSへ変換して保存します。

extrusionを持つ全エンティティへ同じ変換を適用する。
エンティティごとに座標仕様が異なります。特にDIMENSIONの混在座標やELLIPSEの扱いには注意が必要です。

DXFパーサーのデータ構造

DXF解析では、読み取った値と正規化後の座標を分けて保存すると安全です。

{
  "entity_type": "CIRCLE",
  "raw": {
    "center_ocs": [0.0, 0.0, 2.82842712],
    "radius": 1.0,
    "extrusion": [0.0, 0.70710678, 0.70710678]
  },
  "geometry": {
    "center_wcs": [0.0, 2.0, 2.0],
    "normal_wcs": [0.0, 0.70710678, 0.70710678]
  }
}

この構造なら、

DXF原文
    ↓
エンティティ別スキーマ
    ↓
OCS判定
    ↓
WCSへ正規化
    ↓
表示・検査・変換・CAD生成

という処理へ分離できます。

元のOCSデータを残しておけば、DXFへ再出力するときも情報を失いません。

まとめ

WCSは図面全体で固定された世界座標系です。

OCSは、円や文字、2Dポリラインなどの平面的な図形を3次元空間へ配置するための、エンティティ固有の座標系です。

OCSのZ軸は、グループコード210、220、230で表されるextrusionベクトルから決まります。

X軸とY軸は、AutoCADの任意軸アルゴリズムによって一意に生成されます。

WCSからOCSへの変換は、Po=(Pw⋅Ax,Pw⋅Ay,Pw⋅Az)\boxed{ \mathbf{P_o} = \left( \mathbf{P_w}\cdot\mathbf{A_x}, \mathbf{P_w}\cdot\mathbf{A_y}, \mathbf{P_w}\cdot\mathbf{A_z} \right) }

OCSからWCSへの変換は、Pw=xoAx+yoAy+zoAz\boxed{ \mathbf{P_w} = x_o\mathbf{A_x} + y_o\mathbf{A_y} + z_o\mathbf{A_z} }

です。

ただし、座標系はグループコードだけでは判断できません。

エンティティ型
+
各座標属性の仕様
+
extrusion
+
OCS/WCS変換

を一つのスキーマとして扱う必要があります。

OCSを理解すると、個別のDXFエンティティを読む段階から、異なる図形を同じWCS上の幾何モデルへ正規化する段階へ進めます。

これはDXFビューア、図面検査、座標変換、3D処理、CAD自動生成を構築するうえで重要な基盤になります。

参考情報

参考になったらシェアしてください
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

機械設計・油圧・CAD・Python・AIなど、ものづくりに関わる技術を扱っています。工学知識を整理・構造化し、設計や自動化に再利用できる形へ変えていくことを目指しています。

目次