Skip to content

Nested components, as-built joints, MJCF joint ranges, and more #9

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

jgillick
Copy link

@jgillick jgillick commented May 16, 2025

Thanks for this great Fusion 360 Add In. I'd like to contribute the following updates:

  • Do not disable design history - From what I can tell, this add-in doesn't change the model history, so it should be safe to keep the history.
  • Support for as-built joints (more info below)
  • Nested components

As-Built Joints

These can be useful for some robots. The biggest challenge I ran into was that rigid as-built joints do not have parent origin geometry. In these cases, using the parent origin seems sufficient.

Test model

Here's a simple model I used to test these changes: https://a360.co/4kr0hsC

@@ -134,9 +138,6 @@ def run():
constants.set_text_palette(textPalette)

try:
# Set design type into do not capture design history
design.designType = adsk.fusion.DesignTypes.DirectDesignType
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, this shouldn't be necessary. I kept the design history enabled for my model and everything was okay.

ui.messageBox("Finished exporting URDF for Gazebo.", msg_box_title)

elif simulator == "PyBullet":
elif simulator in ["Gazebo", "PyBullet", "MuJoCo"]:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the code in each condition was nearly identical, I combined them into a single block.

self.joint = joint
self.name = joint.name
self.parent = joint.occurrenceTwo # parent link of joint
self.child = joint.occurrenceOne
try:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases, like when creating a rigid joint to an origin (i.e. lock to world), fetching the occurrences throws an exception.

@@ -144,7 +163,9 @@ def get_sdf_origin(self):
# I guess using child joint origin works just because they coincide together for all the text examples

# get parent joint origin as the child joint
if self.joint.geometryOrOriginTwo == adsk.fusion.JointOrigin:
if hasattr(self.joint, 'geometry'):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary for an as-built joint

@@ -182,17 +203,24 @@ def get_joint_frame(self) -> adsk.core.Matrix3D:
translation unit: cm
"""
# get parent joint origin's coordinate w.r.t world frame
if self.joint.geometryOrOriginTwo == adsk.fusion.JointOrigin:
w_P_J = self.joint.geometryOrOriginTwo.geometry.origin.asArray()
if hasattr(self.joint, 'geometry'):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As-built joint origin geometry.

@@ -69,18 +69,14 @@ def get_parent_joint(self) -> adsk.fusion.Joint:
# TODO: in closed loop mechanism, there exits one assembly method that
# make one link have two parent joint, write a instruction or fix this bug
# text_palette = constants.get_text_palette()
joint_list: adsk.fusion.JointList = self.link.joints
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases, when there are nested components, retrieving link.joints will throw the exception: "RuntimeError: 3 : object does not belong to the occurrence's component"

The solution is to fetch all the joints in the design and perform the same logic.

@@ -161,6 +161,13 @@ def get_mjcf_joint(joint: Joint) -> Element:
joint_ele.attrib = {"name": name_att, "type": joint_type,
"axis": "{} {} {}".format(axis[0], axis[1], axis[2]),
"pos": "{} {} {}".format(pose[0], pose[1], pose[2])}


limits = joint.get_limits()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add joint limits to mjcf

all_occs = root_comp.allOccurrences

joints = [j for j in root_comp.allJoints] + [j for j in root_comp.allAsBuiltJoints]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include as-built joints in mjcf

@@ -236,6 +244,8 @@ def add_body_element(parent: adsk.fusion.Occurrence, parent_body_ele: Element) -

# traverse all the occs for body elements
for occ in all_occs:
if not occ.isLightBulbOn or not utils.component_has_bodies(occ.component):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore components that don't directly have bodies or that are not visible.

@@ -283,44 +283,6 @@ def get_joint_type(joint: Joint) -> str:
pass
return sdf_joint_type

def get_joint_pose(joint: Joint) -> list[float]:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looked to be the same code as joint.get_sdf_origin

@@ -539,7 +548,7 @@ def get_link_inertial_origin(link: Link) -> list[float]:
unit: m, radian
"""
# the link is the first link so called base-link
if link.get_parent_joint() is None:
if not joint_has_geometry(link.get_parent_joint()):
Copy link
Author

@jgillick jgillick May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

joint_has_geometry will return false if there is no parent joint, or if it's an as-built rigid joint.

Comment on lines -689 to -693
link_frame: adsk.core.Matrix3D = link.pose
from_origin, from_xAxis, from_yAxis, from_zAxis = parent_joint_frame.getAsCoordinateSystem()
to_origin, to_xAsix, to_yAxis, to_zAxis = link_frame.getAsCoordinateSystem()
if joint.has_origin():
parent_joint_frame: adsk.core.Matrix3D = joint.get_joint_frame()
transform = math_op.coordinate_transform(parent_joint_frame, link.pose)
mesh_origin = math_op.matrix3d_2_pose(transform)
else:
mesh_origin = math_op.matrix3d_2_pose(link.pose)

transform = adsk.core.Matrix3D.create()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables weren't being used

@jgillick jgillick changed the title Multiple enhancements: nested components, as-built joints, mjcf joint ranges, etc Nested components, as-built joints, MJCF joint ranges, and more May 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant