Skip to content

fix: correct scale-mode object_transform to keep the gizmo pivot fixed - #59

Open
ericgozzi wants to merge 3 commits into
fix/websocket-max-sizefrom
fix/object-transform-scale
Open

ericgozzi wants to merge 3 commits into
fix/websocket-max-sizefrom
fix/object-transform-scale

Conversation

@ericgozzi

@ericgozzi ericgozzi commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Shape.transform() (the base for Box/Sphere/...) applies only to the shape's frame and ignores scale. A scale-mode TransformControls drag therefore lost the resize's effect on frame.point, and the object's pivot could end up a full grid unit away from where the gizmo showed it.
  • Inbox._apply_transform_with_scale now decomposes the delta. For a Shape it applies the full delta (translation + scale) to frame.point and only the rotation to the frame axes, then resizes via Shape.scale(). Non-Shape geometry (Point/Mesh/Polyline) keeps getting the full delta through its own .transform().

Stacked on #58 (WebSocket max frame size); merge that first. Frontend counterpart: compas-dev/compas_threejs_ts#33.

Test plan

  • pytest (new tests/test_object_transform_scale.py)

🤖 Generated with Claude Code

@gonzalocasas gonzalocasas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This one is a bit tricky, I would say that the real problem is in compas itself, and it should actually be handled there. Which makes this a potentially problematic workaround, because if compas core ever fixes this, this might misbehave (or in the best case, just do extra work).

Since there's probably not an easy and quick way to change this in compas (note that there are some draft PRs open to tackle this), I guess it's fine to keep the workaround here, but it should be rather strictly marked as such

if not has_scale:
# No resize at all - Shape.transform (== frame.transform) already does
# exactly the right thing here: moves the point, rotates the axes.
geometry.transform(translation * rotation)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why recompose the matrix, which incurs into additional computing, instead of passing the original transformation if scale will be ignored anyway?

Comment on lines +41 to +47
if not hasattr(geometry, "frame"):
# Not a Shape (e.g. a Point/Mesh/Polyline, transformed via its own raw
# coordinates) - unlike Shape, these support a general affine `.transform()`
# including scale directly (Shape is the one deliberate exception - see its
# own docstring above), so the full delta can just be applied as-is.
geometry.transform(transformation)
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is a very weird way to check if this is a shape or not, why not an isinstance of the correct super-class of Shape?

@ericgozzi
ericgozzi force-pushed the fix/object-transform-scale branch from 642ebe6 to e320641 Compare October 13, 2026 09:23
ericgozzi and others added 3 commits October 15, 2026 13:52
Shape.transform() (the base for Box/Sphere/...) only ever applies to the
shape's frame and explicitly does not support scale, so applying a
TransformControls scale-mode gizmo drag straight to geometry.transform()
silently dropped the resize's effect on frame.point, and applying only the
rigid (translation * rotation) part of the delta - as the old code path
effectively did - could land the object's pivot a full grid unit away from
where the gizmo showed it for a real-world resize amount.

_apply_transform_with_scale now decomposes the incoming delta and, for a
Shape being resized, applies the FULL delta (translation and scale
together) to frame.point while only the rotation part goes to the frame's
axes, then resizes via Shape.scale(). Non-Shape geometry (Point/Mesh/
Polyline) isn't scale-blind the way Shape is, so it keeps getting the full
delta applied directly via its own .transform().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericgozzi
ericgozzi force-pushed the fix/object-transform-scale branch from e320641 to 6aae427 Compare October 15, 2026 11:52

This branch has not been deployed

No deployments
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.

2 participants