Skip to content
Draft
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
6 changes: 4 additions & 2 deletions isaaclab_arena/environments/arena_env_graph_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self

from pydantic import BaseModel, Field, SerializeAsAny, field_validator, model_validator
from pydantic import BaseModel, Field, SerializeAsAny, ValidationInfo, field_validator, model_validator

from isaaclab_arena.assets.registries import TaskRegistry
from isaaclab_arena.environments.arena_env_graph_types import (
Expand Down Expand Up @@ -158,8 +158,10 @@ class ArenaEnvInitialGraphSpec(ArenaEnvGraphSpecBase):
initial_state_spec: ArenaEnvGraphStateSpec

@model_validator(mode="after")
def validate(self) -> Self:
def validate(self, info: ValidationInfo) -> Self:
"""Check unique IDs, constraint references, and spatial constraint shapes."""
if info.context and info.context.get("skip_registry"):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

benchmark how much this helps, remove them if the impact is small.

return self
assert_unique_ids(self.nodes, [], [self.initial_state_spec])
assert_constraint_references(self.nodes, [self.initial_state_spec])
assert_spatial_constraint_shapes([self.initial_state_spec])
Expand Down
10 changes: 7 additions & 3 deletions isaaclab_arena/environments/arena_env_graph_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field, field_validator, model_validator
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator

from isaaclab_arena.assets.object_type import ObjectType
from isaaclab_arena.assets.registries import ObjectRelationLibraryRegistry, TaskRegistry
Expand Down Expand Up @@ -115,7 +115,9 @@ class TaskSpec(BaseModel):

@field_validator("kind")
@classmethod
def _validate_registered_task_type(cls, value: str) -> str:
def _validate_registered_task_type(cls, value: str, info: ValidationInfo) -> str:
if info.context and info.context.get("skip_registry"):
return value
registry = TaskRegistry()
assert registry.is_registered(value), f"Unknown task kind '{value}'"
return value
Expand Down Expand Up @@ -169,7 +171,9 @@ class SpatialRelationSpec(BaseModel):
)

@model_validator(mode="after")
def _validate_kind_and_arity(self) -> SpatialRelationSpec:
def _validate_kind_and_arity(self, info: ValidationInfo) -> SpatialRelationSpec:
if info.context and info.context.get("skip_registry"):
return self
registry = ObjectRelationLibraryRegistry()
assert registry.is_registered(self.kind), f"Unknown relation kind '{self.kind}'"
relation_cls = registry.get_object_relation_by_name(self.kind)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from isaaclab_arena_examples.agentic_environment_generation.review_gui.render.styles import DASHBOARD_CSS


def render_dashboard_html(spec: ArenaEnvInitialGraphSpec) -> str:
def render_dashboard_html(spec: ArenaEnvInitialGraphSpec, thumbnails: dict[str, bytes] | None = None) -> str:
"""Render the self-contained review dashboard HTML for ``spec``."""
initial_state = spec.initial_state_spec
thumbnails = thumbnails or {}
return f"""<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -36,7 +37,7 @@ def render_dashboard_html(spec: ArenaEnvInitialGraphSpec) -> str:
<main>
<section class="panel nodes-panel">
<h2>Nodes</h2>
<div class="node-grid">{render_node_cards(spec)}</div>
<div class="node-grid">{render_node_cards(spec, thumbnails)}</div>
</section>
<section class="panel graph-panel">
<h2>Spatial graph <span class="muted">(initial state: <code>{html_lib.escape(initial_state.id)}</code>)</span></h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@

from isaaclab_arena.environments.arena_env_graph_spec import ArenaEnvInitialGraphSpec
from isaaclab_arena.environments.arena_env_graph_types import ArenaEnvGraphNodeSpec, ArenaEnvGraphStateSpec
from isaaclab_arena_examples.agentic_environment_generation.review_gui.render.thumbnails import (
render_placeholder_thumbnail,
)
from isaaclab_arena_examples.agentic_environment_generation.review_gui.render.thumbnails import render_node_thumbnail


def render_unary_constraints(state: ArenaEnvGraphStateSpec) -> str:
Expand Down Expand Up @@ -63,16 +61,17 @@ def render_tasks_table(spec: ArenaEnvInitialGraphSpec) -> str:
)


def render_node_cards(spec: ArenaEnvInitialGraphSpec) -> str:
def render_node_cards(spec: ArenaEnvInitialGraphSpec, thumbnails: dict[str, bytes] | None = None) -> str:
"""Render one card per graph node for the dashboard nodes panel."""
return "\n".join(render_node_card(node) for node in spec.nodes)
thumbnails = thumbnails or {}
return "\n".join(render_node_card(node, thumbnails.get(node.id)) for node in spec.nodes)


def render_node_card(node: ArenaEnvGraphNodeSpec) -> str:
"""Render a single node card with placeholder thumbnail and YAML dump."""
def render_node_card(node: ArenaEnvGraphNodeSpec, png_bytes: bytes | None = None) -> str:
"""Render a single node card with USD snapshot or placeholder thumbnail and YAML dump."""
node_dict = node.model_dump(mode="json", exclude_none=True)
node_yaml = yaml.safe_dump(node_dict, sort_keys=False).rstrip()
thumb = render_placeholder_thumbnail(node)
thumb = render_node_thumbnail(node, png_bytes)
return f"""<article class="node-card type-{html_lib.escape(node.type.value)}">
{thumb}
<div class="node-meta">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@

from __future__ import annotations

import base64
import html as html_lib

from isaaclab_arena.environments.arena_env_graph_types import ArenaEnvGraphNodeSpec


# TODO(qianl): Replace placeholder thumbnails with sim-rendered snapshots .
def render_placeholder_thumbnail(node: ArenaEnvGraphNodeSpec) -> str:
"""Per-node placeholder thumbnail — two-letter initial."""
def render_node_thumbnail(node: ArenaEnvGraphNodeSpec, png_bytes: bytes | None = None) -> str:
"""Per-node thumbnail: USD capture if available, else two-letter placeholder."""
if png_bytes:
b64 = base64.b64encode(png_bytes).decode("ascii")
return (
'<div class="thumb thumb-rendered">'
f'<img src="data:image/png;base64,{b64}" alt="{html_lib.escape(node.name)} thumbnail">'
f'<span class="thumb-name">{html_lib.escape(node.name)}</span>'
"</div>"
)
initial = (node.name[:2] if node.name else "?").upper()
return f"""<div class="thumb">
<span class="thumb-initial">{html_lib.escape(initial)}</span>
Expand Down
Loading
Loading