Skip to content

COMPAS FAB 2.0 release#458

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

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

Conversation

@gonzalocasas
Copy link
Copy Markdown
Member

@gonzalocasas gonzalocasas commented May 19, 2026

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 6 commits May 26, 2026 22:43
The CPython Grasshopper componentizer (componentize_cpy.py) imports clr
directly, which requires pythonnet. The Phase 1 build job was failing
with ModuleNotFoundError.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add Cf_SetRobotConfiguration to bridge IK output back into a RobotCellState
  for visualization. Closes the IK loop in the offline demo path; was a
  missing piece from Phase 1.
- Drop the `compute` gate on Cf_InverseKinematics. IK is fast enough that
  always-on evaluation is fine, and the gate added wiring noise.
- Drop `typeHintID: "bool"` from `load_geometry` on the Library and ROS
  loaders. CPython GH coerced unwired bool inputs to False, defeating the
  in-code `None -> True` default; without the hint the input stays None
  when unwired and the default kicks in.
- Cf_LoadRobotCellFromRos: surface the detected ROS distro on a new output
  and print a targeted hint when the load fails with HTTP 404 (typical
  symptom of ROS 1 falling back to the ROS 2 HTTP loader when rosapi is
  missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove the deep RobotCellState copy from PyBullet set_robot_cell_state and apply the same by-reference behavior in the analytical planner for consistency.

Why: Data.copy() performs a deep serialize/deserialize copy and was a hot-path cost during collision checking (check_collision -> set_robot_cell_state). set_robot_cell_state only reads state fields and does not mutate the passed object; the copy mainly prevented aliasing with client._robot_cell_state.

Side effect: client._robot_cell_state now stores the caller-provided object by reference. Added explicit docstring notes in PyBullet set_robot_cell_state and check_collision, and analytical set_robot_cell_state, advising callers to pass state.copy() when post-call mutation is intended.

Validation performed: - Benchmarked 2000 collision checks with random configurations in PyBullet: ur5 1.449s -> 1.022s (29.5 percent faster); ur5_cone_tool 3.009s -> 1.844s (38.7 percent faster); abb_irb4600_40_255_gripper_one_beam 3.419s -> 1.932s (43.5 percent faster); collision counts unchanged between variants. - Regression tests: 105 passed (tests with --ignore=tests/backends/ros).
The old default sat on top of the rosbridge port used when remapping a
ROS 2 stack to coexist with a ROS 1 stack (rosbridge 9090 + ROS 2 rosbridge
9091). A user pointing the loader at their local ROS 2 stack got HTTP 404s
because the loader was hitting the bridge, not the file server.

9190 stays mnemonic ("near rosbridge") while sitting clear of the 909x
cluster where ROS-adjacent stuff tends to land. Both reference docker
stacks (integration tests and the ros2-ur10e-demo) default the host AND
container-internal port to 9190 so the stack speaks one number end to end.

The ROS 2 rosbridge port (9091) is unchanged — only the file server moved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Unreleased section had grown four ### Changed, two ### Fixed, two
### Added, and two ### Removed blocks as separate prep-release patches
were merged. Collapsed each Keep-a-Changelog category into a single
heading and dropped the custom \"Fixed (examples)\" heading by folding
example fixes into the regular ### Fixed list. No entries added or
removed; pure reorganization.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The official ros:jazzy image is smaller (no desktop metapackage we don't
use — moveit/ur/rosbridge/zenoh are installed explicitly anyway) and
publishes multi-arch images including arm64, which lets Apple Silicon
hosts run the demo natively.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@gonzalocasas gonzalocasas mentioned this pull request May 29, 2026
10 tasks
Credit for contributions in #449
(not merged as-is, but the author is recognized in the project's author list
and CITATION metadata).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gonzalocasas and others added 12 commits May 29, 2026 11:09
Atomic builders that let users construct a RobotCell and RobotCellState
from scratch on the canvas, plus the missing waypoint and Configuration
helpers needed to drive Cartesian motion planning.

New components (Robot Cell):
  - Cf_LoadRobotCellFromUrdfSrdf  (offline URDF/SRDF loader)
  - Cf_RigidBodyFromMesh          (Rhino mesh -> RigidBody)
  - Cf_RigidBodyFromLibrary       (RigidBodyLibrary entries)
  - Cf_ToolFromLibrary            (ToolLibrary entries)
  - Cf_AddToolToCell              (passthrough builder)
  - Cf_AddRigidBodyToCell         (passthrough builder)

New components (Cell State):
  - Cf_AttachToolToRobot          (set_tool_attached_to_group)
  - Cf_AttachRigidBodyToTool      (set_rigid_body_attached_to_tool)
  - Cf_AttachRigidBodyToLink      (set_rigid_body_attached_to_link)
  - Cf_SetRigidBodyFrame          (place static body at WCF frame)
  - Cf_SetTouchLinks              (allowed-collision links)

New components (Targets):
  - Cf_FrameWaypoints             (planes -> FrameWaypoints)
  - Cf_PointAxisWaypoints         (points + axes -> PointAxisWaypoints)
  - Cf_Configuration              (joint values + names + types -> Configuration)

All cell-state builders use deepcopy to keep the input state immutable —
chaining Attach*/Set* components composes cleanly without surprise.

Placeholder icons recycled as before.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s compas_fab.robots

The Sphinx -> MkDocs Material migration left RST role markup in the
docstrings. This sweep converts the entire compas_fab.robots module
(and its reachability_map subpackage) to mkdocstrings syntax:

  :class:`compas_fab.robots.Foo`    -> [\`Foo\`][compas_fab.robots.Foo]
  :class:`~compas_fab.robots.Foo`   -> [\`Foo\`][compas_fab.robots.Foo]
  :meth:`Foo.bar()`                  -> \`Foo.bar()\` (unqualified - no cross-ref)
  :meth:`compas_fab.robots.Foo.bar` -> [\`Foo.bar\`][compas_fab.robots.Foo.bar]
  :attr:`compas_fab.robots.Foo.bar` -> [\`Foo.bar\`][compas_fab.robots.Foo.bar]
  :obj:`bool/float/str/...`          -> \`bool\`/\`float\`/\`str\`/...
  :exc:`ValueError`                  -> \`ValueError\`
  :ref:`targets`                     -> \`targets\`

Also fixes RST hyperlinks (\`Label <url>\`_) to markdown ([Label](url)),
the singular typo \`compas_fab.robot.plan_motion\` -> \`compas_fab.robots...\`,
and a handful of stale cross-references to the removed \`Robot\` class
(updated to point at \`RobotCell\`).

509 roles converted across 14 files. Net diff is roughly neutral on
line count (-492/+478). All files still parse.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
\`uv run invoke docs\` was emitting 6 warnings from the doc build:

- trajectory.py: \`compas_robots.Joint.TYPE\` / \`.REVOLUTE\` cross-refs.
  Joint lives at \`compas_robots.model.Joint\`, but class-level attributes
  aren't in compas_robots' Sphinx inventory. Switched to plain backticks
  since cross-linking external class attributes isn't supported.
- semantics.py: stale refs to \`PyBulletClient.load_semantics\` and
  \`RosClient.load_robot\` (both methods removed/renamed during the API
  redesign). Replaced with a single working ref to \`RosClient.load_robot_cell\`.
- targets.py: refs to \`compas_fab.robots.plan_motion\` /
  \`plan_cartesian_motion\`. These were never top-level functions; the
  methods live on planner backends. Reworded to "the planner's plan_motion
  method".
- robot_library.py: ref to instance attribute \`RobotCellState.tool_states\`.
  mkdocstrings can't resolve instance attributes set in __init__; kept the
  class link and made \`.tool_states\` a plain trailing access.

Docs build now warning-free.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lToRobot

Attaching a tool with no `group` wired in was a silent no-op — confusing for
the common case where the user has a single-group robot (UR, ABB, Panda)
and shouldn't have to look up the group name.

Added an optional `robot_cell` input. When `group` is empty:
  - if `robot_cell` is wired, fall back to `robot_cell.main_group_name`
  - otherwise, raise with a message that points at the fix.

Reported during Phase 3 testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…matics

Swallowing the exception with `print(...) + return None` hid the failure —
on the canvas you got an empty output and a console line that's easy to
miss. Removed the try/except so the exception bubbles up; Grasshopper
will then turn the component red and show the message in its tooltip.

Reported during Phase 3 testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nents

Replaces silent exceptions and console prints with Grasshopper-native
runtime messages (component balloons), so failures are visible without
needing the user to wire an `error` output or watch the console.

Components updated to use error() instead of raise / print:
  Cf_AddRigidBodyToCell, Cf_AddToolToCell, Cf_AnalyticalKinematicsPlanner,
  Cf_AttachToolToRobot, Cf_Configuration, Cf_InverseKinematics,
  Cf_LoadRobotCellFromLibrary, Cf_LoadRobotCellFromRos,
  Cf_PointAxisWaypoints, Cf_RigidBodyFromLibrary, Cf_ToolFromLibrary.

Components updated to use warning() (partial success) + error() (no result):
  Cf_PlanMotion, Cf_PlanCartesianMotion.

Cf_RosClient: warning() when the previous client fails to close cleanly,
remark() when ROS distro detection fails (informational).

Also adds a `visual_meshes` output to Cf_ToolFromLibrary so users can preview
the tool's geometry (Rhino meshes, drawn in the tool's own base frame) before
attaching it to a robot.

Reported during Phase 3 testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mLibrary

Adds compas_fab.ghpython.ensure_value_list, a reusable helper that
auto-creates a Grasshopper Value List on an unconnected component input.
Cf_LoadRobotCellFromLibrary now uses it to expose every RobotCellLibrary
entry as a dropdown (default "ur5") instead of a free-text panel.
Loader calls are wrapped in try/except and surfaced via compas_ghpython.error
with the sticky cache cleared on failure, so retries actually retry.

Also fixes ToolLibrary.cone(): it now calls add_link("cone_link", ...) like
ToolLibrary.printing_tool() does. Without the link, the cone's underlying
ToolModel.root tree was malformed and iter_joints() crashed with
"'NoneType' object has no attribute 'joints'". This is what made
RobotCellLibrary.ur5_cone_tool(load_geometry=False) always crash and
manifested in the GH component when toggling load_geometry off while
clicking through robot names.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jf---
Copy link
Copy Markdown

jf--- commented May 29, 2026

witnessing you pushing HARD Mr @gonzalocasas
one EPIC PR... hang in there ;)

gonzalocasas and others added 8 commits May 30, 2026 01:27
ensure_value_list wired the new GH_ValueList to the input during the
current solve, but the input's value collection had already happened, so
the dropdown's selection didn't reach the component until the user
manually re-triggered the solver. Calling doc.ScheduleSolution(5)
immediately after AddSource queues a fresh solve so the value flows on
the very first drop with no user intervention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds compas_fab.ghpython.ensure_joint_sliders, a helper that on first
solve (or whenever the wired robot's joint signature changes) registers
one Param_Number input per configurable joint and auto-creates a
GH_NumberSlider feeding each input, with min/max set to the joint's URDF
limits (±2π fallback for continuous joints, ±1m for prismatic with no
declared limit). Joint signature is tracked in sticky so swapping
robot_cell rebuilds the bank rather than appending to it; obsolete
sliders are removed from the document on rebuild. doc.ScheduleSolution(5)
is used so values flow on the first drop.

The new Cf_RobotConfiguration component (subcategory Targets) takes one
robot_cell input, returns a Configuration with joint_names and
joint_types populated from the cell — the ergonomic counterpart to the
existing Cf_Configuration, which stays as the manual-list escape hatch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The CPython userobjects build (invoke build-cpython-ghuser-components,
run in CI by the build-cpython-components job) requires icon.png in every
component folder; without it the build fails. Used Cf_Configuration's
icon as a placeholder since the two components are siblings in subcategory
Targets — can be swapped for a dedicated icon later.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
integration.yml brings up two docker compose stacks (ROS 1 on 9090, ROS 2
on 9091) with `up -d --build` and immediately runs pytest. On cold-cache
runs the rosbridge containers aren't accepting websocket connections yet
when `--doctest-modules` reaches `RosClient`'s `>>> with RosClient()`
example at client.py:162, and roslibpy's 10s connection timeout fires
with `RosTimeoutError('Failed to connect to ROS')`. The dedicated
tests/backends/ros/test_ros_client.py cases pass because they run later
in the session when the bridge is warm.

Added a `Wait for rosbridge endpoints` step that TCP-probes ports 9090
and 9091 on localhost for up to 120s before pytest. 120s is well above
the worst observed cold-start; hitting it means the container itself is
unhealthy, in which case `docker ps -a` output is dumped before exit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ation

Temporarily point requirements.txt at the lifecycle-fixes branch on
gramaziokohler/roslibpy to exercise the 2.1 candidates (singleton
twisted log observer, Ros.close() blocking until clientConnectionLost)
against the integration workflow. If the RosClient doctest at
client.py:162 stops flaking under --doctest-modules, the upstream
roslibpy fixes are doing their job and we revert this pin back to
roslibpy >= 2.0, < 3 once 2.1 is released.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The two >>> with RosClient() examples in compas_fab.backends.ros.client
(RosClient class docstring at line 162; RosClient.load_robot_cell at
line 257) keep flaking under pytest --doctest-modules in the integration
workflow with `RosTimeoutError('Failed to connect to ROS')` — always on
the FIRST one, with the second succeeding milliseconds later. Marking
both # doctest: +SKIP eliminates the flake; the actual code path is
exercised by tests/backends/ros/test_ros_client.py (9 cases under the
module-scoped ros1_client fixture, never observed to flake), so the
doctests are only losing their role as a self-verifying illustration.

Also reverts the temporary `roslibpy @ git+...#lifecycle-fixes` pin
back to `roslibpy >= 2.0, < 3`. The pin was added to validate two
candidate roslibpy 2.1 fixes (singleton log observer, close() blocking
until clientConnectionLost) against this exact symptom — the
integration run with that branch failed identically, falsifying the
hypothesis that this flake was caused by either of those bugs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…yncio

Pin roslibpy to the websockets-asyncio branch (new asyncio/websockets
transport) and opt the integration job into it via ROSLIBPY_TRANSPORT.
Drop the two `# doctest: +SKIP` markers on the RosClient examples so
we actually exercise the live path again.

Validation only — not for merge as-is. Reverts the temporary +SKIP from
2cc6602 and the lifecycle-fixes pin from b64d020.

Co-Authored-By: Claude Opus 4.7 <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.

5 participants