Skip to content

Commit f325672

Browse files
committed
Add deformations for cad files
1 parent 5ff4542 commit f325672

File tree

8 files changed

+683
-388
lines changed

8 files changed

+683
-388
lines changed

pygem/cad/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@
1616
from .stephandler import StepHandler
1717
from .igeshandler import IgesHandler
1818
from .ffd import FFD
19+
from .rbf import RBF
20+
from .idw import IDW
21+
from .cad_deformation import CADDeformation

pygem/cad/cad_deformation.py

Lines changed: 410 additions & 0 deletions
Large diffs are not rendered by default.

pygem/cad/ffd.py

Lines changed: 10 additions & 348 deletions
Large diffs are not rendered by default.

pygem/cad/idw.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
Module focused on the Inverse Distance Weighting interpolation technique.
3+
The IDW algorithm is an average moving interpolation that is usually applied to
4+
highly variable data. The main idea of this interpolation strategy lies in
5+
fact that it is not desirable to honour local high/low values but rather to look
6+
at a moving average of nearby data points and estimate the local trends.
7+
The node value is calculated by averaging the weighted sum of all the points.
8+
Data points that lie progressively farther from the node inuence much less the
9+
computed value than those lying closer to the node.
10+
11+
:Theoretical Insight:
12+
13+
This implementation is based on the simplest form of inverse distance
14+
weighting interpolation, proposed by D. Shepard, A two-dimensional
15+
interpolation function for irregularly-spaced data, Proceedings of the 23 rd
16+
ACM National Conference.
17+
18+
The interpolation value :math:`u` of a given point :math:`\\mathrm{x}`
19+
from a set of samples :math:`u_k = u(\\mathrm{x}_k)`, with
20+
:math:`k = 1,2,\\dotsc,\\mathcal{N}`, is given by:
21+
22+
.. math::
23+
u(\\mathrm{x}) = \\displaystyle\\sum_{k=1}^\\mathcal{N}
24+
\\frac{w(\\mathrm{x},\\mathrm{x}_k)}
25+
{\\displaystyle\\sum_{j=1}^\\mathcal{N} w(\\mathrm{x},\\mathrm{x}_j)}
26+
u_k
27+
28+
where, in general, :math:`w(\\mathrm{x}, \\mathrm{x}_i)` represents the
29+
weighting function:
30+
31+
.. math::
32+
w(\\mathrm{x}, \\mathrm{x}_i) = \\| \\mathrm{x} - \\mathrm{x}_i \\|^{-p}
33+
34+
being :math:`\\| \\mathrm{x} - \\mathrm{x}_i \\|^{-p} \\ge 0` is the
35+
Euclidean distance between :math:`\\mathrm{x}` and data point
36+
:math:`\\mathrm{x}_i` and :math:`p` is a power parameter, typically equal to
37+
2.
38+
"""
39+
40+
import numpy as np
41+
from pygem import IDW as OriginalIDW
42+
from .cad_deformation import CADDeformation
43+
44+
class IDW(CADDeformation, OriginalIDW):
45+
"""
46+
Class that handles the Free Form Deformation on the mesh points.
47+
48+
:param FFDParameters ffd_parameters: parameters of the Free Form
49+
Deformation.
50+
:param numpy.ndarray original_mesh_points: coordinates of the original
51+
points of the mesh.
52+
53+
:param list n_control_points: number of control points in the x, y, and z
54+
direction. If not provided it is set to [2, 2, 2].
55+
56+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
57+
x, y and z direction (local coordinate system).
58+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
59+
the FFD bounding box.
60+
:cvar numpy.ndarray rot_angle: rotation angle around x, y and z axis of the
61+
FFD bounding box.
62+
:cvar numpy.ndarray n_control_points: the number of control points in the
63+
x, y, and z direction.
64+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
65+
x, normalized with the box length x.
66+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
67+
y, normalized with the box length y.
68+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
69+
z, normalized with the box length z.
70+
71+
:Example:
72+
73+
>>> from pygem.cad import IDW
74+
>>> idw = IDW()
75+
>>> idw.read_parameters(
76+
>>> 'tests/test_datasets/parameters_test_idw_iges.prm')
77+
>>> input_cad_file_name = "input.iges"
78+
>>> modified_cad_file_name = "output.iges"
79+
>>> idw(input_cad_file_name, modified_cad_file_name)
80+
"""
81+
def __init__(self,
82+
original_control_points=None,
83+
deformed_control_points=None,
84+
power=2,
85+
u_knots_to_add=30,
86+
v_knots_to_add=30,
87+
t_knots_to_add=30,
88+
tolerance=1e-4):
89+
OriginalIDW.__init__(self,
90+
original_control_points=original_control_points,
91+
deformed_control_points=deformed_control_points,
92+
power=power)
93+
CADDeformation.__init__(self,
94+
u_knots_to_add=u_knots_to_add,
95+
v_knots_to_add=v_knots_to_add,
96+
t_knots_to_add=t_knots_to_add,
97+
tolerance=tolerance)

pygem/cad/rbf.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
Module focused on the implementation of the Radial Basis Functions interpolation
3+
technique. This technique is still based on the use of a set of parameters, the
4+
so-called control points, as for FFD, but RBF is interpolatory. Another
5+
important key point of RBF strategy relies in the way we can locate the control
6+
points: in fact, instead of FFD where control points need to be placed inside a
7+
regular lattice, with RBF we hano no more limitations. So we have the
8+
possibility to perform localized control points refiniments.
9+
The module is analogous to the freeform one.
10+
11+
:Theoretical Insight:
12+
13+
As reference please consult M.D. Buhmann, Radial Basis Functions, volume 12
14+
of Cambridge monographs on applied and computational mathematics. Cambridge
15+
University Press, UK, 2003. This implementation follows D. Forti and G.
16+
Rozza, Efficient geometrical parametrization techniques of interfaces for
17+
reduced order modelling: application to fluid-structure interaction coupling
18+
problems, International Journal of Computational Fluid Dynamics.
19+
20+
RBF shape parametrization technique is based on the definition of a map,
21+
:math:`\\mathcal{M}(\\boldsymbol{x}) : \\mathbb{R}^n \\rightarrow
22+
\\mathbb{R}^n`, that allows the possibility of transferring data across
23+
non-matching grids and facing the dynamic mesh handling. The map introduced
24+
is defines as follows
25+
26+
.. math::
27+
\\mathcal{M}(\\boldsymbol{x}) = p(\\boldsymbol{x}) +
28+
\\sum_{i=1}^{\\mathcal{N}_C} \\gamma_i
29+
\\varphi(\\| \\boldsymbol{x} - \\boldsymbol{x_{C_i}} \\|)
30+
31+
where :math:`p(\\boldsymbol{x})` is a low_degree polynomial term,
32+
:math:`\\gamma_i` is the weight, corresponding to the a-priori selected
33+
:math:`\\mathcal{N}_C` control points, associated to the :math:`i`-th basis
34+
function, and :math:`\\varphi(\\| \\boldsymbol{x} - \\boldsymbol{x_{C_i}}
35+
\\|)` a radial function based on the Euclidean distance between the control
36+
points position :math:`\\boldsymbol{x_{C_i}}` and :math:`\\boldsymbol{x}`.
37+
A radial basis function, generally, is a real-valued function whose value
38+
depends only on the distance from the origin, so that
39+
:math:`\\varphi(\\boldsymbol{x}) = \\tilde{\\varphi}(\\| \\boldsymbol{x}
40+
\\|)`.
41+
42+
The matrix version of the formula above is:
43+
44+
.. math::
45+
\\mathcal{M}(\\boldsymbol{x}) = \\boldsymbol{c} +
46+
\\boldsymbol{Q}\\boldsymbol{x} +
47+
\\boldsymbol{W^T}\\boldsymbol{d}(\\boldsymbol{x})
48+
49+
The idea is that after the computation of the weights and the polynomial
50+
terms from the coordinates of the control points before and after the
51+
deformation, we can deform all the points of the mesh accordingly. Among
52+
the most common used radial basis functions for modelling 2D and 3D shapes,
53+
we consider Gaussian splines, Multi-quadratic biharmonic splines, Inverted
54+
multi-quadratic biharmonic splines, Thin-plate splines, Beckert and
55+
Wendland :math:`C^2` basis and Polyharmonic splines all defined and
56+
implemented below.
57+
"""
58+
59+
import numpy as np
60+
from pygem import RBF as OriginalRBF
61+
from .cad_deformation import CADDeformation
62+
63+
class RBF(CADDeformation, OriginalRBF):
64+
"""
65+
Class that handles the Free Form Deformation on the mesh points.
66+
67+
:param FFDParameters ffd_parameters: parameters of the Free Form
68+
Deformation.
69+
:param numpy.ndarray original_mesh_points: coordinates of the original
70+
points of the mesh.
71+
72+
:param list n_control_points: number of control points in the x, y, and z
73+
direction. If not provided it is set to [2, 2, 2].
74+
75+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
76+
x, y and z direction (local coordinate system).
77+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
78+
the FFD bounding box.
79+
:cvar numpy.ndarray rot_angle: rotation angle around x, y and z axis of the
80+
FFD bounding box.
81+
:cvar numpy.ndarray n_control_points: the number of control points in the
82+
x, y, and z direction.
83+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
84+
x, normalized with the box length x.
85+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
86+
y, normalized with the box length y.
87+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
88+
z, normalized with the box length z.
89+
90+
:Example:
91+
92+
>>> from pygem.cad import RBF
93+
>>> rbf = RBF()
94+
>>> rbf.read_parameters(
95+
>>> 'tests/test_datasets/parameters_test_ffd_iges.prm')
96+
>>> input_cad_file_name = "input.iges"
97+
>>> modified_cad_file_name = "output.iges"
98+
>>> rbf(input_cad_file_name, modified_cad_file_name)
99+
"""
100+
def __init__(self,
101+
original_control_points=None,
102+
deformed_control_points=None,
103+
func='gaussian_spline',
104+
radius=0.5,
105+
extra_parameter=None,
106+
u_knots_to_add=30,
107+
v_knots_to_add=30,
108+
t_knots_to_add=30,
109+
tolerance=1e-4):
110+
OriginalRBF.__init__(self,
111+
original_control_points=original_control_points,
112+
deformed_control_points=deformed_control_points,
113+
func=func,
114+
radius=radius,
115+
extra_parameter=extra_parameter)
116+
CADDeformation.__init__(self,
117+
u_knots_to_add=u_knots_to_add,
118+
v_knots_to_add=v_knots_to_add,
119+
t_knots_to_add=t_knots_to_add,
120+
tolerance=tolerance)

