-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmesh_boolean_api.py
More file actions
279 lines (235 loc) · 9.66 KB
/
mesh_boolean_api.py
File metadata and controls
279 lines (235 loc) · 9.66 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
"""
Python API for mesh boolean operations in SALOME
no imports from mesh_boolean_dialog = GUI independent
This file is in charge of everything related to Boolean Operations
and gives directly the .med file to the GUI
"""
import tempfile
from enum import Enum
from salome.kernel import salome
from salome.smesh import smeshBuilder
from meshbooleanplugin.mesh_boolean_utils import meshIOConvert
from meshbooleanplugin.vtk import exec_vtk
from meshbooleanplugin.irmb import exec_irmb
from meshbooleanplugin.cork import exec_cork
from meshbooleanplugin.mcut import exec_mcut
from meshbooleanplugin.libigl import exec_libigl
from meshbooleanplugin.cgal import exec_cgal
# To know if the import is already done
import_Dump_Done = False
# Dictionary to track naming increments
_counter = {
"union_num" : 1,
"intersection_num" : 1,
"difference_num" : 1
}
class BooleanMeshAlgorithm(str, Enum):
""" Enumeration of supported mesh boolean algorithms """
CGAL = 'CGAL'
IGL = 'igl'
VTK = 'vtk'
IRMB = 'irmb'
CORK = 'cork'
MCUT = 'mcut'
def runAlgo(algo, operator, mesh_left, mesh_right, result_file):
""" Asserts the boolean operation to the specific algorithm """
if algo == BooleanMeshAlgorithm.VTK :
p = exec_vtk.VTK_main(operator, mesh_left, mesh_right, result_file)
elif algo == BooleanMeshAlgorithm.IRMB :
p = exec_irmb.IRMB_main(operator, mesh_left, mesh_right, result_file)
elif algo == BooleanMeshAlgorithm.CORK :
p = exec_cork.cork_main(operator, mesh_left, mesh_right, result_file)
elif algo == BooleanMeshAlgorithm.MCUT :
p = exec_mcut.mcut_main(operator, mesh_left, mesh_right, result_file)
elif algo == BooleanMeshAlgorithm.IGL :
p = exec_libigl.libigl_main(operator, mesh_left, mesh_right, result_file)
elif algo == BooleanMeshAlgorithm.CGAL :
p = exec_cgal.cgal_main(operator, mesh_left, mesh_right, result_file)
else:
raise ValueError("Unknown algorithm!")
return p
def tmpDir():
""" Creates a secure temporary directory for computation files """
return tempfile.TemporaryDirectory(prefix="BooleanMeshCompute_")
def tmpFile(suffix, prefix="BooleanMeshCompute", tmp_path= None):
""" Generates a temporary file path within the provided directory """
if tmp_path is None:
raise RuntimeError("tmpfile() called without tmp_path")
return tempfile.NamedTemporaryFile(suffix=suffix, prefix=prefix, dir=tmp_path, delete=False).name
def exportToObj(source, tmp_path):
""" Converts a SMESH object or a file path into an obj file """
obj_file = tmpFile(".obj", tmp_path=tmp_path)
stl_tmp = tmpFile(".stl", tmp_path=tmp_path)
#Smesh Object
if hasattr(source, "ExportSTL"):
try:
source.ExportSTL(stl_tmp, False)
meshIOConvert(stl_tmp, obj_file)
return obj_file
except Exception as e:
raise RuntimeError(f"Mesh export failed: {e}") from e
# if the source is already a file path
try:
# always convert to stl, as some elements types are not available in obj format
# converting to stl discard other elements than triangles (and other dimension than 2)
# BE CAREFUL: converting with meshIO does not split quadrangles in triangles
# whereas SMESH's ExportSTL automatically split quadrangles in triangles
meshIOConvert(str(source), stl_tmp)
meshIOConvert(stl_tmp, obj_file)
return obj_file
except Exception as e:
raise RuntimeError(f"Conversion to OBJ failed: {e}") from e
# Algorithms aliases for easier access
CGAL = BooleanMeshAlgorithm.CGAL
IGL = BooleanMeshAlgorithm.IGL
VTK = BooleanMeshAlgorithm.VTK
IRMB = BooleanMeshAlgorithm.IRMB
CORK = BooleanMeshAlgorithm.CORK
MCUT = BooleanMeshAlgorithm.MCUT
#Divide the jobs that loadResult does in mesh_boolean_dialog.py
def convertAlgorithmResult(algo, med_file):
""" Converts the output into a proper MED file that can be read by SALOME """
if algo == CGAL:
exec_cgal.convert_result(med_file)
elif algo == MCUT:
exec_mcut.convert_result(med_file)
elif algo == CORK:
exec_cork.convert_result(med_file)
elif algo == IRMB:
exec_irmb.convert_result(med_file)
elif algo == IGL:
exec_libigl.convert_result(med_file)
elif algo == VTK:
exec_vtk.convert_result(med_file)
def resetCounter():
""" Resets the naming counters for automatic object naming """
for key, value in _counter.items():
_counter[key] = 1
#what CreateMeshesFromMed does in mesh_boolean_dialog.py
def importMedToSmesh(med_file, operator_name = None, name = None):
"""Imports a MED file into the SALOME SMESH study """
smesh = smeshBuilder.New()
smesh.UpdateStudy()
try:
meshes, _ = smesh.CreateMeshesFromMED(med_file)
except Exception as e:
raise RuntimeError(f"Error importing result: {e}") from e
if not meshes:
raise RuntimeError("MED result file not found or empty")
mesh = meshes[0]
#The user can set the name manually
if name :
smesh.SetName(mesh, name)
#if name = None automatically set the file names
else:
if operator_name is None:
raise RuntimeError("No operator name defined")
operator = operator_name.lower()
if operator == 'union' :
auto_name = f"{operator_name}_{_counter['union_num']}"
_counter["union_num"] += 1
elif operator == 'intersection' :
auto_name = f"{operator_name}_{_counter['intersection_num']}"
_counter["intersection_num"]+= 1
else :
auto_name = f"{operator_name}_{_counter['difference_num']}"
_counter["difference_num"] +=1
smesh.SetName(mesh, auto_name)
try:
salome.sg.updateObjBrowser() #Files appear in the Object browser
except Exception:
pass
return mesh
def getMeshIDOrFilename(mesh):
"""Get the mesh id in the object browser or its filename.
Useful for python dump to work in both cases.
"""
if hasattr(mesh, "GetMesh") :
# mesh objet
mesh_id = salome.ObjectToSObject(mesh.GetMesh()).GetID()
else:
# source file
mesh_id = f"'{mesh}'"
return mesh_id
def getMeshObject(mesh):
"""Get the mesh object behind the python mesh"""
if hasattr(mesh, "GetMesh"):
mesh = mesh.GetMesh()
return mesh
def booleanOperation(operator_name, mesh_left, mesh_right, algo, name = None, worker=None):
"""
Main function for boolean operations
Handles temporary directory lifecycle, file conversion, execution and SALOME import
"""
global import_Dump_Done
smesh_builder = smeshBuilder.New()
# Stop the dump recording before the imports to avoid having useless code in the python dump
smesh_builder.PausePythonDumpRecording()
# We now take care of everything related to temporary files management in this fonction rather than in the GUI
try:
with tmpDir() as tmp_path:
# the with assures that the directory is deleted after the compute or if there is an exception
print(f"Temporary directory created: {tmp_path}")
# Convert left and right
objL = exportToObj(mesh_left, tmp_path)
objR = exportToObj(mesh_right, tmp_path)
med_result = tmpFile(".med", tmp_path=tmp_path)
# call runAlgo
process = runAlgo(algo,
operator_name.lower(),
objL,
objR,
med_result
)
if worker is not None:
worker.process = process
# Wait the end of the process if there is one
if process :
rc = process.wait()
if rc != 0:
if worker and not worker._isRunning:
print("Process killed by user")
return None
raise RuntimeError("Boolean operation ended in error")
if worker and not worker._isRunning:
return None
#Convert the result
convertAlgorithmResult(algo, med_result)
#Import in SALOME
result_mesh = importMedToSmesh(med_result, operator_name = operator_name, name = name)
#Add to python dump if not already done
if not import_Dump_Done:
smesh_builder.AddToPythonScript("from meshbooleanplugin import mesh_boolean_api")
import_Dump_Done = True
#Access the IDs of the meshes to set the mesh in the python dump
left_id = getMeshIDOrFilename(mesh_left)
right_id = getMeshIDOrFilename(mesh_right)
result_id = salome.ObjectToSObject(result_mesh.GetMesh()).GetID()
operation_name = operator_name.capitalize()
algo_name = f"mesh_boolean_api.{algo.name}"
cmd = f"{result_id} = mesh_boolean_api.{operation_name}({left_id}, {right_id}, algo = {algo_name})"
#Add the command line to the dump study
smesh_builder.AddToPythonScript(cmd)
print("End of compute, temporary directory will be erased")
return result_mesh
finally:
# Resume the recording of the python dump in any case
smesh_builder.ResumePythonDumpRecording()
#Mesh boolean operations that can be called in terminal
def Union(mesh_left, mesh_right, algo, name = None):
""" Performs a Union operation between two meshes """
# Essential so that in the python dump : if we receive a study object, we extract the mesh of it
# If we select a Mesh object python knows where this objects points and doesn't add .GetMesh() on it
mesh_left = getMeshObject(mesh_left)
mesh_right = getMeshObject(mesh_right)
return booleanOperation("union", mesh_left, mesh_right, algo, name = name)
def Difference(mesh_left, mesh_right, algo, name = None):
""" Performs a Difference operation (left minus right) """
mesh_left = getMeshObject(mesh_left)
mesh_right = getMeshObject(mesh_right)
return booleanOperation("difference", mesh_left, mesh_right, algo, name = name)
def Intersection(mesh_left, mesh_right, algo, name = None):
""" Performs an Intersection operation between two meshes """
mesh_left = getMeshObject(mesh_left)
mesh_right = getMeshObject(mesh_right)
return booleanOperation("intersection", mesh_left, mesh_right, algo, name = name)