"""STL ファイル読み込みモジュール.
trimesh を使って STL を読み込み,頂点・面・単位情報を LoadedMesh として返す.
"""
from __future__ import annotations
import pathlib
from dataclasses import dataclass
from typing import Literal
import numpy
import trimesh
@dataclass(frozen=True)
class LoadedMesh:
"""STL から読み込んだメッシュ情報.
Attributes:
vertices: 頂点配列 (N_v, 3), float64.座標は読み込み時の単位そのまま.
faces: 三角形面配列 (N_f, 3), int64.各値は vertices の行インデックス.
unit: 元 STL の単位("mm" または "m").scale_vertices で変換するときの参照.
source_path: 元ファイルのパス(デバッグ用).
"""
vertices: numpy.ndarray
faces: numpy.ndarray
unit: Literal["mm", "m"]
source_path: pathlib.Path
def load_stl(path: str | pathlib.Path, unit: Literal["mm", "m"]) -> LoadedMesh:
"""STL ファイルを trimesh で読み込み LoadedMesh として返す.
trimesh は STL の単位情報を持たないため,呼び出し側が `unit` を明示する必要がある.
バイナリ・ASCII の両形式は trimesh が自動判別するため,特別な処理は不要.
Args:
path: STL ファイルパス.
unit: STL の単位指定.SPEC_02 の `geometry.meshes[].unit` と一致する.
Returns:
頂点・面・単位を保持する LoadedMesh.
Raises:
FileNotFoundError: ファイルが存在しない場合.
ValueError: STL のパースに失敗した場合,または三角形メッシュでない場合.
"""
resolved = pathlib.Path(path).resolve()
if not resolved.exists():
msg = f"STL file not found: {resolved}"
raise FileNotFoundError(msg)
loaded = trimesh.load_mesh(str(resolved), file_type="stl")
# trimesh.load_mesh は複数メッシュの場合 Scene を返す場合がある
if not isinstance(loaded, trimesh.Trimesh):
msg = (
f"Expected a single Trimesh, got {type(loaded).__name__}. "
"Multi-mesh STL files are not supported."
)
raise ValueError(msg)
vertices = loaded.vertices.astype(numpy.float64)
faces = loaded.faces.astype(numpy.int64)
return LoadedMesh(
vertices=vertices,
faces=faces,
unit=unit,
source_path=resolved,
)