Skip to content

Refine URDF assembly component prefixes and name casing policy#236

Merged
yuecideng merged 4 commits into
mainfrom
cjt/main/prof_urdf_assembly
Apr 19, 2026
Merged

Refine URDF assembly component prefixes and name casing policy#236
yuecideng merged 4 commits into
mainfrom
cjt/main/prof_urdf_assembly

Conversation

@chase6305
Copy link
Copy Markdown
Collaborator

Summary

This PR refines the URDF assembly pipeline with configurable component name prefixes, a global name casing policy for links and joints, and extended signature semantics so that naming‑related configuration changes correctly invalidate cached assemblies.
img_v3_0210q_eacc1551-3694-4433-b839-6ffb23faa26g


Motivation

  • Make component name prefixes configurable in a predictable, patch‑style way without exposing internal ordering details.
  • Centralize and expose the link/joint lower()/upper() behavior via a single name_case policy instead of hard‑coded transformations scattered across managers.
  • Ensure that changes to prefixes and naming policies are reflected in the assembly signature so that cached URDFs are rebuilt when naming semantics change.
  • Align URDFCfg and documentation with the new configuration surface.

Changes

  1. URDFAssemblyManager

    • Add support for per‑component name prefixes via the component_prefix property:
      • Internal storage remains _component_order_and_prefix: list[tuple[str, str | None]].
      • component_prefix acts as a patch‑style interface:
        • Accepts a list of (component_name, prefix) tuples.
        • Only existing component names (e.g. chassis, torso, left_arm, …) may be overridden.
        • Components not mentioned keep their original prefix.
        • Unknown component names raise a ValueError.
    • Introduce a global name casing policy via the optional name_case argument:
      • Signature: URDFAssemblyManager(..., name_case: dict[str, str] | None = None).
      • Valid keys: "joint", "link".
      • Valid values: "upper", "lower", "none".
      • Default behavior matches the legacy implementation:
        • joint names → upper case,
        • link names → lower case.
      • Provide a helper _apply_case(kind: str, name: str | None) -> str | None and remove scattered hard‑coded .lower() / .upper() calls in favor of this policy.
    • Propagate name_case to internal managers:
      • URDFComponentManager(mesh_manager, name_case=self._name_case)
      • URDFConnectionManager(self.base_link_name, name_case=self._name_case)
      • (Optional / if implemented) URDFSensorManager can also honor the same policy.
    • Extend assembly signature input:
      • When building component_info for URDFAssemblySignatureManager, inject:
        • __component_order_and_prefix__ = list(self.component_order_and_prefix)
        • __name_case__ = dict(self._name_case)
      • This ensures that changing prefixes or casing policy invalidates the cached assembly and forces a rebuild.
  2. URDFAssemblySignatureManager

    • Extend signature_data to include:
      • component_order_and_prefix
      • name_case
    • Handle the special metadata keys when iterating components:
      • __component_order_and_prefix__ → stored in signature_data["component_order_and_prefix"]
      • __name_case__ → stored in signature_data["name_case"]
    • Keep existing component hashing logic unchanged, so previous behavior is preserved aside from the new fields.
  3. URDFCfg integration

    • Add component_prefix support to URDFCfg:
      • Field type: list[tuple[str, str | None]] with a default matching URDFAssemblyManager’s internal order.
      • Initialization logic allows:
        • None → uses the default prefix list.
        • dict[str, str] → converted to list(component_prefix.items()) for convenience (e.g. {"left_hand": "l_", "right_hand": "r_"}).
        • list[tuple[str, str | None]] → used as is.
    • In assemble_urdf(), pass self.component_prefix directly to URDFAssemblyManager.component_prefix so that URDFCfg can drive per‑component prefixes from robot configs.
    • (If implemented) Add optional name_case to URDFCfg and forward it to URDFAssemblyManager to allow configuring naming policy from robot configuration.
  4. Component and connection managers (if part of this PR)

    • URDFComponentManager
      • Accept name_case: dict[str, str] | None = None and store internally.
      • Add a local _apply_case(kind, name) helper mirroring the assembly manager.
      • Replace hard‑coded .lower() / .upper() on link and joint names with the configured casing policy.
    • URDFConnectionManager
      • Accept name_case: dict[str, str] | None = None.
      • Use _apply_case("joint", ...) for generated joint names.
      • Use _apply_case("link", ...) for parent/child link names in connection joints.
    • (Optional) ComponentRegistry
      • If included, normalize component keys via a key_case policy while keeping logical component names stable in the public API.
  5. Documentation

    • Update urdf_assembly.md:
      • Add Component name prefixes (component_prefix) section describing:
        • The default prefix list.
        • Patch‑style semantics and error behavior on unknown components.
        • The fact that prefixes participate in the assembly signature, so changing them forces a rebuild.
      • Add Name casing policy (name_case) section describing:
        • Configuration via the URDFAssemblyManager constructor.
        • Valid keys/values and default behavior.
        • Propagation to internal managers and inclusion in the assembly signature.
        • The impact on cache invalidation.
      • Extend the Using with URDFCfg section to mention URDFCfg.component_prefix and its identical semantics to URDFAssemblyManager.component_prefix.

