Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1624a61
Add Newton Support for MJCF parsing and actuation of Spatial Tendons …
vreutskyy Jul 17, 2025
dbb3768
fix pre-commit errors
vreutskyy Jul 17, 2025
3219690
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 18, 2025
67c7a7a
Small fixes
vreutskyy Jul 18, 2025
f5c1ce9
Small fixes
vreutskyy Jul 18, 2025
daf974c
Small fixes
vreutskyy Jul 18, 2025
1e14d17
Refactor: Standardize tendon/site naming conventions
vreutskyy Jul 21, 2025
bb39a4e
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 21, 2025
bf10dea
A small fix
vreutskyy Jul 21, 2025
56b5fc2
Added a kernel applying tendon controls, removed tendon_f as currentl…
vreutskyy Jul 21, 2025
59effa1
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 22, 2025
78c6b92
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 23, 2025
71faa56
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 25, 2025
1b315df
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Jul 29, 2025
c774cc4
selects all tendons prototype
nv-rgresia Jul 23, 2025
67d276f
adds selection features and unit tests
nv-rgresia Aug 5, 2025
1d419f4
cleanup
nv-rgresia Aug 5, 2025
59314ed
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Aug 5, 2025
f46d4b9
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Aug 6, 2025
228e433
Post-merge fix
vreutskyy Aug 6, 2025
9e16859
Merge pull request #1 from vreutskyy/462-add-support-for-spatial-tend…
vreutskyy Aug 6, 2025
9a31604
Post-merge fix
vreutskyy Aug 6, 2025
61453ea
Small fix
vreutskyy Aug 6, 2025
c632e01
Small UT fix
vreutskyy Aug 6, 2025
596f014
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Aug 7, 2025
11ad4cc
Merge branch 'main' into 233-add-newton-support-for-mjcf-parsing-and-…
vreutskyy Aug 7, 2025
28ae501
Merge branch 'main' into 520-add-support-for-parsing-physx-spatial-te…
nv-rgresia Aug 19, 2025
94ec50f
updates USD importer to support spatial tendons
nv-rgresia Aug 21, 2025
6843ccc
updates builder to copy spatial tendons in copy contructor
nv-rgresia Aug 21, 2025
ec922dc
merges in main
nv-rgresia Aug 27, 2025
4f2ef16
updates usd importer
nv-rgresia Aug 27, 2025
3b3218d
updates tendon related code for merge and fixes usd physx tendon impo…
nv-rgresia Sep 5, 2025
1dc5e48
bug fix
nv-rgresia Sep 5, 2025
e68022c
adds unit test for spatial tendon conversion
nv-rgresia Sep 15, 2025
642b5fb
cleanup
nv-rgresia Sep 16, 2025
c7bf3cf
merges main
nv-rgresia Sep 24, 2025
f362a7a
updates spatial tendon asset
nv-rgresia Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions newton/_src/sim/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,30 @@ def __init__(self, up_axis: AxisType = Axis.Z, gravity: float = -9.81):
# if setting is None, the number of worst-case number of contacts will be calculated in self.finalize()
self.num_rigid_contacts_per_env = None

self.body_armature = self.default_body_armature

# sites (reference points on bodies for tendons)
self.site_key = []
self.site_body = []
self.site_xform = []

# tendons
self.tendon_key = []
self.tendon_type = []
self.tendon_site_ids = []
self.tendon_damping = []
self.tendon_stiffness = []
self.tendon_rest_length = []

# tendon actuators
self.tendon_actuator_tendon_id = []
self.tendon_actuator_key = []
self.tendon_actuator_ke = []
self.tendon_actuator_kd = []
self.tendon_actuator_force_range = []

# rigid articulations

# equality constraints
self.equality_constraint_type = []
self.equality_constraint_body1 = []
Expand Down Expand Up @@ -1155,6 +1179,20 @@ def transform_mul(a, b):
"equality_constraint_polycoef",
"equality_constraint_key",
"equality_constraint_enabled",
"site_key",
"site_body",
"site_xform",
"tendon_key",
"tendon_type",
"tendon_site_ids",
"tendon_damping",
"tendon_stiffness",
"tendon_rest_length",
"tendon_actuator_tendon_id",
"tendon_actuator_key",
"tendon_actuator_ke",
"tendon_actuator_kd",
"tendon_actuator_force_range",
]

