Fix Issue #586 translate/rotate
Some checks are pending
benchmarks / benchmarks (macos-14, 3.12) (push) Waiting to run
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Waiting to run
benchmarks / benchmarks (windows-latest, 3.12) (push) Waiting to run
Upload coverage reports to Codecov / run (push) Waiting to run
pylint / lint (3.10) (push) Waiting to run
Wheel building and publishing / Build wheel on ubuntu-latest (push) Waiting to run
Wheel building and publishing / upload_pypi (push) Blocked by required conditions
tests / tests (macos-14, 3.10) (push) Waiting to run
tests / tests (macos-14, 3.14) (push) Waiting to run
tests / tests (macos-15-intel, 3.10) (push) Waiting to run
tests / tests (macos-15-intel, 3.14) (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
tests / tests (ubuntu-latest, 3.14) (push) Waiting to run
tests / tests (windows-latest, 3.10) (push) Waiting to run
tests / tests (windows-latest, 3.14) (push) Waiting to run
Run type checking / typecheck (3.10) (push) Waiting to run
Run type checking / typecheck (3.14) (push) Waiting to run

This commit is contained in:
Roger Maitland 2026-03-02 13:37:25 -05:00
parent ce0d99a4d1
commit 992de4074b
3 changed files with 61 additions and 10 deletions

View file

@ -113,15 +113,15 @@ Transformation a.k.a. Translation and Rotation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. note::
These methods don't work in the same way as the previous methods in that they don't just change
the object's internal :class:`~geometry.Location` but transform the base object itself which
is quite slow and potentially problematic.
These methods have an optional ``transform`` parameter which allows the user to transform the base
object itself which is quite slow and potentially problematic as opposed to just changing the
object's internal :class:`~geometry.Location`.
- **Translation:** Move a shape relative to its current position.
.. code-block:: build123d
relocated_shape = shape.translate(x, y, z)
relocated_shape = shape.translate((x, y, z))
- **Rotation:** Rotate a shape around a specified axis by a given angle.

View file

@ -1711,7 +1711,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
self.wrapped = tcast(TOPODS, downcast(builder.Shape()))
self.wrapped.Location(loc.wrapped)
def rotate(self, axis: Axis, angle: float) -> Self:
def rotate(self, axis: Axis, angle: float, transform: bool = False) -> Self:
"""rotate a copy
Rotates a shape around an axis.
@ -1719,14 +1719,23 @@ class Shape(NodeMixin, Generic[TOPODS]):
Args:
axis (Axis): rotation Axis
angle (float): angle to rotate, in degrees
transform (bool): regenerate the shape instead of just changing its location.
Defaults to False.
Returns:
a copy of the shape, rotated
"""
if self._wrapped is None: # For backwards compatibility
return self
transformation = gp_Trsf()
transformation.SetRotation(axis.wrapped, angle * DEG2RAD)
return self._apply_transform(transformation)
if transform:
rotated_self = self._apply_transform(transformation)
else:
rotated_self = self.moved(Location(TopLoc_Location(transformation)))
return rotated_self
def scale(self, factor: float) -> Self:
"""Scales this shape through a transformation.
@ -2239,20 +2248,28 @@ class Shape(NodeMixin, Generic[TOPODS]):
t_o.SetTranslation(Vector(offset).wrapped)
return self._apply_transform(t_o * t_rx * t_ry * t_rz)
def translate(self, vector: VectorLike) -> Self:
def translate(self, vector: VectorLike, transform: bool = False) -> Self:
"""Translates this shape through a transformation.
Args:
vector: VectorLike:
vector (VectorLike): relative movement vector
transform (bool): regenerate the shape instead of just changing its location
Defaults to False.
Returns:
object with a relative move applied
"""
if self._wrapped is None: # For backwards compatibility
return self
transformation = gp_Trsf()
transformation.SetTranslation(Vector(vector).wrapped)
return self._apply_transform(transformation)
if transform:
self_translated = self._apply_transform(transformation)
else:
self_translated = self.moved(Location(TopLoc_Location(transformation)))
return self_translated
def wire(self) -> Wire | None:
"""Return the Wire"""

View file

@ -28,6 +28,7 @@ license:
# Always equal to any other object, to test that __eq__ cooperation is working
import unittest
import math
from random import uniform
from unittest.mock import PropertyMock, patch
@ -634,6 +635,39 @@ class TestShape(unittest.TestCase):
self.assertIsNone(Vertex(1, 1, 1).solid())
self.assertIsNone(Vertex(1, 1, 1).compound())
def test_rotate(self):
line = Edge.make_line((0, 0), (1, 0))
rotated_line = line.rotate(Axis((1, 0, 0), (0, 0, 1)), 45)
root_2o2 = math.sqrt(2) / 2
self.assertAlmostEqual(rotated_line @ 0, (1 - root_2o2, -root_2o2))
self.assertAlmostEqual(rotated_line @ 1, (1, 0))
self.assertTrue(line.wrapped.IsPartner(rotated_line.wrapped))
rotated_line = line.rotate(Axis((1, 0, 0), (0, 0, 1)), 45, transform=True)
self.assertAlmostEqual(rotated_line @ 0, (1 - root_2o2, -root_2o2))
self.assertAlmostEqual(rotated_line @ 1, (1, 0))
self.assertFalse(line.wrapped.IsPartner(rotated_line.wrapped))
line._wrapped = None
rotated_line = line.rotate(Axis((1, 0, 0), (0, 0, 1)), 45)
self.assertIsNone(rotated_line._wrapped)
def test_translate(self):
line = Edge.make_line((0, 0), (1, 0))
translated_line = line.translate((0, 1, 0))
self.assertAlmostEqual(translated_line @ 0, (0, 1, 0))
self.assertAlmostEqual(translated_line @ 1, (1, 1, 0))
self.assertTrue(line.wrapped.IsPartner(translated_line.wrapped))
translated_line = line.translate((0, 1, 0), transform=True)
self.assertAlmostEqual(translated_line @ 0, (0, 1, 0))
self.assertAlmostEqual(translated_line @ 1, (1, 1, 0))
self.assertFalse(line.wrapped.IsPartner(translated_line.wrapped))
line._wrapped = None
translated_line = line.translate((0, 1, 0))
self.assertIsNone(translated_line._wrapped)
class TestGlobalLocation(unittest.TestCase):
def test_global_location_hierarchy(self):