test.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121

2222

2323
test_cad = [
24-
'tests/test_igeshandler.py',
25-
'tests/test_nurbshandler.py',
26-
'tests/test_stephandler.py',
24+
'tests/test_ffdcad.py',
2725
]
2826

2927
default_argv = ['--tests'] + test_defaults

tests/test_ffd.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -382,40 +382,3 @@ def test_ffd_sphere_mod(self):
382382
'tests/test_datasets/meshpoints_sphere_mod.npy')
383383
mesh_points_test = ffd(mesh_points)
384384
np.testing.assert_array_almost_equal(mesh_points_test, mesh_points_ref)
385-
386-
def test_ffd_iges_pipe_mod_through_files(self):
387-
from pygem.cad import FFD
388-
ffd = FFD()
389-
ffd.read_parameters(
390-
filename='tests/test_datasets/parameters_test_ffd_iges.prm')
391-
ffd('tests/test_datasets/test_pipe.iges', 'test_pipe_result.iges')
392-
with open('test_pipe_result.iges', "r") as created, \
393-
open('tests/test_datasets/test_pipe_out_true.iges', "r") as reference:
394-
ref = reference.readlines()[5:]
395-
cre = created.readlines()[5:]
396-
self.assertEqual(len(ref),len(cre))
397-
for i in range(len(cre)):
398-
self.assertMultiLineEqual(ref[i], cre[i])
399-
self.addCleanup(os.remove, 'test_pipe_result.iges')
400-
401-
def test_ffd_iges_pipe_mod_through_topods_shape(self):
402-
from pygem.cad.igeshandler import IgesHandler
403-
from pygem.cad import FFD
404-
from OCC.Core.TopoDS import TopoDS_Shape
405-
iges_handler = IgesHandler()
406-
orig_shape = iges_handler.load_shape_from_file(
407-
filename='tests/test_datasets/test_pipe_hollow.iges')
408-
ffd = FFD()
409-
ffd.read_parameters(
410-
filename='tests/test_datasets/parameters_test_ffd_iges.prm')
411-
mod_shape = ffd(orig_shape)
412-
iges_handler.write_shape_to_file(mod_shape, 'test_pipe_hollow_result.iges')
413-
with open('test_pipe_hollow_result.iges', "r") as created, \
414-
open('tests/test_datasets/test_pipe_hollow_out_true.iges', "r") as reference:
415-
ref = reference.readlines()[5:]
416-
cre = created.readlines()[5:]
417-
self.assertEqual(len(ref),len(cre))
418-
for i in range(len(cre)):
419-
self.assertMultiLineEqual(ref[i], cre[i])
420-
self.addCleanup(os.remove, 'test_pipe_hollow_result.iges')
421-

