In this tutorial we will show the typical workflow to perform a deformation on a generic geometry using the free-form deformation method implemented within PyGeM. A brief teoretical overview of the method is introduced in the pygem.ffd
module, while in the README you can find several references that focus on FFD.
First of all we import the required PyGeM class and we set matplotlib for 3D plots. The version of PyGeM we are using in this tutorial is the 2.0.0.
%matplotlib inline
import numpy as np
import mpl_toolkits.mplot3d
import matplotlib.pyplot as plt
import pygem
print(pygem.__version__)
from pygem import FFD
2.0.0
Then, the other ingredient is the original geometry we want to deform. In this simple tutorial we just span some points around a sphere and morph their coordinates using the FFD
class.
def mesh_points(num_pts = 2000):
indices = np.arange(0, num_pts, dtype=float) + 0.5
phi = np.arccos(1 - 2*indices/num_pts)
theta = np.pi * (1 + 5**0.5) * indices
return np.array([np.cos(theta) * np.sin(phi), np.sin(theta) * np.sin(phi), np.cos(phi)]).T
mesh = mesh_points()
plt.figure(figsize=(8,8)).add_subplot(111, projection='3d').scatter(*mesh.T);
plt.show()
Well, we have our sphere!
Now it is time to create the new instance of the FFD
class, that will actually perform the points deformation. We can read from the documentation that the only parameter to the FFD
constructor is the number of control points we want to create, in the three directions. In this simple tutorial we create just a $2\times 2 \times 2$ lattice of points.
ffd = FFD([2, 2, 2])
print(ffd)
conversion_unit = 1.0 n_control_points = [2 2 2] box_length = [1. 1. 1.] box_origin = [0. 0. 0.] rot_angle = [0. 0. 0.] array_mu_x = [[[0. 0.] [0. 0.]] [[0. 0.] [0. 0.]]] array_mu_y = [[[0. 0.] [0. 0.]] [[0. 0.] [0. 0.]]] array_mu_z = [[[0. 0.] [0. 0.]] [[0. 0.] [0. 0.]]] rotation_matrix = [[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]] position_vertices = [[0. 0. 0.] [1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]
We can print the FFD
object itself, in order to summarize all the parameters. It's important to note that the lattice has the default length equal to 1, the origin at $(0, 0, 0)$ and no rotation. We can edit manually these quantities to change the configuration of the lattice, but in this simple case the default values are fine.
To control the displacement of the control points, we can touch the arrays array_mu_x
, array_mu_y
and array_mu_z
: these structures contain the movements in the three directions of all the lattice points. By default, the movements is imposed to zero but we can change those values to apply the deformation. It is important to know also that the points movement is normalized by the length of the lattice.
Let's try together to move a point. We can manipulate the movements of each point along a specific direction by accessing to the relative array: in this case we move the right-top-rear point!
print('Movements of point[{}, {}, {}] along x: {}'.format(1, 1, 1, ffd.array_mu_x[1, 1, 1]))
print('Movements of point[{}, {}, {}] along z: {}'.format(1, 1, 1, ffd.array_mu_z[1, 1, 1]))
ffd.array_mu_x[1, 1, 1] = 2
ffd.array_mu_z[1, 1, 1] = 0.8
print()
print('Movements of point[{}, {}, {}] along x: {}'.format(1, 1, 1, ffd.array_mu_x[1, 1, 1]))
print('Movements of point[{}, {}, {}] along z: {}'.format(1, 1, 1, ffd.array_mu_z[1, 1, 1]))
Movements of point[1, 1, 1] along x: 0.0 Movements of point[1, 1, 1] along z: 0.0 Movements of point[1, 1, 1] along x: 2.0 Movements of point[1, 1, 1] along z: 0.8
Now we can deform our sphere (in particular the coordinates of the points that define it) just calling the __call__()
method and passing to it the numpy array of the points coordinates.
new_mesh = ffd(mesh)
print(type(new_mesh), new_mesh.shape)
<class 'numpy.ndarray'> (2000, 3)
Done! The new array (in this tutorial the array new_mesh
) contains the deformed coordinates. To visualize the new shape, we plot the sphere points and, in red, the control points.
ax = plt.figure(figsize=(8,8)).add_subplot(111, projection='3d')
ax.scatter(*new_mesh.T)
ax.scatter(*ffd.control_points().T, s=50, c='red')
plt.show()