From 63c93e322fe9fb555c9365e76465ada939e962e2 Mon Sep 17 00:00:00 2001 From: Arian Moeini Date: Mon, 5 Oct 2026 10:48:09 +0200 Subject: [PATCH 1/2] feat(Frame): accept an origin in world frame constructors --- AUTHORS.md | 1 + src/compas/geometry/frame.py | 33 +++++++++++++++++++++++------ tests/compas/geometry/test_frame.py | 16 ++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index b2886dbc3656..53acaeba7059 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -46,3 +46,4 @@ - Eric Gozzi <> [@ericgozzi](https://github.com/ericgozzi) - Daniel Nunes Locatelli <> [@daniel-locatelli](https://github.com/daniel-locatelli) - Mengxi He <> [@mengxihex](https://github.com/mengxihex) +- Arian Moeini [@ArianMoeini](https://github.com/ArianMoeini) diff --git a/src/compas/geometry/frame.py b/src/compas/geometry/frame.py index bfb52f4a1ef0..addce933a3a7 100644 --- a/src/compas/geometry/frame.py +++ b/src/compas/geometry/frame.py @@ -216,9 +216,14 @@ def axis_angle_vector(self): # ========================================================================== @classmethod - def worldXY(cls): # type: () -> Frame + def worldXY(cls, point=None): # type: (...) -> Frame """Construct the world XY frame. + Parameters + ---------- + point : [float, float, float] | :class:`compas.geometry.Point`, optional + The origin of the frame. Defaults to the world origin. + Returns ------- :class:`compas.geometry.Frame` @@ -233,14 +238,21 @@ def worldXY(cls): # type: () -> Frame Vector(x=1.000, y=0.000, z=0.000) >>> print(frame.yaxis) Vector(x=0.000, y=1.000, z=0.000) + >>> Frame.worldXY([1, 2, 3]).point + Point(x=1.000, y=2.000, z=3.000) """ - return cls([0, 0, 0], [1, 0, 0], [0, 1, 0]) + return cls(point if point is not None else [0, 0, 0], [1, 0, 0], [0, 1, 0]) @classmethod - def worldZX(cls): # type: () -> Frame + def worldZX(cls, point=None): # type: (...) -> Frame """Construct the world ZX frame. + Parameters + ---------- + point : [float, float, float] | :class:`compas.geometry.Point`, optional + The origin of the frame. Defaults to the world origin. + Returns ------- :class:`compas.geometry.Frame` @@ -255,14 +267,21 @@ def worldZX(cls): # type: () -> Frame Vector(x=0.000, y=0.000, z=1.000) >>> print(frame.yaxis) Vector(x=1.000, y=0.000, z=0.000) + >>> Frame.worldZX([1, 2, 3]).point + Point(x=1.000, y=2.000, z=3.000) """ - return cls([0, 0, 0], [0, 0, 1], [1, 0, 0]) + return cls(point if point is not None else [0, 0, 0], [0, 0, 1], [1, 0, 0]) @classmethod - def worldYZ(cls): # type: () -> Frame + def worldYZ(cls, point=None): # type: (...) -> Frame """Construct the world YZ frame. + Parameters + ---------- + point : [float, float, float] | :class:`compas.geometry.Point`, optional + The origin of the frame. Defaults to the world origin. + Returns ------- :class:`compas.geometry.Frame` @@ -277,9 +296,11 @@ def worldYZ(cls): # type: () -> Frame Vector(x=0.000, y=1.000, z=0.000) >>> print(frame.yaxis) Vector(x=0.000, y=0.000, z=1.000) + >>> Frame.worldYZ([1, 2, 3]).point + Point(x=1.000, y=2.000, z=3.000) """ - return cls([0, 0, 0], [0, 1, 0], [0, 0, 1]) + return cls(point if point is not None else [0, 0, 0], [0, 1, 0], [0, 0, 1]) @classmethod def from_points(cls, point, point_xaxis, point_xyplane): # type: (...) -> Frame diff --git a/tests/compas/geometry/test_frame.py b/tests/compas/geometry/test_frame.py index 8b4b6f74beae..0d78e53c7a32 100644 --- a/tests/compas/geometry/test_frame.py +++ b/tests/compas/geometry/test_frame.py @@ -72,6 +72,22 @@ def test_frame_predefined(): assert frame.yaxis == Vector(1, 0, 0) +@pytest.mark.parametrize("point", [[0.33, -1.66, 0.444], Point(0.33, -1.66, 0.444)]) +@pytest.mark.parametrize( + "constructor,xaxis,yaxis", + [ + (Frame.worldXY, Vector(1, 0, 0), Vector(0, 1, 0)), + (Frame.worldYZ, Vector(0, 1, 0), Vector(0, 0, 1)), + (Frame.worldZX, Vector(0, 0, 1), Vector(1, 0, 0)), + ], +) +def test_frame_predefined_at_point(constructor, xaxis, yaxis, point): + frame = constructor(point) + assert frame.point == Point(0.33, -1.66, 0.444) + assert frame.xaxis == xaxis + assert frame.yaxis == yaxis + + def test_interpolate_frame_start_end(): frame1 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) frame2 = Frame(Point(1, 1, 1), Vector(0, 0, 1), Vector(0, 1, 0)) From ec2d652828dafd19f5a67a500da3146feef4504b Mon Sep 17 00:00:00 2001 From: Arian Moeini Date: Mon, 5 Oct 2026 10:49:09 +0200 Subject: [PATCH 2/2] docs: note world frame origin support --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec62de4c1632..a43ee276577c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added optional origin points to `Frame.worldXY()`, `Frame.worldYZ()`, and `Frame.worldZX()`. * Added `TOL.update()` method for explicit global state modification. * Added `TOL.temporary()` context manager for scoped changes. * Added missing implementation of `Brep.to_polygons()` in `compas_rhino.geometry.RhinoBrep`.