From eed6e20faf7814132588f50a1af88809e56eb0ff Mon Sep 17 00:00:00 2001 From: Davide Oberto Date: Tue, 23 Dec 2025 15:29:38 +0100 Subject: [PATCH 1/4] Added mirroring wrt to plane defined by three points --- bladex/blade.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/bladex/blade.py b/bladex/blade.py index 161c666..04d0c0e 100644 --- a/bladex/blade.py +++ b/bladex/blade.py @@ -463,6 +463,54 @@ def rotate(self, deg_angle=None, rad_angle=None, axis='x'): elif id == 3: self.root_face = face + def mirror(self, point1, point2, point3): + """ + 3D mirroring of the blade with respect to a plane defined by three points + + :param list point1: coordinates of point1 + :param list point2: coordinates of point2 + :param list point3: coordinates of point3 + + """ + if len(self.blade_coordinates_up) == 0: + raise ValueError('You must apply transformations before rotation.') + + from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform + from OCC.Core.gp import gp_Pnt + from OCC.Core.gp import gp_Vec + from OCC.Core.gp import gp_Dir + from OCC.Core.gp import gp_Ax2 + from OCC.Core.gp import gp_Trsf + + point1 = gp_Pnt(*point1) + point2 = gp_Pnt(*point2) + point3 = gp_Pnt(*point3) + + # Two vectors defining the directions of the plane + vector1 = gp_Vec(point1, point2) + vector2 = gp_Vec(point2, point3) + # Normal versor to the plane passing through the three points + normal = gp_Dir(vector1.Crossed(vector2)) + # Ax2 object identifying the plane + ax2 = gp_Ax2(point1, normal) + + # Mirroring wrt plane transformation + trsf = gp_Trsf() + trsf.SetMirror(ax2) + + for id, face in enumerate([self.upper_face, self.lower_face, + self.tip_face, self.root_face]): + brep_tr = BRepBuilderAPI_Transform(face, trsf, True, True) + face = brep_tr.Shape() + if id == 0: + self.upper_face = face + elif id == 1: + self.lower_face = face + elif id == 2: + self.tip_face = face + elif id == 3: + self.root_face = face + def scale(self, factor): """ Scale the blade coordinates by a specified factor. From dd3c0d762c1206db183fdab35785f324924f5c66 Mon Sep 17 00:00:00 2001 From: Davide Oberto Date: Tue, 30 Dec 2025 16:59:20 +0100 Subject: [PATCH 2/4] Mirroring now can be done both if three points are given or one point and normal to plane --- bladex/blade.py | 57 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/bladex/blade.py b/bladex/blade.py index 04d0c0e..74f52f5 100644 --- a/bladex/blade.py +++ b/bladex/blade.py @@ -463,18 +463,26 @@ def rotate(self, deg_angle=None, rad_angle=None, axis='x'): elif id == 3: self.root_face = face - def mirror(self, point1, point2, point3): + def mirror(self, *args, **kwargs): """ - 3D mirroring of the blade with respect to a plane defined by three points - - :param list point1: coordinates of point1 - :param list point2: coordinates of point2 - :param list point3: coordinates of point3 + 3D mirroring of the blade with respect to a plane. + The plane can be defined either by three points or by a point and its normal direction. + Which option to be used depends on the inputs taken by the funtion. + args: -three entries means three points + -two entries means one point and one normal vector + kwargs: -only accepted arguments: point1, point2, point3, normal """ if len(self.blade_coordinates_up) == 0: raise ValueError('You must apply transformations before rotation.') + if len(args) + len(kwargs) not in [2, 3]: + raise ValueError("Wrong number of arguments") + + admissible_kwargs = ['point1', 'point2', 'point3', 'point', 'normal'] + if any(key not in admissible_kwargs for key in kwargs.keys()): + raise ValueError('Wrong argument. Admissible arguments are only {}'.format(admissible_kwargs)) + from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform from OCC.Core.gp import gp_Pnt from OCC.Core.gp import gp_Vec @@ -482,15 +490,36 @@ def mirror(self, point1, point2, point3): from OCC.Core.gp import gp_Ax2 from OCC.Core.gp import gp_Trsf - point1 = gp_Pnt(*point1) - point2 = gp_Pnt(*point2) - point3 = gp_Pnt(*point3) + if len(args) == 3: + # Case where the three points are passed + point1 = gp_Pnt(*args[0]) + point2 = gp_Pnt(*args[1]) + point3 = gp_Pnt(*args[2]) + # Two vectors defining the directions of the plane + vector1 = gp_Vec(point1, point2) + vector2 = gp_Vec(point2, point3) + # Normal versor to the plane passing through the three points + normal = gp_Dir(vector1.Crossed(vector2)) + elif list(kwargs.keys()) == ['point1', 'point2', 'point3']: + # Case where the three points are passed + point1 = gp_Pnt(*kwargs['point1']) + point2 = gp_Pnt(*kwargs['point2']) + point3 = gp_Pnt(*kwargs['point3']) + # Two vectors defining the directions of the plane + vector1 = gp_Vec(point1, point2) + vector2 = gp_Vec(point2, point3) + # Normal versor to the plane passing through the three points + normal = gp_Dir(vector1.Crossed(vector2)) + elif len(args) == 2: + # Case where one point and the normal are passed + point1 = gp_Pnt(*args[0]) + normal = gp_Dir(*args[1]) + elif sorted(kwargs.keys()) == sorted(['point', 'normal']): + point1 = gp_Pnt(*kwargs['point']) + normal = gp_Dir(*kwargs['normal']) + else: + raise ValueError('Wrong arguments passed') - # Two vectors defining the directions of the plane - vector1 = gp_Vec(point1, point2) - vector2 = gp_Vec(point2, point3) - # Normal versor to the plane passing through the three points - normal = gp_Dir(vector1.Crossed(vector2)) # Ax2 object identifying the plane ax2 = gp_Ax2(point1, normal) From e53217325b68227eae335875324bb6aa2e17a002 Mon Sep 17 00:00:00 2001 From: Davide Oberto Date: Tue, 30 Dec 2025 17:15:38 +0100 Subject: [PATCH 3/4] Added tests for mirroring in blade --- tests/test_blade.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_blade.py b/tests/test_blade.py index c09f75c..91da27c 100644 --- a/tests/test_blade.py +++ b/tests/test_blade.py @@ -386,6 +386,18 @@ def test_rotate_rad(self): np.testing.assert_almost_equal(blade.blade_coordinates_down[1][2], rotated_coordinates) + def test_blade_mirror_exceptions(self): + blade = create_sample_blade_NACA() + blade.apply_transformations() + with self.assertRaises(ValueError): + blade.mirror(wrong_argument=[0., 0., 0.], point2=[0., 1., 0.], point3=[0., 1., 1.]) + + def test_blade_mirror_exceptions_2(self): + blade = create_sample_blade_NACA() + blade.apply_transformations() + with self.assertRaises(ValueError): + blade.mirror([0., 1., 1.]) + def test_plot_view_elev_init(self): blade = create_sample_blade_NACA() blade.apply_transformations() From c93ec1e43121cedf255b6c23e604baf963c38c01 Mon Sep 17 00:00:00 2001 From: Davide Oberto Date: Fri, 9 Jan 2026 09:25:14 +0100 Subject: [PATCH 4/4] Fixed deform implementation to run with python 3.11 --- bladex/deform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bladex/deform.py b/bladex/deform.py index 8c26d46..1fdda05 100644 --- a/bladex/deform.py +++ b/bladex/deform.py @@ -305,7 +305,7 @@ def compute_deformed_parameters(self, param, tol=1e-3): if index.shape[0] > 1: # In case more neighbors are found, then take first value only. index = index[0] - self.deformed_parameters[param][i] = self.spline[param][index, 1] + self.deformed_parameters[param][i] = self.spline[param][index, 1].item() def compute_all(self, rbf_points=1000,