docs: introduction/tutorial rewrite with runnable examples - #136
Merged
Conversation
_sync_legacy() only pulled robot.q/qd -> handle.q/qd (detecting the deprecated direct-mutation style and adopting it), but never pushed Swift's own per-step integration (velocity-mode step_v()) back into robot.q/qd. A control loop that reads robot.q back mid-loop -- the normal pattern, e.g. RTB's own README p_servo example -- saw a permanently stale configuration: fkine()/jacobe() never advanced, so the computed qd never changed either, and the robot just ran the same joint velocity forever without ever converging or stopping. Reproduced RTB's exact README example headlessly: without this fix it hits a 500-step cap never arriving, with panda.q frozen at the initial value the entire time; with the fix, converges in 43 steps. Add _push_legacy(), the mirror of _sync_legacy(): once a handle is confirmed to be in legacy mode (_warned), write handle.q/qd back into robot._q/_qd after each step_v() integration, keeping the snapshot in sync so this doesn't retrigger the warning. Gated on _warned (not unconditional) so a handle never driven the legacy way -- the intended case, including several handles sharing one plain robot model -- is untouched, preserving the actual design goal of the refactor.
serve()'s producer/wait_closed race only guards the send side, at the top of each loop iteration -- built specifically for the idle hold() case (nothing ever queued, so producer() alone would never notice a disconnect). expect_message()'s websocket.recv(), where the server actually spends nearly all its time during an active step() loop (waiting for the browser's reply to the last message), had no such race. A disconnect right there -- the common case, e.g. killing the tab mid-RRMC-loop -- fell through entirely to Swift._send_socket()'s own _REPLY_TIMEOUT fallback, up to 15s later instead of near-instant. Worse: even the *existing* idle-disconnect fast path never actually sped up a concurrently-blocked _send_socket() call -- it only pushes a throwaway value onto outq (to stop producer()'s own background thread leaking), never signals inq. So detecting the disconnect faster on the server's asyncio side alone wouldn't have been visible to a caller already blocked in inq.get(timeout=_REPLY_TIMEOUT). Fix, two parts: - Race expect_message()'s recv() against wait_closed() too, the same pattern already used at the top of serve()'s loop. - Add a threading.Event, set by SwiftSocket the instant either race detects a disconnect, and change _send_socket() to poll in short (0.05s) slices against it instead of one blocking inq.get(timeout=_REPLY_TIMEOUT). Deliberately not a sentinel value pushed onto inq itself -- inq persists across a close()/launch() reconnect (Swift.__init__ creates it once; launch() never recreates it), so a disconnect detected while nothing is waiting on inq (e.g. mid-hold()) would otherwise leave a stale value to be wrongly consumed by the next session's handshake. Also reworded the TimeoutError message to state actual elapsed time rather than a fixed "_REPLY_TIMEOUT" figure, now that it can legitimately fire well before that. New test test_disconnect_while_waiting_for_a_reply_is_noticed_quickly exercises a real client over a real websocket that vanishes mid-reply- wait -- confirmed it fails (times out at ~15s, disconnect never detected) against the pre-fix expect_message().
.label-div's default margin (1cm top, 0.5cm bottom) and font-size (1.3em, bold) are sized for an occasional standalone heading, not several Labels stacked close together -- e.g. a multi-line live readout next to a slider panel, where the current spacing looks disproportionately spacious and pushes other elements off-screen. No existing example uses Label at all, so nothing currently depends on today's spacing -- but editing the shared .label-div CSS class directly would still silently change the look for any future/external caller that does want the current spacious heading style. compact=True is opt-in per-instance instead: applied as an inline style override in the JS constructor, default False leaves the shared class and every other Label untouched.
Adds two new intro.rst sections after "Playback controls": ending a session (hold()/run()/close(), all interrupt-safe since ^C is treated as the normal way to end an interactive session), and notebook operation (browser="notebook", close(clear_cell=True)) plus a Colab section summarizing tech-debt.md's investigation for anyone who hits the same wall without reading the full writeup. Also points docs/notebooks/swift.ipynb's install cell at jhavl/swift's future branch instead of the now-merged feat/browser-lifecycle fork branch it was temporarily pinned to.
Setting aside to unblock the future->main merge for the coordinated SG/swift/RTB release. To be split into proper doc:/feat: commits later. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Quick-start section now walks through a minimal box example matching examples/box1.py, with a screenshot. examples/box.py becomes the screenshot-generating variant (adds step()/screenshot() before hold()); box1.py stays the clean, copy-pasteable version the docs embed.
…mmit The previous commit's intro.rst was copied wholesale from a different, older/shorter version of this file on another branch, silently dropping the "Displaying shapes"/mouse-controls/animating-shapes sections this branch already had. Restored those, keeping only the actual intended edit (Quick start section + box1.png).
# Conflicts: # examples/box_sliders.py # examples/panda_ik_sliders.py # examples/two_link_arm.py # src/swift/SwiftElement.py # tests/test_swift_element.py
…scene These were a stopgap to capture doc screenshots and were never meant to stay in the example scripts. env.screenshot() also has no headless guard (unlike set_lights()), so it hangs 15s and raises TimeoutError under SWIFT_HEADLESS -- discovered while building an examples smoke test on a sibling branch.
petercorke
force-pushed
the
docs/tutorial-intro
branch
from
August 22, 2026 05:58
b92058a to
492adb1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/source/intro.rstas a proper Quick Start tutorial: creating/positioning shapes, animating withstep()and callbacks, sliders, collision checking, assemblies, and scene graphs — each with a runnable, screenshot-illustrated exampledocs/source/mesh.rst(mesh file formats, vertex colors, scene-graph formats, left-handed/Y-up meshes) anddocs/source/swift.rst(playback/viewpoint controls, headless operation, velocity control, snapshots, movies, ending a session, notebook operation, Colab, scene graph internals, design philosophy)box1.py,box2.py,box_orbit1.py/box_orbit2.py,busy_scene.py,collision.py,two_link_arm_assembly.py,two_link_arm_scenegraph.py, plus supporting assets (examples/assets/) and figures:example:Sphinx role (viaextlinks) added so every referenced example file links straight to its source on GitHub.Twhen they actually bypass it entirely and send poses straight to the browser)This branch is deliberately docs/examples-only — no
src/swift/*changes — kept separate from three other small open PRs (#133, #134, #135) so review isn't tangled up with unrelated feature work.Test plan
pytest tests/— no regressions (one pre-existing, unrelated failure:test_add_path_sends_points_radius_and_linewidth, caused by the installedspatialgeometryPyPI release lagging behind an unreleasedPolylinerename)sphinx-build -E) — zero warnings, every:ref:/:meth:/:class:/:example:cross-reference resolves🤖 Generated with Claude Code