morphopt 3.1.1__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.

Potentially problematic release.


This version of morphopt might be problematic. Click here for more details.

Files changed (49) hide show
  1. morphopt/__init__.py +32 -0
  2. morphopt/opt_runner.py +130 -0
  3. morphopt/optcore/baseobject.py +48 -0
  4. morphopt/optcore/controller.py +404 -0
  5. morphopt/optcore/history.py +440 -0
  6. morphopt/optcore/modelparams/__init__.py +4 -0
  7. morphopt/optcore/modelparams/base_params.py +97 -0
  8. morphopt/optcore/modelparams/feamodel/__init__.py +0 -0
  9. morphopt/optcore/modelparams/feamodel/feainterface/__init__.py +10 -0
  10. morphopt/optcore/modelparams/feamodel/feainterface/basefeainterface.py +50 -0
  11. morphopt/optcore/modelparams/feamodel/feainterface/bodyforceinterface.py +71 -0
  12. morphopt/optcore/modelparams/feamodel/feainterface/boundaryconditioninterface.py +59 -0
  13. morphopt/optcore/modelparams/feamodel/feainterface/contactinterface.py +113 -0
  14. morphopt/optcore/modelparams/feamodel/feainterface/coupleinterface.py +31 -0
  15. morphopt/optcore/modelparams/feamodel/feainterface/pointinterface.py +79 -0
  16. morphopt/optcore/modelparams/feamodel/feainterface/pressureinterface.py +68 -0
  17. morphopt/optcore/modelparams/feamodel/feainterface/referencepointinterface.py +29 -0
  18. morphopt/optcore/modelparams/feamodel/feainterface/springinterface.py +104 -0
  19. morphopt/optcore/modelparams/feamodel/feaparams.py +264 -0
  20. morphopt/optcore/modelparams/geometry/__init__.py +3 -0
  21. morphopt/optcore/modelparams/geometry/geometryinterfaces/__init__.py +1 -0
  22. morphopt/optcore/modelparams/geometry/geometryinterfaces/basesurfaceinterface.py +763 -0
  23. morphopt/optcore/modelparams/geometry/geometryinterfaces/bspsurfaceinterface.py +723 -0
  24. morphopt/optcore/modelparams/geometry/geometryinterfaces/cpgeosurfaceinterface.py +374 -0
  25. morphopt/optcore/modelparams/geometry/geometryparams.py +715 -0
  26. morphopt/optcore/modelparams/materials/__init__.py +1 -0
  27. morphopt/optcore/modelparams/materials/materialparams.py +110 -0
  28. morphopt/optcore/modelparams/params.py +88 -0
  29. morphopt/optcore/objfunc.py +248 -0
  30. morphopt/optcore/solver.py +159 -0
  31. morphopt/optcore/updaters/__init__.py +2 -0
  32. morphopt/optcore/updaters/base_updater.py +110 -0
  33. morphopt/optcore/updaters/geometry/__init__.py +2 -0
  34. morphopt/optcore/updaters/geometry/objectivefuncs/__init__.py +5 -0
  35. morphopt/optcore/updaters/geometry/objectivefuncs/basefuncs.py +117 -0
  36. morphopt/optcore/updaters/geometry/objectivefuncs/boundarys.py +91 -0
  37. morphopt/optcore/updaters/geometry/objectivefuncs/distancesurface.py +123 -0
  38. morphopt/optcore/updaters/geometry/objectivefuncs/shapederivative.py +179 -0
  39. morphopt/optcore/updaters/geometry/objectivefuncs/surfacefairness.py +40 -0
  40. morphopt/optcore/updaters/geometry/update_geometry.py +347 -0
  41. morphopt/optcore/updaters/optimizer.py +177 -0
  42. morphopt/optcore/updaters/updaters.py +82 -0
  43. morphopt/optcore/utils/plot_history_surface.py +239 -0
  44. morphopt/taskoptmization.py +75 -0
  45. morphopt/taskui.py +458 -0
  46. morphopt-3.1.1.dist-info/METADATA +242 -0
  47. morphopt-3.1.1.dist-info/RECORD +49 -0
  48. morphopt-3.1.1.dist-info/WHEEL +5 -0
  49. morphopt-3.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,763 @@
