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 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