11"""
22Utilities for reading and writing parameters files to perform the desired geometrical morphing.
33"""
4- import os
54import ConfigParser
5+ import os
6+
67import numpy as np
8+ from OCC .BRepBndLib import brepbndlib_Add
9+ from OCC .BRepMesh import BRepMesh_IncrementalMesh
10+ from OCC .Bnd import Bnd_Box
11+
712import pygem .affine as at
813
914
@@ -51,19 +56,28 @@ class FFDParameters(object):
5156 :cvar numpy.ndarray position_vertex_2: position of the third vertex of the FFD bounding box.
5257 :cvar numpy.ndarray position_vertex_3: position of the fourth vertex of the FFD bounding box.
5358
54- :Example:
59+ :Example: from file
5560
5661 >>> import pygem.params as ffdp
5762
5863 >>> # Reading an existing file
5964 >>> params1 = ffdp.FFDParameters()
6065 >>> params1.read_parameters(filename='tests/test_datasets/parameters_test_ffd_identity.prm')
6166
62- >>> # Creating a defaul paramters file with the right dimensions (if the file does not exists
67+ >>> # Creating a default parameters file with the right dimensions (if the file does not exists
6368 >>> # it is created with that name). So it is possible to manually edit it and read it again.
6469 >>> params2 = ffdp.FFDParameters(n_control_points=[2, 3, 2])
6570 >>> params2.read_parameters(filename='parameters_test.prm')
66-
71+
72+ >>> # Creating bounding box of the given shape
73+ >>> from OCC.IGESControl import IGESControl_Reader
74+ >>> params3 = ffdp.FFDParameters()
75+ >>> reader = IGESControl_Reader()
76+ >>> reader.ReadFile('tests/test_datasets/test_pipe.igs')
77+ >>> reader.TransferRoots()
78+ >>> shape = reader.Shape()
79+ >>> params3.build_bounding_box(shape)
80+
6781 .. note::
6882 Four vertex (non coplanar) are sufficient to uniquely identify a parallelepiped.
6983 If the four vertex are coplanar, an assert is thrown when affine_points_fit is used.
@@ -116,7 +130,7 @@ def read_parameters(self, filename='parameters.prm'):
116130 if not os .path .isfile (filename ):
117131 self .write_parameters (filename )
118132 return
119-
133+
120134 config = ConfigParser .RawConfigParser ()
121135 config .read (filename )
122136
@@ -175,12 +189,12 @@ def read_parameters(self, filename='parameters.prm'):
175189 self .psi_mapping = np .diag ([1. / self .lenght_box_x , 1. / self .lenght_box_y , 1. / self .lenght_box_z ])
176190 self .inv_psi_mapping = np .diag ([self .lenght_box_x , self .lenght_box_y , self .lenght_box_z ])
177191
178-
192+
179193 def write_parameters (self , filename = 'parameters.prm' ):
180194 """
181195 This method writes a parameters file (.prm) called `filename` and fills it with all
182196 the parameters class members.
183-
197+
184198 :param string filename: parameters file to be written out.
185199 """
186200 if not isinstance (filename , basestring ):
@@ -230,7 +244,7 @@ def write_parameters(self, filename='parameters.prm'):
230244 output_file .write ('# | 0 | 1 | 1 | 0.0 | --> you can erase this line without effects\n ' )
231245 output_file .write ('# | 0 | 1 | 0 | -2.1 |\n ' )
232246 output_file .write ('# | 0 | 0 | 1 | 3.4 |\n ' )
233-
247+
234248 output_file .write ('\n # parameter x collects the displacements along x, normalized with the box lenght x.' )
235249 output_file .write ('\n parameter x:' )
236250 offset = 1
@@ -294,6 +308,104 @@ def print_info(self):
294308 print '\n position_vertex_3 ='
295309 print self .position_vertex_3
296310
311+ def build_bounding_box (self , shape , tol = 1e-6 , triangulate = False , triangulate_tol = 1e-1 ):
312+ """
313+ Builds a bounding box around the given shape. ALl parameters (with the exception of array_mu_x/y/z)
314+ are set to match the computed box.
315+
316+ :param TopoDS_Shape shape: or a subclass such as TopoDS_Face
317+ the shape to compute the bounding box from
318+ :param float tol: tolerance of the computed bounding box
319+ :param bool triangulate: Should shape be triangulated before the boudning box is created.
320+
321+ If ``True`` only the dimensions of the bb will take into account every part of the shape (also not *visible*)
322+
323+ If ``False`` only the *visible* part is taken into account
324+
325+ *Explanation:* every UV-Surface has to be rectangular. When a solid is created surfaces are trimmed.
326+ the trimmed part, however, is still saved inside a file. It is just *invisible* when drawn in a program
327+
328+ :param float triangulate_tol: tolerance of triangulation (size of created triangles)
329+ """
330+ min_xyz , max_xyz = self ._calculate_bb_dimension (shape , tol , triangulate , triangulate_tol )
331+ self .origin_box = min_xyz
332+ self ._set_box_dimensions (min_xyz , max_xyz )
333+ self ._set_position_of_vertices ()
334+ self ._set_mapping ()
335+ self ._set_transformation_params_to_zero ()
336+
337+ def _set_box_dimensions (self , min_xyz , max_xyz ):
338+ """
339+ Dimensions of the cage are set as distance from the origin (minimum) of the cage to
340+ the maximal point in each dimension.
341+
342+ :param iterable min_xyz: three values representing the minimal values of the bounding box in XYZ respectively
343+ :param iterable max_xyz: three values representing the maximal values of the bounding box in XYZ respectively
344+ """
345+ dims = [max_xyz [i ] - min_xyz [i ] for i in range (3 )]
346+ self .lenght_box_x = dims [0 ]
347+ self .lenght_box_y = dims [1 ]
348+ self .lenght_box_z = dims [2 ]
349+
350+ def _set_position_of_vertices (self ):
351+ """
352+ Vertices of the control box around the object are set in this method.
353+ Four vertices (non coplanar) are sufficient to uniquely identify a parallelepiped -- the
354+ second half of the box is created as a mirror reflection of the first four vertices.
355+ """
356+ origin_array = np .array (self .origin_box )
357+ dim = [self .lenght_box_x , self .lenght_box_y , self .lenght_box_z ]
358+ self .position_vertex_0 = origin_array
359+ self .position_vertex_1 = origin_array + np .array ([dim [0 ], .0 , .0 ])
360+ self .position_vertex_2 = origin_array + np .array ([.0 , dim [1 ], .0 ])
361+ self .position_vertex_3 = origin_array + np .array ([.0 , .0 , dim [2 ]])
362+
363+ def _set_mapping (self ):
364+ """
365+ This method sets mapping from physcial domain to the reference domain (``psi_mapping``)
366+ as well as inverse mapping (``inv_psi_mapping``).
367+ """
368+ dim = [self .lenght_box_x , self .lenght_box_y , self .lenght_box_z ]
369+ self .psi_mapping = np .diag ([1. / dim [i ] for i in range (3 )])
370+ self .inv_psi_mapping = np .diag (dim )
371+
372+ def _set_transformation_params_to_zero (self ):
373+ """
374+ Sets transfomration parameters (``array_mu_x, array_mu_y, array_mu_z``) to arrays of zeros
375+ (``numpy.zeros``). The shape of arrays corresponds to the number of control points in each dimension.
376+ """
377+ ctrl_pnts = self .n_control_points
378+ self .array_mu_x = np .zeros (ctrl_pnts )
379+ self .array_mu_y = np .zeros (ctrl_pnts )
380+ self .array_mu_z = np .zeros (ctrl_pnts )
381+
382+ @staticmethod
383+ def _calculate_bb_dimension (shape , tol = 1e-6 , triangulate = False , triangulate_tol = 1e-1 ):
384+ """ Calculate dimensions (minima and maxima) of a box bounding the
385+
386+ :param TopoDS_Shape shape: or a subclass such as TopoDS_Face
387+ the shape to compute the bounding box from
388+ :param float tol: tolerance of the computed bounding box
389+ :param bool triangulate: Should shape be triangulated before the boudning box is created.
390+
391+ If ``True`` only the dimensions of the bb will take into account every part of the shape (also not *visible*)
392+
393+ If ``False`` only the *visible* part is taken into account
394+
395+ \*See :meth:`~params.FFDParameters.build_bounding_box`
396+ :param float triangulate_tol: tolerance of triangulation (size of created triangles)
397+ :return: coordinates of minima and maxima along XYZ
398+ :rtype: tuple
399+ """
400+ bbox = Bnd_Box ()
401+ bbox .SetGap (tol )
402+ if triangulate :
403+ BRepMesh_IncrementalMesh (shape , triangulate_tol )
404+ brepbndlib_Add (shape , bbox , triangulate )
405+ xmin , ymin , zmin , xmax , ymax , zmax = bbox .Get ()
406+ xyz_min = np .array ([xmin , ymin , zmin ])
407+ xyz_max = np .array ([xmax , ymax , zmax ])
408+ return xyz_min , xyz_max
297409
298410
299411class RBFParameters (object ):
@@ -346,7 +458,7 @@ def read_parameters(self, filename='parameters_rbf.prm'):
346458 0. , 1. , 1. , 1. , 0. , 1. , 1. , 1. , 0. , 1. , 1. , 1. ]).reshape ((8 , 3 ))
347459 self .write_parameters (filename )
348460 return
349-
461+
350462 config = ConfigParser .RawConfigParser ()
351463 config .read (filename )
352464
@@ -378,7 +490,7 @@ def write_parameters(self, filename='parameters_rbf.prm'):
378490 """
379491 This method writes a parameters file (.prm) called `filename` and fills it with all
380492 the parameters class members. Default value is parameters_rbf.prm.
381-
493+
382494 :param string filename: parameters file to be written out.
383495 """
384496 if not isinstance (filename , basestring ):
@@ -402,7 +514,7 @@ def write_parameters(self, filename='parameters_rbf.prm'):
402514
403515 output_file .write ('\n \n [Control points]\n ' )
404516 output_file .write ('# This section describes the RBF control points.\n ' )
405-
517+
406518 output_file .write ('\n # original control points collects the coordinates of the interpolation ' + \
407519 'control points before the deformation.\n ' )
408520 output_file .write ('original control points:' )
@@ -435,4 +547,3 @@ def print_info(self):
435547 print self .original_control_points
436548 print '\n deformed_control_points ='
437549 print self .deformed_control_points
438-
0 commit comments