1
+ import datetime
2
+ import math
3
+ import numpy as np
4
+ import torch
5
+
6
+ import pyvista as pv
7
+
8
+ class BaseInterface():
9
+ """
10
+ Class to handle the surface of the morphable model.
11
+ """
12
+
13
+ def __init__(self, symmetric: list[int] = None, *args, **kwargs) -> None:
14
+ """
15
+ Initialize the Surface class.
16
+
17
+ Parameters:
18
+ surface (Surface_Base) : The surface model.
19
+ surf_type (int) : The type of the surface.
20
+ - 0: bspline surface
21
+ - 1: closed surface
22
+ symmetric (list[int]) : The symmetry of the surface.
23
+ - 0: no symmetry
24
+ - 1: axis symmetry
25
+ 0: x-axis symmetry
26
+ 1: y-axis symmetry
27
+ 2: z-axis symmetry
28
+ """
29
+
30
+ self.symmetric: list[int] = symmetric
31
+ """
32
+ the symmetry of the surface.
33
+ # 0: no symmetry
34
+ # 1: axis symmetry
35
+ ## 0: x-axis symmetry
36
+ ## 1: y-axis symmetry
37
+ ## 2: z-axis symmetry
38
+ """
39
+
40
+ def initialize(self) -> None:
41
+ """
42
+ Initialize the surface.
43
+ """
44
+ pass
45
+
46
+ def reinitialize(self) -> None:
47
+ """
48
+ ReInitialize the surface.
49
+ """
50
+ pass
51
+
52
+ @property
53
+ def control_points(self) -> torch.Tensor:
54
+ """
55
+ Get the control points of the surface.
56
+
57
+ Returns:
58
+ torch.Tensor: The control points of the surface.
59
+ """
60
+ raise NotImplementedError("The control_points property is not implemented in the BaseInterface class. Please implement it in the derived class.")
61
+
62
+ def output_data(self, path_output, name_output, seed_size=-1, flip=False, ):
63
+ """
64
+ Output the surface data to a file.
65
+ """
66
+ raise NotImplementedError("The output_data method is not implemented in the BaseInterface class. Please implement it in the derived class.")
67
+
68
+ def get_surface_parameters(self) -> torch.Tensor:
69
+ """
70
+ Get the design variables of the surface.
71
+
72
+ Returns:
73
+ torch.Tensor: The design variables of the surface.
74
+ """
75
+ raise NotImplementedError("The get_variables method is not implemented in the BaseInterface class. Please implement it in the derived class.")
76
+
77
+ def set_surface_parameters(self, x: torch.Tensor) -> None:
78
+ """
79
+ Set the design variables of the surface.
80
+
81
+ Parameters:
82
+ x (torch.Tensor): The new design variables to be set.
83
+ """
84
+ raise NotImplementedError("The set_variables method is not implemented in the BaseInterface class. Please implement it in the derived class.")
85
+
86
+ def update_variables(self, x_change: torch.Tensor) -> None:
87
+ """
88
+ Update the surface with the new design variables.
89
+
90
+ Parameters:
91
+ x_change (torch.Tensor): The change of design variables to be applied.
92
+ """
93
+
94
+ raise NotImplementedError("The update_variables method is not implemented in the BaseInterface class. Please implement it in the derived class.")
95
+
96
+ def get_geometry_values(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
97
+ """
98
+ Get the geometry values of the surface.
99
+
100
+ Returns:
101
+ tuple: A tuple containing the geometry values of the surface.
102
+ - r (torch.Tensor): The point coordinates of the surface.
103
+ - rdu (torch.Tensor): The partial derivatives of the surface.
104
+ - rdu2 (torch.Tensor): The second partial derivatives of the surface.
105
+ """
106
+ raise NotImplementedError("The get_geometry_values method is not implemented in the BaseInterface class. Please implement it in the derived class.")
107
+
108
+ def get_penalty_fairness(self, r: torch.Tensor, rdu: torch.Tensor, rdu2: torch.Tensor) -> torch.Tensor:
109
+ """
110
+ Get the penalty fairness of the surface.
111
+
112
+ Returns:
113
+ torch.Tensor: The penalty fairness of the surface.
114
+ """
115
+ return 0.0
116
+
117
+ def get_points_weight(self) -> torch.Tensor:
118
+ """
119
+ Get the points weight of the surface.
120
+
121
+ Returns:
122
+ torch.Tensor: The points weight of the surface.
123
+ """
124
+ raise NotImplementedError("The get_points_weight method is not implemented in the BaseInterface class. Please implement it in the derived class.")
125
+
126
+ @staticmethod
127
+ def barrier_function(f: torch.Tensor, f_max: torch.Tensor|float, ratio: float, p: int):
128
+ """
129
+ Apply a barrier function to the objective function.
130
+
131
+ Args:
132
+ f (torch.Tensor): The objective function value.
133
+ f_max (float or torch.Tensor): The maximum value of the objective function.
134
+ ratio (float): The ratio for the barrier function.
135
+ p (float): The exponent for the barrier function.
136
+
137
+ Returns:
138
+ index (torch.Tensor): The indices of the elements that are greater than the barrier.
139
+ fnew (torch.Tensor): The new objective function value after applying the barrier function.
140
+
141
+ """
142
+ if type(f_max) != torch.Tensor:
143
+ f_max = torch.tensor(f_max).repeat(f.shape)
144
+ index = torch.where(f > f_max * ratio)[0]
145
+
146
+ if index.numel() > 0:
147
+ f = f[index]
148
+ f_max = f_max[index]
149
+ fnew = ((f - f_max * ratio) / (f_max - f_max * ratio))**(p)
150
+ else:
151
+ fnew = f[index]
152
+ return index, fnew
153
+
154
+ @property
155
+ def num_variables(self) -> int:
156
+ """
157
+ Get the number of design variables.
158
+
159
+ Returns:
160
+ int: The number of design variables.
161
+ """
162
+ raise NotImplementedError("The num_variables property is not implemented in the BaseInterface class. Please implement it in the derived class.")
163
+
164
+ def save(self, filename: str) -> None:
165
+ """
166
+ Save the surface data to a file.
167
+
168
+ Parameters:
169
+ filename (str): The name of the file to save the surface data.
170
+ """
171
+ raise NotImplementedError("The save method is not implemented in the BaseInterface class. Please implement it in the derived class.")
172
+
173
+ def load(self, filename: str) -> None:
174
+ """
175
+ Load the surface data from a file.
176
+
177
+ Parameters:
178
+ filename (str): The name of the file to load the surface data from.
179
+ """
180
+ raise NotImplementedError("The load method is not implemented in the BaseInterface class. Please implement it in the derived class.")
181
+
182
+ def get_mesh(self) -> pv.PolyData:
183
+ """
184
+ Get the mesh for the surface.
185
+
186
+ Returns:
187
+ object: The mesh object.
188
+ """
189
+ raise NotImplementedError("The get_mesh method is not implemented in the BaseInterface class. Please implement it in the derived class.")
190
+
191
+ def plot(self):
192
+ """
193
+ Plot the surface mesh.
194
+ """
195
+ mesh = self.get_mesh()
196
+ plotter = pv.Plotter()
197
+ plotter.add_mesh(mesh, color='lightblue', show_edges=True)
198
+ plotter.show()
199
+
200
+ class MeshSurfaceConverter:
201
+ def __init__(self):
202
+ self.vertices: np.ndarray
203
+ self.faces: np.ndarray
204
+ self.entity_counter = 10
205
+
206
+ def parse_obj_file(self, obj_content):
207
+ """解析OBJ文件内容"""
208
+ self.vertices = []
209
+ self.faces = []
210
+
211
+ lines = obj_content.strip().split('\n')
212
+
213
+ for line in lines:
214
+ line = line.strip()
215
+ if line.startswith('v '):
216
+ # 解析顶点
217
+ parts = line.split()
218
+ if len(parts) >= 4:
219
+ x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
220
+ self.vertices.append((x, y, z))
221
+ elif line.startswith('f '):
222
+ # 解析面
223
+ parts = line.split()
224
+ face_vertices = []
225
+ for part in parts[1:]:
226
+ # 处理面索引,可能包含纹理和法向量信息 (v/vt/vn)
227
+ vertex_index = int(part.split('/')[0]) - 1 # OBJ索引从1开始
228
+ face_vertices.append(vertex_index)
229
+ self.faces.append(face_vertices)
230
+
231
+ def _generate_stp_header(self, filename="model"):
232
+ """生成STP文件头部"""
233
+ timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+08:00')
234
+
235
+ header = f"""ISO-10303-21;
236
+ HEADER;
237
+ /* Generated by ObjToStpConverter */
238
+
239
+ FILE_DESCRIPTION(
240
+ /* description */ ('Converted from OBJ file'),
241
+ /* implementation_level */ '2;1');
242
+
243
+ FILE_NAME(
244
+ /* name */ '{filename}',
245
+ /* time_stamp */ '{timestamp}',
246
+ /* author */ (''),
247
+ /* organization */ (''),
248
+ /* preprocessor_version */ 'ObjToStpConverter v1.0',
249
+ /* originating_system */ 'Python Script',
250
+ /* authorisation */ '');
251
+
252
+ FILE_SCHEMA (('CONFIG_CONTROL_DESIGN'));
253
+ ENDSEC;
254
+
255
+ DATA;"""
256
+ return header
257
+
258
+ def _generate_stp_footer(self):
259
+ """生成STP文件尾部"""
260
+ return "ENDSEC;\nEND-ISO-10303-21;"
261
+
262
+ def _get_next_entity_id(self):
263
+ """获取下一个实体ID"""
264
+ current_id = self.entity_counter
265
+ self.entity_counter += 1
266
+ return current_id
267
+
268
+ def _find_unique_vertices(self):
269
+ """去重顶点,合并重复的顶点"""
270
+ unique_vertices = []
271
+ vertex_map = {}
272
+ tolerance = 1e-6
273
+
274
+ for i, vertex in enumerate(self.vertices):
275
+ found = False
276
+ for j, unique_vertex in enumerate(unique_vertices):
277
+ if (abs(vertex[0] - unique_vertex[0]) < tolerance and
278
+ abs(vertex[1] - unique_vertex[1]) < tolerance and
279
+ abs(vertex[2] - unique_vertex[2]) < tolerance):
280
+ vertex_map[i] = j
281
+ found = True
282
+ break
283
+
284
+ if not found:
285
+ vertex_map[i] = len(unique_vertices)
286
+ unique_vertices.append(vertex)
287
+
288
+ return unique_vertices, vertex_map
289
+
290
+ def _find_shared_edges(self, vertex_map):
291
+ """找到共享的边"""
292
+ edge_usage = {}
293
+
294
+ for face in self.faces:
295
+ mapped_face = [vertex_map[v] for v in face]
296
+ for i in range(len(mapped_face)):
297
+ v1 = mapped_face[i]
298
+ v2 = mapped_face[(i + 1) % len(mapped_face)]
299
+
300
+ edge = (min(v1, v2), max(v1, v2))
301
+
302
+ if edge not in edge_usage:
303
+ edge_usage[edge] = 0
304
+ edge_usage[edge] += 1
305
+
306
+ return edge_usage
307
+
308
+ def convert_mesh_to_stp(self, faces: np.ndarray, vertices: np.ndarray, filename="model"):
309
+ """将OBJ内容转换为STP格式,严格按照原始STP结构"""
310
+ # 解析OBJ文件
311
+ self.vertices = vertices
312
+ self.faces = faces
313
+
314
+ # 去重顶点
315
+ unique_vertices, vertex_map = self._find_unique_vertices()
316
+ edge_usage = self._find_shared_edges(vertex_map)
317
+
318
+ # 生成STP内容
319
+ stp_content = []
320
+ stp_content.append(self._generate_stp_header(filename))
321
+
322
+ # 统一分配所有ID,避免冲突
323
+
324
+ # 1. 顶级结构实体(固定ID范围:10-20)
325
+ shape_rel_id = 10
326
+ brep_id = 11
327
+ brep_shape_rep_id = 12
328
+ closed_shell_id = 13
329
+
330
+ # 2. 面实体(从14开始)
331
+ face_ids = list(range(14, 14 + len(self.faces)))
332
+ face_bound_ids = list(range(14 + len(self.faces), 14 + 2 * len(self.faces)))
333
+ edge_loop_ids = list(range(14 + 2 * len(self.faces), 14 + 3 * len(self.faces)))
334
+
335
+ current_id = 14 + 3 * len(self.faces)
336
+
337
+ # 3. 为每个面的边创建ORIENTED_EDGE
338
+ oriented_edges_per_face = []
339
+ for face in self.faces:
340
+ face_oriented_edges = []
341
+ for _ in range(len(face)):
342
+ face_oriented_edges.append(current_id)
343
+ current_id += 1
344
+ oriented_edges_per_face.append(face_oriented_edges)
345
+
346
+ # 4. 创建共享的EDGE_CURVE
347
+ edge_curve_map = {}
348
+ for edge in edge_usage.keys():
349
+ edge_curve_map[edge] = current_id
350
+ current_id += 1
351
+
352
+ # 5. 创建B_SPLINE_CURVE
353
+ spline_map = {}
354
+ for edge in edge_usage.keys():
355
+ spline_map[edge] = current_id
356
+ current_id += 1
357
+
358
+ # 6. 创建VERTEX_POINT
359
+ vertex_point_map = {}
360
+ for i in range(len(unique_vertices)):
361
+ vertex_point_map[i] = current_id
362
+ current_id += 1
363
+
364
+ # 7. 创建PLANE
365
+ plane_ids = []
366
+ for _ in range(len(self.faces)):
367
+ plane_ids.append(current_id)
368
+ current_id += 1
369
+
370
+ # 8. 预留产品结构实体的ID
371
+ shape_def_rep_id = current_id
372
+ current_id += 1
373
+ product_def_shape_id = current_id
374
+ current_id += 1
375
+ product_def_id = current_id
376
+ current_id += 1
377
+ product_def_context_id = current_id
378
+ current_id += 1
379
+
380
+ # 9. 分配固定上下文实体的ID
381
+ product_def_formation_id = current_id
382
+ current_id += 1
383
+ application_context_id = current_id
384
+ current_id += 1
385
+ product_id = current_id
386
+ current_id += 1
387
+ product_context_id = current_id
388
+ current_id += 1
389
+ length_unit_id = current_id
390
+ current_id += 1
391
+ plane_angle_unit_id = current_id
392
+ current_id += 1
393
+ solid_angle_unit_id = current_id
394
+ current_id += 1
395
+ uncertainty_measure_id = current_id
396
+ current_id += 1
397
+ geometric_rep_context_id = current_id
398
+ current_id += 1
399
+ shape_representation_id = current_id
400
+ current_id += 1
401
+ axis2_placement_1_id = current_id
402
+ current_id += 1
403
+ axis2_placement_2_id = current_id
404
+ current_id += 1
405
+ direction_1_id = current_id
406
+ current_id += 1
407
+ direction_2_id = current_id
408
+ current_id += 1
409
+ direction_3_id = current_id
410
+ current_id += 1
411
+ direction_4_id = current_id
412
+ current_id += 1
413
+ cartesian_point_1_id = current_id
414
+ current_id += 1
415
+ cartesian_point_2_id = current_id
416
+ current_id += 1
417
+
418
+ # 10. 方向和轴放置
419
+ direction_ids = []
420
+ axis_placement_ids = []
421
+ for _ in range(len(self.faces)):
422
+ direction_ids.append(current_id)
423
+ current_id += 1
424
+ axis_placement_ids.append(current_id)
425
+ current_id += 1
426
+
427
+ # 11. 为CARTESIAN_POINT分配ID
428
+ cartesian_point_map = {}
429
+ for i in range(len(unique_vertices)):
430
+ cartesian_point_map[i] = current_id
431
+ current_id += 1
432
+
433
+ # 12. 为B_SPLINE控制点分配ID
434
+ spline_point_map = {}
435
+ for edge in edge_usage.keys():
436
+ spline_point_map[edge] = (current_id, current_id + 1)
437
+ current_id += 2
438
+
439
+ # 开始生成实体
440
+
441
+ # 核心结构
442
+ stp_content.append(f"#{shape_rel_id}=SHAPE_REPRESENTATION_RELATIONSHIP('','',#{shape_representation_id},#{brep_shape_rep_id});")
443
+ stp_content.append(f"#{brep_id}=MANIFOLD_SOLID_BREP('brep_1',#{closed_shell_id});")
444
+ stp_content.append(f"#{brep_shape_rep_id}=ADVANCED_BREP_SHAPE_REPRESENTATION('brep_rep_0',(#{brep_id},#{axis2_placement_2_id}),#{geometric_rep_context_id});")
445
+
446
+ # CLOSED_SHELL
447
+ face_refs = ','.join([f"#{fid}" for fid in face_ids])
448
+ stp_content.append(f"#{closed_shell_id}=CLOSED_SHELL('',({face_refs}));")
449
+
450
+ # ADVANCED_FACE
451
+ for i, face_id in enumerate(face_ids):
452
+ stp_content.append(f"#{face_id}=ADVANCED_FACE('',(#{face_bound_ids[i]}),#{plane_ids[i]},.T.);")
453
+
454
+ # FACE_OUTER_BOUND
455
+ for i, bound_id in enumerate(face_bound_ids):
456
+ stp_content.append(f"#{bound_id}=FACE_OUTER_BOUND('',#{edge_loop_ids[i]},.T.);")
457
+
458
+ # EDGE_LOOP
459
+ for i, loop_id in enumerate(edge_loop_ids):
460
+ oriented_refs = ','.join([f"#{oe_id}" for oe_id in oriented_edges_per_face[i]])
461
+ stp_content.append(f"#{loop_id}=EDGE_LOOP('',({oriented_refs}));")
462
+
463
+ # ORIENTED_EDGE
464
+ for face_idx, face in enumerate(self.faces):
465
+ mapped_face = [vertex_map[v] for v in face]
466
+ for edge_idx in range(len(mapped_face)):
467
+ v1 = mapped_face[edge_idx]
468
+ v2 = mapped_face[(edge_idx + 1) % len(mapped_face)]
469
+ edge = (min(v1, v2), max(v1, v2))
470
+
471
+ oriented_edge_id = oriented_edges_per_face[face_idx][edge_idx]
472
+ edge_curve_id = edge_curve_map[edge]
473
+
474
+ # 修正方向逻辑:确保边的方向与面的定义一致
475
+ if (v1, v2) == edge:
476
+ orientation = '.T.'
477
+ else:
478
+ orientation = '.F.'
479
+
480
+ stp_content.append(f"#{oriented_edge_id}=ORIENTED_EDGE('',*,*,#{edge_curve_id},{orientation});")
481
+
482
+ # EDGE_CURVE
483
+ for edge, edge_id in edge_curve_map.items():
484
+ v1, v2 = edge
485
+ vertex_point_1 = vertex_point_map[v1]
486
+ vertex_point_2 = vertex_point_map[v2]
487
+ spline_id = spline_map[edge]
488
+ stp_content.append(f"#{edge_id}=EDGE_CURVE('',#{vertex_point_1},#{vertex_point_2},#{spline_id},.T.);")
489
+
490
+ # B_SPLINE_CURVE_WITH_KNOTS
491
+ for edge, spline_id in spline_map.items():
492
+ point1_id, point2_id = spline_point_map[edge]
493
+ stp_content.append(f"#{spline_id}=B_SPLINE_CURVE_WITH_KNOTS('',1,(#{point1_id},#{point2_id}),.UNSPECIFIED.,.F.,.F.,(2,2),(0.,1.),.UNSPECIFIED.);")
494
+
495
+ # VERTEX_POINT
496
+ for i, vertex_point_id in vertex_point_map.items():
497
+ cartesian_id = cartesian_point_map[i]
498
+ stp_content.append(f"#{vertex_point_id}=VERTEX_POINT('',#{cartesian_id});")
499
+
500
+ # PLANE and supporting entities
501
+ for i, plane_id in enumerate(plane_ids):
502
+ stp_content.append(f"#{plane_id}=PLANE('',#{axis_placement_ids[i]});")
503
+
504
+ # 生成产品结构
505
+ stp_content.append(f"#{shape_def_rep_id}=SHAPE_DEFINITION_REPRESENTATION(#{product_def_shape_id},#{shape_representation_id});")
506
+ stp_content.append(f"#{product_def_shape_id}=PRODUCT_DEFINITION_SHAPE('Document','',#{product_def_id});")
507
+
508
+ # 添加所有支持实体
509
+ self._add_supporting_entities(stp_content, unique_vertices, vertex_map, cartesian_point_map,
510
+ spline_point_map, direction_ids, axis_placement_ids,
511
+ product_def_id, product_def_context_id, product_def_formation_id,
512
+ application_context_id, product_id, product_context_id,
513
+ length_unit_id, plane_angle_unit_id, solid_angle_unit_id,
514
+ uncertainty_measure_id, geometric_rep_context_id, shape_representation_id,
515
+ axis2_placement_1_id, axis2_placement_2_id, direction_1_id, direction_2_id,
516
+ direction_3_id, direction_4_id, cartesian_point_1_id, cartesian_point_2_id)
517
+
518
+ stp_content.append(self._generate_stp_footer())
519
+ return '\n'.join(stp_content)
520
+
521
+ def _add_supporting_entities(self, stp_content, unique_vertices, vertex_map, cartesian_point_map,
522
+ spline_point_map, direction_ids, axis_placement_ids,
523
+ product_def_id, product_def_context_id, product_def_formation_id,
524
+ application_context_id, product_id, product_context_id,
525
+ length_unit_id, plane_angle_unit_id, solid_angle_unit_id,
526
+ uncertainty_measure_id, geometric_rep_context_id, shape_representation_id,
527
+ axis2_placement_1_id, axis2_placement_2_id, direction_1_id, direction_2_id,
528
+ direction_3_id, direction_4_id, cartesian_point_1_id, cartesian_point_2_id):
529
+ """添加支持实体,使用唯一ID避免冲突"""
530
+
531
+ # 产品定义结构
532
+ stp_content.append(f"#{product_def_context_id}=PRODUCT_DEFINITION_CONTEXT('3D Mechanical Parts',#{application_context_id},'design');")
533
+ stp_content.append(f"#{product_def_id}=PRODUCT_DEFINITION('A','First version',#{product_def_formation_id},#{product_def_context_id});")
534
+ stp_content.append(f"#{product_def_formation_id}=PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE('A','First version',#{product_id},.MADE.);")
535
+ stp_content.append(f"#{application_context_id}=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies');")
536
+ stp_content.append(f"#{product_id}=PRODUCT('Document','Document','',(#{product_context_id}));")
537
+ stp_content.append(f"#{product_context_id}=PRODUCT_CONTEXT('3D Mechanical Parts',#{application_context_id},'mechanical');")
538
+
539
+ # 单位定义
540
+ stp_content.append(f"#{length_unit_id}=(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.));")
541
+ stp_content.append(f"#{plane_angle_unit_id}=(NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.));")
542
+ stp_content.append(f"#{solid_angle_unit_id}=(NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT());")
543
+
544
+ # 几何表示上下文
545
+ stp_content.append(f"#{uncertainty_measure_id}=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.001),#{length_unit_id},'DISTANCE_ACCURACY_VALUE','Maximum model space distance');")
546
+ stp_content.append(f"#{geometric_rep_context_id}=(GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#{uncertainty_measure_id})) GLOBAL_UNIT_ASSIGNED_CONTEXT((#{solid_angle_unit_id},#{plane_angle_unit_id},#{length_unit_id})) REPRESENTATION_CONTEXT('ID1','3D'));")
547
+
548
+ # 形状表示
549
+ stp_content.append(f"#{shape_representation_id}=SHAPE_REPRESENTATION('Document',(#{axis2_placement_1_id},#{axis2_placement_2_id}),#{geometric_rep_context_id});")
550
+ stp_content.append(f"#{axis2_placement_1_id}=AXIS2_PLACEMENT_3D('',#{cartesian_point_1_id},#{direction_1_id},#{direction_2_id});")
551
+ stp_content.append(f"#{axis2_placement_2_id}=AXIS2_PLACEMENT_3D('',#{cartesian_point_2_id},#{direction_3_id},#{direction_4_id});")
552
+ stp_content.append(f"#{direction_1_id}=DIRECTION('',(0.,0.,1.));")
553
+ stp_content.append(f"#{direction_2_id}=DIRECTION('',(1.,0.,0.));")
554
+ stp_content.append(f"#{direction_3_id}=DIRECTION('',(0.,0.,1.));")
555
+ stp_content.append(f"#{direction_4_id}=DIRECTION('',(1.,0.,0.));")
556
+ stp_content.append(f"#{cartesian_point_1_id}=CARTESIAN_POINT('',(0.,0.,0.));")
557
+ stp_content.append(f"#{cartesian_point_2_id}=CARTESIAN_POINT('',(0.,0.,0.));")
558
+
559
+ # 生成面的方向和轴放置
560
+ for i, (direction_id, axis_id) in enumerate(zip(direction_ids, axis_placement_ids)):
561
+ # 计算面法向量
562
+ face = self.faces[i]
563
+ mapped_face = [vertex_map[v] for v in face]
564
+
565
+ v1 = unique_vertices[mapped_face[0]]
566
+ v2 = unique_vertices[mapped_face[1]]
567
+ v3 = unique_vertices[mapped_face[2]]
568
+
569
+ edge1 = (v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2])
570
+ edge2 = (v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2])
571
+ normal = (
572
+ edge1[1] * edge2[2] - edge1[2] * edge2[1],
573
+ edge1[2] * edge2[0] - edge1[0] * edge2[2],
574
+ edge1[0] * edge2[1] - edge1[1] * edge2[0]
575
+ )
576
+
577
+ length = math.sqrt(normal[0]**2 + normal[1]**2 + normal[2]**2)
578
+ if length > 0:
579
+ normal = (normal[0]/length, normal[1]/length, normal[2]/length)
580
+ else:
581
+ normal = (0, 0, 1)
582
+
583
+ stp_content.append(f"#{direction_id}=DIRECTION('',({normal[0]},{normal[1]},{normal[2]}));")
584
+
585
+ # 使用第一个顶点作为原点
586
+ point_id = cartesian_point_map[mapped_face[0]]
587
+ stp_content.append(f"#{axis_id}=AXIS2_PLACEMENT_3D('',#{point_id},#{direction_id},$);")
588
+
589
+ # 生成CARTESIAN_POINT
590
+ for i, (x, y, z) in enumerate(unique_vertices):
591
+ point_id = cartesian_point_map[i]
592
+ stp_content.append(f"#{point_id}=CARTESIAN_POINT('',({x},{y},{z}));")
593
+
594
+ # 生成B样条控制点
595
+ for edge, (point1_id, point2_id) in spline_point_map.items():
596
+ v1, v2 = edge
597
+ v1_coords = unique_vertices[v1]
598
+ v2_coords = unique_vertices[v2]
599
+ stp_content.append(f"#{point1_id}=CARTESIAN_POINT('',({v1_coords[0]},{v1_coords[1]},{v1_coords[2]}));")
600
+ stp_content.append(f"#{point2_id}=CARTESIAN_POINT('',({v2_coords[0]},{v2_coords[1]},{v2_coords[2]}));")
601
+
602
+
603
+
604
+ class CpBasedInterface(BaseInterface):
605
+ """
606
+ Class to handle the surface of the morphable model.
607
+ """
608
+
609
+ def __init__(self, *args, **kwargs) -> None:
610
+ """
611
+ Initialize the Surface class.
612
+
613
+ Parameters:
614
+ surface (Surface_Base) : The surface model.
615
+ surf_type (int) : The type of the surface.
616
+ - 0: bspline surface
617
+ - 1: closed surface
618
+ symmetric (list[int]) : The symmetry of the surface.
619
+ - 0: no symmetry
620
+ - 1: axis symmetry
621
+ 0: x-axis symmetry
622
+ 1: y-axis symmetry
623
+ 2: z-axis symmetry
624
+ """
625
+
626
+ super().__init__(*args, **kwargs)
627
+
628
+ self._cps: torch.Tensor
629
+ """Control points tensor."""
630
+ self._preload_uv: torch.Tensor
631
+ """Preloaded UV parameters for the surface."""
632
+ self._indices: torch.Tensor
633
+ """Indices in the knot vector at each dimension.
634
+
635
+ shape: (2, num_pairs)
636
+ - [0]: indices for the required points
637
+ - [1]: indices for the control points
638
+ """
639
+ self._weights: torch.Tensor
640
+ """Weights for the control points."""
641
+ self._weights_du: torch.Tensor
642
+ """derivative of the weights with respect to the `first` parameter."""
643
+ self._weights_dv: torch.Tensor
644
+ """derivative of the weights with respect to the `second` parameter."""
645
+ self._weights_du2: torch.Tensor
646
+ """Second derivative of the weights with respect to the `first` parameter."""
647
+ self._weights_dudv: torch.Tensor
648
+ """Mixed derivative of the weights with respect to the `first` and `second` parameters."""
649
+ self._weights_dv2: torch.Tensor
650
+ """Second derivative of the weights with respect to the `second` parameter."""
651
+
652
+
653
+
654
+ def _map(self, weights: torch.Tensor, indices: torch.Tensor, num_pts: int = None) -> torch.Tensor:
655
+
656
+ if num_pts is None:
657
+ num_pts = indices[0].max().item() + 1
658
+
659
+ result = torch.zeros([num_pts, 3], dtype=self._cps.dtype)
660
+ for i in range(3):
661
+ result[:, i].scatter_add_(0, indices[0], weights * self._cps[indices[1], i])
662
+ return result
663
+
664
+ def get_r(self):
665
+ """
666
+ Get the point coordinates of the surface.
667
+
668
+ Returns:
669
+ torch.Tensor: The point coordinates of the surface.
670
+ """
671
+ return self._map(self._weights, self._indices, num_pts=self._preload_uv.shape[0])
672
+
673
+ def get_rdu(self):
674
+ """
675
+ Get the first partial derivatives of the surface.
676
+
677
+ Returns:
678
+ torch.Tensor: The first partial derivatives of the surface.
679
+ - shape: (num_points, 3, 2), the first derivative with respect to the first parameter and the second parameter.
680
+ """
681
+ rdu = self._map(self._weights_du, self._indices, num_pts=self._preload_uv.shape[0])
682
+ rdv = self._map(self._weights_dv, self._indices, num_pts=self._preload_uv.shape[0])
683
+ return torch.stack([rdu, rdv], dim=2)
684
+
685
+ def get_rdu2(self):
686
+ """
687
+ Get the second partial derivatives of the surface.
688
+
689
+ Returns:
690
+ torch.Tensor: The second partial derivatives of the surface.
691
+ - shape: (num_points, 3, 2, 2), the second derivative with respect to the first parameter and the second parameter.
692
+ """
693
+
694
+ rdu2 = self._map(self._weights_du2, self._indices, num_pts=self._preload_uv.shape[0])
695
+ rduv = self._map(self._weights_dudv, self._indices, num_pts=self._preload_uv.shape[0])
696
+ rdv2 = self._map(self._weights_dv2, self._indices, num_pts=self._preload_uv.shape[0])
697
+ return torch.stack([torch.stack([rdu2, rduv], dim=2),
698
+ torch.stack([rduv, rdv2], dim=2)], dim=2)
699
+
700
+ @property
701
+ def control_points(self) -> torch.Tensor:
702
+ """
703
+ Get the control points of the surface.
704
+
705
+ Returns:
706
+ torch.Tensor: The control points of the surface.
707
+ """
708
+ return self._cps
709
+
710
+ def get_surface_parameters(self) -> torch.Tensor:
711
+ """
712
+ Get the design variables of the surface.
713
+
714
+ Returns:
715
+ x (torch.Tensor): The design variables of the surface.
716
+ """
717
+ return self._cps.flatten()
718
+
719
+
720
+ def set_surface_parameters(self, x: torch.Tensor) -> None:
721
+ """
722
+ Set the design variables of the surface.
723
+
724
+ Parameters:
725
+ x (torch.Tensor): The new design variables to be set.
726
+ """
727
+ self._cps = x.reshape(self._cps.shape)
728
+
729
+ def update_variables(self, x_change: torch.Tensor) -> None:
730
+ """
731
+ Update the surface with the new design variables.
732
+
733
+ Parameters:
734
+ x_change (torch.Tensor): The change of design variables to be applied.
735
+ """
736
+ self._cps = self._cps + x_change.reshape(self._cps.shape)
737
+
738
+ def get_geometry_values(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
739
+ """
740
+ Get the geometry values of the surface.
741
+
742
+ Returns:
743
+ tuple: A tuple containing the geometry values of the surface.
744
+ - r (torch.Tensor): The point coordinates of the surface.
745
+ - rdu (torch.Tensor): The partial derivatives of the surface.
746
+ - rdu2 (torch.Tensor): The second partial derivatives of the surface.
747
+ """
748
+ r = self.get_r()
749
+ rdu = self.get_rdu()
750
+ rdu2 = self.get_rdu2()
751
+ return r, rdu, rdu2
752
+
753
+ @property
754
+ def num_variables(self) -> int:
755
+ """
756
+ Get the number of design variables.
757
+
758
+ Returns:
759
+ int: The number of design variables.
760
+ """
761
+ return self._cps.numel()
762
+
763
+