Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions docs/source/api_reference/public_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,100 @@ embodichain.data_pipeline.depth_video
DEPTH_MILLIMETER_UNIT
DEPTH_QMAX

embodichain.gen_sim.scene_engine.core.scene_edit_plan
------------------------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.core.scene_edit_plan

.. autosummary::

SceneEditOperation
SceneEditPlan

embodichain.gen_sim.scene_engine.core.scene_graph
--------------------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.core.scene_graph

.. autosummary::

GENERATED_SCENE_GRAPH_SCHEMA
GeneratedSceneGraph
GeneratedSceneNode
GeneratedSceneRelation
SceneGraph
SceneGraphNode
SceneGraphRelation
OrientationState
PlanarRelationType
SceneConstraintType
SupportRelationType
TABLE_OBJECT_ID
TABLE_REGIONS
TableRegion

embodichain.gen_sim.scene_engine.core.scene_object
---------------------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.core.scene_object

.. autosummary::

ObjectPhysics
SceneObject

embodichain.gen_sim.scene_engine.errors
-----------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.errors

.. autosummary::

SceneServiceError

embodichain.gen_sim.scene_engine.pipeline
-----------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.pipeline

.. autosummary::

SCENE_BLUEPRINT_SCHEMA
SCENE_EDIT_BLUEPRINT_SCHEMA
SceneBlueprintPackage
SceneEditBlueprintPackage
SceneMaterialization
analyze_edit
analyze_image
materialize_blueprint
materialize_edit

embodichain.gen_sim.scene_engine.pipeline.api
---------------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.pipeline.api

.. autosummary::

SCENE_BLUEPRINT_SCHEMA
SCENE_EDIT_BLUEPRINT_SCHEMA
SceneBlueprintPackage
SceneEditBlueprintPackage
SceneMaterialization
analyze_edit
analyze_image
materialize_blueprint
materialize_edit

embodichain.gen_sim.scene_engine.pipeline.editing.scene_edit_asset_preparation
-----------------------------------------------------------------------------

.. currentmodule:: embodichain.gen_sim.scene_engine.pipeline.editing.scene_edit_asset_preparation

.. autosummary::

prepare_scene_edit_assets

embodichain.gen_sim.simready_pipeline.cli.start
-----------------------------------------------

Expand Down
36 changes: 33 additions & 3 deletions embodichain/gen_sim/scene_engine/clients/geometry_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

import requests

from embodichain.gen_sim.scene_engine.errors import SceneServiceError

from embodichain.gen_sim.scene_engine.configs.environment import (
read_scene_engine_env_values,
)
Expand Down Expand Up @@ -77,7 +79,7 @@ def check_health(self) -> None:
last_error = exc

