@@ -896,6 +896,15 @@ class CustomAttribute:
896896 urdf_value_transformer: Callable[[str, dict[str, Any] | None], Any] | None = None
897897 """Transformer function that converts a URDF attribute value string to a valid Warp dtype. If undefined, the generic converter from :func:`newton.utils.parse_warp_value_from_string` is used. Receives an optional context dict with parsing-time information."""
898898
899+ reference_value_transformer: Callable[[Any, dict[str, Any]], Any] | None = None
900+ """Transformer for entity references copied by :meth:`ModelBuilder.add_builder`.
901+
902+ Use this instead of :attr:`references` when the referenced entity type depends on
903+ other values in the same row. The callback receives the value and a context with
904+ ``builder`` (the source builder), ``destination_builder``, ``entity_offsets``,
905+ ``custom_frequency_offsets``, ``row_index``, ``world``, and ``label_prefix``.
906+ """
907+
899908 def __post_init__(self):
900909 """Initialize default values and validate dtype compatibility."""
901910 # Allow str dtype for string attributes (stored as Python lists, not warp arrays)
@@ -1707,6 +1716,7 @@ def _custom_attribute_specs_match(existing: CustomAttribute, incoming: CustomAtt
17071716 and existing.assignment == incoming.assignment
17081717 and existing.namespace == incoming.namespace
17091718 and existing.references == incoming.references
1719+ and existing.reference_value_transformer is incoming.reference_value_transformer
17101720 )
17111721
17121722 @staticmethod
@@ -4033,8 +4043,13 @@ def get_offset(entity_or_key: str | None) -> int:
40334043 value_offset = 0 if use_current_world else get_offset(attr.references)
40344044 is_equality_target_attr = full_key == "mujoco:equality_constraint_target"
40354045 is_collision_mask_domain_attr = full_key == collision_mask_domain_key and bool(collision_mask_domain_remap)
4046+ has_reference_value_transformer = attr.reference_value_transformer is not None
40364047 needs_remap = (
4037- value_offset != 0 or use_current_world or is_equality_target_attr or is_collision_mask_domain_attr
4048+ value_offset != 0
4049+ or use_current_world
4050+ or is_equality_target_attr
4051+ or is_collision_mask_domain_attr
4052+ or has_reference_value_transformer
40384053 )
40394054
40404055 if needs_remap:
@@ -4092,7 +4107,22 @@ def transform_enum_value(
40924107 value: Any,
40934108 is_equality_target: bool = is_equality_target_attr,
40944109 is_collision_mask_domain: bool = is_collision_mask_domain_attr,
4110+ reference_value_transformer: Callable[[Any, dict[str, Any]], Any]
4111+ | None = attr.reference_value_transformer,
40954112 ) -> Any:
4113+ if reference_value_transformer is not None:
4114+ return reference_value_transformer(
4115+ value,
4116+ {
4117+ "builder": builder,
4118+ "destination_builder": self,
4119+ "entity_offsets": entity_offsets,
4120+ "custom_frequency_offsets": custom_frequency_offsets,
4121+ "row_index": entity_idx,
4122+ "world": world,
4123+ "label_prefix": label_prefix,
4124+ },
4125+ )
40964126 if is_equality_target:
40974127 return transform_equality_target_value(entity_idx, value)
40984128 if is_collision_mask_domain:
0 commit comments