-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathplugin.py
More file actions
324 lines (257 loc) · 13.1 KB
/
Copy pathplugin.py
File metadata and controls
324 lines (257 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import tempfile
from OCP.TopoDS import TopoDS_Shape
import cadquery as cq
import gmsh
def get_tagged_gmsh(self):
"""
Allows the user to get a gmsh object from the assembly, respecting assembly part names and face
tags, but have more control over how it is meshed.
"""
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0)
gmsh.model.add("coil_assembly")
# The mesh volume and surface ids should line up with the order of solids and faces in the assembly
vol_id = 1
surface_id = 1
# Tracks multi-surface physical groups
multi_material_groups = {}
surface_groups = {}
# Holds the collection of individual faces that are tagged
tagged_faces = {}
for obj, name, loc, _ in self:
# CadQuery assembly code prepends a UUID to names sometimes that we do not need
short_name = name.split("/")[-1]
# Separate tagged faces by solid since they might be duplicates
tagged_faces[short_name] = {}
# Extract the tagged faces and make sure they are in the appropriate relative locations
# in the assembly. Tags can hold multiple faces, so we have to extract all of them.
for tag, wp in self.objects[short_name].obj.ctx.tags.items():
for face in wp.faces().all():
# Check to see if we have found this tag before (multi-material tag)
if tag in tagged_faces[short_name]:
tagged_faces[short_name][tag].append(face.val())
else:
tagged_faces[short_name][tag] = [face.val()]
# Extract the tagged faces that have been added by the addSubshape method of cq.Assembly
if self._subshape_names:
for subshape, subshape_tag in self._subshape_names.items():
if subshape_tag in tagged_faces[short_name]:
# Check to see if this is a duplicate
if subshape in tagged_faces[short_name][subshape_tag]:
print(
f"WARNING: Duplicate subshape found for tag {subshape_tag}."
)
tagged_faces[short_name][subshape_tag].append(subshape)
else:
tagged_faces[short_name][subshape_tag] = [subshape]
# Extract the tagged faces that have been added by the addSubshape method of cq.Assembly
if self._subshape_layers:
for subshape, subshape_tag in self._subshape_layers.items():
if subshape_tag in tagged_faces[short_name]:
# Check to see if this is a duplicate
if subshape in tagged_faces[short_name][subshape_tag]:
print(
f"WARNING: Duplicate subshape found for tag {subshape_tag}."
)
tagged_faces[short_name][subshape_tag].append(subshape)
else:
tagged_faces[short_name][subshape_tag] = [subshape]
# All the solids in the current part should be added to the mesh
for s in obj.moved(loc).Solids():
# Add the current solid to the mesh
with tempfile.NamedTemporaryFile(suffix=".brep") as temp_file:
s.exportBrep(temp_file.name)
ps = gmsh.model.occ.importShapes(temp_file.name)
# TODO find a way to check if the OCC in gmsh is compatible with the
# OCC in CadQuery. When pip installed they tend to be incompatible
# and this importShapesNativePointer will seg fault. When both
# packages are conda installed the importShapesNativePointer works.
# Work around that works in both cases is to write a brep and import
# it into gmsh. This is slower but works in all cases.
# gmsh.model.occ.importShapesNativePointer(s.wrapped._address())
gmsh.model.occ.synchronize()
# Technically, importShapes could import multiple entities/dimensions, so filter those
vol_ents = []
for p in ps:
if p[0] == 3:
vol_ents.append(p[1])
# Set the physical name to be the part name in the assembly for all the solids
ps2 = gmsh.model.addPhysicalGroup(3, vol_ents)
gmsh.model.setPhysicalName(3, ps2, f"{name.split('/')[-1]}")
# All the faces in the current part should be added to the mesh
for face in s.Faces():
# Face name can be based on a tag, or just be a generic name
found_tag = False
#
# Handle tagged faces
# Step through the faces in the solid and check them against all the tagged faces
#
for tag, tag_faces in tagged_faces[short_name].items():
for tag_face in tag_faces:
# Move the face to the correct location in the assembly
tag_face = tag_face.moved(loc)
# If OpenCASCADE says the faces are the same, we have a match for the tag
if TopoDS_Shape.IsEqual(face.wrapped, tag_face.wrapped):
# Make sure a generic surface is not added for this face
found_tag = True
# Find out if this is a multi-material tag
if tag.startswith("~"):
# Set the surface name to be the name of the tag without the ~
group_name = tag.replace("~", "").split("-")[0]
# Add this face to the multi-material group
if group_name in multi_material_groups:
multi_material_groups[group_name].append(surface_id)
else:
multi_material_groups[group_name] = [surface_id]
else:
# We want to track all surfaces that might be in a tag group
cur_tag_name = f"{short_name}_{tag}"
if cur_tag_name in surface_groups:
print(
"Append: ", cur_tag_name, short_name, surface_id
)
surface_groups[cur_tag_name].append(surface_id)
else:
print("New: ", cur_tag_name, short_name, surface_id)
surface_groups[cur_tag_name] = [surface_id]
# If no tag was found, set a physical group generic name
if not found_tag:
face_name = f"{short_name}_surface_{surface_id}"
ps = gmsh.model.addPhysicalGroup(2, [surface_id])
gmsh.model.setPhysicalName(2, ps, f"{face_name}")
# Move to the next surface id
surface_id += 1
# Move to the next volume id
vol_id += 1
# Handle tagged surface groups
for t_name, surf_group in surface_groups.items():
ps = gmsh.model.addPhysicalGroup(2, surf_group)
gmsh.model.setPhysicalName(2, ps, t_name)
# Handle multi-material tags
for group_name, mm_group in multi_material_groups.items():
ps = gmsh.model.addPhysicalGroup(2, mm_group)
gmsh.model.setPhysicalName(2, ps, f"{group_name}")
gmsh.model.occ.synchronize()
return gmsh
def assembly_to_gmsh(self, mesh_path="tagged_mesh.msh"):
"""
Pack the assembly into a gmsh object, respecting assembly part names and face tags when creating
the physical groups.
"""
# Turn this assembly with potentially tagged faces into a gmsh object
gmsh = get_tagged_gmsh(self)
gmsh.model.mesh.field.setAsBackgroundMesh(2)
gmsh.model.mesh.generate(3)
gmsh.write(mesh_path)
gmsh.finalize()
def get_imprinted_gmsh(self):
"""
Allows the user to get a gmsh object from the assembly, with the assembly being imprinted.
"""
# Initialize gmsh and create a new model
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0)
gmsh.model.add("assembly")
# The mesh volume and surface ids should line up with the order of solids and faces in the assembly
vol_id = 1
surface_id = 1
# Tracks multi-surface physical groups
multi_material_groups = {}
surface_groups = {}
# Tracks the solids with tagged faces
tagged_faces = {}
solids_with_tagged_faces = {}
# Imprint the assembly
imprinted_assembly, imprinted_solids_with_orginal_ids = (
cq.occ_impl.assembly.imprint(self)
)
for solid, id in imprinted_solids_with_orginal_ids.items():
# Add the current solid to the mesh
# Work-around for a segfault with in-memory passing of OCCT objects
with tempfile.NamedTemporaryFile(suffix=".brep") as temp_file:
solid.exportBrep(temp_file.name)
ps = gmsh.model.occ.importShapes(temp_file.name)
gmsh.model.occ.synchronize()
# Technically, importShapes could import multiple entities/dimensions, so filter those
vol_ents = []
for p in ps:
if p[0] == 3:
vol_ents.append(p[1])
# Set the physical name to be the part name in the assembly for all the solids
ps2 = gmsh.model.addPhysicalGroup(3, vol_ents)
gmsh.model.setPhysicalName(3, ps2, f"{id[0].split('/')[-1]}")
# Get the original assembly part
object_name = id[0].split("/")[-1]
assembly_part = self.objects[object_name]
# Collect any tags from the part
for tag, wp in assembly_part.obj.ctx.tags.items():
tagged_face = wp.faces().all()[0].val()
for face in wp.faces().all():
tagged_faces[face.val()] = tag
# Iterate over the faces of the assembly part
for face in assembly_part.obj.faces():
for tagged_face, tag in tagged_faces.items():
if TopoDS_Shape.IsEqual(face.wrapped, tagged_face.wrapped):
print(f"{vol_id}_{surface_id}", tag)
solids_with_tagged_faces[f"{vol_id}_{surface_id}"] = (
object_name,
tag,
)
surface_id += 1
vol_id += 1
# Reset the volume and surface IDs
vol_id = 1
surface_id = 1
# Step through the imprinted assembly/shape and check for tagged faces
for solid in imprinted_assembly.solids():
for face in solid.faces().Faces():
# Check to see if this face has been tagged
if f"{vol_id}_{surface_id}" in solids_with_tagged_faces.keys():
short_name = solids_with_tagged_faces[f"{vol_id}_{surface_id}"][0]
tag = solids_with_tagged_faces[f"{vol_id}_{surface_id}"][1]
# Find out if this is a multi-material tag
if tag.startswith("~"):
# Set the surface name to be the name of the tag without the ~
group_name = tag.replace("~", "").split("-")[0]
# Add this face to the multi-material group
if group_name in multi_material_groups:
multi_material_groups[group_name].append(surface_id)
else:
multi_material_groups[group_name] = [surface_id]
else:
# We want to track all surfaces that might be in a tag group
cur_tag_name = f"{short_name}_{tag}"
if cur_tag_name in surface_groups:
print("Append: ", cur_tag_name, short_name, surface_id)
surface_groups[cur_tag_name].append(surface_id)
else:
print("New: ", cur_tag_name, short_name, surface_id)
surface_groups[cur_tag_name] = [surface_id]
surface_id += 1
vol_id += 1
# Handle tagged surface groups
for t_name, surf_group in surface_groups.items():
ps = gmsh.model.addPhysicalGroup(2, surf_group)
gmsh.model.setPhysicalName(2, ps, t_name)
# Handle multi-material tags
for group_name, mm_group in multi_material_groups.items():
ps = gmsh.model.addPhysicalGroup(2, mm_group)
gmsh.model.setPhysicalName(2, ps, f"{group_name}")
gmsh.model.occ.synchronize()
return gmsh
def assembly_to_imprinted_gmsh(self, mesh_path="tagged_mesh.msh"):
"""
Exports an imprinted assembly to capture conformal meshes.
"""
# Turn this assembly into a imprinted gmsh object
gmsh = get_imprinted_gmsh(self)
gmsh.model.mesh.field.setAsBackgroundMesh(2)
gmsh.model.mesh.generate(3)
gmsh.write(mesh_path)
gmsh.finalize()
# Patch the new assembly functions into CadQuery's importers package
cq.Assembly.assemblyToGmsh = assembly_to_gmsh
cq.Assembly.saveToGmsh = assembly_to_gmsh # Alias name that works better on cq.Assembly
cq.Assembly.getTaggedGmsh = get_tagged_gmsh
cq.Assembly.assemblyToImprintedGmsh = assembly_to_imprinted_gmsh
cq.Assembly.getImprintedGmsh = get_imprinted_gmsh