Backward compatibility

  • Default behavior is preserved:
    • Joint names remain upper‑cased.
    • Link names remain lower‑cased.
    • Default component prefix list and processing order are unchanged.
  • Existing code that does not set component_prefix or name_case should observe identical URDF outputs.
  • New configuration is opt‑in and only affects behavior when explicitly set.

Checklist

  • I have run the black . command to format the code base.
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • Dependencies have been updated, if applicable.

@chase6305 chase6305 requested review from Copilot and yuecideng and removed request for Copilot April 16, 2026 11:56
Copilot AI review requested due to automatic review settings April 16, 2026 11:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refines the URDF assembly pipeline’s naming semantics by introducing configurable per-component prefixes, a global link/joint casing policy, and extending the assembly signature so naming-policy changes invalidate cached assemblies.

Changes:

  • Add component_prefix patch-style configuration and include prefix/order + name_case in the signature inputs.
  • Introduce a reusable NameNormalizer and propagate casing behavior into component/connection naming.
  • Update URDFCfg and documentation to expose/describe the new configuration surface.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py Adds global name_case, per-component prefix patching, signature metadata injection, and material merging/fallbacks during merge.
embodichain/toolkits/urdf_assembly/signature.py Extends signature payload to include component prefix/order and casing policy metadata.
embodichain/toolkits/urdf_assembly/name_normalizer.py New helper for consistent link/joint name normalization.
embodichain/toolkits/urdf_assembly/connection.py Refactors connection logic and normalizes names via NameNormalizer; improves transform formatting and sensor legacy attachment handling.
embodichain/toolkits/urdf_assembly/component.py Applies configurable casing policy when generating/rewriting link and joint names.
embodichain/lab/sim/cfg.py Adds URDFCfg.component_prefix and URDFCfg.name_case and forwards them into URDFAssemblyManager.
embodichain/lab/sim/robots/cobotmagic.py Minor formatting fix (trailing commas).
docs/source/features/toolkits/urdf_assembly.md Documents component_prefix and name_case and updates import paths/examples.
Comments suppressed due to low confidence (1)

