Skip to content

Fix(gizmo): update to new Gizmo API#213

Merged
yuecideng merged 2 commits intomainfrom
yueci/fix-gizmo-issue
Apr 2, 2026
Merged

Fix(gizmo): update to new Gizmo API#213
yuecideng merged 2 commits intomainfrom
yueci/fix-gizmo-issue

Conversation

@yuecideng
Copy link
Copy Markdown
Contributor

Description

This PR updates the Gizmo class in embodichain/lab/sim/objects/gizmo.py to use the new Gizmo API, replacing the deprecated .node accessor pattern with direct method calls on the _gizmo object.

Key changes:

  • _gizmo.node.update_gizmo_follow(node)_gizmo.follow(node)
  • _gizmo.node.set_flush_transform_callback(cb)_gizmo.set_transform_flush_callback(cb)
  • _gizmo.node.detach_parent()_gizmo.detach_parent()
  • Updated all getters/setters (set_world_pose, get_local_pose, set_visible, etc.) to call directly on _gizmo instead of _gizmo.node
  • Updated _proxy_gizmo_callback signature to accept the new (node, local_pose, flag) format where local_pose is a 4×4 matrix, replacing the old (node, translation, rotation, flag) signature with separate translation/rotation vectors
  • Added a try/except guard when setting the camera gizmo callback for robustness

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which improves an existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (existing functionality will not work without user modification)
  • Documentation update

Screenshots

N/A

Checklist

  • I have run the black . command to format the code base.
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • Dependencies have been updated, if applicable.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings April 1, 2026 04:29
@yuecideng yuecideng added bug Something isn't working interaction The interaction simulation features labels Apr 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates Gizmo to the new Gizmo API by removing the deprecated .node accessor pattern and switching to direct method calls on the underlying _gizmo, including updating the transform flush callback pathway.

Changes:

  • Replaced several _gizmo.node.* calls with new direct _gizmo.* API methods (e.g., follow, set_transform_flush_callback, detach_parent, pose/visibility accessors).
  • Updated proxy-based gizmo callback handling to consume the new (node, local_pose, flag) signature and derive translation/rotation from a 4×4 matrix.
  • Added defensive error handling when registering the camera gizmo transform callback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +221 to +231
def _proxy_gizmo_callback(self, *args):
"""Generic callback for proxy-based gizmo.

def _proxy_gizmo_callback(self, node, translation, rotation, flag):
"""Generic callback for proxy-based gizmo: only updates proxy cube transform, defers actual updates"""
Supports both old signature: (node, translation, rotation, flag)
and new signature: (node, local_pose, flag) where local_pose is a 4x4 matrix.
Updates the proxy cube transform and sets `_pending_target_transform`.
"""
# New API callback signature: (node, local_pose, flag)
if len(args) != 3:
return
node, local_pose, flag = args
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

_proxy_gizmo_callback docstring says it supports both the old (node, translation, rotation, flag) and new (node, local_pose, flag) signatures, but the implementation returns early unless len(args) == 3. This will silently drop callbacks from the old API. Either handle the 4-argument form (and map it into a 4x4 pose) or update the docstring/usage so the behavior matches the supported API.

Copilot uses AI. Check for mistakes.
node.set_translation(translation)
elif flag == (TransformMask.TRANSFORM_LOCAL | TransformMask.TRANSFORM_R):
node.set_rotation_rpy(rotation)
# convert to numpy 4x4 matrix
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Line 239 is indented inside the prior if ...: return block, making the comment unreachable and suggesting the conversion block is mis-indented. Dedent the comment (and ensure the conversion logic is aligned correctly) to avoid confusing dead code / future indentation mistakes.

Suggested change
# convert to numpy 4x4 matrix
# convert to numpy 4x4 matrix

Copilot uses AI. Check for mistakes.
Comment on lines +360 to +365
if self._target_type == "rigidobject":
# RigidObject: direct node access through MeshObject
self._gizmo.node.update_gizmo_follow(self.target._entities[0].node)
self._gizmo.node.set_flush_transform_callback(create_gizmo_callback())
# RigidObject: direct node access through MeshObject — use follow/attach
tgt_node = self.target._entities[0].node
self._gizmo.follow(tgt_node)
# set callback (localpose-style)
self._gizmo.set_transform_flush_callback(create_gizmo_callback())
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

create_gizmo_callback() (from embodichain/lab/sim/utility/gizmo_utils.py) returns a callback with the old 4-argument signature (node, translation, rotation, flag), but here it’s registered via the new set_transform_flush_callback(...) API which (per this PR) now invokes callbacks as (node, local_pose, flag). This will raise a TypeError at runtime when manipulating rigid objects. Update create_gizmo_callback() to accept the new signature (or wrap/adapter it here).

Copilot uses AI. Check for mistakes.
Comment on lines 529 to +534
if (
hasattr(self, "_gizmo")
and self._gizmo
and hasattr(self._gizmo, "node")
):
self._gizmo.node.detach_parent()
self._gizmo.detach_parent()
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

