Skip to content

COMPAS FAB 2.0 release#458

Open
gonzalocasas wants to merge 561 commits into
mainfrom
prep-release
Open

COMPAS FAB 2.0 release#458
gonzalocasas wants to merge 561 commits into
mainfrom
prep-release

Conversation

@gonzalocasas

@gonzalocasas gonzalocasas commented May 19, 2026

Copy link
Copy Markdown
Member
image image

This PR superceedes #456 and contains the entire Project Theseus + ROS 2 / MoveIt 2 support.

What type of change is this?

  • Bug fix in a backwards-compatible manner.
  • New feature in a backwards-compatible manner.
  • Breaking change: bug fix or new feature that involve incompatible API changes.
  • Other (e.g. doc update, configuration, etc)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I added a line to the CHANGELOG.md file in the Unreleased section under the most fitting heading (e.g. Added, Changed, Removed).
  • I ran all tests on my computer and it's all green (i.e. invoke test).
  • I ran lint on my computer and there are no errors (i.e. invoke lint).
  • I added new functions/classes and made them available on a second-level import, e.g. compas_fab.robots.CollisionMesh.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if appropriate)

gonzalocasas and others added 30 commits June 11, 2026 02:16
Strip the redundant type from numpydoc Parameters/Attributes entries
across compas_fab.robots, relying on the signature/property annotations
that mkdocstrings already renders (it shows the type from the signature,
so the docstring copy only duplicated it and drifted — and the
`[`X`][path]` cross-ref syntax rendered as raw markdown in those
sections anyway).

Where the type lived only in a `# type:` comment, convert it to a real
inline annotation so it still renders: Duration (time_.py) and Inertia
(inertia.py). Returns/Yields blocks and the TargetMode enum members are
left as-is (no signature slot to move those types to).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strip the redundant type from numpydoc Parameters/Attributes entries
across compas_fab.backends where the signature/property already carries
the annotation that mkdocstrings renders. Removes a lot of leftover RST
`:class:` / `:obj:` type markup that rendered as raw text in those
sections.

Entries whose signature is not yet annotated are left untouched (the type
would otherwise be lost) and will be handled by adding the annotation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same numpydoc cleanup as the robots/backends commits, applied to the
remaining modules (scene base objects, ghpython scene object, utilities).
Strip the docstring type where the signature already carries it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e too

Finish the numpydoc type move for entries the automated strip had to
leave because the signature/attribute carried no annotation to fall back
on. Add the missing annotations and drop the docstring copy:

* PlannerInterface.__init__ `client` -> Optional[ClientInterface]
* RosClient.__init__ host/port/is_secure/transport
* register_models_into_cell (ghpython.cell_builder) robot_cell/tools/rigid_bodies

Also fix docstring bugs surfaced along the way:
* AnalyticalSetRobotCell.set_robot_cell documented its params under an
  `Attributes` heading (should be `Parameters`).
* AnalyticalKinematics documented `base_frame_offset`/`flange_frame_offset`
  attributes that don't exist (the attributes are `base_frame`/`flange_frame`).
* inverse_kinematics_spherical_wrist documented `frame` (the param is
  `target_frame`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lazy-loader guard only bound the `pybullet` name when the module was
not yet imported; when something had already imported the real module
(e.g. the user `import pybullet` in the same Grasshopper script), being in
`sys.modules` never created a local binding, so `pybullet.connect(...)`
raised `NameError`. Bind the real (cached) module in that branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pute

`ensure_value_list` / `ensure_boolean_toggle` did AddObject + AddSource
inline during the running solution. Those mutations only commit when the
solution finishes, so the follow-up solves still read SourceCount == 0 and
kept adding more widgets (observed 2-4 stacked value lists per target_mode
input, and duplicate load_geometry toggles). Move the whole creation into
the ScheduleSolution callback (between solves, where AddSource commits) and
guard with a sticky pending flag so only one is ever scheduled - the same
deferred+tracked pattern ensure_dynamic_value_list already uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The component reads the RobotCellObject's child scene objects directly, but
those are only built when a state is applied via update(). With no cell_state
wired, update() was skipped and the draws hit a None object. Default to
robot_cell.default_cell_state() when none is wired, restoring the previous
behaviour of drawing the cell in its default configuration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The state held by the planner's client is whatever was last set internally
(typically a stale/default one), so surfacing it as an output invites wiring
a wrong state into planning. Output only robot_cell and robot_model; build
the state explicitly via Default Cell State / the Attach* components instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RigidBodyLibrary.floor() built a flat plane at Z=0, which collides with the
robot base under distance-0 collision checking, making every configuration
fail. A base's collision geometry can dip several mm below the origin (the
UR5 base_link_inertia reaches about -7 mm), so add a `clearance` parameter
that sits the floor below Z=0, defaulting to 1 cm. touch_links remains the
robot-agnostic alternative (documented).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Demonstrates collision-checked IK against a static rigid body: the UR5 IK
now plans with a `RigidBodyLibrary.floor()` present in the cell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onChain

