Newer
Older
SmTIAS-LightSim / src / sm_tias_lightsim / geometry / transforms.py
"""座標変換ユーティリティモジュール.

単位変換(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)