feat(simulation): auto-build source mesh collision
This commit is contained in:
@@ -156,11 +156,12 @@ def test_store_preserves_provider_job_and_stage_timing(tmp_path: Path) -> None:
|
||||
|
||||
|
||||
class _ReadyProvider:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, *, source_mesh: bool = False) -> None:
|
||||
self.deleted: list[str] = []
|
||||
self.upload_calls = 0
|
||||
self.submit_calls = 0
|
||||
self.submitted_document: dict[str, object] | None = None
|
||||
self.source_mesh = source_mesh
|
||||
|
||||
def capabilities(self) -> dict[str, object]:
|
||||
return {"outputs": ["preview.sog", "streamed-sog"]}
|
||||
@@ -173,17 +174,26 @@ class _ReadyProvider:
|
||||
source_format: str,
|
||||
) -> GaussianSourceBundleUpload:
|
||||
self.upload_calls += 1
|
||||
members = (
|
||||
members = [
|
||||
GaussianSourceMemberUpload("upload-1", entrypoint, "a" * 64, 23),
|
||||
GaussianSourceMemberUpload("upload-2", "export/data.bin", "b" * 64, 4),
|
||||
GaussianSourceMemberUpload("upload-3", "export/index.bin", "c" * 64, 5),
|
||||
)
|
||||
]
|
||||
if self.source_mesh:
|
||||
members.append(
|
||||
GaussianSourceMemberUpload(
|
||||
"upload-4",
|
||||
"export/Mesh_Files/scene.ply",
|
||||
"e" * 64,
|
||||
3,
|
||||
)
|
||||
)
|
||||
return GaussianSourceBundleUpload(
|
||||
format=source_format,
|
||||
entrypoint=entrypoint,
|
||||
bundle_sha256="d" * 64,
|
||||
total_byte_length=32,
|
||||
members=members,
|
||||
total_byte_length=sum(member.byte_length for member in members),
|
||||
members=tuple(members),
|
||||
)
|
||||
|
||||
def submit_build(self, document: dict[str, object]) -> dict[str, object]:
|
||||
@@ -208,6 +218,39 @@ class _ReadyProvider:
|
||||
}
|
||||
|
||||
def get_result(self, _job_id: str) -> dict[str, object]:
|
||||
artifacts = [
|
||||
{
|
||||
"role": "preview",
|
||||
"logical_path": "preview.sog",
|
||||
"media_type": "application/octet-stream",
|
||||
"sha256": "1" * 64,
|
||||
"byte_length": 7,
|
||||
},
|
||||
{
|
||||
"role": "stream-manifest",
|
||||
"logical_path": "streamed/lod-meta.json",
|
||||
"media_type": "application/json",
|
||||
"sha256": "2" * 64,
|
||||
"byte_length": 2,
|
||||
},
|
||||
]
|
||||
if self.source_mesh:
|
||||
artifacts.extend([
|
||||
{
|
||||
"role": "collision-mesh",
|
||||
"logical_path": "collision/scene.collision.glb",
|
||||
"media_type": "model/gltf-binary",
|
||||
"sha256": "3" * 64,
|
||||
"byte_length": 3,
|
||||
},
|
||||
{
|
||||
"role": "collision-repair-report",
|
||||
"logical_path": "collision/scene.repair.json",
|
||||
"media_type": "application/json",
|
||||
"sha256": "4" * 64,
|
||||
"byte_length": 2,
|
||||
},
|
||||
])
|
||||
return {
|
||||
"schema_version": "gaussian-pipeline.build-result/v1",
|
||||
"job_id": "gsp-20260826000000-deadbeef",
|
||||
@@ -215,22 +258,7 @@ class _ReadyProvider:
|
||||
"source_revision": "e" * 40,
|
||||
"image_digest": f"sha256:{'f' * 64}",
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"role": "preview",
|
||||
"logical_path": "preview.sog",
|
||||
"media_type": "application/octet-stream",
|
||||
"sha256": "1" * 64,
|
||||
"byte_length": 7,
|
||||
},
|
||||
{
|
||||
"role": "stream-manifest",
|
||||
"logical_path": "streamed/lod-meta.json",
|
||||
"media_type": "application/json",
|
||||
"sha256": "2" * 64,
|
||||
"byte_length": 2,
|
||||
},
|
||||
],
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
|
||||
def download_artifact(
|
||||
@@ -240,7 +268,13 @@ class _ReadyProvider:
|
||||
destination: Path,
|
||||
) -> Path:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_bytes(b"preview" if descriptor["role"] == "preview" else b"{}")
|
||||
payload = {
|
||||
"preview": b"preview",
|
||||
"stream-manifest": b"{}",
|
||||
"collision-mesh": b"glb",
|
||||
"collision-repair-report": b"{}",
|
||||
}[str(descriptor["role"])]
|
||||
destination.write_bytes(payload)
|
||||
return destination
|
||||
|
||||
def delete_job(self, job_id: str) -> None:
|
||||
@@ -318,6 +352,59 @@ def test_service_builds_visual_world_without_automatic_collision(tmp_path: Path)
|
||||
assert provider.deleted == ["gsp-20260826000000-deadbeef"]
|
||||
|
||||
|
||||
def test_service_automatically_builds_repaired_source_mesh_collision(tmp_path: Path) -> None:
|
||||
store = SimulationProjectStore(tmp_path)
|
||||
files = [
|
||||
*_folder_files(),
|
||||
{"logical_path": "export/Mesh_Files/scene.ply", "byte_length": 3},
|
||||
]
|
||||
project = store.create(
|
||||
name="Source mesh scene",
|
||||
scene_type="outdoor",
|
||||
source_kind="folder",
|
||||
files=files,
|
||||
)
|
||||
payloads = {
|
||||
"export/scene.lcc": b'{"fileType":"Portable"}',
|
||||
"export/index.bin": b"index",
|
||||
"export/data.bin": b"data",
|
||||
"export/Mesh_Files/scene.ply": b"ply",
|
||||
}
|
||||
for source_file in project["source"]["files"]:
|
||||
store.append_upload(
|
||||
project["project_id"],
|
||||
source_file["file_id"],
|
||||
offset=0,
|
||||
payload=payloads[source_file["logical_path"]],
|
||||
)
|
||||
store.begin_build(project["project_id"])
|
||||
provider = _ReadyProvider(source_mesh=True)
|
||||
service = SimulationProjectService(store, provider_factory=lambda: provider) # type: ignore[arg-type]
|
||||
|
||||
service.process(project["project_id"])
|
||||
|
||||
ready = store.get(project["project_id"])
|
||||
assert ready["status"] == "ready"
|
||||
assert ready["world_manifest"]["collision"]["available"] is True
|
||||
assert ready["world_manifest"]["collision"]["mesh_url"].endswith(
|
||||
"/collision/scene.collision.glb"
|
||||
)
|
||||
assert provider.submitted_document is not None
|
||||
assert provider.submitted_document["outputs"] == {
|
||||
"preview_sog": True,
|
||||
"streamed_sog": True,
|
||||
"collision": True,
|
||||
}
|
||||
assert provider.submitted_document["collision_profile"] == {
|
||||
"scene_type": "outdoor",
|
||||
"seed_position": [0.0, 0.0, 0.0],
|
||||
"capsule_height": 0.4,
|
||||
"capsule_radius": 0.4,
|
||||
"voxel_size": 0.05,
|
||||
"mesh_shape": "source",
|
||||
}
|
||||
|
||||
|
||||
def test_service_queue_processes_projects_strictly_one_at_a_time(tmp_path: Path) -> None:
|
||||
store = SimulationProjectStore(tmp_path)
|
||||
projects: list[dict[str, Any]] = []
|
||||
|
||||
Reference in New Issue
Block a user