Skip to content

Commit bafec58

Browse files
committed
[fix] fix qmeshcut, fix mergemesh
1 parent 5e73a04 commit bafec58

File tree

2 files changed

+118
-40
lines changed

2 files changed

+118
-40
lines changed

iso2mesh/modify.py

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import subprocess
4141
from iso2mesh.utils import *
4242
from iso2mesh.io import saveoff, readoff
43-
from iso2mesh.trait import meshconn
43+
from iso2mesh.trait import meshconn, mesheuler
4444

4545
##====================================================================================
4646
## implementations
@@ -179,52 +179,129 @@ def qmeshcut(elem, node, value, cutat):
179179
nodeid: Interpolation info for intersection points
180180
"""
181181

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")
184188

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+
):
187195
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)
195212
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+
)
196217
dist = value - cutat
218+
asign = np.where(dist > 0, 1, -1)
197219

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+
)
200251

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]])
204261

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-
)
210262
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]]
213265
)
214266

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
221291

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]
226297

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]]])
228305

229306
return cutpos, cutvalue, facedata, elemid, nodeid
230307

@@ -740,15 +817,15 @@ def mergemesh(node, elem, *args):
740817
To remove self-intersecting elements, use mergesurf() instead.
741818
"""
742819
# Initialize newnode and newelem with input mesh
743-
newnode = node
744-
newelem = elem
820+
newnode = np.copy(node)
821+
newelem = np.copy(elem)
745822

746823
# Check if the number of extra arguments is valid
747824
if len(args) > 0 and len(args) % 2 != 0:
748825
raise ValueError("You must give node and element in pairs")
749826

750827
# Compute the Euler characteristic
751-
X = mesheuler(newelem)
828+
X = mesheuler(newelem)[0]
752829

753830
# Add a 5th column to tetrahedral elements if not present
754831
if newelem.shape[1] == 4 and X >= 0:
@@ -771,6 +848,7 @@ def mergemesh(node, elem, *args):
771848
# Update element indices and append nodes/elements to the merged mesh
772849
if el.shape[1] == 5 or el.shape[1] == 4:
773850
el[:, :4] += baseno
851+
774852
if el.shape[1] == 4 and X >= 0:
775853
el = np.column_stack(
776854
(el, np.ones((el.shape[0], 1), dtype=int) * (i // 2 + 1))

iso2mesh/plot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def plotsurf(node, face, *args, **kwargs):
2727
rngstate = np.random.get_state()
2828
h = []
2929

30-
randseed = int("623F9A9E", 16) # "U+623F U+9A9E"
30+
randseed = int("623F9A9E", 16)
3131

3232
if "ISO2MESH_RANDSEED" in globals():
3333
randseed = globals()["ISO2MESH_RANDSEED"]
@@ -161,7 +161,7 @@ def plottetra(node, elem, *args, **kwargs):
161161
rngstate = np.random.get_state()
162162

163163
# Set deterministic seed for consistent coloring
164-
randseed = int("623F9A9E", 16) # "U+623F U+9A9E"
164+
randseed = int("623F9A9E", 16)
165165

166166
if "ISO2MESH_RANDSEED" in globals():
167167
randseed = globals()["ISO2MESH_RANDSEED"]

0 commit comments

Comments
 (0)