40
40
import subprocess
41
41
from iso2mesh .utils import *
42
42
from iso2mesh .io import saveoff , readoff
43
- from iso2mesh .trait import meshconn
43
+ from iso2mesh .trait import meshconn , mesheuler
44
44
45
45
##====================================================================================
46
46
## implementations
@@ -179,52 +179,129 @@ def qmeshcut(elem, node, value, cutat):
179
179
nodeid: Interpolation info for intersection points
180
180
"""
181
181
182
- if len (value ) != len (node ) and len (value ) != len (elem ) and len (value ) != 0 :
183
- raise ValueError ("Length of value must match either node or elem" )
182
+ if (
183
+ value .shape [0 ] != node .shape [0 ]
184
+ and value .shape [0 ] != elem .shape [0 ]
185
+ and value .size != 0
186
+ ):
187
+ raise ValueError ("the length of value must be either that of node or elem" )
184
188
185
- # Handle implicit plane definitions
186
- if isinstance (cutat , str ):
189
+ if value .size == 0 :
190
+ cutvalue = []
191
+
192
+ if isinstance (cutat , str ) or (
193
+ isinstance (cutat , list ) and len (cutat ) == 2 and isinstance (cutat [0 ], str )
194
+ ):
187
195
x , y , z = node [:, 0 ], node [:, 1 ], node [:, 2 ]
188
- expr = cutat .split ("=" )
189
- if len (expr ) != 2 :
190
- raise ValueError ('Expression must contain a single "=" sign' )
191
- dist = eval (expr [0 ]) - eval (expr [1 ])
192
- elif isinstance (cutat , list ) and len (cutat ) == 4 :
193
- a , b , c , d = cutat
194
- dist = a * node [:, 0 ] + b * node [:, 1 ] + c * node [:, 2 ] + d
196
+ if isinstance (cutat , str ):
197
+ match = re .match (r"(.+)=([^=]+)" , cutat )
198
+ if not match :
199
+ raise ValueError ('single expression must contain a single "=" sign' )
200
+ expr1 , expr2 = match .groups ()
201
+ dist = eval (expr1 ) - eval (expr2 )
202
+ else :
203
+ dist = eval (cutat [0 ]) - cutat [1 ]
204
+ asign = np .where (dist <= 0 , - 1 , 1 )
205
+ elif not isinstance (cutat , (int , float )) and (len (cutat ) == 9 or len (cutat ) == 4 ):
206
+ if len (cutat ) == 9 :
207
+ a , b , c , d = getplanefrom3pt (np .array (cutat ).reshape (3 , 3 ))
208
+ else :
209
+ a , b , c , d = cutat
210
+ dist = np .dot (node , np .array ([a , b , c ])) + d
211
+ asign = np .where (dist >= 0 , 1 , - 1 )
195
212
else :
213
+ if value .shape [0 ] != node .shape [0 ]:
214
+ raise ValueError (
215
+ "must use nodal value list when cutting mesh at an isovalue"
216
+ )
196
217
dist = value - cutat
218
+ asign = np .where (dist > 0 , 1 , - 1 )
197
219
198
- # Determine which nodes are above/below the cut surface
199
- asign = np .sign (dist )
220
+ esize = elem .shape [1 ]
221
+ if esize == 4 :
222
+ edges = np .vstack (
223
+ [
224
+ elem [:, [0 , 1 ]],
225
+ elem [:, [0 , 2 ]],
226
+ elem [:, [0 , 3 ]],
227
+ elem [:, [1 , 2 ]],
228
+ elem [:, [1 , 3 ]],
229
+ elem [:, [2 , 3 ]],
230
+ ]
231
+ )
232
+ elif esize == 3 :
233
+ edges = np .vstack ([elem [:, [0 , 1 ]], elem [:, [0 , 2 ]], elem [:, [1 , 2 ]]])
234
+ elif esize == 10 :
235
+ edges = np .vstack (
236
+ [
237
+ elem [:, [0 , 4 ]],
238
+ elem [:, [0 , 7 ]],
239
+ elem [:, [0 , 6 ]],
240
+ elem [:, [1 , 4 ]],
241
+ elem [:, [1 , 5 ]],
242
+ elem [:, [1 , 8 ]],
243
+ elem [:, [2 , 5 ]],
244
+ elem [:, [2 , 6 ]],
245
+ elem [:, [2 , 9 ]],
246
+ elem [:, [3 , 7 ]],
247
+ elem [:, [3 , 8 ]],
248
+ elem [:, [3 , 9 ]],
249
+ ]
250
+ )
200
251
201
- # Find edges that cross the cut
202
- edges = np .vstack ([elem [:, [i , j ]] for i in range (3 ) for j in range (i + 1 , 4 )])
203
- cutedges = np .where (np .sum (asign [edges ], axis = 1 ) == 0 )[0 ]
252
+ edgemask = np .sum (asign [edges - 1 ], axis = 1 )
253
+ cutedges = np .where (edgemask == 0 )[0 ]
254
+
255
+ cutweight = dist [edges [cutedges ] - 1 ]
256
+ totalweight = np .diff (cutweight , axis = 1 )[:, 0 ]
257
+ cutweight = np .abs (cutweight / totalweight [:, np .newaxis ])
258
+
259
+ nodeid = edges [cutedges ] - 1
260
+ nodeid = np .column_stack ([nodeid , cutweight [:, 1 ]])
204
261
205
- # Interpolation for cut positions
206
- nodeid = edges [cutedges ]
207
- cutweight = np .abs (
208
- dist [nodeid ] / (dist [nodeid [:, 0 ]] - dist [nodeid [:, 1 ]]).reshape (- 1 , 1 )
209
- )
210
262
cutpos = (
211
- node [nodeid [: , 0 ]] * cutweight [:, 1 ][:, None ]
212
- + node [nodeid [: , 1 ]] * cutweight [:, 0 ][:, None ]
263
+ node [edges [ cutedges , 0 ] - 1 ] * cutweight [:, [ 1 ] ]
264
+ + node [edges [ cutedges , 1 ] - 1 ] * cutweight [:, [ 0 ] ]
213
265
)
214
266
215
- cutvalue = None
216
- if len (value ) == len (node ):
217
- cutvalue = (
218
- value [nodeid [:, 0 ]] * cutweight [:, 1 ]
219
- + value [nodeid [:, 1 ]] * cutweight [:, 0 ]
220
- )
267
+ if value .shape [0 ] == node .shape [0 ]:
268
+ if isinstance (cutat , (str , list )) or (
269
+ not isinstance (cutat , (int , float )) and len (cutat ) in [4 , 9 ]
270
+ ):
271
+ cutvalue = (
272
+ value [edges [cutedges , 0 ] - 1 ] * cutweight [:, [1 ]]
273
+ + value [edges [cutedges , 1 ] - 1 ] * cutweight [:, [0 ]]
274
+ )
275
+ elif np .isscalar (cutat ):
276
+ cutvalue = np .full ((cutpos .shape [0 ], 1 ), cutat )
277
+
278
+ emap = np .zeros (edges .shape [0 ], dtype = int )
279
+ emap [cutedges ] = np .arange (1 , len (cutedges ) + 1 )
280
+ emap = emap .reshape ((- 1 , int (edges .shape [0 ] / elem .shape [0 ])))
281
+
282
+ etag = np .sum (emap > 0 , axis = 1 )
283
+ if esize == 3 :
284
+ linecut = np .where (etag == 2 )[0 ]
285
+ lineseg = emap [linecut ].T
286
+ facedata = lineseg [lineseg > 0 ].reshape ((2 , len (linecut ))).T
287
+ elemid = linecut
288
+ if value .shape [0 ] == elem .shape [0 ] and "cutvalue" not in locals ():
289
+ cutvalue = value [elemid ]
290
+ return cutpos , cutvalue , facedata , elemid , nodeid
221
291
222
- # Organize intersection polygons (faces) and element ids
223
- emap = np .zeros (edges .shape [0 ])
224
- emap [cutedges ] = np .arange (len (cutedges ))
225
- elemid = np .where (np .sum (emap .reshape (- 1 , 6 ), axis = 1 ) > 0 )[0 ]
292
+ tricut = np .where (etag == 3 )[0 ]
293
+ quadcut = np .where (etag == 4 )[0 ]
294
+ elemid = np .concatenate ([tricut , quadcut ])
295
+ if value .shape [0 ] == elem .shape [0 ] and "cutvalue" not in locals ():
296
+ cutvalue = value [elemid ]
226
297
227
- facedata = np .vstack ([emap [cutedges ].reshape (- 1 , 3 )])
298
+ tripatch = emap [tricut ].T
299
+ tripatch = tripatch [tripatch > 0 ].reshape ((3 , len (tricut ))).T
300
+
301
+ quadpatch = emap [quadcut ].T
302
+ quadpatch = quadpatch [quadpatch > 0 ].reshape ((4 , len (quadcut ))).T
303
+
304
+ facedata = np .vstack ([tripatch [:, [0 , 1 , 2 , 2 ]], quadpatch [:, [0 , 1 , 3 , 2 ]]])
228
305
229
306
return cutpos , cutvalue , facedata , elemid , nodeid
230
307
@@ -740,15 +817,15 @@ def mergemesh(node, elem, *args):
740
817
To remove self-intersecting elements, use mergesurf() instead.
741
818
"""
742
819
# Initialize newnode and newelem with input mesh
743
- newnode = node
744
- newelem = elem
820
+ newnode = np . copy ( node )
821
+ newelem = np . copy ( elem )
745
822
746
823
# Check if the number of extra arguments is valid
747
824
if len (args ) > 0 and len (args ) % 2 != 0 :
748
825
raise ValueError ("You must give node and element in pairs" )
749
826
750
827
# Compute the Euler characteristic
751
- X = mesheuler (newelem )
828
+ X = mesheuler (newelem )[ 0 ]
752
829
753
830
# Add a 5th column to tetrahedral elements if not present
754
831
if newelem .shape [1 ] == 4 and X >= 0 :
@@ -771,6 +848,7 @@ def mergemesh(node, elem, *args):
771
848
# Update element indices and append nodes/elements to the merged mesh
772
849
if el .shape [1 ] == 5 or el .shape [1 ] == 4 :
773
850
el [:, :4 ] += baseno
851
+
774
852
if el .shape [1 ] == 4 and X >= 0 :
775
853
el = np .column_stack (
776
854
(el , np .ones ((el .shape [0 ], 1 ), dtype = int ) * (i // 2 + 1 ))
0 commit comments