"""geometry/loader.py の仕様ベーステスト.
仕様出典:
- docs/04_SPEC/SPEC_01_アーキテクチャ設計.md(geometry 層の責務)
- docs/04_SPEC/SPEC_02_シーン定義仕様.md(unit は mm/m のみ)
- docs/03_PLAN/PLAN_02_開発ステップ.md(Step 3: 簡単な四面体 STL を読み頂点数が一致する)
"""
import dataclasses
import pathlib
import numpy
import pytest
from sm_tias_lightsim.geometry import LoadedMesh, load_stl
# ---------------------------------------------------------------------------
# 正常系: 返り値の型
# ---------------------------------------------------------------------------
def test_load_stl_returns_loaded_mesh(tetrahedron_stl):
"""load_stl が LoadedMesh インスタンスを返すこと.
仕様: 出力: 頂点・面配列 + 単位情報 (SPEC_01 geometry 層の責務)
"""
result = load_stl(tetrahedron_stl, "mm")
assert isinstance(result, LoadedMesh)
# ---------------------------------------------------------------------------
# 正常系: dtype
# ---------------------------------------------------------------------------
def test_load_stl_vertices_dtype_float64(tetrahedron_stl):
"""vertices の dtype が float64 であること.
仕様: vertices: numpy.ndarray (N_v, 3) float64 (loader.py LoadedMesh docstring)
"""
result = load_stl(tetrahedron_stl, "mm")
assert result.vertices.dtype == numpy.float64
def test_load_stl_faces_dtype_int64(tetrahedron_stl):
"""faces の dtype が int64 であること.
仕様: faces: numpy.ndarray (N_f, 3) int64 (loader.py LoadedMesh docstring)
"""
result = load_stl(tetrahedron_stl, "mm")
assert result.faces.dtype == numpy.int64
# ---------------------------------------------------------------------------
# 正常系: shape(PLAN_02 完了条件「頂点数が一致する」)
# ---------------------------------------------------------------------------
def test_load_stl_vertices_shape(tetrahedron_stl):
"""四面体 STL の頂点配列 shape が (4, 3) であること.
仕様: 簡単な四面体 STL を読み頂点数が一致する (PLAN_02 Step 3 完了条件)
"""
result = load_stl(tetrahedron_stl, "mm")
assert result.vertices.shape == (4, 3)
def test_load_stl_faces_shape(tetrahedron_stl):
"""四面体 STL の面配列 shape が (4, 3) であること.
仕様: 面配列 (N_f, 3) (loader.py LoadedMesh docstring)
"""
result = load_stl(tetrahedron_stl, "mm")
assert result.faces.shape == (4, 3)
def test_load_stl_cube_vertices_shape(cube_stl):
"""立方体 STL の頂点配列 shape が (8, 3) であること.
trimesh.creation.box() の既知頂点数(8)と面数(12)が一致する.
"""
result = load_stl(cube_stl, "mm")
assert result.vertices.shape == (8, 3)
def test_load_stl_cube_faces_shape(cube_stl):
"""立方体 STL の面配列 shape が (12, 3) であること."""
result = load_stl(cube_stl, "mm")
assert result.faces.shape == (12, 3)
# ---------------------------------------------------------------------------
# 正常系: unit フィールドの保持
# ---------------------------------------------------------------------------
def test_load_stl_preserves_unit_mm(tetrahedron_stl):
"""unit="mm" で読み込むと loaded.unit == "mm" であること.
仕様: unit は mm/m のみ (SPEC_02 geometry セクション)
"""
result = load_stl(tetrahedron_stl, "mm")
assert result.unit == "mm"
def test_load_stl_preserves_unit_m(tetrahedron_stl):
"""unit="m" で読み込むと loaded.unit == "m" であること.
仕様: unit は mm/m のみ (SPEC_02 geometry セクション)
"""
result = load_stl(tetrahedron_stl, "m")
assert result.unit == "m"
# ---------------------------------------------------------------------------
# 正常系: source_path
# ---------------------------------------------------------------------------
def test_load_stl_source_path_is_resolved(tetrahedron_stl):
"""source_path が Path 型で絶対パスであること.
仕様: source_path: pathlib.Path(デバッグ用)(loader.py LoadedMesh docstring)
"""
result = load_stl(tetrahedron_stl, "mm")
assert isinstance(result.source_path, pathlib.Path)
assert result.source_path.is_absolute()
# ---------------------------------------------------------------------------
# 正常系: パス型の受け入れ
# ---------------------------------------------------------------------------
def test_load_stl_accepts_str_path(tetrahedron_stl):
"""str でパスを渡してもロードできること.
仕様: path: str | Path (loader.py load_stl シグネチャ)
"""
result = load_stl(str(tetrahedron_stl), "mm")
assert isinstance(result, LoadedMesh)
assert result.vertices.shape == (4, 3)
def test_load_stl_accepts_pathlib_path(tetrahedron_stl):
"""pathlib.Path をそのまま渡してもロードできること.
仕様: path: str | Path (loader.py load_stl シグネチャ)
"""
result = load_stl(tetrahedron_stl, "mm")
assert isinstance(result, LoadedMesh)
# ---------------------------------------------------------------------------
# 異常系: ファイル不存在
# ---------------------------------------------------------------------------
def test_load_stl_raises_file_not_found(tmp_path):
"""存在しないパスを渡すと FileNotFoundError が発生すること.
仕様: FileNotFoundError: ファイルが存在しない場合 (loader.py load_stl docstring)
"""
missing = tmp_path / "no_such_file.stl"
with pytest.raises(FileNotFoundError):
load_stl(missing, "mm")
# ---------------------------------------------------------------------------
# 異常系: イミュータビリティ
# ---------------------------------------------------------------------------
def test_load_stl_loaded_mesh_is_frozen(tetrahedron_stl):
"""パース後の LoadedMesh へのフィールド代入で FrozenInstanceError が発生すること.
仕様: @dataclass(frozen=True) (loader.py LoadedMesh クラス定義)
"""
result = load_stl(tetrahedron_stl, "mm")
with pytest.raises(dataclasses.FrozenInstanceError):
result.unit = "m" # type: ignore[misc]