Skip to content

Add browser skill-sequence authoring for atomic skills - #627

Open
Yuan-Xinyi wants to merge 10 commits into
mainfrom
xinyi/vis01
Open

Yuan-Xinyi wants to merge 10 commits into
mainfrom
xinyi/vis01

Conversation

@Yuan-Xinyi

@Yuan-Xinyi Yuan-Xinyi commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

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:

pick entity → choose skill → Add card → set params → Compile → Preview → Execute

The authored artifact is a plain ActionInvocation sequence, so AtomicActionEngine remains the single source of truth for skill semantics — nothing about skills is reimplemented in the UI. M1 covers move_end_effector, pick_up, place on 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/:

Module Role
protocol.py Immutable card model with five states (unconfigured / ready / running / succeeded / failed), sequence snapshots, command objects; card params frozen recursively
session.py Sequence editing, compilation into grounded invocations with per-card waypoint segments back-filled from the compiled trajectory, preview joint positions, execution
preview.py Playback cursor and per-link anchored FK driving the translucent preview robot
panel.py / bridge.py Viser sidebar panel; simulation-thread bridge turning browser commands into session calls
execution.py StepwiseExecution, a generator advancing one simulation step per call so the browser keeps rendering during execution

Generic capabilities added to the visualization stack

Each is inert until used, so existing behaviour is unchanged:

  • panels.py — a backend-neutral PanelSpec(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 to 1.0; the visibility mask is multiplied by it, which is exactly equivalent at the default (x * 1.0 is 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 from ee_link downward: 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 (URSolver reporting success with a 1 cm FK residual), tracked separately; once it is fixed the cheaper root anchoring can be restored.

Type of change

  • New feature (non-breaking change which adds functionality)

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.

Skill sequence authoring panel

What it shows, in order:

  1. Pick an entity. Click-to-pick is enabled and the cube is clicked in the 3-D view; the panel reports Picked entity: cube.
  2. Build the sequence. A move_end_effector card is added and stays yellow unconfigured until a target is typed and applied, then turns ready. A pick_up card is added and bound to the picked cube. A place card is added with its own target.
  3. Compile. The panel reports 320 waypoints and 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.
  4. Preview. The translucent preview robot replays the trajectory next to the real one, with the end-effector path drawn as a polyline. Play/Pause, Step forward and the frame slider all work, and a marker walks down the card list to show which skill the current frame belongs to. The physical scene does not move during any of this.
  5. Execute. The translucent robot hides for the duration — the plan and its execution never animate side by side on separate cursors — while the orange polyline stays, leaving the planned path visible for the real robot to follow. The real robot runs the trajectory while the status line counts waypoints and the cards turn running then succeeded one at a time; the browser keeps rendering throughout, which is what the stepwise execution driver in this PR is for. The preview returns when the run ends.

The same sequence runs headlessly via --headless_smoke, which is what CI can assert on.

Checklist

  • I have run the black . command to format the code base.
  • I have made corresponding changes to the documentation
  • Public API changes are reflected in the API docs
  • I have added tests that prove my feature works
  • Dependencies have been updated, if applicable (none)

Validation

pytest tests/visualization/ -o addopts="" --run-gpu   -> 183 passed
  (the 121 pre-existing visualization tests are unmodified and still pass)
python scripts/tutorials/visualization/skill_sequencer.py --headless_smoke
  -> compiles 3 cards to 320 waypoints, segments [0,80) [80,200) [200,320)
  -> preview leaves physics untouched: max |dqpos| = max |dpose| = 0
  -> stepwise execution runs 350 host ticks without blocking the browser
  -> object moves 0.43 m; all cards end succeeded
python scripts/tutorials/visualization/skill_sequencer.py --viser
  -> server and panel come up; interactive click-through not yet signed off
black --fast --check .                 -> 952 files unchanged
python docs/scripts/check_api_docs.py  -> 2001/2001 exports documented
context.py check                       -> agent context map: ok

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

  • The Sphinx build could not be verified locally (sphinx and pypandoc are not installed in this environment). The new page was checked mechanically: it is in the overview/sim toctree, every {doc} target resolves, and fences are balanced.
  • Compilation still runs synchronously on the simulation thread, so a long grasp-sampling pass freezes the browser. Execution no longer does; compilation is the remaining half of that problem.
  • Rotation for move_end_effector / place is not exposed in the panel (cards fall back to the top-down default), and pick_up targets 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.
  • Simulation-backed tests carry the gpu marker and need --run-gpu with the not slow filter removed.

Unrelated but worth flagging for a separate fix: whenever allow_commands=True (which visualization/cli.py sets for any --viser run), 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.

…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>
@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the latest preview-suppression changes have coherent lifecycle handling and focused regression coverage.

Summary

The latest changes hide and pause the translucent preview robot while stepwise execution controls the real robot, restore it after completion or cancellation, retain the planned path overlay, and add documentation and regression coverage for this lifecycle.

  • Synchronizes preview visibility with stepwise execution state.
  • Preserves the preview cursor while hidden and pauses invisible playback.
  • Restores preview visibility immediately after cancellation or completion.
  • Updates the tutorial recording and user-facing lifecycle documentation.
Diagram
sequenceDiagram
    participant Host
    participant Bridge as AuthoringBridge
    participant Exec as StepwiseExecution
    participant Preview as SequencePreview
    participant Runtime as VisualizationRuntime

    Host->>Bridge: update()
    Bridge->>Exec: advance()
    alt execution remains active
        Bridge->>Preview: set_suppressed(true)
        Bridge->>Preview: pause()
    else execution finishes or is cancelled
        Bridge->>Preview: set_suppressed(false)
    end
    Bridge->>Runtime: publish panel state
    Host->>Preview: capture_inputs()
    Preview-->>Runtime: preview visibility + planned path
Loading

Reviews (8) · Last reviewed commit: "Re-record the sequencer GIF and show it ..."

Comment thread scripts/tutorials/visualization/skill_sequencer.py
Comment thread embodichain/lab/visualization/authoring/bridge.py Outdated
Comment thread embodichain/lab/visualization/authoring/bridge.py Outdated
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>
@Yuan-Xinyi

Copy link
Copy Markdown
Collaborator Author

三条 P1 已全部修复并推送(12cf3f5),逐条回复见上方 inline。它们有个共同点:都在浏览器命令到达仿真线程的路由与生命周期上。

其中「点选被 sim.update() 抢先消费」这条特别值得记一笔——自动化测试全绿却没能拦住它,因为 --headless_smoke 是用编程方式绑定实体的,绕开了真实的 UI 路径。已补上显式断言反向顺序会丢 pick 的回归测试。

验收(本机,py3.11 + dexsim 0.5.0):

pytest tests/visualization/ -o addopts="" --run-gpu   -> 188 passed  (183 基线 + 5 新增)
skill_sequencer.py --headless_smoke                    -> PASS,物体位移 0.407 m,三卡 succeeded
black --fast --check .                                 -> 952 files unchanged
check_api_docs.py                                      -> 2001/2001
context.py check                                       -> ok

另外提一下:本 PR 目前只触发了 Greptile,lint / build / test 三个 workflow 没有起来(分支上查不到 workflow run),对比 #620 是三项都跑的。可能需要 maintainer 批准首次运行。

Comment thread embodichain/lab/visualization/runtime.py
Yuan-Xinyi and others added 2 commits September 15, 2026 10:22
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>
@Yuan-Xinyi

Copy link
Copy Markdown
Collaborator Author

已合并 main(7f8bd58),冲突解决,分支恢复 MERGEABLE

冲突只有一处:docs/source/overview/sim/index.rst——main 把 "Documentation quality notes" 标题改成了小写,而本分支在同一区块加了 skill_sequencer 条目。两边意图都保留。

适配 main 的 API 变更(都在测试侧,产品代码无需改动):

  • RigidBodyAttributesCfg 已被有意从公开 cfg 门面移除(main 里有测试专门断言这一点),仿真测试改用 create_tutorial_rigid_body_physics() 建 cube,与 main 更新原子技能教程的写法一致,并透传 sim.is_newton_backend
  • exporter 的 soft-object / cloth-object 两套查询在 main 里合并成了 deformable 一套,场景导出测试替身相应改为 get_deformable_object_uid_list / get_deformable_object

本机验证到什么程度(如实说明):

pytest tests/visualization/ -o addopts="" --run-gpu   -> 186 passed, 5 errors

186 个非仿真测试全部通过。剩下 5 个依赖仿真的测试在本机无法运行,原因是合并后的 main 需要比本机所装 dexsim 0.5.0 更新的引擎,与本 PR 无关——已验证 main 自带的 tests/sim/test_sim_manager.py 在本机同样失败,报同一个错:

AttributeError: 'dexsim.cuda.pybind.WorldConfig' object has no attribute 'log_startup_info'

逐层加本地守卫绕过后还会撞上 articulation.py:1580 的下一处不兼容,因此没有继续。这 5 个测试在合并前(99e7eec)是通过的(190 passed),需要 CI 用正确版本的 dexsim 复验。--headless_smoke 同理,本机跑不了。

顺带:CI 目前仍只触发了 Greptile,lint / build / test 三个 workflow 始终没有起来。

Yuan-Xinyi and others added 6 commits September 16, 2026 00:30
…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>
Merge main and satisfy the visualization coverage gate this branch adds.

scripts/tutorials/sim/ik_manipulability_selection.py arrived on main with #614,
after this branch introduced the gate requiring every SimulationManagerCfg in
examples and tutorials to pass a visualization configuration. Neither change
conflicts textually, so the break only appears in the merge commit CI builds.

The script runs headless, so the browser scene is its only inspection path.
It now takes the shared viser CLI arguments and builds its configuration with
visualization_cfg_from_args, matching the other sim tutorials. The backend
stays "none" unless --viser is passed, so reported timings are unchanged by
default.

Co-Authored-By: Claude <noreply@anthropic.com>
The translucent preview and the real robot were drawn on top of each other in
the browser. AuthoringBridge now parks an idle preview cursor at the end of
the selected card's segment instead of leaving it where compilation put it.

SequencePreview.sync() resets the cursor to waypoint zero on every new
compilation, and autoplay is off by default. Waypoint zero is the robot's
current pose, so after compiling, the preview sat exactly on the live robot:
two arms in one place, with nothing to tell them apart until playback started.

Parking separates them along sequence time: the solid robot shows the present
and the preview shows the pose the selected card produces. Cards already carry
[segment_start, segment_stop) back-filled by the session, so the split follows
the sequence's own semantics rather than a fixed offset. With no card selected
the preview rests on the final pose of the whole sequence.

Playback and stepwise execution still own the cursor while they run, and an
explicit browser seek is preserved until the selection or the compiled
trajectory changes.

The preview stub in the panel tests gained the length and is_playing
properties it was missing from the real driver interface.

Co-Authored-By: Claude <noreply@anthropic.com>
The translucent preview kept its own playback cursor running after
Execute: the tutorial loop advances it every iteration and it is built
with autoplay and loop enabled, so the preview arm replayed the compiled
trajectory while the real arm walked the same waypoints at the execution
step budget. Two arms traced one path at once, out of phase, and the
browser gave no way to tell the plan from its execution.

Give SequencePreview a suppression flag and drive it from the bridge's
execution_active, so the preview arm steps aside for the duration and
returns on the waypoint it was hidden on. Suppression reaches
preview_update() only, so the compiled polyline stays on screen and the
planned path remains visible for the real robot to follow.

Suppression is re-synced on every update() rather than set once at the
start and cleared at the end, which makes it impossible to desync from
the execution state, and cancel_execution() syncs too so a host that
cancels and captures without a further update() keeps its preview.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The recording predated the preview-suppression fix, so it showed the
translucent arm replaying the trajectory beside the real one during
execution -- the behaviour this branch now removes. Re-recorded end to
end against the current code: every state change is a real browser input
event dispatched at the page, and the execution segment now shows the
preview arm hidden with the compiled polyline still drawn.

The file was also an orphan. Its eighteen siblings under
_static/atomic_actions are all embedded from builtin_actions.md, but this
one was referenced only from the pull-request description, so it never
appeared on the documentation site. Embed it in skill_sequencer.md with
the same raw-img convention the siblings use, at a width that keeps the
sidebar text legible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Proposal] Support interactive motion creation for robot

1 participant