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.
- morphopt/__init__.py +32 -0
- morphopt/opt_runner.py +130 -0
- morphopt/optcore/baseobject.py +48 -0
- morphopt/optcore/controller.py +404 -0
- morphopt/optcore/history.py +440 -0
- morphopt/optcore/modelparams/__init__.py +4 -0
- morphopt/optcore/modelparams/base_params.py +97 -0
- morphopt/optcore/modelparams/feamodel/__init__.py +0 -0
- morphopt/optcore/modelparams/feamodel/feainterface/__init__.py +10 -0
- morphopt/optcore/modelparams/feamodel/feainterface/basefeainterface.py +50 -0
- morphopt/optcore/modelparams/feamodel/feainterface/bodyforceinterface.py +71 -0
- morphopt/optcore/modelparams/feamodel/feainterface/boundaryconditioninterface.py +59 -0
- morphopt/optcore/modelparams/feamodel/feainterface/contactinterface.py +113 -0
- morphopt/optcore/modelparams/feamodel/feainterface/coupleinterface.py +31 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pointinterface.py +79 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pressureinterface.py +68 -0
- morphopt/optcore/modelparams/feamodel/feainterface/referencepointinterface.py +29 -0
- morphopt/optcore/modelparams/feamodel/feainterface/springinterface.py +104 -0
- morphopt/optcore/modelparams/feamodel/feaparams.py +264 -0
- morphopt/optcore/modelparams/geometry/__init__.py +3 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/__init__.py +1 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/basesurfaceinterface.py +763 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/bspsurfaceinterface.py +723 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/cpgeosurfaceinterface.py +374 -0
- morphopt/optcore/modelparams/geometry/geometryparams.py +715 -0
- morphopt/optcore/modelparams/materials/__init__.py +1 -0
- morphopt/optcore/modelparams/materials/materialparams.py +110 -0
- morphopt/optcore/modelparams/params.py +88 -0
- morphopt/optcore/objfunc.py +248 -0
- morphopt/optcore/solver.py +159 -0
- morphopt/optcore/updaters/__init__.py +2 -0
- morphopt/optcore/updaters/base_updater.py +110 -0
- morphopt/optcore/updaters/geometry/__init__.py +2 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/__init__.py +5 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/basefuncs.py +117 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/boundarys.py +91 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/distancesurface.py +123 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/shapederivative.py +179 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/surfacefairness.py +40 -0
- morphopt/optcore/updaters/geometry/update_geometry.py +347 -0
- morphopt/optcore/updaters/optimizer.py +177 -0
- morphopt/optcore/updaters/updaters.py +82 -0
- morphopt/optcore/utils/plot_history_surface.py +239 -0
- morphopt/taskoptmization.py +75 -0
- morphopt/taskui.py +458 -0
- morphopt-3.1.1.dist-info/METADATA +242 -0
- morphopt-3.1.1.dist-info/RECORD +49 -0
- morphopt-3.1.1.dist-info/WHEEL +5 -0
- 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
|
+
|