Skip to content

[Fix] Resolve package:// meshes when merging fixed joints in URDF conversion - #7084

Open
hujc7 wants to merge 4 commits into
isaac-sim:developfrom
hujc7:jichuanh/urdf-ros-package-root
Open

[Fix] Resolve package:// meshes when merging fixed joints in URDF conversion#7084
hujc7 wants to merge 4 commits into
isaac-sim:developfrom
hujc7:jichuanh/urdf-ros-package-root

Conversation

@hujc7

@hujc7 hujc7 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Description

Two independent changes to URDF conversion and the kit-less image.

1. package:// meshes are dropped when fixed joints are merged

Merging fixed joints rewrites the URDF into a scratch directory
(/tmp/urdf_import_<robot>_*/), and the importer resolves package://<pkg>/...
relative to that copy. No meshes live there, so every visual and collision mesh
resolves to a missing file. Conversion still succeeds: the resulting asset has the
full joint and rigid-body hierarchy but no geometry at all, and nothing renders.

This affects any ROS-style description converted with the default
UrdfConverterCfg(merge_fixed_joints=True), unless the user already knew to set
ros_package_paths by hand.

UrdfConverter now derives the mapping from the URDF's own location: it walks up to
the nearest package.xml, reads its <name>, and passes the resulting
{"name", "path"} entry to the importer. An explicitly configured
ros_package_paths still takes precedence, so unconventional layouts are unaffected.

Measured on anymal_d_simple_description, merge_fixed_joints=True, no explicit
ros_package_paths:

payloads/geometries.usd
before 0 B
after 376,081 B

Tracked against the Isaac Sim URDF importer as nvbug 6583703; this works around it
from the Isaac Lab side.

2. Kit-less image passes an install token that no longer exists

docker/Dockerfile.kitless still passes importers to isaaclab.sh --install. That
extra was removed in #6935, when the standalone URDF/MJCF importers became base
dependencies. An unknown token is warned about and skipped (install.py:1195), so the
image is correct today — this only drops the dead token and its stale comment, so
kit-less builds stop logging Unknown install token 'importers'.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

hujc7 added 2 commits August 13, 2026 13:09
`--install ... ,importers` names a token the install CLI does not define, so
every kit-less image build logs "Unknown install token 'importers'. Skipping"
and the selector reads as if it installs something. Nothing else references it.
Merging fixed joints rewrites the URDF into a scratch directory, and the
importer then resolves `package://` URLs against that copy, where no meshes
exist. Every mesh silently dropped, so conversion produced an articulation
with no geometry. Deriving the package from the URDF's own `package.xml`
keeps the URLs anchored to the source tree, so `ros_package_paths` is only
needed for packages laid out unconventionally.
@github-actions github-actions Bot added isaac-lab Related to Isaac Lab team infrastructure labels Aug 13, 2026
@hujc7
hujc7 marked this pull request as ready for review August 13, 2026 23:49
@hujc7
hujc7 requested a review from a team August 13, 2026 23:49
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR derives a ROS package mapping from a URDF’s nearest package.xml so package:// meshes survive fixed-joint merging, and removes an obsolete kit-less installation token.

  • Adds automatic ROS package discovery and forwards the inferred mapping to the URDF importer.
  • Adds helper-level package discovery tests and a changelog entry.
  • Removes the obsolete importers selector from the kit-less Docker build.

Confidence Score: 4/5

The unreadable-manifest failure should be fixed before merging because automatic discovery can now abort otherwise valid URDF conversions.

The new package discovery catches XML syntax errors but not filesystem errors raised while opening a discovered manifest, and the regression test does not cover the actual fixed-joint geometry path.

