From 8227bad73177786cce856f995b3bb8ae4fb11234 Mon Sep 17 00:00:00 2001 From: javimixet Date: Sun, 9 Nov 2025 18:39:54 +0100 Subject: [PATCH 1/6] Plane(face) x_dir handling Change to use `normal.cross(Vector(0,0,-1))` instead of the u derivative of the surface for x_dir when x_dir is not given. --- src/build123d/geometry.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/build123d/geometry.py b/src/build123d/geometry.py index e4c0eeb..8efefcd 100644 --- a/src/build123d/geometry.py +++ b/src/build123d/geometry.py @@ -2719,25 +2719,21 @@ class Plane(metaclass=PlaneMeta): if arg_plane: self.wrapped = arg_plane elif arg_face: - surface = BRep_Tool.Surface_s(arg_face.wrapped) if not arg_face.is_planar: raise ValueError("Planes can only be created from planar faces") - properties = GProp_GProps() - BRepGProp.SurfaceProperties_s(arg_face.wrapped, properties) - self._origin = Vector(properties.CentreOfMass()) + face_normal = Plane.get_topods_face_normal(arg_face.wrapped) + face_z_dir = Vector(face_normal).normalized() - if isinstance(surface, Geom_BoundedSurface): - point = gp_Pnt() - face_x_dir = gp_Vec() - tangent_v = gp_Vec() - surface.D1(0.5, 0.5, point, face_x_dir, tangent_v) + z_threshold = 1 - TOLERANCE + if abs(face_z_dir.Z) > z_threshold: + face_x_dir = Vector(1, 0, 0) else: - face_x_dir = surface.Position().XDirection() + face_x_dir = face_z_dir.cross(Vector(0, 0, -1)).normalized() + self._origin = arg_face.center() self.x_dir = Vector(arg_x_dir) if arg_x_dir else Vector(face_x_dir) self.x_dir = Vector(round(i, 14) for i in self.x_dir) - self.z_dir = Plane.get_topods_face_normal(arg_face.wrapped) - self.z_dir = Vector(round(i, 14) for i in self.z_dir) + self.z_dir = Vector(round(i, 14) for i in face_z_dir) elif arg_location: topo_face = BRepBuilderAPI_MakeFace( Plane.XY.wrapped, -1.0, 1.0, -1.0, 1.0 From 5ae07844d4751b1b97fdb082a30ec2ab3954fe5d Mon Sep 17 00:00:00 2001 From: javimixet Date: Mon, 10 Nov 2025 22:40:43 +0100 Subject: [PATCH 2/6] Update direction assertions in test_plane.py Update tests for new Plane(face) x_dir normal.cross(-Z) behavior. --- tests/test_direct_api/test_plane.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_direct_api/test_plane.py b/tests/test_direct_api/test_plane.py index e9e7faa..49d4b7c 100644 --- a/tests/test_direct_api/test_plane.py +++ b/tests/test_direct_api/test_plane.py @@ -156,9 +156,9 @@ class TestPlane(unittest.TestCase): p_deep_copy = copy.deepcopy(p_from_face) for p in [p_from_face, p_from_named_face, plane_from_gp_pln, p_deep_copy]: self.assertAlmostEqual(p.origin, (1, 2, 3), 6) - self.assertAlmostEqual(p.x_dir, (math.sqrt(2) / 2, 0.5, 0.5), 6) - self.assertAlmostEqual(p.y_dir, (-math.sqrt(2) / 2, 0.5, 0.5), 6) - self.assertAlmostEqual(p.z_dir, (0, -math.sqrt(2) / 2, math.sqrt(2) / 2), 6) + self.assertAlmostEqual(p.x_dir, (1, 0, 0), 6) + self.assertAlmostEqual(p.y_dir, (0, 0.7071067811865, 0.7071067811865), 6) + self.assertAlmostEqual(p.z_dir, (0, -0.7071067811865, 0.7071067811865), 6) self.assertAlmostEqual(f.location.position, p.location.position, 6) self.assertAlmostEqual(f.location.orientation, p.location.orientation, 6) @@ -359,7 +359,7 @@ class TestPlane(unittest.TestCase): with BuildPart() as p: add(box) with BuildSketch(pln): - with Locations((-0.5, 0.5)): + with Locations((-0.5, -0.5)): Circle(0.5) extrude(amount=-1, mode=Mode.SUBTRACT) self.assertAlmostEqual(p.part.volume, 1**3 - math.pi * (0.5**2) * 1, 5) From d4cc4a448932a0ec3aa288cc1b9a747aaec3184a Mon Sep 17 00:00:00 2001 From: javimixet Date: Mon, 10 Nov 2025 23:13:08 +0100 Subject: [PATCH 3/6] Change orientation assertion in test_plane.py Updated orientation assertion to check against fixed values. --- tests/test_direct_api/test_plane.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_direct_api/test_plane.py b/tests/test_direct_api/test_plane.py index 49d4b7c..56c57e6 100644 --- a/tests/test_direct_api/test_plane.py +++ b/tests/test_direct_api/test_plane.py @@ -160,7 +160,7 @@ class TestPlane(unittest.TestCase): self.assertAlmostEqual(p.y_dir, (0, 0.7071067811865, 0.7071067811865), 6) self.assertAlmostEqual(p.z_dir, (0, -0.7071067811865, 0.7071067811865), 6) self.assertAlmostEqual(f.location.position, p.location.position, 6) - self.assertAlmostEqual(f.location.orientation, p.location.orientation, 6) + self.assertAlmostEqual((45, 0, 0), p.location.orientation, 6) # from a face with x_dir f = Face.make_rect(1, 2) From 6bb5cb47f6370777775ba0b6f8d16ced3f15129c Mon Sep 17 00:00:00 2001 From: javimixet Date: Mon, 10 Nov 2025 23:29:14 +0100 Subject: [PATCH 4/6] Update orientation assertion to use Vector --- tests/test_direct_api/test_plane.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_direct_api/test_plane.py b/tests/test_direct_api/test_plane.py index 56c57e6..9bdbb20 100644 --- a/tests/test_direct_api/test_plane.py +++ b/tests/test_direct_api/test_plane.py @@ -160,7 +160,7 @@ class TestPlane(unittest.TestCase): self.assertAlmostEqual(p.y_dir, (0, 0.7071067811865, 0.7071067811865), 6) self.assertAlmostEqual(p.z_dir, (0, -0.7071067811865, 0.7071067811865), 6) self.assertAlmostEqual(f.location.position, p.location.position, 6) - self.assertAlmostEqual((45, 0, 0), p.location.orientation, 6) + self.assertAlmostEqual(Vector(45, 0, 0), p.location.orientation, 6) # from a face with x_dir f = Face.make_rect(1, 2) From 86a69a4d04210ce6f2a8600138cf25d9063ff120 Mon Sep 17 00:00:00 2001 From: javimixet Date: Mon, 10 Nov 2025 23:30:13 +0100 Subject: [PATCH 5/6] Fix coordinates in Locations context --- docs/assets/ttt/ttt-ppp0101.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assets/ttt/ttt-ppp0101.py b/docs/assets/ttt/ttt-ppp0101.py index efd0a27..84d9a51 100644 --- a/docs/assets/ttt/ttt-ppp0101.py +++ b/docs/assets/ttt/ttt-ppp0101.py @@ -25,7 +25,7 @@ with BuildPart() as p: fillet(edgs, 9) with Locations(zz.faces().sort_by(Axis.Y)[0]): - with Locations((42 / 2 + 6, 0)): + with Locations((0, 42 / 2 + 6)): CounterBoreHole(24 / 2, 34 / 2, 4) mirror(about=Plane.XZ) From f57d02b10d0d14e4927d10a9f6f26649d007057e Mon Sep 17 00:00:00 2001 From: javimixet Date: Wed, 17 Dec 2025 00:23:12 +0100 Subject: [PATCH 6/6] Refactor face direction calculation in geometry.py Switched to checking the cross-product magnitude directly instead of relying on a Z-component threshold, so we avoid normalizing near-zero vectors and reduce tolerance-sensitive behavior. --- src/build123d/geometry.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/build123d/geometry.py b/src/build123d/geometry.py index cd496b4..246d7a7 100644 --- a/src/build123d/geometry.py +++ b/src/build123d/geometry.py @@ -2814,13 +2814,11 @@ class Plane(metaclass=PlaneMeta): raise ValueError("Planes can only be created from planar faces") face_normal = Plane.get_topods_face_normal(arg_face.wrapped) face_z_dir = Vector(face_normal).normalized() - - z_threshold = 1 - TOLERANCE - if abs(face_z_dir.Z) > z_threshold: + candidate = face_z_dir.cross(Vector(0, 0, -1)) + if candidate.length < TOLERANCE: face_x_dir = Vector(1, 0, 0) else: - face_x_dir = face_z_dir.cross(Vector(0, 0, -1)).normalized() - + face_x_dir = candidate.normalized() self._origin = arg_face.center() self.x_dir = Vector(arg_x_dir) if arg_x_dir else Vector(face_x_dir) self.x_dir = Vector(round(i, 14) for i in self.x_dir)