1919from unittest .mock import patch
2020
2121import numpy as np
22+ import warp as wp
2223
2324import newton
2425import newton .examples
@@ -204,8 +205,12 @@ def parse_urdf(urdf: str, builder: newton.ModelBuilder, res_dir: dict[str, str]
204205 urdf: URDF file to parse
205206 res_dir: dict[str, str]: (filename, content): extra resources files to include in the directory"""
206207
208+ # Default to up_axis="Y" if not specified in kwargs
209+ if "up_axis" not in kwargs :
210+ kwargs ["up_axis" ] = "Y"
211+
207212 if not res_dir :
208- builder .add_urdf (urdf , up_axis = "Y" , ** kwargs )
213+ builder .add_urdf (urdf , ** kwargs )
209214 return
210215
211216 urdf_filename = "robot.urdf"
@@ -220,7 +225,7 @@ def parse_urdf(urdf: str, builder: newton.ModelBuilder, res_dir: dict[str, str]
220225
221226 # Parse the URDF file
222227 urdf_path = Path (temp_dir ) / urdf_filename
223- builder .add_urdf (str (urdf_path ), up_axis = "Y" , ** kwargs )
228+ builder .add_urdf (str (urdf_path ), ** kwargs )
224229
225230 def test_sphere_urdf (self ):
226231 # load a urdf containing a sphere with r=0.5 and pos=(1.0,2.0,3.0)
@@ -462,6 +467,95 @@ def test_joint_body_ordering_bfs(self):
462467 ]
463468 assert builder .joint_key == ["fixed_base" , "joint_2" , "joint_1" , "joint_2b" , "joint_2a" , "joint_1a" , "joint_1b" ]
464469
470+ def test_xform_with_floating_false (self ):
471+ """Test that xform parameter is respected when floating=False"""
472+
473+ # Create a simple URDF with a link (no position/orientation in URDF for root link)
474+ urdf_content = """<?xml version="1.0" encoding="utf-8"?>
475+ <robot name="test_xform">
476+ <link name="base_link">
477+ <inertial>
478+ <mass value="1.0"/>
479+ <inertia ixx="0.1" ixy="0.0" ixz="0.0" iyy="0.1" iyz="0.0" izz="0.1"/>
480+ </inertial>
481+ <visual>
482+ <geometry>
483+ <sphere radius="0.1"/>
484+ </geometry>
485+ </visual>
486+ </link>
487+ </robot>
488+ """
489+ # Create a non-identity transform to apply
490+ xform_pos = wp .vec3 (5.0 , 10.0 , 15.0 )
491+ xform_quat = wp .quat_from_axis_angle (wp .vec3 (0.0 , 0.0 , 1.0 ), wp .pi / 4.0 ) # 45 degree rotation around Z
492+ xform = wp .transform (xform_pos , xform_quat )
493+
494+ # Parse with floating=False and the xform
495+ # Use up_axis="Z" to match builder default and avoid axis transformation
496+ builder = newton .ModelBuilder ()
497+ self .parse_urdf (urdf_content , builder , floating = False , xform = xform , up_axis = "Z" )
498+ model = builder .finalize ()
499+
500+ # Verify the model has a fixed joint
501+ self .assertEqual (model .joint_count , 1 )
502+ joint_type = model .joint_type .numpy ()[0 ]
503+ self .assertEqual (joint_type , newton .JointType .FIXED )
504+
505+ # Verify the fixed joint has the correct parent_xform
506+ # In URDF, the xform is applied directly to the root body (no local transform)
507+ joint_X_p = model .joint_X_p .numpy ()[0 ]
508+
509+ # Expected transform is just xform (URDF root links don't have position/orientation)
510+ expected_xform = xform
511+
512+ # Check position
513+ np .testing .assert_allclose (
514+ joint_X_p [:3 ],
515+ [expected_xform .p [0 ], expected_xform .p [1 ], expected_xform .p [2 ]],
516+ rtol = 1e-5 ,
517+ atol = 1e-5 ,
518+ err_msg = "Fixed joint parent_xform position does not match expected xform" ,
519+ )
520+
521+ # Check quaternion (note: quaternions can be negated and still represent the same rotation)
522+ expected_quat = np .array ([expected_xform .q [0 ], expected_xform .q [1 ], expected_xform .q [2 ], expected_xform .q [3 ]])
523+ actual_quat = joint_X_p [3 :7 ]
524+
525+ # Check if quaternions match (accounting for q and -q representing the same rotation)
526+ quat_match = np .allclose (actual_quat , expected_quat , rtol = 1e-5 , atol = 1e-5 ) or np .allclose (
527+ actual_quat , - expected_quat , rtol = 1e-5 , atol = 1e-5
528+ )
529+ self .assertTrue (
530+ quat_match ,
531+ f"Fixed joint parent_xform quaternion does not match expected xform.\n "
532+ f"Expected: { expected_quat } \n Actual: { actual_quat } " ,
533+ )
534+
535+ # Verify body_q after eval_fk also matches the expected transform
536+ state = model .state ()
537+ newton .eval_fk (model , model .joint_q , model .joint_qd , state )
538+
539+ body_q = state .body_q .numpy ()[0 ]
540+ np .testing .assert_allclose (
541+ body_q [:3 ],
542+ [expected_xform .p [0 ], expected_xform .p [1 ], expected_xform .p [2 ]],
543+ rtol = 1e-5 ,
544+ atol = 1e-5 ,
545+ err_msg = "Body position after eval_fk does not match expected xform" ,
546+ )
547+
548+ # Check body quaternion
549+ body_quat = body_q [3 :7 ]
550+ quat_match = np .allclose (body_quat , expected_quat , rtol = 1e-5 , atol = 1e-5 ) or np .allclose (
551+ body_quat , - expected_quat , rtol = 1e-5 , atol = 1e-5
552+ )
553+ self .assertTrue (
554+ quat_match ,
555+ f"Body quaternion after eval_fk does not match expected xform.\n "
556+ f"Expected: { expected_quat } \n Actual: { body_quat } " ,
557+ )
558+
465559
466560if __name__ == "__main__" :
467561 unittest .main (verbosity = 2 )
0 commit comments