Files Needing Attention: source/isaaclab/isaaclab/sim/converters/urdf_converter.py; source/isaaclab/test/sim/test_urdf_converter.py

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/sim/converters/urdf_converter.py Adds source-package inference correctly shaped for the importer, but an unreadable automatically discovered manifest can now abort conversion.
source/isaaclab/test/sim/test_urdf_converter.py Covers helper discovery and the no-package case but does not exercise the package:// fixed-joint conversion regression end to end.
docker/Dockerfile.kitless Removes the obsolete importers installation selector and its stale explanatory comment.
source/isaaclab/changelog.d/jichuanh-urdf-ros-package-root.rst Documents automatic package discovery and the remaining need for explicit mappings in unconventional layouts.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  U[URDF asset path] --> D{Explicit ROS package paths?}
  D -->|Yes| E[Use configured mappings]
  D -->|No| F[Walk ancestors for package.xml]
  F --> P[Parse package name]
  P --> I[Build URDFImporterConfig]
  E --> I
  I --> M[Merge fixed joints]
  M --> R[Resolve package:// meshes]
  R --> O[Generated USD geometry]
Loading

Reviews (1): Last reviewed commit: "Derive the ROS package root from the URD..." | Re-trigger Greptile

Comment on lines +38 to +42
try:
name = ElementTree.parse(manifest).getroot().findtext("name")
except ElementTree.ParseError:
logger.warning(f"UrdfConverter: could not parse '{manifest}' to resolve 'package://' URLs.")
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Manifest read errors abort conversion

If an ancestor package.xml exists but cannot be opened, ElementTree.parse raises an OSError that bypasses this handler, causing conversion to abort even for an otherwise readable URDF.

Suggested change
try:
name = ElementTree.parse(manifest).getroot().findtext("name")
except ElementTree.ParseError:
logger.warning(f"UrdfConverter: could not parse '{manifest}' to resolve 'package://' URLs.")
return None
try:
name = ElementTree.parse(manifest).getroot().findtext("name")
except (ElementTree.ParseError, OSError):
logger.warning(f"UrdfConverter: could not read or parse '{manifest}' to resolve 'package://' URLs.")
return None

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in df007d0. Verified the failure mode directly rather than by inspection: with chmod 000 on the manifest, Path.is_file() still returns True and ElementTree.parse raises PermissionError. ElementTree.ParseError derives from SyntaxError, not OSError, so the old handler could never have caught it. The handler is now except (ElementTree.ParseError, OSError).

urdf.write_text("<robot name='robot'/>")

assert _find_ros_package(str(urdf)) == {"name": "my_robot_description", "path": str(package)}
# a URDF outside any package has no mapping to derive

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regression test skips conversion path

This assertion tests _find_ros_package directly but never runs fixed-joint conversion with a package:// mesh and verifies generated geometry, so the reported geometry-loss regression can recur while the test still passes.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and this was a real gap — the helper test still passed with the call-site wiring deleted, which is exactly the failure mode you describe.

Added test_package_url_meshes_survive_fixed_joint_merge in 8b501c3: it builds a throwaway ROS package (a package.xml, an OBJ mesh, and a URDF whose only geometry is a package:// visual behind a fixed joint), converts with merge_fixed_joints=True, and asserts a mesh survives. Verified it fails with the wiring reverted and passes with it.

One detail worth recording for anyone extending these tests: the importer writes the geometry behind an instanceable reference, so the assertion traverses with Usd.TraverseInstanceProxies(). A plain Stage.Traverse() stops at the instance boundary and reports zero meshes even on a perfectly good conversion.

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isaac Lab Review Bot

The change derives a ROS package mapping from the nearest ancestor package.xml when no explicit ros_package_paths are configured, preserving package:// mesh resolution after fixed-joint merging. It also removes the obsolete importers selector from the kit-less image install command.

  • Design and architecture: The package-discovery workaround is localized at the URDF importer boundary, uses only the standard library, and preserves explicit ros_package_paths as the authoritative configuration. Walking to the nearest ancestor manifest follows the conventional ROS package layout, with the residual tradeoff that URDFs nested beneath an unrelated package manifest will inherit that package.
  • API: No public fields or importer argument shapes change. The user-visible behavior of an empty ros_package_paths list now includes automatic package derivation, while explicitly configured mappings retain their previous behavior. The source-package changelog records this semantic change.
  • Implementation: The derived mapping is deterministic from asset_path; absent or blank package names fall back to the prior empty mapping, while malformed XML logs a warning and falls back. The helper test covers package discovery and the no-package case. Existing cached conversions may still require forced regeneration or output removal, consistent with the converter's documented cache behavior.

No blocking issues. No inline issue met the actionable-evidence threshold; the assessment above records the review feedback.

Automated review; human maintainers own approval decisions.

hujc7 added 2 commits August 13, 2026 17:09
`ElementTree.ParseError` derives from `SyntaxError`, so an ancestor
`package.xml` that exists but cannot be opened raised an uncaught `OSError`
and aborted an otherwise valid conversion.
The helper test asserted only that a package root is discoverable, so it still
passed with the call-site wiring deleted. The new test converts a URDF whose
only geometry is a `package://` visual behind a fixed joint and asserts a mesh
survives, which fails without the wiring.

The geometry is behind an instanceable reference, so it is reached through
`Usd.TraverseInstanceProxies()`; a plain `Stage.Traverse()` stops at the
instance boundary and sees no meshes at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infrastructure isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants