-
Notifications
You must be signed in to change notification settings - Fork 229
Support USD parsing from physX fixed tendons and plumb through to MJWarp #654 #688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2240,6 +2240,93 @@ def add_geoms(newton_body_id: int, incoming_xform: wp.transform | None = None): | |||||||||||||||||||||||||||||||||||||||||||||||||
| eq.data[6:10] = wp.transform_get_rotation(eq_constraint_relpose[i]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eq.data[10] = eq_constraint_torquescale[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Add fixed tendons from Newton model | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasattr(model, "tendon_count") and model.tendon_count > 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Access tendon data arrays on host | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_start = model.tendon_start.numpy() if hasattr(model.tendon_start, "numpy") else model.tendon_start | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_params = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| model.tendon_params.numpy() if hasattr(model.tendon_params, "numpy") else model.tendon_params | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_joints = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| model.tendon_joints.numpy() if hasattr(model.tendon_joints, "numpy") else model.tendon_joints | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_gearings = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| model.tendon_gearings.numpy() if hasattr(model.tendon_gearings, "numpy") else model.tendon_gearings | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Track tendon names to ensure uniqueness across environments | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_name_counts = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Add each tendon | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for i in range(model.tendon_count): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get the joints and gearings for this tendon | ||||||||||||||||||||||||||||||||||||||||||||||||||
| start_idx = tendon_start[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end_idx = tendon_start[i + 1] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if all joints in this tendon are in the selected set | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Only add tendons where all joints are selected | ||||||||||||||||||||||||||||||||||||||||||||||||||
| all_joints_selected = True | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for j in range(start_idx, end_idx): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| joint_idx = tendon_joints[j] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if joint_idx not in selected_joints: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| all_joints_selected = False | ||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Skip this tendon if not all joints are selected | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if not all_joints_selected: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2268
to
+2277
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add bounds checking for joint indices. The code should validate that joint indices are within valid bounds before using them to avoid potential crashes or undefined behavior. for j in range(start_idx, end_idx):
joint_idx = tendon_joints[j]
+ if joint_idx < 0 or joint_idx >= model.joint_count:
+ print(f"Warning: Tendon {i} references invalid joint index {joint_idx}, skipping tendon")
+ all_joints_selected = False
+ break
if joint_idx not in selected_joints:
all_joints_selected = False
break📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create fixed tendon | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon = spec.add_tendon() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Ensure unique tendon name (similar to body naming) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| base_name = model.tendon_key[i] if i < len(model.tendon_key) else f"tendon_{i}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| name = base_name | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if name not in tendon_name_counts: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_name_counts[name] = 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon_name_counts[name] += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| name = f"{base_name}_{tendon_name_counts[name]}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.name = name | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get tendon parameters (stiffness, damping, rest_length, lower_limit, upper_limit) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| params = tendon_params[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| stiffness = params[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| damping = params[1] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rest_length = params[2] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| lower_limit = params[3] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| upper_limit = params[4] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Set tendon properties | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # In MuJoCo, fixed tendons use the following fields: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # - springlength: rest length of the tendon (needs to be array [2, 1]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # - stiffness: elastic stiffness | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # - damping: damping coefficient | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # - limited: whether limits are enabled | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # - range: [lower_limit, upper_limit] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # springlength needs to be a 2x1 array for MuJoCo spec API | ||||||||||||||||||||||||||||||||||||||||||||||||||
| springlength_array = np.array([[rest_length], [0.0]], dtype=np.float64) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.springlength = springlength_array | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.stiffness = stiffness | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.damping = damping | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Set limits if they are finite | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if not (np.isinf(lower_limit) or np.isinf(upper_limit)): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.limited = True | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.range[0] = lower_limit | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.range[1] = upper_limit | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Add joints to the fixed tendon | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for j in range(start_idx, end_idx): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| joint_idx = tendon_joints[j] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| gearing = tendon_gearings[j] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| joint_name = model.joint_key[joint_idx] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # In MuJoCo spec API, use wrap_joint to add a joint to the tendon | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # This is the Python equivalent of mjs_wrapJoint in C API | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tendon.wrap_joint(joint_name, gearing) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2322
to
+2328
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for missing joint names. The code assumes joint names exist in the model but doesn't handle the case where a joint might not have a name. for j in range(start_idx, end_idx):
joint_idx = tendon_joints[j]
gearing = tendon_gearings[j]
- joint_name = model.joint_key[joint_idx]
+ joint_name = model.joint_key[joint_idx] if joint_idx < len(model.joint_key) else f"joint_{joint_idx}"
# In MuJoCo spec API, use wrap_joint to add a joint to the tendon
# This is the Python equivalent of mjs_wrapJoint in C API
- tendon.wrap_joint(joint_name, gearing)
+ try:
+ tendon.wrap_joint(joint_name, gearing)
+ except Exception as e:
+ print(f"Warning: Failed to wrap joint '{joint_name}' to tendon '{name}': {e}")
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| assert len(spec.geoms) == colliding_shapes_per_env, ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "The number of geoms in the MuJoCo model does not match the number of colliding shapes in the Newton model." | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
add_tendon lacks essential validation (ids, dof, limits, degeneracy, duplicate name).
Invalid joint ids or non-scalar joints will explode later in the solver. Limits can be inverted; all-zero gearings yield a no-op tendon.
Apply:
def add_tendon( self, name: str, joint_ids: list[int], gearings: list[float], stiffness: float = 0.0, damping: float = 0.0, rest_length: float = 0.0, lower_limit: float = float("-inf"), upper_limit: float = float("inf"), ) -> int: @@ - if len(joint_ids) != len(gearings): + if len(joint_ids) != len(gearings): raise ValueError("Number of joint IDs must match number of gearings") - if len(joint_ids) < 2: + if len(joint_ids) < 2: raise ValueError("Tendon must connect at least 2 joints") + # Basic parameter sanity + if lower_limit > upper_limit: + raise ValueError(f"lower_limit ({lower_limit}) must be <= upper_limit ({upper_limit})") + if name in self.tendon_key: + raise ValueError(f"Duplicate tendon name: {name!r}") + if len(set(joint_ids)) != len(joint_ids): + raise ValueError("Duplicate joint indices in tendon are not allowed") + if not any(abs(g) > 0.0 for g in gearings): + raise ValueError("All gearings are zero; tendon would be degenerate") + + # Validate joint indices and that each is 1-DoF (revolute or prismatic) + invalid = [j for j in joint_ids if j < 0 or j >= self.joint_count] + if invalid: + raise IndexError(f"Invalid joint indices for tendon {name}: {invalid}") + for j in set(joint_ids): + lin, ang = self.joint_dof_dim[j] + if (lin + ang) != 1: + raise ValueError( + f"Tendon {name} expects scalar joints; joint {j} has {lin+ang} DoFs" + ) + # Store the start index for this tendon's joint data self.tendon_start.append(len(self.tendon_joints)) @@ - for joint_id, gearing in zip(joint_ids, gearings, strict=False): + for joint_id, gearing in zip(joint_ids, gearings, strict=False): self.tendon_joints.append(joint_id) self.tendon_gearings.append(gearing)📝 Committable suggestion