"""座標変換ユーティリティモジュール.
単位変換(mm ↔ m)および LoadedMesh への適用を提供する.
座標系の回転・並進は Step 4(シーン構築)で実装する.
"""
from __future__ import annotations
import dataclasses
from typing import Literal
import numpy
from sm_tias_lightsim.geometry.loader import LoadedMesh
_MM_PER_M = 1000.0
_M_PER_MM = 0.001
def mm_to_m(value: numpy.ndarray | float) -> numpy.ndarray | float:
"""ミリメートル単位の数値配列をメートル単位に変換する.
Args:
value: スカラまたは ndarray(任意 shape).
Returns:
入力と同じ shape の値.スカラなら float 1 つ,ndarray なら新しい配列.
"""
return value * _M_PER_MM
def scale_vertices(mesh: LoadedMesh, target_unit: Literal["mm", "m"]) -> LoadedMesh:
"""LoadedMesh の頂点座標を指定単位にスケール変換した新インスタンスを返す.
frozen dataclass の不変性を維持するため,常に新規インスタンスを返す.
元と同じ単位が指定された場合も頂点配列はコピーされる(関数の純粋性のため).
Args:
mesh: 元のメッシュ.
target_unit: 変換先の単位.
Returns:
頂点が target_unit にスケールされた新 LoadedMesh.
unit フィールドは target_unit に更新される.
Raises:
ValueError: 未対応の unit の組み合わせが指定された場合.
"""
if mesh.unit == target_unit:
new_vertices = mesh.vertices.copy()
elif mesh.unit == "mm" and target_unit == "m":
new_vertices = mesh.vertices * _M_PER_MM
elif mesh.unit == "m" and target_unit == "mm":
new_vertices = mesh.vertices * _MM_PER_M
else:
msg = f"Unsupported unit conversion: '{mesh.unit}' -> '{target_unit}'"
raise ValueError(msg)
return dataclasses.replace(mesh, vertices=new_vertices, unit=target_unit)