Promote ActionChain's private `_cell_signature` helper to a public
`RobotCell.structural_signature()` method - a stable fingerprint (robot
model name + sorted tool/body ids -> sha256) of a cell's structural
identity. ActionChain now calls the method; the duplicated function and
its `hashlib` import are removed. The MoveIt Planner GH component reuses
it to decide when the planning scene needs re-uploading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MoveIt's planning scene is defined by the URDF/SRDF loaded into
move_group, so the cell can only come from ROS - splitting "load the
cell" from "make the planner" was artificial and needed a ros_client
pass-through hack to enforce load-before-plan ordering. The MoveIt
Planner now loads the cell from ROS itself and the separate Load Robot
Cell From ROS component is removed.

MoveIt Planner inputs: ros_client, tools, rigid_bodies, load_geometry
(auto-toggle), options, reload. Outputs: planner (first), robot_cell,
detected_distro. The base cell is fetched once and cached (reload to
refetch); the scene is re-uploaded only when the cell's structural
signature changes (or on reload / planner rebuild).

Advanced load parameters (urdf/srdf param names, HTTP file server URL)
move to a new MoveIt Planner Options component, backed by a tiny
`MoveItPlannerOptions` class in compas_fab.ghpython - a plain (non
iterable) class so it crosses a Grasshopper wire as one object rather
than being exploded like a dict/namedtuple. The default flow is just
RosClient -> MoveIt Planner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ROS Client component now auto-creates a Boolean Toggle on its
`connect` input when nothing is wired, defaulting to False so a
freshly-dropped client does not try to connect until the user flips it on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When planning fails with no (partial) trajectory, `trajectory` is None but
`_viz()` still called `trajectory.to_frames_and_polyline(...)`, raising a
NullReferenceException instead of just flagging the component. `_viz` now
returns empty geometry for a None trajectory, on both the cached-replay and
just-planned paths. Applied to Plan Motion and Plan Cartesian Motion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MoveItPlanner now implements the CheckCollision interface - the same
planner.check_collision(state) as PyBulletPlanner - backed by MoveIt's
/check_state_validity service (moveit_msgs/GetStateValidity). It does a
stateless collision + constraint check of the state against the planning
scene currently loaded into move_group (no planning, no IK) and raises
CollisionCheckError listing the colliding body pairs. Adds the
GetStateValidityRequest / GetStateValidityResponse message wrappers and
mixes MoveItCheckCollision into MoveItPlanner.

Live-tested against a running MoveIt: a folded UR config reports the
colliding link pairs; a zero config returns collision-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…state

When inverse kinematics fails, the component now (if the planner supports
collision checking) runs check_collision on the start state and, when it is
in collision, appends the colliding pairs to the error - turning an opaque
"no IK solution" into an actionable cause. Planners without the feature are
handled gracefully (BackendFeatureNotSupportedError / AttributeError), and
the diagnostic never masks the original error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ners

Consolidate the IK collision hint into a single `collision_diagnostic`
helper in `compas_fab.ghpython` and reuse it from Inverse Kinematics, Plan
Motion and Plan Cartesian Motion. On a failure it runs a concise collision
check on the start state - listing the colliding pairs (capped, e.g.
`a<->b, +2 more`) or noting the state is collision-free / likely
unreachable - and never masks the real error. Planners without collision
checking degrade gracefully.

Each of the three components also gains a `debug_info` output that mirrors
the warning/error messages as one string, so they can be read in a wired
panel instead of only the component balloon. The IK error message is now
just `str(e)` (the backend message is already descriptive). Also guards
Plan Cartesian Motion's `fraction` read against a None trajectory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ctory guards

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JointTrajectory.to_frames_and_polyline gained an optional `target_mode`
(defaults to TargetMode.ROBOT, coercing None -> ROBOT), threading it into the
per-point forward kinematics instead of hardcoding ROBOT. The Plan Motion and
Plan Cartesian Motion components pass the target_mode of their target /
waypoints, so the previewed planes/polyline match what was planned (e.g. the
tool-tip path for a TOOL-mode target). The mode is cached with the trajectory
so replays preview the same frames.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants