COMPAS FAB 2.0 release#458
Open
gonzalocasas wants to merge 428 commits into
Open
Conversation
…sh and PlanningScene
…sion_meshes to have correct origin
The compose stack's ur-sim and ur-driver were already configured for UR5 (ROBOT_MODEL=UR5 / ur_type:=ur5), but moveit-demo was launching ur_moveit.launch.py with ur_type:=ur5e. The integration tests target UR5; aligning moveit-demo to ur_type:=ur5 removes the URDF/SRDF inconsistency between the driver and the planning side. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds an opt-in integration suite (gated on COMPAS_FAB_RUN_ROS_INTEGRATION_TESTS=1) that runs the docs examples end-to-end against a live ROS 2 Jazzy MoveIt 2 stack on rosbridge. Covers connection, robot-cell loading, FK, IK (single + iter + full-config + collision-allow + unreachable), plan_motion (frame / configuration / tolerance / with-obstacle), plan_cartesian_motion (single + step-size + partial), and rosbridge pub/sub. The suite reflects what actually differs in ROS 2 Jazzy's UR description: - `world` root link (vs `base_link` on Noetic). - `ur_manipulator` planning group (vs `manipulator`). - `compute_cartesian_path` returns a fixed-count trajectory regardless of `max_step`; the step-size test only asserts both variants succeed and reach fraction == 1. - `plan_motion_with_obstacle` retries 5x with 5s budget each to absorb OMPL RRTConnect's stochastic failure rate. - `cartesian_motion_target_mode` is skipped when the library cell's SRDF group name doesn't match what MoveIt has loaded (`manipulator` vs `ur_manipulator`). Also adds unit tests for the message-level ROS 2 adaptations: header serialisation per distro, Time stamp keys (`secs`/`nsecs` vs `sec`/`nanosec`), MoveItErrorCodes shape, and nested headers through cartesian / motion-plan / planning-scene requests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`follow_joint_trajectory` and `execute_joint_trajectory` were both pinned to `roslibpy.ros1.actionlib.ActionClient` (topic-based ROS 1 actionlib), so calling them against a ROS 2 / MoveIt 2 stack would fail to wire up the goal/feedback/result topics. Add a distro dispatch in both methods: - ROS 1 distros: unchanged (Goal + topic-based actionlib). - ROS 2 distros: route through `roslibpy.ActionClient` (the rosbridge `send_action_goal` op) with action types in the ROS 2 long form (`control_msgs/action/FollowJointTrajectory`, `moveit_msgs/action/ExecuteTrajectory`). A thin `_Ros2GoalHandle` adapter exposes the Goal-like `is_finished` / `cancel()` surface that `CancellableRosActionResult` relies on, so the public execution API stays the same regardless of distro. Adds three unit tests that mock the ROS 1 and ROS 2 ActionClient classes to confirm: - `follow_joint_trajectory` routes to the ROS 2 client for ROS 2 distros and never instantiates the ROS 1 client. - `execute_joint_trajectory` does the same with the right action type string. - `_Ros2GoalHandle` flips `is_finished` on result delivery and forwards cancel calls. End-to-end verification with a real robot still has to happen against a properly-configured ROS 2 stack — the demo compose here does not bring up `FollowJointTrajectory`/`ExecuteTrajectory` servers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Existing docstrings pointed at Kinetic / Melodic / Fuerte / unspecified-
distro docs.ros.org pages. Update all single-distro URLs to Noetic (the
final ROS 1 LTS) and add the ROS 2 (Jazzy) equivalent on a second line
when the message exists in ROS 2.
ROS-1-only types keep just the Noetic URL:
- actionlib_msgs/* (ROS 2 uses action_msgs / unique_identifier_msgs)
- control_msgs/FollowJointTrajectory{Goal,Feedback,Result} and the
matching *Action{Goal,Feedback,Result} expansions (ROS 2 has the
consolidated control_msgs/action/FollowJointTrajectory instead)
- moveit_msgs/ExecuteTrajectory{Goal,Feedback,Result} (same pattern;
ROS 2 has moveit_msgs/action/ExecuteTrajectory)
- object_recognition_msgs/ObjectType (no current ROS 2 home)
std_msgs/Time is rehomed in ROS 2: the ROS 2 link points to
builtin_interfaces/msg/Time.
Sweep done with /tmp/update_docs_urls.py (regex-based) for the 70+
single-line URL docstrings; the 3 multi-line docstrings in geometry_msgs
(Wrench, WrenchStamped, Inertia) were edited by hand.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ROS 2 testing
The ROS 1 test stack and the ROS 2 demo stack both used to publish
rosbridge on host port 9090, so only one could run at a time. Move
the ROS 2 stack onto adjacent ports (9091 for rosbridge, 9092 for the
HTTP asset server) so the two can coexist and the integration suite
can be exercised against both without re-cycling Docker.
All published ports are env-driven via `${VAR:-DEFAULT}` so a host
that already holds the defaults can override:
| Var | Stack | Default | Container |
|--------------------|-------|---------|-----------|
| ROS1_BRIDGE_PORT | ROS 1 | 9090 | 9090 |
| ROS1_CORE_PORT | ROS 1 | 11311 | 11311 |
| ROS2_BRIDGE_PORT | ROS 2 | 9091 | 9090 |
| ROS2_HTTP_PORT | ROS 2 | 9092 | 9091 |
The container-internal ports stay unchanged (rosbridge on 9090, the
file server on 9091), so service-to-service references inside the
docker network (`file-server:9091`, etc.) keep working.
Updates the test fixture skip message to point at both stacks and
adds `tests/integration_setup/README.md` documenting the
single-stack and parallel-run patterns.
Per-robot demo stacks under `docs/installation/docker_files/*-demo/`
are unchanged — they're standalone, not part of the test
infrastructure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s2_client The integration fixture used to be a single `ros_client` that read `COMPAS_FAB_ROS_PORT` and targeted whichever stack happened to be there. With both stacks now able to run in parallel (different host ports), tests should be explicit about which one they target. `tests/backends/ros/conftest.py` adds two module-scoped fixtures: - `ros1_client` — reads `COMPAS_FAB_ROS1_PORT` (default 9090). - `ros2_client` — reads `COMPAS_FAB_ROS2_PORT` (default 9091). Each fixture skips independently if either: - `COMPAS_FAB_RUN_ROS_INTEGRATION_TESTS != "1"` (the opt-in switch); or - rosbridge isn't reachable at the configured host:port. So a suite can run with only one stack up — the other half just skips. `COMPAS_FAB_ROS_PORT` is honoured as a legacy alias for the ROS 1 port to keep older invocations working. The existing integration tests in `test_doc_examples_integration.py` assert ROS-2-specific URDF/SRDF shapes (`world` root link, `ur_manipulator` planning group), so they switch to `ros2_client`. Future ROS-1-only tests can add a parallel file that uses `ros1_client`. The shared module imports `RosClient` *lazily* inside the fixture body. The repo's top-level `conftest.py` installs Twisted's `selectreactor` in `pytest_configure`, but pytest imports every `conftest.py` in the tree before dispatching that hook. Importing `RosClient` at module load would pull in roslibpy → `twisted.internet.reactor` → the default reactor, and the subsequent `selectreactor.install()` would then raise `ReactorAlreadyInstalledError`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two compose files now share the same Dockerfile and image:
tests/integration_setup/docker-compose-ros2.yml — lightweight
test stack. Just enough MoveIt 2 + rosbridge + HTTP file server to
answer service calls. No URSim, no robot driver, no GUI. Lives next
to the ROS 1 test stack for symmetry.
docs/installation/docker_files/ros2-ur10e-demo/docker-compose.yml —
full demo. Adds URSim, the UR ROS 2 driver, and a noVNC RViz
viewport on top of the test stack. Restored to its full-featured
form after being temporarily trimmed for testing.
Container names and image (`compas-fab/ros-jazzy-moveit2`) are
identical between the two stacks, so a single build serves both and
they don't run simultaneously (which is fine — you're either testing
or demoing).
The test stack's `build:` directive points at the demo directory
(`../../docs/installation/docker_files/ros2-ur10e-demo`) so a fresh
checkout can bring up just the test stack without first running the
demo.
References updated:
- tests/backends/ros/conftest.py: skip message points at the new
test-stack path.
- tests/integration_setup/README.md: documents both stacks and
where each lives.
- docs/backends/ros2.md: presents the two stacks side-by-side so
users can pick the right one.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Commit a3317ba (Aug 2025) replaced if "pybullet" not in sys.modules: pybullet = LazyLoader("pybullet", globals(), "pybullet") with try: import pybullet except ImportError: pybullet = LazyLoader("pybullet", globals(), "pybullet") defeating the lazy pattern when pybullet is installed. Because `compas_fab.backends.__init__` pulls in `compas_fab.backends.pybullet` to re-export `PyBulletClient` and friends, just importing `RosClient` was loading the pybullet native extension (~8s on macOS). Reverts to the `sys.modules` guard so the `LazyLoader` proxy is installed unconditionally unless something else in the process has already imported the real `pybullet`. Attribute access on the proxy loads the real module on first touch, so PyBullet code paths are unaffected — only the cold startup cost moves from `import` time to first use. Verified: - `from compas_fab.backends import RosClient, MoveItPlanner` — ~1.3s wall clock (was ~9s); no `pybullet` line in `-X importtime`. - `PyBulletClient(connection_type='direct')` still connects (which proves the LazyLoader proxy still resolves correctly). - pytest tests/backends/{ros,pybullet}: 58 passed, 21 skipped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tests
build.yml — widen pre-merge coverage:
- matrix now {ubuntu, macos, windows} × {3.9, 3.13} instead of
ubuntu × 3.11. 3.9 and 3.13 are the bookend versions declared in
pyproject.toml; the release workflow continues to exercise the
full 3.9-3.13 range at tag time.
- drops stale `wip_process` branch trigger.
- adds `fail-fast: false` so one OS / Python failure doesn't mask
others.
integration.yml — actually run the integration tests:
- bumps `actions/checkout` v2 → v5 and `actions/setup-python`
v2 → v5.
- brings up both the ROS 1 and the new ROS 2 test stacks (default
ports 9090 / 9091, set by their compose files).
- sets `COMPAS_FAB_RUN_ROS_INTEGRATION_TESTS=1` so the
`ros1_client` / `ros2_client` fixtures actually connect instead of
silently skipping the suite — without this, CI was reporting green
while running zero integration assertions.
- replaces `pytest --doctest-modules` + `pytest docs` with just
`pytest --doctest-modules`; the second invocation was a leftover
from the Sphinx era and produces nothing useful against the new
Markdown docs tree.
- tears down both stacks unconditionally (`if: always()`).
- drops stale `wip_process` branch trigger.
docs.yml — wire the action up for MkDocs:
- bumps `compas-dev/compas-actions.docs` v3 → v5; the v5 release
added a `generator` input so the same action can deploy MkDocs
sites in addition to the original Sphinx flow.
- passes `generator: mkdocs` so it picks the right code path for
our `invoke docs` build.
pr-checks.yml — bump `actions/checkout` v1 → v5.
release.yml and publish_yak.yml are unchanged (already current).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ools
CI was running `pip install -r requirements-dev.txt`, which installs the
dev tooling but NOT the package itself and NOT its runtime dependencies.
The top-level `conftest.py` then crashed at `pytest_configure` time with
ModuleNotFoundError: No module named 'twisted'
because `twisted` only arrives transitively via `roslibpy` (a runtime
dependency of compas_fab, pulled in by installing the package).
`pyproject.toml` already wires both lists up:
[tool.setuptools.dynamic]
dependencies = { file = "requirements.txt" }
optional-dependencies = { dev = { file = "requirements-dev.txt" } }
so `pip install -e ".[dev]"` covers everything in one call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Default pytest import mode (`prepend`) inserts every collected
module's parent directory into sys.path. When pytest walks
`src/compas_fab/backends/pybullet/`, that puts
`src/compas_fab/backends/` on sys.path, after which a plain
`import pybullet` resolves to our own subpackage
(`compas_fab.backends.pybullet`) instead of the PyPI library:
>>> import pybullet # with src/compas_fab/backends/ on sys.path
>>> pybullet.__file__
'src/compas_fab/backends/pybullet/__init__.py'
>>> pybullet.connect
AttributeError: module 'pybullet' has no attribute 'connect'
Locally that's hidden because the real pybullet is in sys.modules
from a prior import. On a fresh CI runner without pybullet
installed, the subpackage wins, and every `PyBulletClient`-using
test fails with the AttributeError above (seen in the build job
on `compas-actions.build@v5`).
`--import-mode=importlib` uses importlib's own loader, which does
not manipulate sys.path. No more name collision between
`compas_fab.backends.pybullet` and the top-level `pybullet`.
Verified locally: 58 passed, 21 skipped (same suite shape as
before). `import pybullet` no longer leaks into `sys.modules`
when `compas_fab.backends.PyBulletClient` is imported alone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
holy mackerel! congrats @yck011522 & @gonzalocasas. |
…o CI Migration to Rhino 8 CPython-only Grasshopper components. The IronPython components folder is removed entirely, the bumpversion glob and ruff exclusion follow the cpython folder, and the rhino/ghpython install hooks (no longer needed under the Rhino 8 yak deployment model) are removed. build.yml gains a CPython component build job that uploads the resulting .ghuser files as an artifact for testing without needing a local build. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…g (Phase 1) The old components targeted the pre-stateless Robot.plan_motion(...) API and no longer match the current data model (RobotCell + RobotCellState + Planner.plan_motion(target, start_state)). This commit lands the foundation needed for an offline analytical-IK demo path that requires no ROS. New components: - Cf_LoadRobotCellFromLibrary (Robot Cell) - Cf_DefaultCellState (Cell State) - Cf_AnalyticalKinematicsPlanner (Backends) - Cf_FrameTarget (Targets, replaces old Cf_FrameTargetFromPlane) - Cf_PointAxisTarget (Targets, ported from IronPython) - Cf_ConfigurationTarget (Targets, ported from IronPython) - Cf_InverseKinematics (Planning, rewritten against the stateless API) - Cf_ForwardKinematics (Planning, rewritten with target_mode + Plane output) - Cf_VisualizeRobotCell (Display, uses cached RobotCellObject) Removed (depended on Robot.* or pre-RobotCell APIs): Cf_AttachTool, Cf_AttachedCollisionMesh, Cf_CollisionMesh, Cf_ConfigMerge, Cf_ConfigZero, Cf_ConstraintsFromConfiguration, Cf_ConstraintsFromPlane, Cf_PlanCartesianMotion, Cf_PlanMotion, Cf_PlanningScene, Cf_RosRobot, Cf_VisualizeRobot, Cf_VisualizeTrajectory. Placeholder icons are recycled from removed components; proper artwork to follow. Subsequent phases will add the ROS path, cell-construction builders, PyBullet planners, and trajectory playback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the ROS path so users can plan against a live MoveIt backend from Grasshopper. The RosClient handles both ROS 1 and ROS 2 transparently. New components: - Cf_RosClient (Backends, replaces Cf_RosConnect; exposes ros_distro) - Cf_MoveItPlanner (Backends) - Cf_LoadRobotCellFromRos (Robot Cell) - Cf_PlanMotion (Planning, planner-agnostic) - Cf_PlanCartesianMotion (Planning, planner-agnostic) Cf_RosConnect is removed in favor of Cf_RosClient; behavior is the same plus the auto-detected ROS distro on output. Planning components cache results in sticky and gate execution on a `compute` toggle so the canvas does not re-plan on every refresh. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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.
This PR superceedes #456 and contains the entire Project Theseus + ROS 2 / MoveIt 2 support.
What type of change is this?
Checklist
Put an
xin 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.CHANGELOG.mdfile in theUnreleasedsection under the most fitting heading (e.g.Added,Changed,Removed).invoke test).invoke lint).compas_fab.robots.CollisionMesh.