destroy() still guards cleanup with hasattr(self._gizmo, "node"), but the new API removes/deprecates .node. This condition will likely be false, so detach_parent() won’t run and resources may not be cleaned up. Update the guard to check only _gizmo and update remaining .node.* calls in destroy() to the new direct gizmo API (e.g., clear callback via set_transform_flush_callback(None) if supported).

Copilot uses AI. Check for mistakes.
Comment on lines +383 to +384
# Detach gizmo using new API
self._gizmo.detach_parent()
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

detach() used to wrap detach_parent() in a try/except, but now calls self._gizmo.detach_parent() unguarded. If detach_parent() can raise during teardown (as implied by the previous code), this can crash callers. Consider restoring the try/except (or a capability check) for robustness consistent with the earlier behavior.

Suggested change
# Detach gizmo using new API
self._gizmo.detach_parent()
# Detach gizmo using new API, but guard against failures for robustness
try:
detach_fn = getattr(self._gizmo, "detach_parent", None)
if callable(detach_fn):
detach_fn()
except Exception as e:
logger.log_error(f"Error detaching gizmo from parent: {e}")

Copilot uses AI. Check for mistakes.
Comment on lines 386 to +389
def set_transform_callback(self, callback: Callable):
"""Set callback for gizmo transform events (translation/rotation)."""
self._callback = callback
self._gizmo.node.set_flush_transform_callback(callback)
self._gizmo.set_transform_flush_callback(callback)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

set_transform_callback’s docstring still describes a (translation, rotation) style callback, but this PR migrates to a (node, local_pose, flag) callback where local_pose is a 4x4 matrix. Update the docstring (and, if you intend to keep backward compatibility, consider adapting old-style callbacks before registering them).

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +263
def _proxy_gizmo_callback(self, *args):
"""Generic callback for proxy-based gizmo.

def _proxy_gizmo_callback(self, node, translation, rotation, flag):
"""Generic callback for proxy-based gizmo: only updates proxy cube transform, defers actual updates"""
Supports both old signature: (node, translation, rotation, flag)
and new signature: (node, local_pose, flag) where local_pose is a 4x4 matrix.
Updates the proxy cube transform and sets `_pending_target_transform`.
"""
# New API callback signature: (node, local_pose, flag)
if len(args) != 3:
return
node, local_pose, flag = args
if node is None:
return

# Check if proxy cube still exists (not destroyed)
# Check if proxy cube still exists
if not hasattr(self, "_proxy_cube") or self._proxy_cube is None:
return

# Update proxy cube transform
if flag == (TransformMask.TRANSFORM_LOCAL | TransformMask.TRANSFORM_T):
node.set_translation(translation)
elif flag == (TransformMask.TRANSFORM_LOCAL | TransformMask.TRANSFORM_R):
node.set_rotation_rpy(rotation)
# convert to numpy 4x4 matrix
if isinstance(local_pose, torch.Tensor):
lp = local_pose.cpu().numpy()
else:
lp = np.asarray(local_pose)

if lp.shape != (4, 4):
return

trans = lp[:3, 3]
rot_mat = lp[:3, :3]
euler = R.from_matrix(rot_mat).as_euler("xyz", degrees=False)

# Mark that target needs to be updated, save target transform
proxy_pos = self._proxy_cube.get_location()
proxy_rot = self._proxy_cube.get_rotation_euler()
self._proxy_cube.set_location(float(trans[0]), float(trans[1]), float(trans[2]))
self._proxy_cube.set_rotation_euler(
float(euler[0]), float(euler[1]), float(euler[2])
)

# Build pending target transform (1,4,4)
target_transform = torch.eye(4, dtype=torch.float32)
target_transform[:3, 3] = torch.tensor(
[proxy_pos[0], proxy_pos[1], proxy_pos[2]], dtype=torch.float32
)
target_transform[:3, :3] = torch.tensor(
R.from_euler("xyz", proxy_rot).as_matrix(), dtype=torch.float32
[trans[0], trans[1], trans[2]], dtype=torch.float32
)
# Ensure _pending_target_transform is (1, 4, 4)
if isinstance(target_transform, torch.Tensor) and target_transform.shape == (
4,
4,
):
target_transform = target_transform.unsqueeze(0)
self._pending_target_transform = target_transform
target_transform[:3, :3] = torch.tensor(rot_mat, dtype=torch.float32)
self._pending_target_transform = target_transform.unsqueeze(0)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The new callback parsing/building logic in _proxy_gizmo_callback is core to camera/robot gizmo control but there are no unit tests covering it (and the repo has an existing pytest suite under tests/). Consider adding a small test that feeds representative (node, local_pose, flag) inputs (torch tensor + numpy array) and asserts _pending_target_transform shape/value behavior, so future Gizmo API changes are caught quickly.

Copilot uses AI. Check for mistakes.
Co-authored-by: liwenfeng <liwenfeng@dexforce.top>
@yuecideng yuecideng merged commit 30ec8f9 into main Apr 2, 2026
5 checks passed
@yuecideng yuecideng deleted the yueci/fix-gizmo-issue branch April 2, 2026 02:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working interaction The interaction simulation features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants