Repository navigation
Release v0.1.0 - #6
Merged
Merged
Conversation
- build.yml: ruff lint + `python -m build` + --no-deps import check across Py 3.10-3.12 - test.yml: pytest, installing the workspace siblings (motion_spec, coord_dsl, patched rdflib) from git since they are not on PyPI - pyproject: add the missing [build-system] (setuptools) so `python -m build` is reliable - README: build / test / docs status badges and a note on the package layout Pages deploy (docs.yml) already exists. Docs build verified with sphinx -W.
Point the conformance suite at motion-spec-dsl/models and vendor the pick_place_single .robmot/.fsm so the test no longer depends on the bdd_collab_bhv_cpp sibling checkout.
- test.yml: install the full sibling closure the workspace needs -- rdf_utils and jinja2 were missing (siblings under-declare deps); install siblings with deps, package with --no-deps, patched rdflib pinned last. Verified in a clean venv (88 pass). - docs.yml: build on every branch/PR so the badge renders; deploy to Pages only from main.
The app manifest's iri-map now only declares where the model's own imported graphs live; metamodel/ontology prefixes are resolved through the rdf-utils IriToFileResolver instead of being baked in as machine-specific absolute paths, so the generated manifest is portable into run archives.
…fest The .robmot imports a .fsm, but the FSM structure (states/events/transitions/ reactions) was only framed into fsm_ir.json — it never reached the model graph. Serialize the FSM rdflib graph (already built via coord_dsl get_fsm_graph) as a separate named-graph JSON-LD (@id = FSM IRI) and add it to the app manifest's imports, so ir_gen loads it as a named graph and derives the FSM wiring from the combined graph instead of reading fsm_ir.json at codegen time. The .hpp and fsm_ir.json are still generated (standalone header + archive input).
- split the monolithic grammar into grammars/*.tx (base, model, motion_spec, quantities, trajectory, constraint_handler); move textX classes into a classes/ package (namespaces.py → classes/common.py); drop domain.py - add scoping.py + validation/names.py for name resolution; drop the standalone environment validation - integrate scene-dsl: models import .scenex/.scene/.ktree instead of an inline ENVIRONMENT block - rdf builder: emit raw cross-frame pose references; transform derivation now lives downstream in motion-spec - reorg tests: drop the fixtures/ tree and test_validation; add test_scoping Generated artefacts (*.jsonld, *_fsm.hpp, fsm_ir.json, provenance/) intentionally left uncommitted.
A snapshot may now name an FSM event to re-sample on:
pose start-pose = snapshot of <shared.world.pose-ee-base> on event aas.E_ARC_ENTERED
Emits snap:sampling as initial-sampling or event-triggered-sampling plus
snap:trigger, matching the xone pairing in snapshot.shacl.ttl. This replaces
the on task/on entry clause dropped in the metamodel refactor.
EventName moves from constraint_handler.tx to base.tx: quantities.tx needs it
and cannot import constraint_handler without a cycle. base.tx already declares
the coord_dsl_fsm reference, and the EventName.event scope provider is keyed by
rule name so it keeps resolving.
The unqualified form is rejected: it mints a monitor-owned event rather than
referencing a declared el:Event, and has no URI to emit under a snapshot.
State entry needs no new concept -- an FSM reaction's existing FIRES: fires an
entry event from every reaction transitioning into that state.
The arc traced no z motion because plane-normal was sign-flipped. The sagitta direction is arc_normal x chord_hat with the apex at mid + sagitta*perp, so with chord (-0.05, 0.30, 0) the negated normal bulged 0.18 m down into the table instead of up. Touchdown had been ported from a velocity constraint to a position constraint targeting contact-z, putting Kp=200 on a ~0.4 m error, and its until became a bare position threshold that fired while still moving at full speed. Restored the descend-vel velocity constraint and the velocity-stall until, which is actual contact detection. The arc's start-pose re-samples on E_ARC_ENTERED, fired by both reactions that enter S_ARC. Its end-* snapshots stay initial-sampled, reproducing the old start-pose-on-entry + task-pose-on-task split. Verified headless and GUI: S_HOME -> S_TOUCHDOWN -> S_FORWARD -> S_ARC -> S_DONE, descent at 0.053 m/s, arc peaking 0.15 m above the table.
comply-ori held an orientation captured once, but the arc rotates roll by -0.75 rad along its path, so re-entering admittance mid-arc left Kp=240/Kd=160 driving the wrist back toward a stale target. The pre-refactor model sampled this `on entry`; the port dropped the qualifier when the clock was removed. S_ADMITTANCE has one entering reaction, so R_FORCE_DETECTED fires E_ADMITTANCE_ENTERED and the snapshot triggers on it.
until applied one all/any over the whole section, so a motion could carry only
one transition condition. S_ADMITTANCE needs two independent ones -- released on
every force axis goes back to S_ARC, bracelet-on-table ends the task -- which
forced the release test into while, monitored as a single constraint. That test
could then only cover one axis: pushing along Y let X fall under the release
threshold, so the FSM left admittance while the push was still going and
force-y immediately sent it back. A ~0.6 s limit cycle, felt as instability.
until now takes named groups, each with its own logic and its own monitor:
until {
released all { released-x: ..., released-y: ..., released-z: ... },
table-contact all { near-table: ..., contact-table: ... }
}
mon-released: monitor <compliance.released> ...
mon-table-contact: monitor <compliance.table-contact> ...
A group becomes a cstr-ext:ConstraintConjunction (or Disjunction for 'any')
carrying its members, which the metamodel already defined and nothing emitted.
Monitors targeting one aggregate exactly as a whole-section monitor does.
`until all { ... }` is unchanged, so the other motions are untouched.
The comply axes ran Kp=150 with Ki=0, so a sustained push settled where the proportional term balanced it: measured tracking error against the admittance reference was 0.069 m/s at 16 N and 0.115 m/s at 20 N, on a 0.30 m/s reference. A proportional-only velocity loop cannot reject a constant disturbance, and the external force is exactly that, so the end-effector never actually followed the velocity the admittance filter generated. Ki=800 (integral time Kp/Ki ~ 0.19 s) closes that, with the integral clamped to +/-0.02 m so its authority stays near the proportional term's (~16 m/s2) and a long push cannot wind it up into a lunge on release. damping and max-velocity are deliberately unchanged so the effect of the integral term is attributable on its own.
output-saturation was validated against the command type, which infer_command_type reports as LinearVelocity for a position or velocity constraint. But such a controller does not emit a velocity: its signal becomes the solver's acceleration-energy row, and that is what the generated clamp applies to. Authors were required to bound an acceleration with a velocity quantity, and the one existing use did exactly that. Velocity command types now expect their acceleration counterpart; force and torque commands are emitted in their own dimension and are unchanged. Adds tests/test_interaction_contract.py covering the interaction path: velocity constraints reaching the solver, the scenex force-torque sensor reaching the runtime, admittance references being produced before they are consumed, and no motion capturing or scheduling another motion's state. Each was verified to fail when its defect is reintroduced -- none of these were caught by a headless run, which completed start-to-end throughout.
Matches admittance_arc_single: each model owns a directory holding its .robmot, .fsm, .scene and .scenex, with the shared .ktree files staying at the models root and reached by a relative import. The scoping test builds a synthetic probe.robmot at the models root, so its import needed the folder-qualified path rather than the bare file name.
coord-dsl's `.fsm` grammar now uses lowercase keywords, `{ }` scopes with
comma-separated entries and `<...>` references instead of SHOUTY sections and
the `@` sigil. Converted by round-tripping through the parser, so the models
are semantically unchanged -- same states, events, transitions, reactions and
ordering, and identical generated output.
tests/fixtures/base.robmot is a minimal valid model; parse_mutated (conftest.py) replaces one clause of it and parses, so a negative test is a one-line diff against known-good source rather than a hand-built invalid fixture. tests/fixtures/extra_constructs.robmot exercises constructs the two real example models don't use (joint-position, impedance, output-saturation) so those validator/emitter branches run at all.
ProfileSpec (the `= profile { max-velocity: ..., max-acceleration: ... }`
syntax) was drafted in trajectory.tx but never listed as an alternative in
ContextQuantityValue, quantities.tx's only rule for a ContextQuantity's value.
No .robmot source could ever produce one, so `profile_qty.value` could never
be a ProfileSpec instance -- any controller that set `profile: <ref>`
unconditionally failed validation. Nothing caught it: no example model uses
the feature.
ProfileSpec's fields are ContextRef, and ContextRef itself references
ContextQuantity -- the rule ContextQuantityValue extends. That's a same-file
rule cycle, which textX resolves fine, but the moment ContextRef and
ContextQuantity live in files that import each other, the file-level import
graph becomes cyclic and textX's grammar loader breaks outright (reproduced
in isolation). So ProfileSpec has to live wherever ContextQuantity does; renamed
quantities.tx to context.tx since "quantities" no longer covers ContextQuantity,
its value forms, and reference-generator specs (admittance, velocity profile).
Two more bugs surfaced once the path became reachable and testable:
- The standalone profile quantity's RDF placeholder over-emitted as a full
traj:VelocityProfile op (max-velocity/max-acceleration/...), which made
SHACL require a traj:reference output it never provided -- unconditional
conformance failure. Now emits a plain LinearVelocity placeholder, mirroring
how the admittance placeholder already relates to its own filter op.
- The per-controller profile reference output was typed Distance instead of
LinearVelocity -- wrong regardless of what the profile drives, since a
velocity profile's output is always a velocity command.
Verified behavior-preserving: both example models' RDF dataset, app manifest
and FSM .hpp are byte-identical before/after (neither uses `profile:`), and a
hand-authored profiled-controller model now parses, validates, generates and
SHACL-conforms end to end.
coord-dsl added graphviz targets and a provenance.jsonld beside its generated artifacts, but _gen_fsm only ever called the low-level graph/gen_json/ gen_cpp_header functions directly, bypassing the wrapper layer that draws the graph and records generation. So motion-spec-dsl got neither. _gen_fsm now also writes a .dot drawing (always; no dependency) and a .svg render (only when `dot` is on PATH -- generation stays behavior-preserving on a machine without graphviz), and records every FSM artifact into coord-dsl's own provenance.jsonld. Deliberately not reusing coord-dsl's own gen_cpp(): it also runs clang_format_file, which would reformat the .hpp and add a new external-tool dependency to the default generate path. The existing gen_cpp_header() call is untouched, so the .hpp stays byte-identical. motion-spec-dsl's own provenance/dsl.jsonld previously attributed every artifact to a single motion_spec_dsl agent, including the FSM and scenex outputs it doesn't actually produce itself. It now attributes FSM artifacts to a coord_dsl agent and scenex artifacts to a scene_dsl agent, each with their own activity, and links to coord-dsl's provenance.jsonld via a pointer entity. The two documents can't literally share activity nodes: dsl.jsonld is SHACL-validated standalone (prov.shacl.ttl's sh:class constraints need the referenced node's type asserted in the same graph), so the agents are motion-spec-dsl's own self-contained nodes rather than reused coord-dsl IRIs. Also replaced _fsm_output_names, a static predictor of what _gen_fsm would write, with _gen_fsm's actual returned filenames -- it couldn't know whether .svg was produced (that depends on `dot` being on PATH) and so silently under-listed it in the provenance document. Verified: both example models' RDF dataset, app manifest and .hpp stay byte-identical; the new provenance/dsl.jsonld SHACL-conforms (checked explicitly, not just via non-error); both models' app manifests still SHACL-conform end to end.
A name is appended directly to its namespace, so an authored `ns` URI that lacks a trailing separator silently ran the two together, and one with an empty path segment or a fragment name minted a malformed IRI. Reject all of these where the URI enters, so nothing downstream has to defend. The DSL docs now live in the motion-spec Sphinx tree; drop the standalone build here.
`pose from results { of: <frame>, wrt: <frame> }` says the publisher reports
a frame other than the observed quantity -- a marker on a body seen by a
camera -- so the composition into the quantity is the model's, stated with the
geometric vocabulary rather than implied by the channel.
joint-current reads a joint's motor current: qudt:Quantity + kc-stat:JointReference + act:JointCurrent with of-joint, quantity kind ElectricCurrent, unit A. The spec grammar gains the current scalar type and the ampere unit, and the dimension vectors carry a fifth, electric current, component. A current is measured only: a controller on one is refused. norm of <vector view> [across <direction>] is a scalar view backed by geom-op-ext:VectorNorm: the Euclidean norm of a 3-vector view, or of its component across a direction seen in the same frame. Its kind and unit follow the vector's.
A `joint-velocity` is a joint-referenced angular velocity: typed `kc-stat:JointVelocityCoordinate` + `kc-stat:JointReference`, kind AngularVelocity, unit rad/s, with `kc-stat:of-joint`, mirroring joint-position. Only monitors and until terms may read it; a controller on it is refused, as for joint-current.
A wrench world quantity can name the agent whose serial chain a KDL momentum observer runs on, with its gain and filter constant, as the alternative to ft-sensor. The graph types the observer est:MomentumObserver with agn:of-agent and qudt quantities for gain (Hz) and filter; the terms live in vocab.py only. An estimated wrench cannot be commanded, cannot also name an ft-sensor, and needs re-tare-on like a measured one.
…t table contact The world tree now places the arm on the table correctly, so pose-ee-base.z at contact reads 0.040 m; the old 0.03 m threshold was met only because every reading was off by the table's placement, and the arc could never end.
Since motion-spec 51da846 an authored wrench is exerted as J^T f_ext on both backends instead of being absorbed by the constrained solve. The forearm-align impedance (400 Nm/rad on a 0.56 rad error at HOME) was a 224 Nm demand that only ever worked because it was absorbed; exerted, it fought the six tip rows and MuJoCo diverged at t~138 s. Six task rows plus one joint-space elbow pin determine the 7-DOF arm. pick_place_single already had the pin, so its align-forearm goes; the undriven align-forearm-lean row stays as the Plan 10 gradient-row case. pick_place_dual had only the impedance, so each arm gets the same elbow pin as drawer_dual. Both reach S_DONE on dev motion-spec (13.4 s, 16.5 s).
…goal gates The pinch frame reads with the gripper's -90 deg mount yaw since scene-dsl 70ecf10, so every orientation authored before it was a quarter turn off about the tool axis, and a wrench applied at a mid-link is really exerted since motion-spec 51da846. Each example is brought to that world. look_cartesian_test, look_angular_velocity_test: look pose recomputed from the measured home (tool axis along base x, yawed -120 deg); the tool-frame twist rows remapped to the real pinch axes (x down, y tangent); elbow pinned in joint space instead of a forearm-align or elbow-height impedance; the angular-velocity test ends when the reach settles. handover_dual: all authored orientations composed with the mount yaw, elbow pins on both arms, arm1's retreat stops before the wrist fold (0.58 m), transport gates on arm2's orientation too. geometric_expressions, geometric_relations (was tableii_probe): forearm impedances dropped, alignment rows kept undriven, elbow pinned, a heading row so the tilt is fully determined, and every state exits on its goal instead of a timer. tableii_probe renamed for what it exercises.
The parked pose was authored before the gripper's mount yaw entered the world reading, so S_HOME began with a quarter-turn roll error and the wrist rolled 90 deg before the pick. The measured home (tool axis along base x, pads down) replaces it; the hold now starts satisfied.
The wrapper ships kinova_gen3 with the base_link/shoulder_link contact exclusion that look_joint1_test found necessary; every scene and that test now name it instead of the Menagerie cache.
The JSON-LD keeps every distrib:SampledQuantity symbolic: the controller draws it once per run. The jsonld generator loses its seed parameter and the draw module.
rdf-utils, coord_dsl and scene-dsl are published on no index, so resolving this package failed on the first of them wherever it was installed from.
A model's own iri-map claims the whole secorolab host, which outranked the cache and made every generation download the metamodels into its own directory. The cache map now lists the metamodel prefixes too, which are longer still. The generator's ten prints become log records, so a caller can format them.
The generator's prints become log records, and a caller that sets MOTION_SPEC_LOG_FORMAT gets its own stamp and level words on them: textx runs the generator in its own process, so the line is formatted there or not at all.
Every scene carrying an FT mount spelled it 'src/motion-spec-dsl/models/ ft_mount.xml', which only resolved from the root of a workspace that had this repository checked out at src/motion-spec-dsl. A motion-spec install no longer creates that path, so the runs failed with "MJCF attachment not found". The path is now 'models/ft_mount.xml', relative to this repository, so a run from the checkout root finds it.
… cache 'src/motion-spec-dsl/models/ft_mount.xml' only resolved from the root of a workspace holding this repository at that path, which a motion-spec install no longer creates. It is now '../ft_mount.xml', relative to the scene that names it, the way the ktree imports beside it already work; motion-spec resolves it against the model directory at generation time. drawer_open_real reached the wrapper's assets with '../../../mj_kdl_wrapper', walking out of this repository into a sibling checkout. Those files are not in any repository -- they are unpacked into ~/.cache/mj_kdl_wrapper -- so the scene now says so.
A wheel can only carry data that lives inside the package, so models/ moves to src/motion_spec_dsl/models/ and package-data names the model extensions rather than the tree -- which keeps the two 1.9 MB recordings out of it: 272 KB rather than 4.5 MB. `motion-spec examples` copies them into a workspace from wherever pip put the package, so it needs neither a checkout nor a clone of this repository.
…formation per generator
The example tree moved into the package, to src/motion_spec_dsl/models, but the suite still resolved `parents[1] / "models"` and the fixtures still imported `../../models/...`. Neither path exists, so nothing that parses a model could run: 190 of 225 tests failed, every one of them on the import. With the paths corrected the suite is 3 failed, 222 passed, and all three failures are environmental -- two need ROS on the path, one an installed PyKDL carrying setDriverWeights.
The grasp normal was the tool's x, but the 2f85 fingers sit at +/-y of the pinch frame, so every model approached the handle with the jaws parallel to the bar and could not hold it. It is the tool's y in all of them now. door_open: the swing drove a chord to a precomputed point on the hinge circle, which stuttered. It is six tool-frame twist rows turning about the hinge instead. The retreat left one of the seven joints free -- two angle rows pin only two of three turns -- and the wrist sank to 0.028 m off the table; all three orientation components are held, and it clears at 0.226 m. S_DONE had no motion, so the coordinator's default left the arm limp; it now holds a rest pose. look_joint1_test: the motion ran unsafely fast, and its exit compared against a configured pose captured with the wrist at roll 0 while the model holds q7 = pi/2, so the state never ended. The gate is banded and robot.toml holds the measured pose. look_cartesian_test was simply too slow to watch.
The only publisher in the example set named a message from a package outside this workspace, so the model was rejected unless that package happened to be built and sourced. diagnostic_msgs ships with ROS and carries the same shape the row needs: a byte field with named constants, OK where the motion finished and ERROR where its constraint was violated.
geometric_relations and geometric_expressions existed only to exercise the Table II operators; nothing else referenced them. Borghesan's own worked example (sec. VII) is the drawer, so the operators now live in the task that motivates them. Four operators were unique to the probes and are added to drawer_open as watched rows -- point-to-line and line-to-line distance, line-on-line projection, direction-to-plane angle. Watched, because a row costs a solver axis and every state already spends all six. `pull-open` became a distance from the drawer face, which is the exact negation of the projection it replaces, so the driven path exercises the plane operator too. The two tool-riding lines are stated in the base frame: a line pair needs both directions in one frame, and parallel lines share no unique common normal, which the first attempt showed as a zero gradient. The hand-geometry checks move with them, to test_table_ii_values.py. Porting them turned up a wrong expectation: the direction-pair angle gradient is cross(b, a) where the direction-to-plane one is cross(a, b), and correctly so -- turning the tool about cross(a, b) carries a towards b, which raises the elevation over a plane but closes the angle between two directions. The old test asserted cross(a, b) for both and failed against its own model; it skips without a recorded run, so nobody saw it. drawer_open also carries the grasp-axis fix its siblings got, and the contract fixture follows the model.
* Square and centre eddie against the edge it holds
eddie_align squares the platform to the contact line and slides it to the
middle of the two hands, holding both hands where they started.
square: the shoulder-to-shoulder line parallel to the contact line -- stated as
two literal directions, one in a frame bolted to the platform, one in the
table. It commands a moment, so it turns the base without moving it.
centred: each shoulder projected onto the line through its own hand, the two
taken in opposite senses along that line. They agree only when the platform
stands between the hands, so the target is the other projection and no distance
is authored. A projection's gradient is the line itself, so it commands a force
along the edge and no yaw -- a reach distance mixes the two, which is why a
matched pair of them only ever rotated the base.
until any: the motion ends when a hand leaves its contact band. A section is a
conjunction unless it says otherwise, so the guard needs `any` to fire on one.
The DSL gains a scalar defined by a binary view, so a projection can be named
and used as another constraint's reference; a moment routed to a platform
solver is built like a force already was, and a cone alignment's command comes
off its gradient, which is the one thing its controller writes.
eddie_arms_only: the right arm pins its redundant DOF on joint 1, not joint 3.
Joint 3 carries 0.60 of the left arm's self-motion but 0.02 of the right's, so
the pin held one arm and let the other run away through its own null space.
* Follow the MJCF for the platform, and carry the drive models
eddie_base.ktree is transcribed from eddie_description/mujoco/mobile_platform.xml
rather than the xacro: the URDF's wheel hinges are still the eccentric ones the
MJCF re-centred, and body and joint names taken from the MJCF resolve in a run
directly.
Brings in the drive and dual-arm models the platform work was exercised with,
and the scenex viewer used to check a graph by eye.
* Stop linking composition back-edges for imported scenes
scene-dsl no longer keeps them: a chain is declared by the tree that declares
its joints, so an importer has nothing to link.
* Show an object in the colour its scene states
The viewer passes a path-backed object's colour through, and pick_place_dual's
two cubes -- one asset, so one colour until now -- are authored red and green.
* Watch the eddie_align contact from the front left as well
The scene had only the overhead camera, which flattens the approach: the
gripper and the table top project onto each other and the last centimetres of
the insertion are hidden behind the hand. side_cam is a three-quarter view from
the robot's front left, posed on the table top so it follows the table rather
than the world, and renders at the same 1280x720 and 30 Hz as the existing one.
* Place a commanded wrench where the runtime can find it; add the crf-unscrewing and drawer baselines
A force now acts at its constrained quantity's `of` frame, and a couple at its as-seen-by
frame's origin, instead of at a declared point nothing places: motion-spec resolves both
off the world model.
drawer_dual targets drawer 2, the only one stiff enough to slide the cabinet unpressed, with
its own cabinet and low-friction table assets. Adds drawer_dual_unheld, drawer_dual_baseline,
crf-unscrewing (UR10e) and crf-unscrewing-kinova, and the UR10e tree.
* Number and rename the examples by what they show
- Keep 14 examples, ordered 01-14, each named for its task: pick_and_place, base_rotation_*, drawer_opening, door_opening, dual_arm_*, arc_tracing_with_admittance, mobile_base_*, real_*.
- Rename files, identifiers and IRIs inside each example to match.
- Import the robot trees scene-dsl ships (scene_dsl:robots/...) instead of copies beside the examples.
- Drop ft_probe, eddie_push, eddie_arms_only, eddie_dual, drawer_open_real, drawer_dual_baseline and drawer_dual_unheld; move the crf models out of the package.
- Drop object colours from dual_arm_pick_and_place until colour is supported.
- Point tests, fixtures and the README at the new names.
* Scope a named until group's rows to the motion that owns it
- `_owning_section` / `_owning_motion` find the motion a named until group sits in
- Evaluator ids, difference plans, distance carriers and geometric distance plans key on that motion, so two motions' groups of the same name no longer share rows or snapshots
* Merge the base rotations, open two cabinets in one drawer example, drop the door
- One `03_base_rotation` example replaces the joint-space, Cartesian-arc and angular-velocity variants
- `04_drawer_opening`: arm1 pulls cabinet1's stiff drawer and the cabinet slides; arm2 presses cabinet2 down and arm1 opens its drawer; overtravel and opened exits as named until groups, speed-limited pull, release and retreat states
- Drop the door example, which modelled no proper door
- Renumber the rest (handover 07 ... real gripper 12) and point fixtures and tests at the new paths
- Handover caps turn and move accelerations on its orientation and unprofiled reach/rest rows
- Pick-and-place models drop unused quantities
- `test_mobile_platform_solver_contract` finds the metamodels relative to the checkout
* Require a solver algorithm and add forward kinematics
- serial-chain algorithm is required: achd, rne, fpk or fvk
- fpk/fvk declare a read-only chain; validate_kinematics_solvers rejects controllers routed to one
- emit kc:ForwardPositionKinematics and kc-op-ext:ForwardVelocityKinematics
- drop the check.py entry point and the unused PyLD dependency
- test fixtures name their algorithm
* Rework the example models for consistency
- renumber to 01-10 and move shared scenes into models/common
- every example sequences through the until idiom: the current motion's until monitor fires the next event
- ns fsm for every FSM; scene prefixes <abbr>_{env,agn,scene,mjc}
- each gripper is its own agent with its own command-forwarding solver
- common/dual_arm_table.scenex shared by 02 and 05
- 39 Nm arm torque limit on every arm solver
- wrist_ft_site yawed a half turn to match the MJCF sensor site
- 01/02: place gate on the place height, grasp gate held 0.2 s in the on-cube band, one shared grasp orientation
- 06: touchdown ends on contact height and zero descent speed
- remove natural_faults.json, dead content and narrating comments
* Merge the emitter's angle, plan-cache and view paths
- one plan function and one emit loop for the three angle-between forms
- one plan cache for every constraint kind
- one map:View emitter; wrench and twist vector views are world-component views
* Move checks into validation and record provenance in memory
- generate() returns the model dataset and its provenance graph; provenance is validated in memory and written once as provenance/dsl.ld.json
- scene and FSM generation move to motion-spec; the manifest's iri-map path is "." and build_url_map loses the models/ heuristic
- source authors are OS-account agents; the package agent carries its version and commit
- world quantities are sosa:ObservableProperty; an FT wrench is a sosa:Observation made by its sensor
- per-axis moment controllers always carry a control signal
- about 70 checks move from the RDF emitter to classes/validation, with new validators for device bindings, config poses, controller commands, world quantities, context geometry, view operands and perturbation directions
- units: one token-to-QUDT-kind table; Position is qk:PositionVector
- namespace classes flatten to IHasNamespaceDeclare and NamedNamespaceObject
- view_scenex.py removed; motion-spec view replaces it
- build with hatchling and hatch-vcs
* Follow the source API in the tests
- rejections now raised at parse time expect TextXSemanticError
- fixtures follow the moved validators (measured wrench re-tare, monitor triggers) and the reworked examples
- conftest load_authored loads a model in memory
- still failing: geometric expression dimensions, Table II operators
* Cover the geometric expression operators with a table-edge reach
- fixtures/table_edge_reach: hover above the table, level with its edge, fingers across it; drives the point-plane distance, point-on-line projection and planar angle, and watches point-line, line-line, line-on-line and direction-plane in its done monitor
- covers Table II of Borghesan et al., "Introducing Geometric Constraint Expressions Into Robot Constrained Motion Specification and Control", IEEE RA-L 1(2), 2016; no shipped example uses all of them
- fixture renamed geometric_operators_ir, test renamed test_every_geometric_operator_is_complete_and_called
* Cut the test suite to one test per DSL concept
- Every shipped example generates and conforms to the SHACL shapes
- Concepts no example uses each keep a test; rejections in one table
- IR checks and the table_edge_reach fixture move to motion-spec
- Tests call the metamodel directly; support.py holds shared source text
- 5,251 -> 1,625 lines; 104 passed
* Move user-input checks into validation and merge the unit tables
- Validation no longer imports from the emitter; shared helpers in classes/
- Per-motion world scope collected in classes/ and checked by validation
(undeclared world poses and twists, frames, whole-pose bands)
- One DSL_UNITS table: token -> QUDT unit, kinds and dimension
- WORLD_SPECS as named records; geometric properties read one way
- Geometry-kind rejection tested; schema_version dropped from dsl.ld.json
- Not verified: no tests or example runs since these changes
* Split the RDF emitter by concern and emit controlled-expression gradients
- Split rdf/motion_spec.py into emission, geometry, expressions, plans, quantities, constraints, views, handlers, solvers, execution and dataset; build_dataset(model) replaces the dataset builder class
- classes/views.py holds the view and constraint classification the emitter and validation share
- A controlled expression emits its gradient as the same arithmetic over the terms' gradients, normalized; the top operation carries gradient, gradient moment and norm
- Validation of controlled expressions: moved leaves, divisors, frames, halves, and the force/moment half check
- SHACL shapes keyed by vocabulary namespace and chosen from the namespaces the emitted graph uses
- Pending references resolve through scene-dsl's InstancedRefScopeProvider; build_instance_trees lands them
- Enums replaced by string tables, marker classes by kind attributes; user-input checks moved into validation; public names for anything imported across modules
- Not verified: tests and examples not run since these changes
* Resolve scene references through scene-dsl and fix their readers
- Trust scene-dsl's instanced-tree resolution; fall back to the unique suffix match only when it finds nothing, walking the scenes in document order
- Read a geometric prop's IRI from its reference field, so scene frames, joints, sensors and agents give their own IRI
- Read a solver reference's name through the solver, not a copy taken before textX sets it
- Return a derived scalar's declaring context as its owner instead of walking past the model root
- Reword the path-setpoint message so it reads as a sentence
- Give the PID test sources the Ki and Kd the grammar requires and match the current validation messages
* Resolve frame references through scene-dsl's FrameLike
- Geometric props, rotated-in frames, direction endpoints, observed poses and detect targets reference scene.FrameLike, so a body or tree names its default frame in every reference form
- Register scene-dsl's lower_frame_refs after the instanced trees are built, so validation and emission only see frames
- Drop GeoPropPair.value, a copy that would keep the body; geo_prop_value reads the reference fields
- Drop the suffix fallback's own body-to-frame coercion; it now only checks the reference's class
- Test a body, a bare instanced tree, a body in an instanced tree and a frame, and a body with no frame
* Bound arm 1's transfer speed in the drawer-opening example
- Add `transfer-profile`, a trapezoidal profile of 0.1 m/s and 0.3 m/s^2 per axis
- Profile arm 1's x/y/z controllers in `retreat1` and `stage2`: under the acceleration cap alone a 0.6 m transfer reached 0.43 m/s; it now peaks at 0.15 m/s
* Follow the mjkdl rename, register constant-expression classes, use textX tree helpers
- Scene asset paths `src/mj_kdl_wrapper/` -> `src/mjkdl/`
- Register `ConstFactor` and `ConstAtom`, so `const_value` dispatches with `isinstance` instead of `hasattr`
- The suffix scope search walks the scenes with textX `get_children` and skips template internals with `get_parent_of_type`
- Test the parse-time rejection of a whole geometric quantity in an expression
* Negate thresholds inline instead of keeping negated spec constants
- 06: drop neg-force-threshold, neg-release-threshold, neg-comply-int-max; bounds use (-<threshold>), integral saturation is symmetric
- 10: drop neg-force-threshold; force-z bound uses (-<force-threshold>)
* Read a joint's force instead of a gripper's motor current
- `joint-force` replaces `joint-current` as a world quantity; it is a torque (QuantityType.Torque), and the `current` scalar type, the `A` unit and the ElectricCurrent dimension are gone
- Joint scalar quantities (position, velocity, force) may be commanded only through their joint torque; a feedback law without a force or torque command outputs a linear or angular acceleration along its constraint
- Emission, vocabulary and validation follow; the quantity and validation tests cover the new form
* Target mj_kdl_wrapper 0.5.0 and scene-dsl main: frames named explicitly
- Scene asset paths point at the `src/mj_kdl_wrapper/` checkout of mj_kdl_wrapper v0.5.0
- Geometric props, rotated-in frames, direction endpoints, observed poses and detect targets reference scene.Frame again, and lower_frame_refs is no longer registered: scene-dsl main has no FrameLike
- The pick-and-place and handover examples name the cube's origin frame instead of the body
- test_scoping resolves a frame in the graph and one in an instanced tree; the body and tree cases go with FrameLike
* Name frames in the validation tests' scene references
- The detect target and the twist's `of:` frame name `table.table_top` and `cube.cube_origin`; a body no longer stands for its frame
* Clear the ruff findings in the sources
- DefinedNamespace `_extras` are tuples: rdflib only tests membership, and a mutable class default is shared by every subclass
- SSN comes from rdflib.namespace in the two modules that use it, so vocab no longer re-exports an import it never uses
- The SHACL loader catches what loading can raise (OSError, SyntaxError) instead of every exception; its implicit string concatenations are parenthesized
- The division-by-zero check is one condition; imports are sorted
* Run the test workflow against the current repos and published metamodels
- Install the package with its own git dependencies (rdf-utils, scene-dsl, coord-dsl) instead of a stale sibling list, then pin the patched rdflib
- The tests resolve metamodels by their IRIs: conftest no longer forces METAMODELS_PATH to a sibling checkout, and the shape test loads shapes through the metamodel resolver
- The SHACL gate installs motion-spec from the branch of the same name when it exists, else dev, and checks the 01, 02 and 06 examples at their current paths
* Drop the motion-spec SHACL gate from the test workflow
- The gate installed motion-spec to build application graphs, which needs rec and the rest of motion-spec's setup; this repo's CI tests this repo only
- Install from a clone with `pip install -e .`; the git-only dependencies come from pyproject.toml - Link the published language reference instead of a path in motion-spec's dev branch - Development checks are the ones CI runs: pytest with the patched rdflib, `ruff check src`
- CI installs the package and its declared dependencies only; the secorolab rdflib fork is no longer forced over PyPI's rdflib - The deprecation-warning filters stay; their comment no longer names the fork
- A Generate section: `textx generate <model> --target jsonld -o out` and the three files it writes - The Development section is gone
- Pin ruff 0.16.9 in CI and in the pre-commit hook; drop the ruff-format hook the code was never formatted with - Remove pyrightconfig.json: it pointed at a workspace venv and motion-spec's sources, which this package does not import - Drop the stale comp-rob2b/ ignore entry - The package description names the .robmot extension and RDF as the output
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.
First release of motion-spec-dsl: the textX language for guarded robot motion, emitting RDF (JSON-LD) with provenance.