embodichain/toolkits/urdf_assembly/component.py:326

  • _generate_unique_name() checks if new_name in existing_names before the caller applies the configured case policy. Since existing_names is built from already-normalized link/joint names, but new_name here is not normalized, collisions can be missed (e.g. existing left_link vs generated Left_Link), and after _apply_case() the final written name can collide. Normalize base_name/new_name using the same case policy (or normalize existing_names consistently) before performing uniqueness checks and suffixing.
        # For uniqueness checks we always operate on a normalized form that is
        # consistent with the link case policy. This keeps collisions and
        # generated names aligned with how names are written back to the URDF.
        base_name = orig_name
        if prefix and not orig_name.lower().startswith(prefix.lower()):
            base_name = f"{prefix}{orig_name}"

        new_name = base_name

        # Ensure the new name is unique
        if new_name in existing_names:
            counter = 1
            unique_name = f"{new_name}_{counter}"
            while unique_name in existing_names:
                counter += 1
                unique_name = f"{new_name}_{counter}"
            new_name = unique_name

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/source/features/toolkits/urdf_assembly.md Outdated
Comment thread docs/source/features/toolkits/urdf_assembly.md
Comment thread embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py Outdated
Comment thread embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py Outdated
Comment thread embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py Outdated
Comment thread embodichain/lab/sim/cfg.py Outdated
Copilot AI review requested due to automatic review settings April 19, 2026 11:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

embodichain/toolkits/urdf_assembly/component.py:324

  • _generate_unique_name() checks uniqueness against existing_names using the raw new_name (which may differ in case), but callers later apply _apply_case() before writing names back into the URDF. With non-default name_case, this can generate duplicates (e.g., new_name='Left_Foo' not found in {'left_foo'}, then _apply_case('link', ...) produces left_foo which collides). Normalize the candidate name to the relevant case policy before the uniqueness checks (or pass kind into _generate_unique_name so both link/joint paths can do consistent normalization).
        # For uniqueness checks we always operate on a normalized form that is
        # consistent with the link case policy. This keeps collisions and
        # generated names aligned with how names are written back to the URDF.
        base_name = orig_name
        if prefix and not orig_name.lower().startswith(prefix.lower()):
            base_name = f"{prefix}{orig_name}"

        new_name = base_name

        # Ensure the new name is unique
        if new_name in existing_names:
            counter = 1
            unique_name = f"{new_name}_{counter}"
            while unique_name in existing_names:
                counter += 1

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +252 to +262
```python
manager = URDFAssemblyManager()
manager.name_case = {
"joint": "upper", # or "lower" / "none"
"link": "lower", # or "upper" / "none"
}

Semantics:

- Valid keys: `"joint"`, `"link"`.
- Valid values: `"upper"`, `"lower"`, `"none"`.
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

The name_case example code block is missing its closing triple-backtick fence. As written, the subsequent “Semantics:” section is still inside the Python code block, which will break Markdown rendering (and can fail doc builds if the project lint/checks docs). Add a closing ``` after the manager.name_case = {...} snippet.

Copilot uses AI. Check for mistakes.
Comment on lines +365 to 389
# Add sensor links to the links list (ensure lowercase + uniqueness)
for link in sensor_urdf.findall("link"):
# Ensure sensor link names are lowercase
link.set("name", link.get("name").lower())
joints.append(link) # This should be added to links list instead
raw_name = link.get("name")
if not raw_name:
continue

normalized_raw = self._apply_case("link", raw_name)
if not normalized_raw:
continue

base_name = normalized_raw
sensor_suffix = str(sensor_name).lower()
if sensor_suffix and sensor_suffix not in base_name:
base_name = f"{base_name}_{sensor_suffix}"

unique_name = self._make_unique(base_name, existing_link_names)
link.set("name", unique_name)

link_name_map[normalized_raw] = unique_name
processed_link_names.append(unique_name)
existing_link_names.add(unique_name)
links.append(link)

# Add sensor joints to the joints list (ensure uppercase + update link references)
for joint in sensor_urdf.findall("joint"):
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

Comment says “ensure lowercase + uniqueness”, but this code now applies the configurable name_case policy via _apply_case("link", ...), so it may be upper/none as well. Update the comment to reflect that names are normalized according to the configured policy (and then made unique).

Copilot uses AI. Check for mistakes.
@yuecideng yuecideng merged commit 3bb2592 into main Apr 19, 2026
8 checks passed
@yuecideng yuecideng deleted the cjt/main/prof_urdf_assembly branch April 19, 2026 13:04
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.

3 participants