for attr in more_builder_attrs:
Expand Down Expand Up @@ -2370,6 +2408,99 @@ def add_muscle(
# return the index of the muscle
return len(self.muscle_start) - 1

def add_site(
self,
body: int,
xform: Transform | None = None,
key: str | None = None,
) -> int:
"""
Adds a site (reference frame) to the model.

Args:
body: The body index to which the site is attached
xform: The transform of the site relative to the body (default: identity)
key: Key of the site (optional)

Returns:
The index of the site
"""
if xform is None:
xform = wp.transform()

i = len(self.site_key)
self.site_key.append(key or f"site_{i}")
self.site_body.append(body)
self.site_xform.append(xform)

return i

def add_tendon(
self,
tendon_type: str,
site_ids: list[int],
damping: float = 0.0,
stiffness: float = 0.0,
rest_length: float | None = None,
key: str | None = None,
) -> int:
"""
Adds a tendon to the model.

Args:
tendon_type: The type of tendon (e.g., 'spatial')
site_ids: List of site indices that the tendon connects
damping: Damping coefficient for the tendon
stiffness: Stiffness coefficient for the tendon
rest_length: Rest length of the tendon (default: computed from initial configuration)
key: Key of the tendon (optional)

Returns:
The index of the tendon
"""
i = len(self.tendon_key)
self.tendon_key.append(key or f"tendon_{i}")
self.tendon_type.append(tendon_type)
self.tendon_site_ids.append(site_ids)
self.tendon_damping.append(damping)
self.tendon_stiffness.append(stiffness)
self.tendon_rest_length.append(rest_length if rest_length is not None else 0.0)

return i

def add_tendon_actuator(
self,
tendon_id: int,
ke: float = 0.0,
kd: float = 0.0,
force_range: tuple[float, float] | None = None,
key: str | None = None,
) -> int:
"""
Adds a tendon actuator to the model.

Args:
tendon_id: The index of the tendon to actuate
ke: Elastic/stiffness gain for the actuator
kd: Damping gain for the actuator
force_range: Force range [min, max] for the actuator (default: [-inf, inf])
key: Key of the actuator (optional)

Returns:
The index of the tendon actuator
"""
if force_range is None:
force_range = (-float("inf"), float("inf"))

i = len(self.tendon_actuator_key)
self.tendon_actuator_tendon_id.append(tendon_id)
self.tendon_actuator_key.append(key or f"tendon_actuator_{i}")
self.tendon_actuator_ke.append(ke)
self.tendon_actuator_kd.append(kd)
self.tendon_actuator_force_range.append(force_range)

return i

# region shapes

def add_shape(
Expand Down Expand Up @@ -4213,6 +4344,43 @@ def finalize(self, device: Devicelike | None = None, requires_grad: bool = False
m.muscle_points = wp.array(self.muscle_points, dtype=wp.vec3, requires_grad=requires_grad)
m.muscle_activations = wp.array(self.muscle_activations, dtype=wp.float32, requires_grad=requires_grad)

# -----------------------
# sites, tendons, and tendon actuators

m.site_key = self.site_key
m.site_body = wp.array(self.site_body, dtype=wp.int32) if self.site_body else None
m.site_xform = wp.array(self.site_xform, dtype=wp.transform) if self.site_xform else None

m.tendon_key = self.tendon_key
m.tendon_type = self.tendon_type
m.tendon_site_ids = self.tendon_site_ids
m.tendon_damping = wp.array(self.tendon_damping, dtype=wp.float32) if self.tendon_damping else None
m.tendon_stiffness = wp.array(self.tendon_stiffness, dtype=wp.float32) if self.tendon_stiffness else None
m.tendon_rest_length = (
wp.array(self.tendon_rest_length, dtype=wp.float32) if self.tendon_rest_length else None
)

m.tendon_actuator_tendon_id = (
wp.array(self.tendon_actuator_tendon_id, dtype=wp.int32) if self.tendon_actuator_tendon_id else None
)
m.tendon_actuator_key = self.tendon_actuator_key
m.tendon_actuator_ke = (
wp.array(self.tendon_actuator_ke, dtype=wp.float32) if self.tendon_actuator_ke else None
)
m.tendon_actuator_kd = (
wp.array(self.tendon_actuator_kd, dtype=wp.float32) if self.tendon_actuator_kd else None
)
m.tendon_actuator_force_range = (
wp.array(self.tendon_actuator_force_range, dtype=wp.vec2) if self.tendon_actuator_force_range else None
)

# Initialize tendon control arrays
if self.tendon_actuator_key:
# Initialize with zeros (no default targets for tendons unlike joints)
m.tendon_target = wp.zeros(
len(self.tendon_actuator_key), dtype=wp.float32, device=device, requires_grad=requires_grad
)

# --------------------------------------
# rigid bodies

Expand Down Expand Up @@ -4392,6 +4560,9 @@ def finalize(self, device: Devicelike | None = None, requires_grad: bool = False
m.edge_count = len(self.edge_rest_angle)
m.spring_count = len(self.spring_rest_length)
m.muscle_count = len(self.muscle_start)
m.site_count = len(self.site_key)
m.tendon_count = len(self.tendon_key)
m.tendon_actuator_count = len(self.tendon_actuator_key)
m.articulation_count = len(self.articulation_start)
m.equality_constraint_count = len(self.equality_constraint_type)

Expand Down
7 changes: 7 additions & 0 deletions newton/_src/sim/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ def __init__(self):
The joint targets are defined for any joint type, except for free joints.
"""

self.tendon_target: wp.array | None = None
"""
Array of tendon targets with shape ``(tendon_count,)`` and type ``float``.
Tendon targets define the target length or target velocity for each tendon actuator,
depending on the tendon actuator configuration.
"""

self.tri_activations: wp.array | None = None
"""Array of triangle element activations with shape ``(tri_count,)`` and type ``float``."""

Expand Down
56 changes: 55 additions & 1 deletion newton/_src/sim/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ def __init__(self, device: Devicelike | None = None):
self.muscle_activations = None
"""Muscle activations, shape [muscle_count], float."""

self.site_key = []
"""List of site keys."""
self.site_body = None
"""Body index for each site, shape [site_count], int."""
self.site_xform = None
"""Transform (position and orientation) for each site relative to its body, shape [site_count, 7], float."""

self.tendon_key = []
"""List of tendon keys."""
self.tendon_type = []
"""Type of each tendon ('spatial', etc.), shape [tendon_count], string."""
self.tendon_site_ids = []
"""List of site IDs for each tendon. For spatial tendons, this is a list of site indices."""
self.tendon_damping = None
"""Damping coefficient for each tendon, shape [tendon_count], float."""
self.tendon_stiffness = None
"""Stiffness coefficient for each tendon, shape [tendon_count], float."""
self.tendon_rest_length = None
"""Rest length for each tendon, shape [tendon_count], float."""

self.tendon_actuator_tendon_id = None
"""Tendon index for each tendon actuator, shape [tendon_actuator_count], int."""
self.tendon_actuator_key = []
"""List of tendon actuator keys."""
self.tendon_actuator_ke = None
"""Elastic/stiffness gain for each tendon actuator, shape [tendon_actuator_count], float."""
self.tendon_actuator_kd = None
"""Damping gain for each tendon actuator, shape [tendon_actuator_count], float."""
self.tendon_actuator_force_range = None
"""Force range [min, max] for each tendon actuator, shape [tendon_actuator_count, 2], float."""

self.body_q = None
"""Rigid body poses for state initialization, shape [body_count, 7], float."""
self.body_qd = None
Expand Down Expand Up @@ -230,6 +261,8 @@ def __init__(self, device: Devicelike | None = None):
"""Generalized joint forces for state initialization, shape [joint_dof_count], float."""
self.joint_target = None
"""Generalized joint target inputs, shape [joint_dof_count], float."""
self.tendon_target = None
"""Tendon position target for control, shape [tendon_actuator_count], float."""
self.joint_type = None
"""Joint type, shape [joint_count], int."""
self.joint_parent = None
Expand Down Expand Up @@ -362,7 +395,13 @@ def __init__(self, device: Devicelike | None = None):
self.joint_dof_count = 0
"""Total number of velocity degrees of freedom of all joints. Equals the number of joint axes."""
self.joint_coord_count = 0
"""Total number of position degrees of freedom of all joints."""
"""Total number of position degrees of freedom of all joints in the system."""
self.site_count = 0
"""Total number of sites in the system."""
self.tendon_count = 0
"""Total number of tendons in the system."""
self.tendon_actuator_count = 0
"""Total number of tendon actuators in the system."""
self.equality_constraint_count = 0
"""Total number of equality constraints in the system."""

Expand Down Expand Up @@ -437,6 +476,16 @@ def __init__(self, device: Devicelike | None = None):
self.attribute_frequency["shape_scale"] = "shape"
self.attribute_frequency["shape_filter"] = "shape"

# attributes per tendon
self.attribute_frequency["tendon_damping"] = "tendon"
self.attribute_frequency["tendon_stiffness"] = "tendon"
self.attribute_frequency["tendon_rest_length"] = "tendon"
self.attribute_frequency["tendon_type"] = "tendon"
self.attribute_frequency["tendon_target"] = "tendon"
self.attribute_frequency["tendon_actuator_kp"] = "tendon"
self.attribute_frequency["tendon_actuator_kv"] = "tendon"
self.attribute_frequency["tendon_actuator_force_range"] = "tendon"

def state(self, requires_grad: bool | None = None) -> State:
"""
Create and return a new :class:`State` object for this model.
Expand Down Expand Up @@ -500,12 +549,17 @@ def control(self, requires_grad: bool | None = None, clone_variables: bool = Tru
c.tet_activations = wp.clone(self.tet_activations, requires_grad=requires_grad)
if self.muscle_count:
c.muscle_activations = wp.clone(self.muscle_activations, requires_grad=requires_grad)
if self.tendon_actuator_count:
c.tendon_target = wp.clone(self.tendon_target, requires_grad=requires_grad)

else:
c.joint_target = self.joint_target
c.joint_f = self.joint_f
c.tri_activations = self.tri_activations
c.tet_activations = self.tet_activations
c.muscle_activations = self.muscle_activations
c.tendon_target = self.tendon_target

return c

def collide(
Expand Down
Loading
Loading