From e3fdefd4bb3ab36db2c7f8f348112c384a08d9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Fr=C3=ADvaldsk=C3=BD?= Date: Tue, 5 Nov 2024 15:15:29 +0100 Subject: [PATCH 1/4] Plane instantiation from planar Geom_BoundedSurface faces Issue #756 --- src/build123d/geometry.py | 21 ++++++++++++--------- tests/test_direct_api.py | 11 +++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/build123d/geometry.py b/src/build123d/geometry.py index d8d0360f..7bc406a9 100644 --- a/src/build123d/geometry.py +++ b/src/build123d/geometry.py @@ -60,8 +60,9 @@ from OCP.BRepGProp import BRepGProp, BRepGProp_Face # used for mass calculation from OCP.BRepMesh import BRepMesh_IncrementalMesh from OCP.BRepTools import BRepTools -from OCP.Geom import Geom_Line, Geom_Plane +from OCP.Geom import Geom_BoundedSurface, Geom_Line, Geom_Plane from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf, GeomAPI_IntCS, GeomAPI_IntSS +from OCP.GeomLib import GeomLib_IsPlanarSurface from OCP.gp import ( gp_Ax1, gp_Ax2, @@ -2092,18 +2093,20 @@ def optarg(kwargs, name, args, index, default): elif arg_face: # Determine if face is planar surface = BRep_Tool.Surface_s(arg_face.wrapped) - if not isinstance(surface, Geom_Plane): + is_surface_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE).IsPlanar() + if not is_surface_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()) - self.x_dir = ( - Vector(arg_x_dir) - if arg_x_dir - else Vector( - BRep_Tool.Surface_s(arg_face.wrapped).Position().XDirection() - ) - ) + 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) + else: + face_x_dir = surface.Position().XDirection() + 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) diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index 6702b0f9..9eca8149 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -2556,6 +2556,17 @@ def test_plane_init(self): with self.assertRaises(TypeError): Plane(Edge.make_line((0, 0), (0, 1))) + # can be instantiated from planar faces of different surface types + # this loft creates new faces of types Geom_Plane and Geom_BSplineSurface + lofted_solid = Solid.make_loft( + [ + Rectangle(3, 1).wire(), + Pos(0, 0, 1) * Rectangle(1, 1).wire(), + ] + ) + for f in lofted_solid.faces(): + Plane(f) + def test_plane_neg(self): p = Plane( origin=(1, 2, 3), From e588107bcb0889588f50d21222635b37fbf3ec92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Fr=C3=ADvaldsk=C3=BD?= Date: Thu, 7 Nov 2024 14:37:47 +0100 Subject: [PATCH 2/4] Extended test coverage for Issue #756 --- tests/test_direct_api.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index 9eca8149..21dbb473 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -14,6 +14,7 @@ from IPython.lib import pretty from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge +from OCP.BRepGProp import BRepGProp from OCP.gp import ( gp, gp_Ax1, @@ -28,6 +29,7 @@ gp_Vec, gp_XYZ, ) +from OCP.GProp import GProp_GProps from build123d.build_common import GridLocations, Locations, PolarLocations from build123d.build_enums import ( @@ -2556,16 +2558,44 @@ def test_plane_init(self): with self.assertRaises(TypeError): Plane(Edge.make_line((0, 0), (0, 1))) - # can be instantiated from planar faces of different surface types - # this loft creates new faces of types Geom_Plane and Geom_BSplineSurface + # can be instantiated from planar faces of surface types other than Geom_Plane + # this loft creates the trapezoid faces of type Geom_BSplineSurface lofted_solid = Solid.make_loft( [ Rectangle(3, 1).wire(), Pos(0, 0, 1) * Rectangle(1, 1).wire(), ] ) - for f in lofted_solid.faces(): - Plane(f) + + def face_props(f: Face) -> GProp_GProps: + assert f.wrapped is not None + f_props = GProp_GProps() + BRepGProp.SurfaceProperties_s(f.wrapped, f_props) + return f_props + + expected = [ + # Trapezoid face, negative y coordinate + ( + lambda f: face_props(f).CentreOfMass(), # plane origin + lambda f: Axis.X.direction, # plane x_dir + lambda f: Axis.Z.direction, # plane y_dir + lambda f: -Axis.Y.direction, # plane z_dir + ), + # Trapezoid face, positive y coordinate + ( + lambda f: face_props(f).CentreOfMass(), + lambda f: -Axis.X.direction, + lambda f: Axis.Z.direction, + lambda f: Axis.Y.direction, + ), + ] + # assert properties of the trapezoid faces + for i, f in enumerate(lofted_solid.faces() | Plane.XZ > Axis.Y): + p = Plane(f) + self.assertVectorAlmostEquals(p.origin, expected[i][0](f), 6) + self.assertVectorAlmostEquals(p.x_dir, expected[i][1](f), 6) + self.assertVectorAlmostEquals(p.y_dir, expected[i][2](f), 6) + self.assertVectorAlmostEquals(p.z_dir, expected[i][3](f), 6) def test_plane_neg(self): p = Plane( From 89c29abd079faf6bf94514b8d68673bcd55d9d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Fr=C3=ADvaldsk=C3=BD?= Date: Thu, 7 Nov 2024 17:40:51 +0100 Subject: [PATCH 3/4] Test code simplificatoin for Issue #756 --- tests/test_direct_api.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index 21dbb473..0c36a475 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -2567,35 +2567,29 @@ def test_plane_init(self): ] ) - def face_props(f: Face) -> GProp_GProps: - assert f.wrapped is not None - f_props = GProp_GProps() - BRepGProp.SurfaceProperties_s(f.wrapped, f_props) - return f_props - expected = [ # Trapezoid face, negative y coordinate ( - lambda f: face_props(f).CentreOfMass(), # plane origin - lambda f: Axis.X.direction, # plane x_dir - lambda f: Axis.Z.direction, # plane y_dir - lambda f: -Axis.Y.direction, # plane z_dir + Axis.X.direction, # plane x_dir + Axis.Z.direction, # plane y_dir + -Axis.Y.direction, # plane z_dir ), # Trapezoid face, positive y coordinate ( - lambda f: face_props(f).CentreOfMass(), - lambda f: -Axis.X.direction, - lambda f: Axis.Z.direction, - lambda f: Axis.Y.direction, + -Axis.X.direction, + Axis.Z.direction, + Axis.Y.direction, ), ] # assert properties of the trapezoid faces for i, f in enumerate(lofted_solid.faces() | Plane.XZ > Axis.Y): p = Plane(f) - self.assertVectorAlmostEquals(p.origin, expected[i][0](f), 6) - self.assertVectorAlmostEquals(p.x_dir, expected[i][1](f), 6) - self.assertVectorAlmostEquals(p.y_dir, expected[i][2](f), 6) - self.assertVectorAlmostEquals(p.z_dir, expected[i][3](f), 6) + f_props = GProp_GProps() + BRepGProp.SurfaceProperties_s(f.wrapped, f_props) + self.assertVectorAlmostEquals(p.origin, f_props.CentreOfMass(), 6) + self.assertVectorAlmostEquals(p.x_dir, expected[i][0], 6) + self.assertVectorAlmostEquals(p.y_dir, expected[i][1], 6) + self.assertVectorAlmostEquals(p.z_dir, expected[i][2], 6) def test_plane_neg(self): p = Plane( From a797e59717c7fa3f6a86ac0a8c6ded929854bead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Fr=C3=ADvaldsk=C3=BD?= Date: Fri, 8 Nov 2024 14:41:45 +0100 Subject: [PATCH 4/4] Plane instantiation from Face uses the new is_planar property Issue #756 --- src/build123d/geometry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/build123d/geometry.py b/src/build123d/geometry.py index 7bc406a9..1f46c7f9 100644 --- a/src/build123d/geometry.py +++ b/src/build123d/geometry.py @@ -62,7 +62,6 @@ from OCP.BRepTools import BRepTools from OCP.Geom import Geom_BoundedSurface, Geom_Line, Geom_Plane from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf, GeomAPI_IntCS, GeomAPI_IntSS -from OCP.GeomLib import GeomLib_IsPlanarSurface from OCP.gp import ( gp_Ax1, gp_Ax2, @@ -2093,8 +2092,7 @@ def optarg(kwargs, name, args, index, default): elif arg_face: # Determine if face is planar surface = BRep_Tool.Surface_s(arg_face.wrapped) - is_surface_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE).IsPlanar() - if not is_surface_planar: + 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)