assert last_error is not None
raise RuntimeError(
raise SceneServiceError(
"Geometry Generation Server health check failed after "
f"{self._max_attempts} attempts."
) from last_error
Expand All @@ -91,6 +93,7 @@ def generate_objects(
image_path: str | Path,
object_masks: list[tuple[str, Path]],
output_root: str | Path,
seed: int | None = None,
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
"""Generate objects through the geometry server's mask-list endpoint.

Expand Down Expand Up @@ -125,6 +128,7 @@ def generate_objects(
response_data, response_objects = self._request_objects(
image_path=resolved_image_path,
object_masks=resolved_object_masks,
seed=seed,
)

resolved_output_root = Path(output_root).expanduser().resolve()
Expand Down Expand Up @@ -158,6 +162,7 @@ def _request_objects(
*,
image_path: Path,
object_masks: list[tuple[str, Path]],
seed: int | None,
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
last_error: Exception | None = None
for _ in range(self._max_attempts):
Expand All @@ -171,6 +176,7 @@ def _request_objects(
]
response = self._session.post(
self._url(self._generate_objects_path),
data=(None if seed is None else {"seed": str(int(seed))}),
files=[
(
"image",
Expand Down Expand Up @@ -201,6 +207,11 @@ def _request_objects(
"Geometry Generation Server response is not valid JSON."
) from exc
response_data = self._wait_for_task_if_needed(response_data)
if seed is not None and _response_seed(response_data) != int(seed):
raise RuntimeError(
"Geometry Generation Server did not acknowledge the "
f"requested seed {int(seed)}."
)
response_objects = _parse_objects_response(
response_data,
object_ids=[object_id for object_id, _ in object_masks],
Expand All @@ -210,7 +221,7 @@ def _request_objects(
last_error = exc

assert last_error is not None
raise RuntimeError(
raise SceneServiceError(
"Geometry Generation Server request failed after "
f"{self._max_attempts} attempts."
) from last_error
Expand Down Expand Up @@ -294,7 +305,7 @@ def _download_glb(self, mesh_path: str, output_path: Path) -> None:
last_error = exc

assert last_error is not None
raise RuntimeError(
raise SceneServiceError(
"Geometry Generation Server GLB download failed after "
f"{self._max_attempts} attempts: {mesh_path}"
) from last_error
Expand Down Expand Up @@ -374,6 +385,25 @@ def _parse_objects_response(
return parsed_objects


def _response_seed(value: object) -> int | None:
"""Return a seed acknowledged by a geometry response envelope."""
if not isinstance(value, dict):
return None
candidates = [value.get("seed")]
for key in ("result", "metadata"):
nested = value.get(key)
if isinstance(nested, dict):
candidates.append(nested.get("seed"))
for candidate in candidates:
if candidate is None or isinstance(candidate, bool):
continue
try:
return int(candidate)
except (TypeError, ValueError):
continue
return None


def _parse_numeric_list(
value: object,
*,
Expand Down
26 changes: 23 additions & 3 deletions embodichain/gen_sim/scene_engine/clients/image_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import requests

from embodichain.gen_sim.scene_engine.errors import SceneServiceError

from embodichain.gen_sim.scene_engine.configs.environment import (
read_scene_engine_env_values,
)
Expand Down Expand Up @@ -73,7 +75,7 @@ def check_health(self) -> None:
last_error = exc

assert last_error is not None
raise RuntimeError(
raise SceneServiceError(
"Image Generation Server health check failed after "
f"{self._max_attempts} attempts."
) from last_error
Expand All @@ -86,6 +88,7 @@ def generate_image_by_prompt(
*,
prompt: str,
output_path: str | Path,
seed: int | None = None,
) -> Path:
"""Generate one PNG image from ``prompt`` and save it to ``output_path``."""
prompt = prompt.strip()
Expand All @@ -100,10 +103,27 @@ def generate_image_by_prompt(
try:
response = self._session.post(
self._url(self._generate_image_by_prompt_path),
json={"prompt": prompt},
json={
"prompt": prompt,
**({} if seed is None else {"seed": int(seed)}),
},
timeout=self._timeout_s,
)
response.raise_for_status()
if seed is not None:
acknowledged = response.headers.get(
"x-generation-seed",
response.headers.get("x-seed"),
)
try:
acknowledged_seed = int(acknowledged)
except (TypeError, ValueError):
acknowledged_seed = None
if acknowledged_seed != int(seed):
raise RuntimeError(
"Image Generation Server did not acknowledge the "
f"requested seed {int(seed)}."
)
content_type = response.headers.get("content-type", "").split(";")[0]
if content_type != "image/png":
raise RuntimeError(
Expand All @@ -115,7 +135,7 @@ def generate_image_by_prompt(
last_error = exc

assert last_error is not None
raise RuntimeError(
raise SceneServiceError(
"Image Generation Server request failed after "
f"{self._max_attempts} attempts."
) from last_error
Expand Down
8 changes: 5 additions & 3 deletions embodichain/gen_sim/scene_engine/core/scene_edit_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
from embodichain.gen_sim.scene_engine.core.scene_graph import (
OrientationState,
SceneConstraintType,
SceneGraph,
GeneratedSceneGraph,
TableRegion,
TABLE_OBJECT_ID,
)

__all__ = ["SceneEditOperation", "SceneEditPlan"]

SceneEditOperationType = Literal["add", "move", "delete"]


Expand Down Expand Up @@ -65,7 +67,7 @@ class SceneEditPlan:
"""Validated operations against one immutable pre-edit scene state."""

scene: Scene
scene_graph: SceneGraph
scene_graph: GeneratedSceneGraph
operations: list[SceneEditOperation] = field(default_factory=list)

def __post_init__(self) -> None:
Expand All @@ -92,7 +94,7 @@ def validate(self) -> None:
# - every target is from the pre-edit scene; new and deleted objects are invalid targets.
# - an existing object has at most one move or delete operation in one plan.
# - delete carries no placement or new-object metadata and must delete every descendant.
# - these checks validate intent only; they do not mutate Scene or SceneGraph.
# - these checks validate intent only; they do not mutate Scene or GeneratedSceneGraph.
# Scene object IDs must remain a one-to-one lookup key for edit operations.
scene_object_ids = {scene_object.id for scene_object in self.scene.objects}
if len(scene_object_ids) != len(self.scene.objects):
Expand Down
Loading