项目文件夹

文件
wehub-resource-sync 2c632336aa
CI / Viewer CI (push) Successful in 13m37s
CI / Core CI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:38 +08:00

212 行
6.5 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
import sdk
def _geometry_aabb(
geometry: sdk.Box | sdk.Cylinder | sdk.Sphere | sdk.Mesh,
*,
asset_root: Path | None = None,
) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
part = sdk.Part("body", visuals=[sdk.Visual(geometry)])
aabb = sdk.part_local_aabb(part, asset_root=asset_root, prefer_collisions=False)
assert aabb is not None
return aabb
def _aabb_size(
aabb: tuple[tuple[float, float, float], tuple[float, float, float]],
) -> tuple[float, float, float]:
return (
float(aabb[1][0]) - float(aabb[0][0]),
float(aabb[1][1]) - float(aabb[0][1]),
float(aabb[1][2]) - float(aabb[0][2]),
)
def test_scale_geometry_to_size_stretches_box() -> None:
scaled = sdk.scale_geometry_to_size(
sdk.Box((0.10, 0.20, 0.30)),
(0.40, None, 0.60),
)
assert isinstance(scaled, sdk.Box)
assert scaled.size == pytest.approx((0.40, 0.20, 0.60))
def test_scale_geometry_to_size_uniformly_scales_box() -> None:
scaled = sdk.scale_geometry_to_size(
sdk.Box((0.10, 0.20, 0.30)),
(0.20, None, None),
mode="uniform",
)
assert isinstance(scaled, sdk.Box)
assert scaled.size == pytest.approx((0.20, 0.40, 0.60))
def test_scale_geometry_to_size_resizes_mesh_using_current_extents(tmp_path: Path) -> None:
mesh = sdk.mesh_from_geometry(
sdk.BoxGeometry((0.50, 1.00, 1.50)),
tmp_path / "assets" / "meshes" / "box.obj",
)
scaled = sdk.scale_geometry_to_size(
mesh,
(1.00, 2.00, 3.00),
asset_root=tmp_path,
)
assert isinstance(scaled, sdk.Mesh)
assert scaled.filename == mesh.filename
assert scaled.scale == pytest.approx((2.0, 2.0, 2.0))
assert scaled.source_geometry == mesh.source_geometry
assert scaled.source_transform == mesh.source_transform
assert _aabb_size(_geometry_aabb(scaled, asset_root=tmp_path)) == pytest.approx(
(1.00, 2.00, 3.00)
)
def test_scale_geometry_to_size_keeps_mesh_origin_fixed(tmp_path: Path) -> None:
mesh = sdk.mesh_from_geometry(
sdk.BoxGeometry((1.0, 1.0, 1.0)).translate(1.0, 0.0, 0.0),
tmp_path / "assets" / "meshes" / "translated_box.obj",
)
scaled = sdk.scale_geometry_to_size(
mesh,
(2.0, None, None),
asset_root=tmp_path,
)
aabb = _geometry_aabb(scaled, asset_root=tmp_path)
assert aabb[0] == pytest.approx((1.0, -0.5, -0.5))
assert aabb[1] == pytest.approx((3.0, 0.5, 0.5))
def test_scale_geometry_to_size_preserves_cylinder_when_xy_match() -> None:
scaled = sdk.scale_geometry_to_size(
sdk.Cylinder(radius=0.05, length=0.20),
(0.20, 0.20, 0.40),
)
assert isinstance(scaled, sdk.Cylinder)
assert scaled.radius == pytest.approx(0.10)
assert scaled.length == pytest.approx(0.40)
def test_scale_geometry_to_size_preserves_sphere_when_axes_match() -> None:
scaled = sdk.scale_geometry_to_size(
sdk.Sphere(radius=0.05),
(0.20, 0.20, 0.20),
)
assert isinstance(scaled, sdk.Sphere)
assert scaled.radius == pytest.approx(0.10)
def test_scale_geometry_to_size_converts_cylinder_to_mesh_when_anisotropic(
tmp_path: Path,
) -> None:
output_path = tmp_path / "assets" / "meshes" / "scaled_cylinder.obj"
scaled = sdk.scale_geometry_to_size(
sdk.Cylinder(radius=0.05, length=0.20),
(0.20, 0.30, 0.40),
filename=output_path,
)
assert isinstance(scaled, sdk.Mesh)
assert scaled.filename == "assets/meshes/scaled_cylinder.obj"
assert output_path.exists()
assert isinstance(scaled.source_geometry, sdk.Cylinder)
assert _aabb_size(_geometry_aabb(scaled, asset_root=tmp_path)) == pytest.approx(
(0.20, 0.30, 0.40),
abs=1e-3,
)
def test_scale_geometry_to_size_converts_sphere_to_mesh_when_anisotropic(
tmp_path: Path,
) -> None:
output_path = tmp_path / "assets" / "meshes" / "scaled_sphere.obj"
scaled = sdk.scale_geometry_to_size(
sdk.Sphere(radius=0.05),
(0.20, 0.30, 0.40),
filename=output_path,
)
assert isinstance(scaled, sdk.Mesh)
assert scaled.filename == "assets/meshes/scaled_sphere.obj"
assert output_path.exists()
assert isinstance(scaled.source_geometry, sdk.Sphere)
assert _aabb_size(_geometry_aabb(scaled, asset_root=tmp_path)) == pytest.approx(
(0.20, 0.30, 0.40),
abs=1e-2,
)
def test_scale_geometry_to_size_requires_mesh_name_for_primitive_mesh_conversion() -> None:
with pytest.raises(
sdk.ValidationError,
match="mesh_name is required when resizing this primitive would convert it into Mesh",
):
sdk.scale_geometry_to_size(
sdk.Cylinder(radius=0.05, length=0.20),
(0.20, 0.30, 0.40),
)
def test_scale_geometry_to_size_rejects_inconsistent_uniform_targets() -> None:
with pytest.raises(sdk.ValidationError, match="inconsistent target ratios"):
sdk.scale_geometry_to_size(
sdk.Box((0.10, 0.20, 0.30)),
(0.20, 0.30, None),
mode="uniform",
)
@pytest.mark.parametrize(
("target_size", "message"),
[
((None, None, None), "must specify at least one axis"),
((-0.10, None, None), "must be a positive finite value or None"),
((0.0, None, None), "must be a positive finite value or None"),
],
)
def test_scale_geometry_to_size_rejects_invalid_target_sizes(
target_size: tuple[float | None, float | None, float | None],
message: str,
) -> None:
with pytest.raises(sdk.ValidationError, match=message):
sdk.scale_geometry_to_size(sdk.Box((0.10, 0.20, 0.30)), target_size)
def test_scale_geometry_to_size_rejects_missing_mesh_file(tmp_path: Path) -> None:
with pytest.raises(sdk.ValidationError, match="Mesh file not found"):
sdk.scale_geometry_to_size(
sdk.Mesh(filename="assets/meshes/missing.obj"),
(0.10, None, None),
asset_root=tmp_path,
)
def test_scale_geometry_to_size_rejects_zero_span_requested_mesh_axis(tmp_path: Path) -> None:
mesh = sdk.mesh_from_geometry(
sdk.MeshGeometry(
vertices=[(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)],
faces=[(0, 1, 2)],
),
tmp_path / "assets" / "meshes" / "planar.obj",
)
with pytest.raises(sdk.ValidationError, match="current span is zero"):
sdk.scale_geometry_to_size(
mesh,
(None, None, 0.10),
asset_root=tmp_path,
)