Replace python-fcl with Coal for aarch64 Linux support#440
Replace python-fcl with Coal for aarch64 Linux support#440KE7 wants to merge 4 commits intoBerkeleyLearnVerify:mainfrom
Conversation
python-fcl does not provide Linux aarch64/ARM64 wheels on PyPI, making Scenic uninstallable on ARM64 Linux platforms (NVIDIA Jetson, AWS Graviton, Ampere servers, etc.). Coal (https://github.com/coal-library/coal) is the actively-maintained successor to FCL with the same collision detection capabilities, BSD-3 license, pre-built wheels for all platforms including linux-aarch64, and 5-15x better performance for GJK-based queries. Changes: - Replace python-fcl dependency with coal>=3.0 in pyproject.toml - Update regions.py to use Coal API (Transform3s, BVHModelOBBRSS, CollisionObject, collide with request/result pattern) - Update requirements.py to use Coal broadphase collision manager (DynamicAABBTreeCollisionManager with CollisionCallBackDefault) - Replace trimesh.collision.CollisionManager usage (which internally depended on python-fcl) with direct Coal BVH collision checks - Update test_regions.py to use Coal API - Remove obsolete python-fcl Apple Silicon install instructions from docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #440 +/- ##
=======================================
Coverage 89.63% 89.63%
=======================================
Files 48 48
Lines 13190 13204 +14
=======================================
+ Hits 11823 11836 +13
- Misses 1367 1368 +1
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Several initial issues with this PR:
- It does not pass CI. Please ensure you are using the provided tools to meet the formatting guidelines and test changes locally with tox, as specified in the developer guide.
trimeshalso usespython-fclextensively, and we are heavily reliant ontrimesh. Have you checked that we still don't inadvertently depend onpython-fclthrough our use oftrimesh? Could you instead just make a request on thepython-fclrepo for the necessary wheels? The maintainer tends to be fairly active there when it comes to wheel changes, and I would heavily prioritize a fix that doesn't bring us out of sync withtrimesh.- In
requirements.pyyou change theBlanketCollisionRequirementto usecoal, but at first glance this update appears to, if there is a collision, re-compute which objects are colliding in a pairwise fashion? If this is correct this seems like it would add significant additional overhead to what is supposed to be a heuristically efficient check. Specifically, it seems like it would ruin the nice time complexity of BVH.
As a final note, please make sure that if you are using AI to generate your PRs you are reviewing what comes out. Reviewing is quite time intensive for the Scenic maintenance team and we would ideally like to spend that time reviewing code that has already passed initial muster.
- Remove unused `import trimesh` from requirements.py (dead code) - Move CollisionRequest construction outside the pairwise loop - Add comment explaining why pairwise identification is needed: Coal's Python bindings do not expose collision pair identity from the broadphase callback; the fallback only runs in the already- failing rejection path where a collision was detected - Run black/isort on changed files to fix formatting CI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Coal's CollisionGeometry subclasses expose .id() (via IdVisitor) which returns the stable C++ object address. This matches contact.o1.id() / contact.o2.id() in collision results, giving O(1) pair identification after the broadphase detects a collision. The fix: - Build geomIdToObj map keyed by collisionGeometry().id() before collide() - Set num_max_contacts=1 on the broadphase request to capture a contact - After collision, read contact.o1.id() / contact.o2.id() for O(1) lookup This preserves the O(n log n) broadphase complexity end-to-end, with no pairwise fallback loop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thank you for the detailed review. We've addressed all three points. 1. CI / formattingBlack and isort have been run on all changed files. The PR now passes formatting checks. 2. trimesh and python-fclWe investigated this carefully. `trimesh`'s dependency on `python-fcl` is optional and lazy: it lives in the `[recommend]` extras group and is guarded by `try: import fcl / except: fcl = None` at module load time. A plain `pip install scenic` does not pull in `python-fcl` through `trimesh`. However, Scenic calls `trimesh.collision.CollisionManager` directly, and that class raises `"No FCL Available!"` at runtime when `python-fcl` is absent. This means ARM64 users who successfully install Scenic would hit a hard crash at first use rather than at install time — which is worse. Replacing those calls with Coal is therefore necessary to fully fix the problem, not incidental. The rest of Scenic's `trimesh` usage (mesh loading, voxelization, proximity queries, ray casting) has no `python-fcl` dependency whatsoever and is untouched. 3. BlanketCollisionRequirement — O(n log n) preservedThe previous version of this PR introduced an O(n²) pairwise fallback to identify which objects collided after the broadphase, because we could not find a clean way to extract pair identity from Coal's callback. After deeper investigation of Coal's Python bindings we found the correct mechanism. Coal applies `eigenpy::IdVisitor` to all concrete `CollisionGeometry` subclasses (`Box`, `Sphere`, `BVHModelOBBRSS`, etc.), which exposes an `.id()` method returning the stable C++ object address (`reinterpret_cast<int64_t>(&self)`). This same value appears on `contact.o1.id()` and `contact.o2.id()` in collision results, even though `contact.o1` otherwise returns a fresh boost.python wrapper on each access with no stable Python identity. The implementation now works as follows: # Build id → scenic_obj map before registering
geomIdToObj[collisionObject.collisionGeometry().id()] = obj
# Request one contact so the broadphase records the pair
callback.data.request.num_max_contacts = 1
manager.collide(callback)
# O(1) lookup — no pairwise loop
if callback.data.result.isCollision():
contact = callback.data.result.getContact(0)
self._collidingObjects = (
geomIdToObj[contact.o1.id()],
geomIdToObj[contact.o2.id()],
)The broadphase complexity is O(n log n) end-to-end. The `unused import trimesh` in `requirements.py` has also been removed. |
Tests that falsifiedByInner correctly identifies the specific pair of colliding objects via contact.o1.id() / contact.o2.id(), using three objects where only two overlap, confirming the non-colliding object is not reported and _collidingObjects is set to the right pair. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Edge case: simultaneous multi-object collisions If one object collides with two or three others at the same time, _collidingObjects reports one pair — whichever the broadphase AABB tree finds first. This is non-deterministic in that specific scenario but not a bug: falsifiedByInner still returns True correctly, and violationMsg only needs a colliding pair to display. The original fcl implementation had identical behaviour — it also read only contacts[0]. |
Summary
python-fcldoes not provide Linux aarch64/ARM64 wheels on PyPI, making Scenic uninstallable on ARM64 platforms (NVIDIA Jetson, DGX Spark, AWS Graviton, Ampere servers, etc.). This PR replacespython-fclwith Coal — the actively-maintained successor to FCL.Why Coal:
manylinux_2_28_aarch64(PyPI)What changed:
pyproject.toml: Replacepython-fcl >= 0.7withcoal >= 3.0src/scenic/core/regions.py: Use Coal API (Transform3s,BVHModelOBBRSS,CollisionObject,collidewith request/result pattern). Replacetrimesh.collision.CollisionManager(which internally used python-fcl) with direct Coal BVH checks.src/scenic/core/requirements.py: Use Coal broadphase (DynamicAABBTreeCollisionManagerwithCollisionCallBackDefault)tests/core/test_regions.py: Updated to use Coal APIdocs/install_notes.rst: Removed obsolete python-fcl Apple Silicon build instructions (Coal installs cleanly everywhere)Test results
Tested on aarch64 Linux (NVIDIA GB10, Python 3.12):
```
tests/core/test_regions.py: 164 passed, 217 skipped (ViewRegion slow tests)
tests/syntax/test_requirements.py: 46 passed, 1 xfailed
Full test suite (excl. simulators): 502 passed, 11 skipped
```
All collision-related tests pass identically with Coal, including:
Test plan