Repository navigation
In Progress: STAR Collision Detection on iSWAP, Channels, and 96 Channel in 3D Visualizer - #1470
Draft
stefangolas wants to merge 48 commits into
Draft
stefangolas wants to merge 48 commits into
stefangolas wants to merge 48 commits into
Conversation
…and the model says what a grip takes
…until it has played
…annel ahead leaves first
A motion is checked without checking everything against everything: each rigid group's solid pieces are swept along its way, and only what a sweep's bounding box meets, found in a bounding-volume tree, is tested exactly by GJK distance. Two groups moving at once are compared over the time they share; groups may ride a shared frame - the carriage they stand on - which is composed against what stands still and eliminated between them. Every resource is a box, taken to be solid or not by how it holds its children; a model may declare its shape as convex hulls in a sidecar (\.collision.json\), which refines its pieces.
Where a plate or lid is gripped, which side from, and how the arm reaches it are worked out from where resources actually are. A plan is made of the iSWAP's primitive moves - rises, the travel with X spanned whole, the jaws - and every candidate reach is ranked and swept before one is chosen. Nothing here is called from a drive path; the planner is a library.
A plan is swept and every component it moves is a swept group: the iSWAP's own parts, what it holds, the X-arm's body, and everything mounted on it that the plan does not drive - the channels and the 96-head, which ride the carriage the plan moves. Groups on one frame are compared relative to it, exactly, and the frame is composed against what stands still; a gripper swinging under lowered channels is caught the same way a plate carried into a rack is. A plan that would meet something is refused with \iSWAPCollisionError\.
… hit drawn on the page
…ion listener before it is sent
…g where they refuse
…ned, and the viewer holds the mover there
…le machine held at its instant
…ece with what carries it
…en there by its own motion
…grip envelope clear
…d a box's way, and a group's pose composed the way its sweeps are
… drives only where it can
…pages do not read
…r its way is where its way ends
… past its way carried by the frame, and the frame and kept scene tested directly
This branch has not been deployed
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.
STAR: Collision Detection on iSWAP, Channels, and 96 Channel
The iSWAP's plate and lid moves are planned from where resources actually are (
iswap_transport.py), and every plan is judged for what it would hit before anything runs (iswap_collisions.py). A command results in swept paths for every component that moves - not just the one planned for: the iSWAP's parts and what it holds, the X-arm's body, and everything mounted on it that the plan does not drive (the channels, the 96-head), which ride the carriage the plan moves. A gripper swinging under lowered channels is caught the same way a plate carried into a rack is. The same judgement covers the pipette and head commands themselves (star_collisions.py): a channel's or the head's move in Y and Z is swept against what stands around the arm and against the other riders, so an iSWAP left out - or anything else in the way - is caught as geometry, not only by the discrete gates. And with a viewer's opt-in flag on, the same checks become a gate on the driver: a command that would hit something never reaches the device.Layering (separation of concerns)
pylabrobot/resources/collision.py- generic, machine-agnostic: box semantics over the resource tree (solid / frame / enclosure, by how a resource holds its children), convex pieces, swept segments, GJK distance, a bounding-volume broadphase. Declared hulls (.collision.jsonper model) refine a resource's pieces where shipped; without one, a resource is its box. Groups may ride a shared moving frame - the engine composes the frame against what stands still and eliminates it exactly between groups on the same frame, so relative checks of parts that share a drive stay tight. A reported sweep is then walked finely (about 1 mm / 1° steps, capped), so what it reports carries the plan time of first contact (Collision.when) and the pose the group stood in then (Collision.at); where a group is at a plan time is one definition,Group.pose_at.hamilton/star/driver/features/star_collisions.py- what the arm's checks share: the kept scene of what stands still, the exemptions policy, and the groups of everything mounted on the X-arm; and the pipette and head command checks built on them. Channels commanded by the same amount move as one rigid row and are let off each other; otherwise the command is what moves the riders relative to each other, so they are judged against each other.hamilton/star/driver/features/iswap_transport.py/iswap_collisions.py- the iSWAP's own kinematics and judgement, besideiswap.py, building onstar_collisions.py. The planner plans; the collision module judges; the sweep is uniform and the pairwise policy is explicit: parts of one machine are let off each other, as is anything mounted together on the carriage, and what a plan means to touch (what is picked up, what it stands on, what it is put down on) is never reported.The raise gate (opt-in)
Viewer3D(..., raise_on_collision=True)is the switch, andattach_viewer_collisions(driver, viewer, transport)(inmotion.py, besideattach_viewer_motion) is the wiring:STARDriver.send_commandhands every non-read command to the driver'scollision_listenerbefore it is sent (before locks; reads exempt). The listener decodes the channels'C0 JY/C0 JZ(tenths), a channel'sP<n> ZA(drive increments), and the head'sYA/ZA; runs the checks ofstar_collisions.py; and on a hit draws viaviewer.show_collisions(...)and raisesCollisionError(origin, collisions), where the origin names the command -"channel 0's Z move (P1 ZA)". A command that would hit something never reaches the device. With the flag off, the listener is a no-op and behaviour is exactly as before;R0commands are never judged.The iSWAP's plans are judged where they are made: the transport draws (
collision_reporterslot) what a refused plan would hit, before its existingiSWAPCollisionError.The viewer broadcasts what a gate caught as a
collisionsevent ({"event": "collisions", ...}, one entry per collided-with resource with its group and index); the page draws red transparent boxes over those resources at the first-contact instant (static/collisions.js). The README's message table says so.Files this PR adds, and what each is for
pylabrobot/resources/collision.pypylabrobot/resources/collision_tests.pypylabrobot/hamilton/star/driver/features/iswap_transport.pypylabrobot/hamilton/star/driver/features/iswap_transport_tests.pypylabrobot/hamilton/star/driver/features/iswap_collisions.pypylabrobot/hamilton/star/driver/features/iswap_collisions_tests.pypylabrobot/hamilton/star/driver/features/star_collisions.pypylabrobot/hamilton/star/driver/features/star_collisions_tests.pypylabrobot/visualizer3D/static/collisions.jscollisionsevent: red transparent boxes over the collided-with resources at the first-contact instant.pylabrobot/visualizer3D/collision_demo.pyOpendriven to the fine-walked meeting instant and frozen with the boxes drawn.pylabrobot/visualizer3D/transport_demo.pypylabrobot/hamilton/star/resource_model/*.collision.json(5)pylabrobot/resources/hamilton/resource_model/hamilton_tip_carrier_L5.collision.jsonFiles this PR touches, and why
hamilton/star/driver/master.py-send_commandhands every non-read command to the driver's optionalcollision_listenerbefore it is sent, before locks; reads exempt. Inert when unset.hamilton/star/driver/features/head.py-get_reference_point_location(): a reported head position converted back into a location, the inverse of the record method, for the head's own check.hamilton/star/motion.py-attach_viewer_collisions(driver, viewer, transport): the gate's wiring, besideattach_viewer_motion.visualizer3D/server.py-raise_on_collision,show_collisions(collisions, groups=None), and thecollisionsevent broadcast.visualizer3D/static/transport.js- thecollisionsmessage dispatch.visualizer3D/static/app.js- what the page does with them: hand the boxes tocollisions.js.visualizer3D/static/constants.js- theCOLLISIONbox style.visualizer3D/README.md- the message table'scollisionsrow.visualizer3D/server_tests.py-ShowCollisionsTests: the event's payload and who it reaches.visualizer3D/demo.py- the deck keeps a track of clearance, so the demo's grip envelope is chosen, not stumbled into; the mfx carrier, whose tall holders block the same way, is off the deck.visualizer3D/browser_tests.py- the page-settle checks what the scene names, not a deck's old size.Demo
python -m pylabrobot.visualizer3D.collision_demo- the tip carrier stands flush against the source carrier, as a packed deck puts them. A clean long-way pick-up ofsource_1is judged first (it passes); anOpentojaw_range[1](133.7 mm) then meets the carrier's wall part way, and the run is driven to the fine-walked meeting instant and frozen there, with the boxes drawn on what would have been hit.Behaviour changes
None on live firmware unless a viewer with
raise_on_collision=Trueis attached - and none in the drive path when it is not. With the flag on, the gate stands between the driver and the device: a command that sweeps into something raisesCollisionErrorand is not sent. The pipette and head checks remain available as a library for anything that wants to report without raising. Simulator only: the simulated iSWAP answers the force-window grip close (C0 GC) by stopping the jaws at the commanded width, so the model's grip is the planned grip. One change in the model's geometry: a lid seated on a plate (or any itemized resource) is now among the solid pieces - the engine looked only at what sat in the items, never at what sat on them.Tests
The engine's battery: solids and frames, swept hulls, GJK, broadphase scale (a sweep among 4,000+ pieces visits a handful), two movers over shared time; a lid seated on a plate is solid with it, above the plate's own box; and the fine walk: what it reports, the instant and pose it reports them at, and the guarded ends of a pairwise timeline.
The planner: every reach ranked from the model, plans refused before anything moves, the joints where they started.
The checker: the sweeps end where the model puts the parts; a second check builds no new scene, said in what it constructs rather than in seconds, which no two machines agree on; and true positives - channels lowered into the iSWAP's way refuse its plan (reported against the channels' own group), and clearing the arm lets the same plan run.
The pipette and head checks: the row of channels moved across the deck is caught when lowered and clear when raised; a channel moved across its neighbours is caught pairwise; a head swept across the deck at working height runs into the tip racks and the tips standing in them, and the same move above them is clear; tips carried on a channel meet a lidded plate's lid where the command means to touch only the plate.
The gate: a refused command is named by its origin and never sent; the flag off leaves the same commands untouched; and a tip carrier butted against the source carrier stops the grip, naming the carrier, with the joints and whatever is held left as they were.
The declared shapes: a carrier whose model ships hulls is those hulls, and seated racks never phantom-hit their own carrier - the pockets are hollow and static-vs-static is never judged.
Notes
Upstream ships no
.collision.jsonhulls, so every resource is checked as its box (conservative, with touch-leniency for box-represented resources). The six sidecars shipped here (the iSWAP's link, gripper body, fingers and pads, the X-arm's dual rails, and the tip carrier - 31 hulls, the pockets hollow so a seated rack never hits its own carrier) were fitted offline from Hamilton's models; a hull conversion utility is not part of this PR.Runtime, measured on the demo deck (2,180 resources, gate on): a whole-plan iSWAP judge runs about 1.7-2.2 s - the fine walk, not the scene build, which is kept and amortized - and the per-command gate costs about 40-60 ms per channel or head command (0.7-1.0 s across the demo's 17 commands), all of it before the command is sent. The broadphase itself buys ~1,700x over naive all-to-all on a 4,000-piece deck (1 exact test instead of 4,097).
Stacked on STAR: act firmware commands out in the viewer (motion playback) #1468 (shares the demo's motion-attachment API and the simulator's grip-close answer); retargets to main once that merges.