tests/test_ffdcad.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import filecmp
2+
import os
3+
from unittest import TestCase
4+
5+
import numpy as np
6+
from OCC.Core.TopoDS import TopoDS_Shape
7+
8+
from pygem.cad import FFD
9+
from pygem.cad import CADDeformation
10+
11+
class TestFFDCAD(TestCase):
12+
13+
def test_ffd_iges_pipe_mod_through_files(self):
14+
ffd = FFD()
15+
ffd.read_parameters(
16+
filename='tests/test_datasets/parameters_test_ffd_iges.prm')
17+
ffd('tests/test_datasets/test_pipe.iges', 'test_pipe_result.iges')
18+
with open('test_pipe_result.iges', "r") as created, \
19+
open('tests/test_datasets/test_pipe_out_true.iges', "r") as reference:
20+
ref = reference.readlines()[5:]
21+
cre = created.readlines()[5:]
22+
self.assertEqual(len(ref),len(cre))
23+
for i in range(len(cre)):
24+
self.assertMultiLineEqual(ref[i], cre[i])
25+
self.addCleanup(os.remove, 'test_pipe_result.iges')
26+
27+
def test_ffd_iges_pipe_mod_through_topods_shape(self):
28+
filename = 'tests/test_datasets/test_pipe_hollow.iges'
29+
orig_shape = CADDeformation.read_shape(filename)
30+
ffd = FFD()
31+
ffd.read_parameters(
32+
filename='tests/test_datasets/parameters_test_ffd_iges.prm')
33+
mod_shape = ffd(orig_shape)
34+
CADDeformation.write_shape('test_pipe_hollow_result.iges', mod_shape)
35+
with open('test_pipe_hollow_result.iges', "r") as created, \
36+
open('tests/test_datasets/test_pipe_hollow_out_true.iges', "r") as reference:
37+
ref = reference.readlines()[5:]
38+
cre = created.readlines()[5:]
39+
self.assertEqual(len(ref),len(cre))
40+
for i in range(len(cre)):
41+
self.assertMultiLineEqual(ref[i], cre[i])
42+
self.addCleanup(os.remove, 'test_pipe_hollow_result.iges')

0 commit comments

Comments
 (0)