Skip to content

Commit 38765c8

Browse files
authored
Added mimic support (#84)
* Added mimic support * Updated newton-usd-schemas 0.1.0rc2
1 parent 00d7fb5 commit 38765c8

File tree

6 files changed

+102
-55
lines changed

6 files changed

+102
-55
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ readme = "README.md"
88
requires-python = ">=3.10,<3.13"
99
dependencies = [
1010
"usd-exchange>=2.2.0",
11-
"newton-usd-schemas>=0.1.0rc1",
11+
"newton-usd-schemas>=0.1.0rc2",
1212
"numpy-stl>=3.2",
1313
"tinyobjloader>=2.0.0rc13",
1414
"pycollada>=0.9.2",

tests/testJoints.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,3 +563,40 @@ def test_fixed_floating_joints(self):
563563
arm_2_prim = stage.GetPrimAtPath(arm2_prim_path)
564564
self.assertTrue(arm_2_prim.IsValid())
565565
self.assertTrue(arm_2_prim.HasAPI(UsdPhysics.RigidBodyAPI))
566+
567+
def test_mimic_joints(self):
568+
input_path = "tests/data/mimic_joint.urdf"
569+
output_dir = self.tmpDir()
570+
571+
converter = urdf_usd_converter.Converter()
572+
asset_path = converter.convert(input_path, output_dir)
573+
574+
self.assertIsNotNone(asset_path)
575+
self.assertTrue(pathlib.Path(asset_path.path).exists())
576+
577+
stage: Usd.Stage = Usd.Stage.Open(asset_path.path)
578+
self.assertIsValidUsd(stage)
579+
580+
default_prim = stage.GetDefaultPrim()
581+
physics_scope_prim = stage.GetPrimAtPath(default_prim.GetPath().AppendChild("Physics"))
582+
self.assertTrue(physics_scope_prim.IsValid())
583+
584+
joint_b_prim = physics_scope_prim.GetChild("jointB")
585+
self.assertTrue(joint_b_prim.IsValid())
586+
self.assertTrue(joint_b_prim.IsA(UsdPhysics.RevoluteJoint))
587+
588+
self.assertTrue(joint_b_prim.HasAPI("NewtonMimicAPI"))
589+
590+
# 'newton:mimicEnabled', 'newton:mimicCoef0' return False because they have default values.
591+
self.assertFalse(joint_b_prim.GetAttribute("newton:mimicEnabled").HasAuthoredValue())
592+
self.assertFalse(joint_b_prim.GetAttribute("newton:mimicCoef0").HasAuthoredValue())
593+
594+
self.assertTrue(joint_b_prim.GetAttribute("newton:mimicCoef1").HasAuthoredValue())
595+
self.assertTrue(joint_b_prim.GetAttribute("newton:mimicEnabled").Get())
596+
self.assertAlmostEqual(joint_b_prim.GetAttribute("newton:mimicCoef1").Get(), 2.0)
597+
598+
self.assertTrue(joint_b_prim.HasRelationship("newton:mimicJoint"))
599+
rel = joint_b_prim.GetRelationship("newton:mimicJoint")
600+
self.assertEqual(len(rel.GetTargets()), 1)
601+
ref_joint = rel.GetTargets()[0]
602+
self.assertEqual(ref_joint, "/mimic_joint/Physics/jointA")

tests/testUndefined.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -347,42 +347,3 @@ def test_check_custom_name(self):
347347
self.assertTrue(item_prim.GetAttribute("urdf:value").IsCustom())
348348
value = item_prim.GetAttribute("urdf:value").Get()
349349
self.assertEqual(value, "foo1")
350-
351-
def test_check_mimic_joint(self):
352-
"""
353-
Test case where the joint has a mimic element.
354-
Currently unsupported, it stores custom elements and custom attributes of mimic joints.
355-
"""
356-
input_path = "tests/data/mimic_joint.urdf"
357-
output_dir = self.tmpDir()
358-
359-
converter = urdf_usd_converter.Converter()
360-
asset_path = converter.convert(input_path, output_dir)
361-
362-
self.assertIsNotNone(asset_path)
363-
self.assertTrue(pathlib.Path(asset_path.path).exists())
364-
365-
stage: Usd.Stage = Usd.Stage.Open(asset_path.path)
366-
self.assertIsValidUsd(stage)
367-
368-
default_prim = stage.GetDefaultPrim()
369-
physics_scope_prim = stage.GetPrimAtPath(default_prim.GetPath().AppendChild("Physics"))
370-
self.assertTrue(physics_scope_prim.IsValid())
371-
372-
joint_b_prim = physics_scope_prim.GetChild("jointB")
373-
self.assertTrue(joint_b_prim.IsValid())
374-
self.assertTrue(joint_b_prim.IsA(UsdPhysics.RevoluteJoint))
375-
376-
# Unsupported parameters: Custom attributes in "mimic".
377-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:joint").HasAuthoredValue())
378-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:joint").IsCustom())
379-
joint = joint_b_prim.GetAttribute("urdf:mimic:joint").Get()
380-
self.assertEqual(joint, "jointA")
381-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:multiplier").HasAuthoredValue())
382-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:multiplier").IsCustom())
383-
multiplier = joint_b_prim.GetAttribute("urdf:mimic:multiplier").Get()
384-
self.assertAlmostEqual(multiplier, 2.0)
385-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:offset").HasAuthoredValue())
386-
self.assertTrue(joint_b_prim.GetAttribute("urdf:mimic:offset").IsCustom())
387-
offset = joint_b_prim.GetAttribute("urdf:mimic:offset").Get()
388-
self.assertAlmostEqual(offset, 0.0)

urdf_usd_converter/_impl/link.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .utils import (
2323
float3_to_quatf,
2424
get_geometry_name,
25+
set_schema_attribute,
2526
set_transform,
2627
)
2728

@@ -204,6 +205,11 @@ def physics_joints(parent: Usd.Prim, link: ElementLink, data: ConversionData):
204205
joint_names = [joint.name for joint in joints] if joints else []
205206
joint_safe_names = data.name_cache.getPrimNames(parent, joint_names)
206207

208+
# Usd.Prim dict for joint name.
209+
# This is used to reference mimic joints.
210+
physics_joints_dict: dict[str, Usd.Prim] = {}
211+
mimic_joints_names: list[str] = []
212+
207213
# Create physics joints.
208214
for joint, joint_safe_name in zip(joints, joint_safe_names):
209215
body0_link_name = joint.parent.get_with_default("link")
@@ -243,12 +249,30 @@ def physics_joints(parent: Usd.Prim, link: ElementLink, data: ConversionData):
243249

244250
# Unsupported attributes and elements within the joint are stored as custom attributes of the PhysicsJoint.
245251
if physics_joint:
252+
# Stores information used by the mimic joint.
253+
physics_joints_dict[joint.name] = physics_joint.GetPrim()
254+
if joint.mimic and joint.mimic.joint:
255+
mimic_joints_names.append(joint.name)
256+
246257
convert_unsupported_attributes_and_elements(joint, physics_joint.GetPrim(), data)
247258

248259
# Store custom attributes and custom elements for the specified element.
249260
if joint.undefined_attributes or joint.undefined_elements or joint.undefined_text:
250261
convert_undefined_elements(joint, physics_joint.GetPrim(), data)
251262

263+
# Set mimic joints.
264+
for mimic_joint_name in mimic_joints_names:
265+
# USD prim associated with mimic_joint_name.
266+
physics_joint_prim = physics_joints_dict[mimic_joint_name]
267+
268+
# Search for the mimic_joint_name among the joints.
269+
joint = next((joint for joint in joints if joint.name == mimic_joint_name), None)
270+
if joint:
271+
# USD prim associated with the joint name referenced by mimic.
272+
ref_joint_prim = physics_joints_dict[joint.mimic.joint]
273+
274+
set_physics_mimic_joint(joint, physics_joint_prim, ref_joint_prim)
275+
252276

253277
def convert_unsupported_attributes_and_elements(element_joint: ElementJoint, prim: Usd.Prim, data: ConversionData):
254278
"""
@@ -300,13 +324,26 @@ def convert_unsupported_attributes_and_elements(element_joint: ElementJoint, pri
300324
if soft_upper_limit is not None:
301325
prim.CreateAttribute("urdf:safety_controller:soft_upper_limit", Sdf.ValueTypeNames.Float, custom=True).Set(soft_upper_limit)
302326

303-
if element_joint.mimic:
304-
joint = element_joint.mimic.get_with_default("joint")
305-
if joint is not None:
306-
prim.CreateAttribute("urdf:mimic:joint", Sdf.ValueTypeNames.String, custom=True).Set(joint)
307-
multiplier = element_joint.mimic.get_with_default("multiplier")
308-
if multiplier is not None:
309-
prim.CreateAttribute("urdf:mimic:multiplier", Sdf.ValueTypeNames.Float, custom=True).Set(multiplier)
310-
offset = element_joint.mimic.get_with_default("offset")
311-
if offset is not None:
312-
prim.CreateAttribute("urdf:mimic:offset", Sdf.ValueTypeNames.Float, custom=True).Set(offset)
327+
328+
def set_physics_mimic_joint(element_joint: ElementJoint, prim: Usd.Prim, ref_prim: Usd.Prim):
329+
"""
330+
Set the mimic joint.
331+
332+
Args:
333+
element_joint: ElementJoint in URDF
334+
prim: PhysicsJoint in USD
335+
ref_prim: PhysicsJoint in USD referenced by mimic
336+
"""
337+
# Both multiplier and offset have default values.
338+
multiplier = element_joint.mimic.get_with_default("multiplier")
339+
offset = element_joint.mimic.get_with_default("offset")
340+
341+
# URDF: joint0 = multiplier * joint1 + offset
342+
# Newton USD Schemas: joint0 = coef1 * joint1 + coef0
343+
prim.ApplyAPI("NewtonMimicAPI")
344+
set_schema_attribute(prim, "newton:mimicEnabled", True)
345+
set_schema_attribute(prim, "newton:mimicCoef0", offset)
346+
set_schema_attribute(prim, "newton:mimicCoef1", multiplier)
347+
348+
rel = prim.CreateRelationship("newton:mimicJoint")
349+
rel.AddTarget(ref_prim.GetPath())

urdf_usd_converter/_impl/utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
# SPDX-License-Identifier: Apache-2.0
33
import math
44
import pathlib
5+
from typing import Any
56

67
import usdex.core
7-
from pxr import Gf, UsdGeom
8+
from pxr import Gf, Tf, Usd, UsdGeom
89

910
from .._version import __version__
1011
from .urdf_parser.elements import (
@@ -18,6 +19,7 @@
1819
"float3_to_quatf",
1920
"get_authoring_metadata",
2021
"get_geometry_name",
22+
"set_schema_attribute",
2123
"set_transform",
2224
]
2325

@@ -122,3 +124,13 @@ def multiply_transforms_preserve_scale(transform1: Gf.Transform, transform2: Gf.
122124
result.SetScale(combined_scale)
123125

124126
return result
127+
128+
129+
def set_schema_attribute(prim: Usd.Prim, name: str, value: Any):
130+
attr: Usd.Attribute = prim.GetAttribute(name)
131+
if not attr.IsValid():
132+
Tf.RaiseCodingError(f'Attribute "{name}" is not valid for prim <{prim.GetPath()}> with schemas {prim.GetAppliedSchemas()}')
133+
# Only set the value if it is different from the schema default value
134+
default = attr.Get()
135+
if default is None or value != default:
136+
attr.Set(value)

uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)