lsdo-geo 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lsdo_geo/__init__.py +60 -0
- lsdo_geo/core/__init__.py +0 -0
- lsdo_geo/core/geometry/__init__.py +0 -0
- lsdo_geo/core/geometry/geometry.py +589 -0
- lsdo_geo/core/geometry/geometry_functions.py +401 -0
- lsdo_geo/core/geometry/mesh.py +51 -0
- lsdo_geo/core/parameterization/__init__.py +0 -0
- lsdo_geo/core/parameterization/ffd_block.py +301 -0
- lsdo_geo/core/parameterization/free_form_deformation_functions.py +191 -0
- lsdo_geo/core/parameterization/parameterization_solver.py +241 -0
- lsdo_geo/core/parameterization/sectional_parameterization.py +976 -0
- lsdo_geo/optimization/__init__.py +1 -0
- lsdo_geo/optimization/optimization.py +431 -0
- lsdo_geo-1.0.0.dist-info/METADATA +166 -0
- lsdo_geo-1.0.0.dist-info/RECORD +18 -0
- lsdo_geo-1.0.0.dist-info/WHEEL +5 -0
- lsdo_geo-1.0.0.dist-info/licenses/LICENSE.txt +165 -0
- lsdo_geo-1.0.0.dist-info/top_level.txt +1 -0
lsdo_geo/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
lsdo_geo: Geometry representation, deformational parameterization,
|
|
3
|
+
and sensitivity analysis for Multidisciplinary Design Optimization (MDO).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.0.0"
|
|
7
|
+
|
|
8
|
+
from .core.geometry.geometry import Geometry
|
|
9
|
+
from .core.geometry.mesh import Mesh
|
|
10
|
+
from .core.geometry.geometry_functions import (
|
|
11
|
+
import_geometry,
|
|
12
|
+
rotate,
|
|
13
|
+
apply_quaternion_rotation,
|
|
14
|
+
hamilton_product,
|
|
15
|
+
)
|
|
16
|
+
from .core.parameterization.ffd_block import FFDBlock
|
|
17
|
+
from .core.parameterization.free_form_deformation_functions import (
|
|
18
|
+
construct_ffd_block_around_entities,
|
|
19
|
+
construct_ffd_block_from_corners,
|
|
20
|
+
construct_tight_fit_ffd_block,
|
|
21
|
+
)
|
|
22
|
+
from .core.parameterization.sectional_parameterization import (
|
|
23
|
+
SectionalParameterization,
|
|
24
|
+
SectionalParameters,
|
|
25
|
+
VolumeSectionalParameterization,
|
|
26
|
+
VolumeSectionalParameterizationInputs,
|
|
27
|
+
)
|
|
28
|
+
from .core.parameterization.parameterization_solver import (
|
|
29
|
+
ParameterizationSolver,
|
|
30
|
+
GeometricVariables,
|
|
31
|
+
)
|
|
32
|
+
from .optimization import (
|
|
33
|
+
Optimization,
|
|
34
|
+
NewtonOptimizer,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"__version__",
|
|
39
|
+
# Geometry
|
|
40
|
+
"Geometry",
|
|
41
|
+
"Mesh",
|
|
42
|
+
"import_geometry",
|
|
43
|
+
"rotate",
|
|
44
|
+
"apply_quaternion_rotation",
|
|
45
|
+
"hamilton_product",
|
|
46
|
+
# Parameterization
|
|
47
|
+
"FFDBlock",
|
|
48
|
+
"construct_ffd_block_around_entities",
|
|
49
|
+
"construct_ffd_block_from_corners",
|
|
50
|
+
"construct_tight_fit_ffd_block",
|
|
51
|
+
"SectionalParameterization",
|
|
52
|
+
"SectionalParameters",
|
|
53
|
+
"VolumeSectionalParameterization",
|
|
54
|
+
"VolumeSectionalParameterizationInputs",
|
|
55
|
+
"ParameterizationSolver",
|
|
56
|
+
"GeometricVariables",
|
|
57
|
+
# Optimization
|
|
58
|
+
"Optimization",
|
|
59
|
+
"NewtonOptimizer",
|
|
60
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
import pickle
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
# import pickle
|
|
9
|
+
import csdl_alpha as csdl
|
|
10
|
+
# from lsdo_geo.splines.b_splines.b_spline_set import BSplineSet
|
|
11
|
+
# from lsdo_geo.splines.b_splines.b_spline_sub_set import BSplineSubSet
|
|
12
|
+
import lsdo_function_spaces as lfs
|
|
13
|
+
import lsdo_geo as lg
|
|
14
|
+
import pyvista as pv
|
|
15
|
+
from typing import Optional, Union, Sequence
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Geometry(lfs.FunctionSet):
|
|
19
|
+
representations:dict[str,lg.Mesh] = None
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
super().__post_init__()
|
|
23
|
+
if self.representations is None:
|
|
24
|
+
self.representations = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def copy(self):
|
|
28
|
+
'''
|
|
29
|
+
Creates a copy of the geometry
|
|
30
|
+
'''
|
|
31
|
+
function_set = super().copy()
|
|
32
|
+
geometry_copy = Geometry(functions=function_set.functions, function_names=function_set.function_names, name=self.name,
|
|
33
|
+
space=function_set.space, representations=self.representations)
|
|
34
|
+
return geometry_copy
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_function_space(self):
|
|
38
|
+
return self.space
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def add_representation(self, representation:lg.Mesh):
|
|
42
|
+
'''
|
|
43
|
+
Adds a representation to the geometry.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
name : str
|
|
48
|
+
The name of the representation.
|
|
49
|
+
representation : lg.Mesh
|
|
50
|
+
The representation to add.
|
|
51
|
+
'''
|
|
52
|
+
self.representations[representation.name] = representation
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def evaluate_representations(self, representations:list[lg.Mesh], plot:bool=False) -> list[csdl.Variable]:
|
|
56
|
+
'''
|
|
57
|
+
Evaluates a representation or a list of representations.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
name : str
|
|
62
|
+
The name of the representation.
|
|
63
|
+
plot : bool, optional
|
|
64
|
+
Whether or not to plot the representation.
|
|
65
|
+
'''
|
|
66
|
+
if isinstance(representations, lg.Mesh):
|
|
67
|
+
representations = [representations]
|
|
68
|
+
|
|
69
|
+
evaluated_representations = []
|
|
70
|
+
for representation in representations:
|
|
71
|
+
# representation = self.representations[representation.name]
|
|
72
|
+
evaluated_representations.append(representation.evaluate(self, plot=plot))
|
|
73
|
+
|
|
74
|
+
if len(evaluated_representations) == 1:
|
|
75
|
+
evaluated_representation = evaluated_representations[0]
|
|
76
|
+
return evaluated_representation
|
|
77
|
+
|
|
78
|
+
return evaluated_representations
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def declare_component(self, function_indices:Optional[Sequence[int]]=None,
|
|
82
|
+
function_search_names:Optional[Sequence[str]]=None,
|
|
83
|
+
ignore_names:Optional[Sequence[str]]=None,
|
|
84
|
+
name:Optional[str]=None) -> lg.Geometry:
|
|
85
|
+
'''
|
|
86
|
+
Declares a component. This component will point to a sub-set of the entire geometry.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
function_names : list[str]
|
|
91
|
+
The names of the functions that make up the component.
|
|
92
|
+
function_search_names : list[str], optional
|
|
93
|
+
The names of the functions to search for. Names of functions will be returned for each B-spline that INCLUDES the search name.
|
|
94
|
+
name : str
|
|
95
|
+
The name of the component.
|
|
96
|
+
'''
|
|
97
|
+
function_set = self.create_subset(function_indices=function_indices, function_search_names=function_search_names, ignore_names=ignore_names, name=name)
|
|
98
|
+
|
|
99
|
+
component = lg.Geometry(functions=function_set.functions, function_names=function_set.function_names, name=name,
|
|
100
|
+
space=function_set.space)
|
|
101
|
+
return component
|
|
102
|
+
|
|
103
|
+
def create_component_copy(self, function_indices:list[int]=None, function_search_names:list[str]=None, name:str=None) -> lg.Geometry:
|
|
104
|
+
'''
|
|
105
|
+
Declares a component. This component will point to a sub-set of the entire geometry.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
function_names : list[str]
|
|
110
|
+
The names of the functions that make up the component.
|
|
111
|
+
function_search_names : list[str], optional
|
|
112
|
+
The names of the functions to search for. Names of functions will be returned for each B-spline that INCLUDES the search name.
|
|
113
|
+
name : str
|
|
114
|
+
The name of the component.
|
|
115
|
+
'''
|
|
116
|
+
component = self.create_subset(function_indices=function_indices, function_search_names=function_search_names, name=name)
|
|
117
|
+
component_copy = component.copy()
|
|
118
|
+
return component_copy
|
|
119
|
+
|
|
120
|
+
# def copy(self) -> lg.Geometry:
|
|
121
|
+
# '''
|
|
122
|
+
# Copies the function set.
|
|
123
|
+
|
|
124
|
+
# Returns
|
|
125
|
+
# -------
|
|
126
|
+
# function_set : lfs.FunctionSet
|
|
127
|
+
# The copied function set.
|
|
128
|
+
# '''
|
|
129
|
+
# functions = {i:function.copy() for i, function in self.functions.items()}
|
|
130
|
+
# function_set = lg.Geometry(functions=functions, function_names=self.function_names, name=self.name)
|
|
131
|
+
# return function_set
|
|
132
|
+
|
|
133
|
+
# def import_geometry(self, file_name:str):
|
|
134
|
+
# '''
|
|
135
|
+
# Imports geometry from a file.
|
|
136
|
+
|
|
137
|
+
# Parameters
|
|
138
|
+
# ----------
|
|
139
|
+
# file_name : str
|
|
140
|
+
# The name of the file (with path) that containts the geometric information.
|
|
141
|
+
# '''
|
|
142
|
+
# from lsdo_geo.splines.b_splines.b_spline_functions import import_file, create_b_spline_set
|
|
143
|
+
# b_splines = import_file(file_name)
|
|
144
|
+
# b_spline_set = create_b_spline_set(self.name, b_splines)
|
|
145
|
+
|
|
146
|
+
# self.space = b_spline_set.space
|
|
147
|
+
# self.coefficients = b_spline_set.coefficients
|
|
148
|
+
# self.num_physical_dimensions = b_spline_set.num_physical_dimensions
|
|
149
|
+
# self.coefficient_indices = b_spline_set.coefficient_indices
|
|
150
|
+
# self.connections = b_spline_set.connections
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def rotate(self, rotation_origin:Union[csdl.Variable, npt.NDArray[np.float64]], axis_vector:Union[csdl.Variable, npt.NDArray[np.float64]],
|
|
154
|
+
angles:Union[csdl.Variable, npt.NDArray[np.float64], float], function_indices:Optional[list[int]]=None,
|
|
155
|
+
units:Optional[str]='radians') -> None:
|
|
156
|
+
'''
|
|
157
|
+
Rotates the geometry about an axis.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
rotation_origin : csdl.Variable
|
|
162
|
+
The origin of the axis of rotation.
|
|
163
|
+
axis_vector : csdl.Variable
|
|
164
|
+
The vector of the axis of rotation.
|
|
165
|
+
angles : csdl.Variable
|
|
166
|
+
The angle of rotation.
|
|
167
|
+
function_indices : list[int]
|
|
168
|
+
The indices of the functions to rotate.
|
|
169
|
+
units : str
|
|
170
|
+
The units of the angle of rotation. {degrees, radians}
|
|
171
|
+
'''
|
|
172
|
+
from lsdo_geo.core.geometry.geometry_functions import rotate as rotate_function
|
|
173
|
+
if units == 'degrees':
|
|
174
|
+
angles = angles * np.pi / 180.
|
|
175
|
+
units = 'radians'
|
|
176
|
+
elif units == 'radians':
|
|
177
|
+
pass
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(f'Invalid units {units}.')
|
|
180
|
+
|
|
181
|
+
if function_indices is None:
|
|
182
|
+
function_indices = list(self.functions.keys())
|
|
183
|
+
if isinstance(function_indices, int):
|
|
184
|
+
function_indices = [function_indices]
|
|
185
|
+
if not isinstance(function_indices, list):
|
|
186
|
+
raise ValueError(f'The function indices must be a list of int, received {type(function_indices)}')
|
|
187
|
+
|
|
188
|
+
if isinstance(rotation_origin, np.ndarray):
|
|
189
|
+
rotation_origin = csdl.Variable(shape=rotation_origin.shape, value=rotation_origin)
|
|
190
|
+
# if type(axis_vector) is np.ndarray:
|
|
191
|
+
# axis_vector = csdl.Variable(shape=axis_vector.shape, value=axis_vector)
|
|
192
|
+
if type(angles) is np.ndarray:
|
|
193
|
+
angles = csdl.Variable(shape=angles.shape, value=angles)
|
|
194
|
+
|
|
195
|
+
# # Unvectorized:
|
|
196
|
+
# for function_index in function_indices:
|
|
197
|
+
# function = self.functions[function_index]
|
|
198
|
+
# rotated_coefficients = rotate_function(
|
|
199
|
+
# points=function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])),
|
|
200
|
+
# rotation_origin=rotation_origin, axis_vector=axis_vector, angles=angles, units=units
|
|
201
|
+
# )
|
|
202
|
+
# function.coefficients = rotated_coefficients.reshape(function.coefficients.shape)
|
|
203
|
+
|
|
204
|
+
# Vectorized:
|
|
205
|
+
if len(function_indices) == 1:
|
|
206
|
+
function = self.functions[function_indices[0]]
|
|
207
|
+
rotated_coefficients = rotate_function(
|
|
208
|
+
points=function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])),
|
|
209
|
+
rotation_origin=rotation_origin, axis_vector=axis_vector, angles=angles, units=units
|
|
210
|
+
)
|
|
211
|
+
function.coefficients = rotated_coefficients.reshape(function.coefficients.shape)
|
|
212
|
+
else:
|
|
213
|
+
stacked_coefficients = []
|
|
214
|
+
for function_index in function_indices:
|
|
215
|
+
function = self.functions[function_index]
|
|
216
|
+
stacked_coefficients.append(function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])))
|
|
217
|
+
stacked_coefficients = csdl.vstack(stacked_coefficients)
|
|
218
|
+
|
|
219
|
+
rotated_coefficients = rotate_function(
|
|
220
|
+
points=stacked_coefficients,
|
|
221
|
+
rotation_origin=rotation_origin, axis_vector=axis_vector, angles=angles, units=units
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
counter = 0
|
|
225
|
+
for i, function_index in enumerate(function_indices):
|
|
226
|
+
function = self.functions[function_index]
|
|
227
|
+
num_coefficient_points = function.coefficients.size // function.coefficients.shape[-1]
|
|
228
|
+
function.coefficients = rotated_coefficients[counter:counter+num_coefficient_points,:].reshape(function.coefficients.shape)
|
|
229
|
+
counter += num_coefficient_points
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def translate(self, translation:Union[csdl.Variable, npt.NDArray[np.float64]], function_indices:Optional[list[int]]=None) -> None:
|
|
233
|
+
'''
|
|
234
|
+
Translates the geometry.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
translation : csdl.Variable
|
|
239
|
+
The translation vector.
|
|
240
|
+
function_indices : list[int]
|
|
241
|
+
The indices of the functions to translate.
|
|
242
|
+
'''
|
|
243
|
+
if function_indices is None:
|
|
244
|
+
function_indices = list(self.functions.keys())
|
|
245
|
+
if isinstance(function_indices, int):
|
|
246
|
+
function_indices = [function_indices]
|
|
247
|
+
if not isinstance(function_indices, list):
|
|
248
|
+
raise ValueError(f'The function indices must be a list of int, received {type(function_indices)}')
|
|
249
|
+
|
|
250
|
+
if isinstance(translation, np.ndarray):
|
|
251
|
+
translation = csdl.Variable(shape=translation.shape, value=translation)
|
|
252
|
+
|
|
253
|
+
# # Unvectorized:
|
|
254
|
+
# for function_index in function_indices:
|
|
255
|
+
# function = self.functions[function_index]
|
|
256
|
+
# translated_coefficients = translate_function(
|
|
257
|
+
# points=function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])),
|
|
258
|
+
# translation=translation
|
|
259
|
+
# )
|
|
260
|
+
# function.coefficients = translated_coefficients.reshape(function.coefficients.shape)
|
|
261
|
+
|
|
262
|
+
# Vectorized:
|
|
263
|
+
if len(function_indices) == 1:
|
|
264
|
+
function = self.functions[function_indices[0]]
|
|
265
|
+
translated_coefficients = function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])) + translation
|
|
266
|
+
function.coefficients = translated_coefficients.reshape(function.coefficients.shape)
|
|
267
|
+
else:
|
|
268
|
+
stacked_coefficients = []
|
|
269
|
+
for function_index in function_indices:
|
|
270
|
+
function = self.functions[function_index]
|
|
271
|
+
stacked_coefficients.append(function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])))
|
|
272
|
+
stacked_coefficients = csdl.vstack(stacked_coefficients)
|
|
273
|
+
|
|
274
|
+
if len(translation.shape) == 1:
|
|
275
|
+
expanded_translation = csdl.expand(translation, stacked_coefficients.shape, 'i->ji',)
|
|
276
|
+
translated_coefficients = stacked_coefficients + expanded_translation
|
|
277
|
+
elif translation.shape == stacked_coefficients.shape:
|
|
278
|
+
translated_coefficients = stacked_coefficients + translation
|
|
279
|
+
else:
|
|
280
|
+
raise ValueError(f'Translation shape {translation.shape} not compatible with stacked coefficients shape {stacked_coefficients.shape}.')
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
counter = 0
|
|
284
|
+
for i, function_index in enumerate(function_indices):
|
|
285
|
+
function = self.functions[function_index]
|
|
286
|
+
num_coefficient_points = function.coefficients.size // function.coefficients.shape[-1]
|
|
287
|
+
function.coefficients = translated_coefficients[counter:counter+num_coefficient_points,:].reshape(function.coefficients.shape)
|
|
288
|
+
counter += num_coefficient_points
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def rotate_using_quaternion(self, rotation_origin:Union[csdl.Variable,npt.NDArray[np.float64]],
|
|
292
|
+
quaternion:Union[csdl.Variable,npt.NDArray[np.float64]],
|
|
293
|
+
function_indices:Optional[list[int]]=None):
|
|
294
|
+
'''
|
|
295
|
+
Rotates the geometry using a quaternion.
|
|
296
|
+
|
|
297
|
+
parameters
|
|
298
|
+
----------
|
|
299
|
+
rotation_origin : Union[csdl.Variable,npt.NDArray[np.float64]]
|
|
300
|
+
The origin of the rotation axis.
|
|
301
|
+
quaternion : Union[csdl.Variable,npt.NDArray[np.float64]]
|
|
302
|
+
The quaternion representing the rotation.
|
|
303
|
+
function_indices : Optional[list[int]]
|
|
304
|
+
The indices of the functions to rotate.
|
|
305
|
+
'''
|
|
306
|
+
from lsdo_geo import apply_quaternion_rotation
|
|
307
|
+
if function_indices is None:
|
|
308
|
+
function_indices = list(self.functions.keys())
|
|
309
|
+
if isinstance(function_indices, int):
|
|
310
|
+
function_indices = [function_indices]
|
|
311
|
+
if not isinstance(function_indices, list):
|
|
312
|
+
raise ValueError(f'The function indices must be a list of int, received {type(function_indices)}')
|
|
313
|
+
|
|
314
|
+
if len(function_indices) == 1:
|
|
315
|
+
function = self.functions[function_indices[0]]
|
|
316
|
+
rotated_coefficients = apply_quaternion_rotation(
|
|
317
|
+
points=function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])),
|
|
318
|
+
rotation_origin=rotation_origin, quaternion=quaternion
|
|
319
|
+
)
|
|
320
|
+
function.coefficients = rotated_coefficients.reshape(function.coefficients.shape)
|
|
321
|
+
|
|
322
|
+
else:
|
|
323
|
+
stacked_coefficients = []
|
|
324
|
+
for function_index in function_indices:
|
|
325
|
+
function = self.functions[function_index]
|
|
326
|
+
stacked_coefficients.append(function.coefficients.reshape((function.coefficients.size // function.coefficients.shape[-1], function.coefficients.shape[-1])))
|
|
327
|
+
stacked_coefficients = csdl.vstack(stacked_coefficients)
|
|
328
|
+
|
|
329
|
+
rotated_coefficients = apply_quaternion_rotation(
|
|
330
|
+
points=stacked_coefficients,
|
|
331
|
+
rotation_origin=rotation_origin, quaternion=quaternion
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
counter = 0
|
|
335
|
+
for i, function_index in enumerate(function_indices):
|
|
336
|
+
function = self.functions[function_index]
|
|
337
|
+
num_coefficient_points = function.coefficients.size // function.coefficients.shape[-1]
|
|
338
|
+
function.coefficients = rotated_coefficients[counter:counter+num_coefficient_points,:].reshape(function.coefficients.shape)
|
|
339
|
+
counter += num_coefficient_points
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def plot_meshes(self, meshes:list[csdl.Variable], mesh_plot_types:list[str]=['wireframe'], mesh_opacity:float=1., mesh_color:str='#F5F0E6',
|
|
343
|
+
mesh_color_map:str='jet', mesh_line_width:float=3.,
|
|
344
|
+
function_indices:Optional[list[str]]=None, function_plot_types:list[str]=['function'], function_opacity:float=0.25, function_color:str='#00629B',
|
|
345
|
+
function_color_map:str='jet', function_surface_texture:str="",
|
|
346
|
+
additional_plotting_elements:list=[], camera:Optional[dict]=None, show:bool=True) -> list:
|
|
347
|
+
'''
|
|
348
|
+
Plots a mesh over the geometry.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
meshes : list
|
|
353
|
+
A list of meshes to plot.
|
|
354
|
+
mesh_plot_types : list, optional = ['wireframe']
|
|
355
|
+
A list of plot types for each mesh. Options are 'wireframe', 'function', and 'points'.
|
|
356
|
+
mesh_opacity : float, optional = 1.
|
|
357
|
+
The opacity of the mesh.
|
|
358
|
+
mesh_color : str, optional = '#F5F0E6'
|
|
359
|
+
The color of the mesh.
|
|
360
|
+
mesh_color_map : str, optional = 'jet'
|
|
361
|
+
The color map for the mesh.
|
|
362
|
+
mesh_line_width : float, optional = 3.
|
|
363
|
+
The line width of the mesh.
|
|
364
|
+
function_indices : list, optional = None
|
|
365
|
+
A list of indices for which functions to plot.
|
|
366
|
+
function_plot_types : list, optional = ['function']
|
|
367
|
+
A list of plot types for each primitive. Options are 'wireframe', 'function', and 'points'.
|
|
368
|
+
function_opacity : float, optional = 0.25
|
|
369
|
+
The opacity of the function.
|
|
370
|
+
function_color : str, optional = '#00629B'
|
|
371
|
+
The color of the function.
|
|
372
|
+
function_color_map : str, optional = 'jet'
|
|
373
|
+
The color map for the function.
|
|
374
|
+
function_surface_texture : str, optional
|
|
375
|
+
The surface texture for the primitive surfaces.
|
|
376
|
+
additional_plotting_elements : list, optional
|
|
377
|
+
A list of additional plotting elements to plot.
|
|
378
|
+
camera : dict, optional
|
|
379
|
+
A dictionary of camera parameters for PyVista.
|
|
380
|
+
show : bool, optional
|
|
381
|
+
Whether or not to show the plot.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
plotting_elements : list
|
|
386
|
+
A list of the PyVista plotting elements.
|
|
387
|
+
'''
|
|
388
|
+
import lsdo_function_spaces.utils.plotting_functions as pf
|
|
389
|
+
import pyvista as pv
|
|
390
|
+
plotting_elements = additional_plotting_elements.copy()
|
|
391
|
+
|
|
392
|
+
if not isinstance(meshes, list) and not isinstance(meshes, tuple):
|
|
393
|
+
meshes = [meshes]
|
|
394
|
+
|
|
395
|
+
# Create plotting meshes for the functions/geometry
|
|
396
|
+
plotting_elements = self.plot(point_types=['evaluated_points'], plot_types=function_plot_types, opacity=function_opacity,
|
|
397
|
+
color=function_color, color_map=function_color_map,
|
|
398
|
+
additional_plotting_elements=plotting_elements, show=False)
|
|
399
|
+
|
|
400
|
+
for mesh in meshes:
|
|
401
|
+
if type(mesh) is csdl.Variable:
|
|
402
|
+
points = mesh.value
|
|
403
|
+
else:
|
|
404
|
+
points = mesh
|
|
405
|
+
|
|
406
|
+
if isinstance(mesh, tuple):
|
|
407
|
+
# Is vector, so draw an arrow
|
|
408
|
+
processed_points = ()
|
|
409
|
+
for point in mesh:
|
|
410
|
+
if type(point) is csdl.Variable:
|
|
411
|
+
processed_points = processed_points + (point.value,)
|
|
412
|
+
else:
|
|
413
|
+
processed_points = processed_points + (point,)
|
|
414
|
+
start = np.asarray(processed_points[0]).reshape((3,))
|
|
415
|
+
direction = np.asarray(processed_points[1]).reshape((3,))
|
|
416
|
+
norm = np.linalg.norm(direction)
|
|
417
|
+
if norm > 0:
|
|
418
|
+
arrow = pv.Arrow(start=start, direction=direction, scale=norm)
|
|
419
|
+
plotting_elements.append({"mesh": arrow, "kwargs": dict(color=mesh_color)})
|
|
420
|
+
continue
|
|
421
|
+
|
|
422
|
+
if 'point_cloud' in mesh_plot_types:
|
|
423
|
+
plotting_elements = pf.plot_points(points, opacity=mesh_opacity, color=mesh_color, color_map=mesh_color_map,
|
|
424
|
+
additional_plotting_elements=plotting_elements, show=False)
|
|
425
|
+
|
|
426
|
+
if points.shape[0] == 1:
|
|
427
|
+
points = points.reshape((points.shape[1:]))
|
|
428
|
+
|
|
429
|
+
if len(points.shape) == 2: # If it's a curve
|
|
430
|
+
plotting_elements = pf.plot_curve(points, opacity=mesh_opacity, color=mesh_color,
|
|
431
|
+
color_map=mesh_color_map, line_width=mesh_line_width,
|
|
432
|
+
additional_plotting_elements=plotting_elements, show=False)
|
|
433
|
+
if 'wireframe' in mesh_plot_types:
|
|
434
|
+
plotting_elements = pf.plot_points(points, opacity=mesh_opacity, color=mesh_color,
|
|
435
|
+
size=6., additional_plotting_elements=plotting_elements, show=False)
|
|
436
|
+
continue
|
|
437
|
+
|
|
438
|
+
if ('surface' in mesh_plot_types or 'wireframe' in mesh_plot_types) and len(points.shape) == 3: # If it's a surface
|
|
439
|
+
surface_types = [t for t in mesh_plot_types if t in ['surface', 'wireframe']]
|
|
440
|
+
# Map 'surface' to 'function' for lfs.plot_surface
|
|
441
|
+
mapped_types = ['function' if t == 'surface' else t for t in surface_types]
|
|
442
|
+
plotting_elements = pf.plot_surface(points, plot_types=mapped_types, opacity=mesh_opacity,
|
|
443
|
+
color=mesh_color, color_map=mesh_color_map,
|
|
444
|
+
line_width=mesh_line_width,
|
|
445
|
+
additional_plotting_elements=plotting_elements, show=False)
|
|
446
|
+
|
|
447
|
+
if show:
|
|
448
|
+
pf.show_plot(plotting_elements, 'Meshes', axes=True, view_up="z", interactive=True, camera=camera if camera else {})
|
|
449
|
+
|
|
450
|
+
return plotting_elements
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def plot_2d_mesh(self, mesh):
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
def export_iges(self, file_name:str):
|
|
457
|
+
'''
|
|
458
|
+
Exports the geometry to an IGES file.
|
|
459
|
+
|
|
460
|
+
Parameters
|
|
461
|
+
----------
|
|
462
|
+
file_name : str
|
|
463
|
+
The name of the file to export to.
|
|
464
|
+
'''
|
|
465
|
+
"""
|
|
466
|
+
Write the surface to IGES format
|
|
467
|
+
Parameters
|
|
468
|
+
----------
|
|
469
|
+
fileName : str
|
|
470
|
+
File name of iges file. Should have .igs extension.
|
|
471
|
+
"""
|
|
472
|
+
f = open(file_name, 'w')
|
|
473
|
+
print('Exporting', file_name)
|
|
474
|
+
#TODO Change to correct information
|
|
475
|
+
f.write(' S 1\n')
|
|
476
|
+
f.write('1H,,1H;,7H128-000,11H128-000.IGS,9H{unknown},9H{unknown},16,6,15,13,15, G 1\n')
|
|
477
|
+
f.write('7H128-000,1.,6,1HM,8,0.016,15H19970830.165254, 0.0001,0., G 2\n')
|
|
478
|
+
f.write('21Hdennette@wiz-worx.com,23HLegacy PDD AP Committee,11,3, G 3\n')
|
|
479
|
+
f.write('13H920717.080000,23HMIL-PRF-28000B0,CLASS 1; G 4\n')
|
|
480
|
+
Dcount = 1
|
|
481
|
+
Pcount = 1
|
|
482
|
+
for surf in self.functions.values():
|
|
483
|
+
space = surf.space
|
|
484
|
+
paraEntries = 13 + (len(space.knot_indices[0])) + (len(space.knot_indices[1])) + space.coefficients_shape[0] * space.coefficients_shape[1] + 3 * space.coefficients_shape[0] * space.coefficients_shape[1] + 1
|
|
485
|
+
paraLines = (paraEntries - 10) // 3 + 2
|
|
486
|
+
if np.mod(paraEntries - 10, 3) != 0:
|
|
487
|
+
paraLines += 1
|
|
488
|
+
f.write(" 128%8d 0 0 1 0 0 000000001D%7d\n" % (Pcount, Dcount))
|
|
489
|
+
f.write(
|
|
490
|
+
" 128 0 2%8d 0 0D%7d\n" % (paraLines, Dcount + 1)
|
|
491
|
+
)
|
|
492
|
+
Dcount += 2
|
|
493
|
+
Pcount += paraLines
|
|
494
|
+
Pcount = 1
|
|
495
|
+
counter = 1
|
|
496
|
+
for surf in self.functions.values():
|
|
497
|
+
space = surf.space
|
|
498
|
+
f.write(
|
|
499
|
+
"%10d,%10d,%10d,%10d,%10d, %7dP%7d\n"
|
|
500
|
+
% (128, space.coefficients_shape[0] - 1, space.coefficients_shape[1] - 1, space.degree[0], space.degree[1], Pcount, counter)
|
|
501
|
+
)
|
|
502
|
+
counter += 1
|
|
503
|
+
f.write("%10d,%10d,%10d,%10d,%10d, %7dP%7d\n" % (0, 0, 1, 0, 0, Pcount, counter))
|
|
504
|
+
|
|
505
|
+
counter += 1
|
|
506
|
+
pos_counter = 0
|
|
507
|
+
if isinstance(space.knots, (tuple, list)):
|
|
508
|
+
knots_u = space.knots[0]
|
|
509
|
+
knots_v = space.knots[1]
|
|
510
|
+
else:
|
|
511
|
+
knots_u = space.knots[space.knot_indices[0]]
|
|
512
|
+
knots_v = space.knots[space.knot_indices[1]]
|
|
513
|
+
for i in range(len(knots_u)):
|
|
514
|
+
pos_counter += 1
|
|
515
|
+
f.write("%20.12g," % (np.real(knots_u[i])))
|
|
516
|
+
if np.mod(pos_counter, 3) == 0:
|
|
517
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
518
|
+
counter += 1
|
|
519
|
+
pos_counter = 0
|
|
520
|
+
|
|
521
|
+
for i in range(len(knots_v)):
|
|
522
|
+
pos_counter += 1
|
|
523
|
+
f.write("%20.12g," % (np.real(knots_v[i])))
|
|
524
|
+
if np.mod(pos_counter, 3) == 0:
|
|
525
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
526
|
+
counter += 1
|
|
527
|
+
pos_counter = 0
|
|
528
|
+
|
|
529
|
+
for i in range(space.coefficients_shape[0] * space.coefficients_shape[1]):
|
|
530
|
+
pos_counter += 1
|
|
531
|
+
f.write("%20.12g," % (1.0))
|
|
532
|
+
if np.mod(pos_counter, 3) == 0:
|
|
533
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
534
|
+
counter += 1
|
|
535
|
+
pos_counter = 0
|
|
536
|
+
|
|
537
|
+
for j in range(space.coefficients_shape[1]):
|
|
538
|
+
for i in range(space.coefficients_shape[0]):
|
|
539
|
+
for idim in range(3):
|
|
540
|
+
pos_counter += 1
|
|
541
|
+
if isinstance(surf.coefficients, csdl.Variable):
|
|
542
|
+
coefficients = surf.coefficients.value
|
|
543
|
+
else:
|
|
544
|
+
coefficients = surf.coefficients
|
|
545
|
+
cntrl_pts = np.reshape(coefficients, (space.coefficients_shape[0], space.coefficients_shape[1],3))
|
|
546
|
+
f.write("%20.12g," % (np.real(cntrl_pts[i, j, idim])))
|
|
547
|
+
if np.mod(pos_counter, 3) == 0:
|
|
548
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
549
|
+
counter += 1
|
|
550
|
+
pos_counter = 0
|
|
551
|
+
|
|
552
|
+
for i in range(4):
|
|
553
|
+
pos_counter += 1
|
|
554
|
+
if i == 0:
|
|
555
|
+
f.write("%20.12g," % (np.real(knots_u[0])))
|
|
556
|
+
if i == 1:
|
|
557
|
+
f.write("%20.12g," % (np.real(knots_u[-1])))
|
|
558
|
+
if i == 2:
|
|
559
|
+
f.write("%20.12g," % (np.real(knots_v[0])))
|
|
560
|
+
if i == 3:
|
|
561
|
+
f.write("%20.12g;" % (np.real(knots_v[-1])))
|
|
562
|
+
if np.mod(pos_counter, 3) == 0:
|
|
563
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
564
|
+
counter += 1
|
|
565
|
+
pos_counter = 0
|
|
566
|
+
else:
|
|
567
|
+
if i == 3:
|
|
568
|
+
for j in range(3 - pos_counter):
|
|
569
|
+
f.write("%21s" % (" "))
|
|
570
|
+
pos_counter = 0
|
|
571
|
+
f.write(" %7dP%7d\n" % (Pcount, counter))
|
|
572
|
+
counter += 1
|
|
573
|
+
|
|
574
|
+
Pcount += 2
|
|
575
|
+
f.write('S%7dG%7dD%7dP%7d%40sT%6s1\n'%(1, 4, Dcount-1, counter-1, ' ', ' '))
|
|
576
|
+
f.close()
|
|
577
|
+
print('Complete export')
|
|
578
|
+
|
|
579
|
+
def export_obj(self, file_name:str):
|
|
580
|
+
'''
|
|
581
|
+
Exports the geometry to an OBJ file.
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
file_name : str
|
|
586
|
+
The name of the file to export to.
|
|
587
|
+
'''
|
|
588
|
+
|
|
589
|
+
pass
|