Add browser skill-sequence authoring for atomic skills - #627
Yuan-Xinyi wants to merge 5 commits into
Conversation
…skills Closes #15. Add an interactive authoring layer on top of the browser visualization: pick a scene entity, choose an atomic skill, append it as a card, compile the sequence, replay it on a translucent preview robot, then execute it. The authored artifact is a plain ActionInvocation sequence, so the atomic action engine stays the single source of truth for skill semantics. M1 covers move_end_effector, pick_up, and place on the UR5 + gripper tutorial scene. Layering (bottom-up), all new code under lab/visualization/authoring/: - protocol.py immutable card model with five states (unconfigured, ready, running, succeeded, failed), sequence snapshots and commands; card params are frozen recursively. - session.py sequence editing, compilation into grounded invocations with per-card waypoint segments back-filled from the compiled trajectory, preview joint positions, and execution. - preview.py playback cursor plus per-link anchored forward kinematics that drive a translucent preview robot. Preview never touches physics. - panel.py / bridge.py the Viser sidebar panel and the simulation-thread bridge that turns browser commands into session calls. - execution.py StepwiseExecution, a generator advancing one simulation step per call so the browser keeps rendering during execution. Supporting generic capabilities added to the visualization stack, each inert until used: - panels.py defines a backend-neutral PanelSpec contract; no authoring concept enters the Viser backend, and with no panel registered the sidebar is byte-for-byte unchanged. - SceneExporter gains preview node groups; SceneNode gains an opacity field defaulting to 1.0, where the visibility mask is multiplied by it (exactly equivalent at the default). - VisualizationRuntime forwards preview updates and carries a panel command channel alongside the existing gizmo and pick channels. Preview is verified side-effect free: producing preview frames leaves joint positions, joint targets and body poses bit-identical. Validation on the tutorial scene (UR5 + PGI gripper, 3 cards): compiles to 320 waypoints with segments [0,80) [80,200) [200,320); preview leaves physics untouched (max |dqpos| = max |dpose| = 0); stepwise execution drives 350 host ticks without blocking the browser and moves the object 0.43 m; all cards end succeeded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Address three review findings on PR #627, all in how browser commands reach the simulation thread. Stale commands: the panel-command loop in AuthoringBridge.update() never checked run_id or scene_revision, so a command queued just before refresh_scene() could edit or execute the session after the browser topology changed. It now applies the same validation the pick path already used. Cross-panel swallowing: PanelCommandQueue held one global queue, so the first bridge to drain it consumed commands addressed to every other panel. Commands are now routed by panel_id at enqueue time into per-panel queues, each entry carrying a monotonic arrival index so the drain-all path still returns true global arrival order. drain_panel_commands() gained an optional panel_id; without it the contract is unchanged. Overflow is now bounded per panel rather than globally, so a flooding panel can no longer evict another's commands. Picks eaten before the bridge sees them: SimulationManager.update() drains the shared pick queue through update_gizmos(), so the tutorial's sim.update() consumed browser clicks before bridge.update() ran and the documented click-to-bind flow for pick_up cards could not work. The bridge's pick draining is now the public drain_picks(), and the tutorial calls it as the first statement of its loop. Keeping it separate from update() preserves the preview.advance() -> bridge.update() ordering, which a plain reorder would have desynchronised by one frame. The constraint is documented on the bridge, in the tutorial page, and in the runtime context notes. Regression tests cover each finding, including two bridges sharing a real VisualizationRuntime and an explicit assertion that the reversed loop order loses the pick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
三条 P1 已全部修复并推送(12cf3f5),逐条回复见上方 inline。它们有个共同点:都在浏览器命令到达仿真线程的路由与生命周期上。 其中「点选被 验收(本机,py3.11 + dexsim 0.5.0): 另外提一下:本 PR 目前只触发了 Greptile,lint / build / test 三个 workflow 没有起来(分支上查不到 workflow run),对比 #620 是三项都跑的。可能需要 maintainer 批准首次运行。 |
Follow-up to the per-panel command routing. Panel-specific draining leaves other panels' commands queued by design, but unregister_panel() discarded only the pending state, not the pending commands. A panel registering later under the same identifier, within the same run and scene revision, would inherit its predecessor's interactions and could edit, compile, or execute the replacement session — the run and revision guards cannot catch that case because neither value changed. PanelCommandQueue gains discard(panel_id), called from unregister_panel() alongside the existing state cleanup. Verified negatively: removing the discard call turns the new test_unregistering_a_panel_drops_its_pending_commands red. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve the authoring branch against main's simulation restructure. Conflict: docs/source/overview/sim/index.rst, where main lowercased the "Documentation quality notes" heading in the same block this branch extended with the skill_sequencer entry. Kept both intents. Adapt to main's renamed APIs in the authoring tests: - RigidBodyAttributesCfg was deliberately dropped from the public cfg facade, so the simulation-backed test builds its cube with create_tutorial_rigid_body_physics(), matching how main updated the atomic-action tutorials, and forwards sim.is_newton_backend. - The exporter's soft-object and cloth-object lookups are now one deformable-object pair, so the scene-exporter test double implements get_deformable_object_uid_list / get_deformable_object instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
已合并 main(7f8bd58),冲突解决,分支恢复 冲突只有一处: 适配 main 的 API 变更(都在测试侧,产品代码无需改动):
本机验证到什么程度(如实说明): 186 个非仿真测试全部通过。剩下 5 个依赖仿真的测试在本机无法运行,原因是合并后的 main 需要比本机所装 dexsim 0.5.0 更新的引擎,与本 PR 无关——已验证 main 自带的 逐层加本地守卫绕过后还会撞上 顺带:CI 目前仍只触发了 Greptile,lint / build / test 三个 workflow 始终没有起来。 |
…cture Merging main left the tutorial importing RigidBodyAttributesCfg, which main deliberately removed from the public cfg facade, so the example failed at import. It now builds its cube through create_tutorial_rigid_body_physics(), drops the removed max_convex_hull_num field, and calls sim.prepare() before stepping, which the restructured simulation requires before robot.body_data exists. The gap was invisible earlier: the engine build installed at merge time could not start a simulation at all, so --headless_smoke could not run. With the current engine it passes end to end again (3 cards, 320 waypoints, object displaced 0.41 m, all cards succeeded). Also track a recording of the browser panel driving that same sequence, captured through real browser input events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Description
Closes #15.
Adds interactive skill-sequence authoring to the browser visualization. Instead of hand-writing motion code, a user picks a scene entity, chooses an atomic skill, appends it as a card, compiles the sequence, replays it on a translucent preview robot, and executes it:
The authored artifact is a plain
ActionInvocationsequence, soAtomicActionEngineremains the single source of truth for skill semantics — nothing about skills is reimplemented in the UI. M1 coversmove_end_effector,pick_up,placeon the UR5 + gripper tutorial scene.Design rationale, including a survey of how MoveIt/RViz, Isaac Sim, CoppeliaSim, viser/pyroki, SAPIEN, Franka Desk, UR PolyScope, MoveIt Task Constructor and RobotStudio approach this, is summarised in the branch handover notes. Three ideas were taken directly: RViz's Plan/Execute split (preview never touches physics), PolyScope's per-node state colouring, and MTC's per-stage segment replay.
Layering (bottom-up)
All new authoring code lives under
embodichain/lab/visualization/authoring/:protocol.pysession.pypreview.pypanel.py/bridge.pyexecution.pyStepwiseExecution, a generator advancing one simulation step per call so the browser keeps rendering during executionGeneric capabilities added to the visualization stack
Each is inert until used, so existing behaviour is unchanged:
panels.py— a backend-neutralPanelSpec(panel_id, build, apply_state, title)contract. No authoring concept enters the Viser backend; with no panel registered the sidebar folder set is byte-for-byte unchanged (guarded by a test).SceneExporter— preview node groups (preview nodes reuse the source robot's geometry IDs and carry their own poses).SceneNode.opacity— new field defaulting to1.0; the visibility mask is multiplied by it, which is exactly equivalent at the default (x * 1.0is exact in IEEE-754).VisualizationRuntime— forwards preview updates and carries a panel-command channel alongside the existing gizmo/pick channels.Notable implementation detail
Preview poses use per-link anchoring (
anchor_i = real_pose_i @ inv(fk_current_i)) rather than anchoring on the chain root. Root-only anchoring left the preview robot offset by exactly 1 cm fromee_linkdownward: the URDF chain and the DexSim model disagree by a fixed offset on this robot. That is the same pre-existing discrepancy identified in #620 (URSolverreporting success with a 1 cm FK residual), tracked separately; once it is fixed the cheaper root anchoring can be restored.Type of change
Screenshots
A recording of the panel driving one sequence end to end. Every state change in it comes from a real browser input event dispatched at the page — clicks on the canvas and the controls, and typed target coordinates — not from calling the session or the bridge directly.
What it shows, in order:
Picked entity: cube.move_end_effectorcard is added and stays yellow unconfigured until a target is typed and applied, then turns ready. Apick_upcard is added and bound to the picked cube. Aplacecard is added with its own target.320 waypointsand gives each card its own range — 0–80, 80–200, 200–320 — so it is visible which part of the trajectory belongs to which skill.The same sequence runs headlessly via
--headless_smoke, which is what CI can assert on.Checklist
black .command to format the code base.Validation
Preview being side-effect free is asserted directly: joint positions, joint targets and body poses are bit-identical before and after producing preview frames.
Known limitations
sphinxandpypandocare not installed in this environment). The new page was checked mechanically: it is in theoverview/simtoctree, every{doc}target resolves, and fences are balanced.move_end_effector/placeis not exposed in the panel (cards fall back to the top-down default), andpick_uptargets can only be bound by clicking.SequencePreview.capture_inputs()assumes it is the only producer of overlays and preview groups; a second producer would need to merge the tuples.gpumarker and need--run-gpuwith thenot slowfilter removed.Unrelated but worth flagging for a separate fix: whenever
allow_commands=True(whichvisualization/cli.pysets for any--viserrun), Viser registers a scene pointer callback, and its frontend disables camera controls on pointer-down and only re-enables them on pointer-up over the canvas. Releasing the mouse outside the canvas leaves the camera permanently disabled until the page is reloaded. This predates this PR and affects every Viser session; making entity picking a togglable mode would fix it.