Skip to content

Commit 034e261

Browse files
committed
[doc] update README, accept subplot/parent/hold in plotmesh, 0.3.4
1 parent d21839e commit 034e261

File tree

7 files changed

+250
-37
lines changed

7 files changed

+250
-37
lines changed

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
PY=python3
3+
4+
all: pretty test
5+
pretty:
6+
$(PY) -m black test/*.py iso2mesh/*.py setup.py
7+
8+
test:
9+
$(PY) -m unittest test.run_test
10+
11+
report:
12+
@echo '====== all imported functions ======'
13+
@grep '^\s*[a-z]*,' iso2mesh/__init__.py | sed -e 's/^\s*//g' -e 's/,//g' | sort | uniq -c
14+
@echo '====== all tested functions ======'
15+
@grep 'def\s*test' test/run_test.py | sed -e 's/\s*def test_//g' -e 's/(self)://g' -e 's/_.*//g' | sort | uniq -c
16+
17+
.DEFAULT_GOAL=all
18+
.PHONY: all pretty test report

README.md

Lines changed: 148 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
![](https://neurojson.org/wiki/upload/neurojson_banner_long.png)
1+
![](https://iso2mesh.sourceforge.net/images/iso2mesh_2015_banner.png)
22

33
# pyiso2mesh - One-liner 3D Surface and Tetrahedral Mesh Generation Toolbox
44

5-
* Copyright: (C) Qianqian Fang (2024-2025) <q.fang at neu.edu>, Edward Xu (2024) <xu.ed at northeastern.edu>
6-
* License: GNU Public License V3 or later
7-
* Version: 0.3.3
8-
* URL: [https://pypi.org/project/iso2mesh/](https://pypi.org/project/iso2mesh/)
9-
* Github: [https://github.com/NeuroJSON/pyiso2mesh](https://github.com/NeuroJSON/pyiso2mesh)
5+
* **Copyright**: (C) Qianqian Fang (2024-2025) <q.fang at neu.edu>
6+
* **License**: GNU Public License V3 or later
7+
* **Version**: 0.3.4
8+
* **URL**: [https://pypi.org/project/iso2mesh/](https://pypi.org/project/iso2mesh/)
9+
* **Homepage**: [https://iso2mesh.sf.net](https://iso2mesh.sf.net)
10+
* **Github**: [https://github.com/NeuroJSON/pyiso2mesh](https://github.com/NeuroJSON/pyiso2mesh)
11+
* **Acknowledgement**: This project is supported by the US National Institute of Health (NIH)
12+
grants [U24-NS124027](https://reporter.nih.gov/project-details/10308329) and
13+
[R01-CA204443](https://reporter.nih.gov/project-details/10982160)
14+
1015

1116
![Python Module](https://github.com/NeuroJSON/pyiso2mesh/actions/workflows/build_all.yml/badge.svg)
1217

@@ -170,3 +175,140 @@ no, fc, _, _ = v2s(img, 0.5, {'distbound': 0.2})
170175
ax = plotmesh(no, fc, 'y < 30', alpha=0.5, edgecolor='none')
171176
plotmesh(no, fc, 'y > 30', parent = ax)
172177
```
178+
179+
## Iso2Mesh function port status
180+
181+
The progress of converting MATLAB-based Iso2Mesh functions to Python is
182+
tracked in https://github.com/NeuroJSON/pyiso2mesh/issues/1
183+
184+
| Ported | Tested | | Ported | Tested |
185+
| ------ | ------ | --- | ------ | ------ |
186+
| > Streamlined mesh generation - shortcuts| | | > File I/O | |
187+
|`v2m.m` | ✅ tested | |`saveasc.m` | ⭕️ tested |
188+
|`v2s.m` | ✅ tested | |`savedxf.m` | ⭕️ tested |
189+
|`s2m.m` | ✅ tested | |`savestl.m` | ⭕️ tested |
190+
|`s2v.m` | ⭕️ tested | |`savebinstl.m` | ⭕️ tested |
191+
|`m2v.m` | ⭕️ tested | |`saveinr.m` | ⭕️ tested |
192+
|`sms.m` | ✅ tested | |`saveoff.m` | ✅ tested |
193+
| > Streamlined mesh generation| | | ⭕️ `savesmf.m` | ⭕️ tested |
194+
|`vol2mesh.m` | ✅ tested | |`savesurfpoly.m` | ✅ tested |
195+
|`vol2surf.m` | ✅ tested | | ⭕️ `savegts.m` | ⭕️ tested |
196+
|`surf2mesh.m` | ✅ tested | | ⭕️ `readgts.m` | ⭕️ tested |
197+
|`surf2vol.m` | ⭕️ tested | | ⭕️ `savemsh.m` | ⭕️ tested |
198+
|`mesh2vol.m` | ⭕️ tested | | ⭕️ `savevrml.m` | ⭕️ tested |
199+
| > Iso2mesh main function backend| | |`readasc.m` | ⭕️ tested |
200+
|`binsurface.m` | ✅ tested | | ⭕️ `readinr.m` | ⭕️ tested |
201+
|`cgalv2m.m` | ✅ tested | |`readmedit.m` | ⭕️ tested |
202+
|`cgals2m.m` | ⭕️ tested | |`readoff.m` | ✅ tested |
203+
|`vol2restrictedtri.m` | ✅ tested | | ⭕️ `readsmf.m` | ⭕️ tested |
204+
|`surf2volz.m` | ⭕️ tested | |`readtetgen.m` | ✅ tested |
205+
|`mesh2mask.m` | ⭕️ tested | |`deletemeshfile.m` | ✅ tested |
206+
| > Iso2mesh primitive meshing functions| | |`mcpath.m` | ✅ tested |
207+
|`meshabox.m` | ✅ tested | |`mwpath.m` | ✅ tested |
208+
|`meshasphere.m` | ✅ tested | |`savemedit.m` | ✅ tested |
209+
|`meshanellip.m` | ✅ tested | | ⭕️ `savejson.m` | ⭕️ tested |
210+
|`meshunitsphere.m` | ✅ tested | | ⭕️ `loadjson.m` | ⭕️ tested |
211+
|`meshacylinder.m` | ✅ tested | | ⭕️ `saveubjson.m` | ⭕️ tested |
212+
|`meshgrid5.m` | ✅ tested | | ⭕️ `loadubjson.m` | ⭕️ tested |
213+
|`meshgrid6.m` | ✅ tested | | ⭕️ `loadmsgpack.m` | ⭕️ tested |
214+
|`latticegrid.m` | ✅ tested | | ⭕️ `savemsgpack.m` | ⭕️ tested |
215+
|`extrudecurve.m` | ⭕️ tested | | ⭕️ `savebj.m` | ⭕️ tested |
216+
|`meshcylinders.m` | ✅ tested | | ⭕️ `loadbj.m` | ⭕️ tested |
217+
| > Mesh decomposition and query| | | ⭕️ `savemphtxt.m` | ⭕️ tested |
218+
|`finddisconnsurf.m` | ✅ tested | | ⭕️ `savetetgenele.m` | ⭕️ tested |
219+
|`surfedge.m` | ✅ tested | | ⭕️ `savetetgennode.m` | ⭕️ tested |
220+
|`volface.m` | ✅ tested | | ⭕️ `saveabaqus.m` | ⭕️ tested |
221+
|`extractloops.m` | ✅ tested | | ⭕️ `savenirfast.m` | ⭕️ tested |
222+
|`meshconn.m` | ✅ tested | | ⭕️ `readnirfast.m` | ⭕️ tested |
223+
|`meshcentroid.m` | ✅ tested | | ⭕️ `readnifti.m` | ⭕️ tested |
224+
|`nodevolume.m` | ✅ tested | | ⭕️ `readmptiff.m` | ⭕️ tested |
225+
|`elemvolume.m` | ✅ tested | | ⭕️ `loadjsnirf.m` | ⭕️ tested |
226+
|`neighborelem.m` | ✅ tested | | ⭕️ `savejsnirf.m` | ⭕️ tested |
227+
|`layersurf.m` | ⭕️ tested | | ⭕️ `loadsnirf.m` | ⭕️ tested |
228+
|`faceneighbors.m` | ✅ tested | | ⭕️ `savesnirf.m` | ⭕️ tested |
229+
|`edgeneighbors.m` | ✅ tested | | ⭕️ `readobjmesh.m` | ⭕️ tested |
230+
|`maxsurf.m` | ⭕️ tested | | ⭕️ `loadjmesh.m` | ⭕️ tested |
231+
|`flatsegment.m` | ⭕️ tested | | ⭕️ `readobjmesh.m` | ⭕️ tested |
232+
|`orderloopedge.m` | ⭕️ tested | | > Volumetric image pre-processing| |
233+
|`mesheuler.m` | ✅ tested | | ⭕️ `bwislands.m` | ⭕️ tested |
234+
|`bbxflatsegment.m` | ⭕️ tested | | ⭕️ `fillholes3d.m` | ⭕️ tested |
235+
|`surfplane.m` | ⭕️ tested | | ⭕️ `deislands2d.m` | ⭕️ tested |
236+
|`surfinterior.m` | ⭕️ tested | | ⭕️ `deislands3d.m` | ⭕️ tested |
237+
|`surfpart.m` | ⭕️ tested | | ⭕️ `ndgaussian.m` | ⭕️ tested |
238+
|`surfseeds.m` | ⭕️ tested | | ⭕️ `ndimfilter.m` | ⭕️ tested |
239+
|`meshquality.m` | ✅ tested | | ⭕️ `imedge3d.m` | ⭕️ tested |
240+
|`meshedge.m` | ✅ tested | | ⭕️ `internalpoint.m` | ⭕️ tested |
241+
|`meshface.m` | ✅ tested | | ⭕️ `smoothbinvol.m` | ⭕️ tested |
242+
|`surfacenorm.m` | ✅ tested | | ⭕️ `thickenbinvol.m` | ⭕️ tested |
243+
|`nodesurfnorm.m` | ✅ tested | | ⭕️ `thinbinvol.m` | ⭕️ tested |
244+
|`uniqedges.m` | ✅ tested | | ⭕️ `maskdist.m` | ⭕️ tested |
245+
|`uniqfaces.m` | ✅ tested | | > Mesh plotting| |
246+
|`advancefront.m` | ⭕️ tested | |`plotmesh.m` | ✅ tested |
247+
|`innersurf.m` | ⭕️ tested | |`plotsurf.m` | ✅ tested |
248+
|`outersurf.m` | ⭕️ tested | |`plottetra.m` | ✅ tested |
249+
|`surfvolume.m` | ✅ tested | |`plotedges.m` | ✅ tested |
250+
|`insurface.m` | ✅ tested | |`qmeshcut.m` | ✅ tested |
251+
| > Mesh processing and reparing| | | > Miscellaneous functions| |
252+
|`meshcheckrepair.m` | ✅ tested | | ⭕️ `surfdiffuse.m` | ⭕️ tested |
253+
|`meshreorient.m` | ✅ tested | | ⭕️ `volmap2mesh.m` | ⭕️ tested |
254+
|`removedupelem.m` | ✅ tested | | ⭕️ `isoctavemesh.m` | ⭕️ tested |
255+
|`removedupnodes.m` | ✅ tested | | ⭕️ `getvarfrom.m` | ⭕️ tested |
256+
|`removeisolatednode.m` | ✅ tested | |`raytrace.m` | ✅ tested |
257+
|`removeisolatedsurf.m` | ⭕️ tested | | ⭕️ `linextriangle.m` | ⭕️ tested |
258+
|`surfaceclean.m` | ⭕️ tested | | ⭕️ `getplanefrom3pt.m` | ⭕️ tested |
259+
|`getintersecttri.m` | ⭕️ tested | |`getexeext.m` | ✅ tested |
260+
|`delendelem.m` | ⭕️ tested | |`fallbackexeext.m` | ✅ tested |
261+
|`surfreorient.m` | ✅ tested | | ⭕️ `iso2meshver.m` | ⭕️ tested |
262+
| > Mesh registration - Metch Toolbox| | | ⭕️ `raysurf.m` | ⭕️ tested |
263+
|`proj2mesh.m` | ⭕️ tested | | ⭕️ `getoptkey.m` | ⭕️ tested |
264+
|`dist2surf.m` | ⭕️ tested | |`rotatevec3d.m` | ⭕️ tested |
265+
|`regpt2surf.m` | ⭕️ tested | |`rotmat2vec.m` | ⭕️ tested |
266+
|`affinemap.m` | ⭕️ tested | |`varargin2struct.m` | ⭕️ tested |
267+
| > Polyline handling| | |`jsonopt.m` | ⭕️ tested |
268+
| ⭕️ `slicesurf.m` | ⭕️ tested | | ⭕️ `mergestruct.m` | ⭕️ tested |
269+
| ⭕️ `slicesurf3.m` | ⭕️ tested | | ⭕️ `orthdisk.m` | ⭕️ tested |
270+
| ⭕️ `polylinelen.m` | ⭕️ tested | | ⭕️ `nestbracket2dim.m` | ⭕️ tested |
271+
| ⭕️ `polylinesimplify.m` | ⭕️ tested | | ⭕️ `memmapstream.m` | ⭕️ tested |
272+
| ⭕️ `polylineinterp.m` | ⭕️ tested | | ⭕️ `aos2soa.m` | ⭕️ tested |
273+
| ⭕️ `closestnode.m` | ⭕️ tested | | ⭕️ `soa2aos.m` | ⭕️ tested |
274+
| > Mesh resampling and optimization| |
275+
|`meshresample.m` | ✅ tested |
276+
|`remeshsurf.m` | ⭕️ tested |
277+
|`smoothsurf.m` | ✅ tested |
278+
|`sortmesh.m` | ⭕️ tested |
279+
|`mergemesh.m` | ⭕️ tested |
280+
|`meshrefine.m` | ⭕️ tested |
281+
|`mergesurf.m` | ⭕️ tested |
282+
|`surfboolean.m` | ✅ tested |
283+
|`fillsurf.m` | ⭕️ tested |
284+
|`highordertet.m` | ⭕️ tested |
285+
|`elemfacecenter.m` | ⭕️ tested |
286+
|`barydualmesh.m` | ⭕️ tested |
287+
|`meshinterp.m` | ⭕️ tested |
288+
|`meshremap.m` | ⭕️ tested |
289+
|`extrudesurf.m` | ⭕️ tested |
290+
291+
292+
293+
## Acknowledgement
294+
295+
The `pyiso2mesh` module was converted from the MATLAB/Octave version of
296+
of Iso2Mesh (https://github.com/fangq/iso2mesh) written by the same author.
297+
298+
We utilized large-language-model (LLM) and AI chatbot in the initial MATLAB-to-Python
299+
conversion, with specific instructions to **faithfully replicate** the algorithms
300+
developed in the MATLAB code to avoid introducing external copyrighted materials
301+
into this toolbox. With the assumption that the AI chatbot functions primarily
302+
as an automated syntax translator without altering the originality of the code,
303+
the upstream author of the original MATLAB-based Iso2Mesh retains the full copyright
304+
of this derived Python module.
305+
306+
The initial translation was further manually tested, adjusted and restructured
307+
to produce matching outputs as the original MATLAB toolbox. Dr. Edward Xu
308+
<xu.ed at northeastern.edu> had contributed to the initial conversion and
309+
testing of a subset of functions in the `geometry.py` and `trait.py` units.
310+
311+
The development of this software is supported by the US National Institute of Health (NIH)
312+
under research awards [U24-NS124027](https://reporter.nih.gov/project-details/10308329) and
313+
[R01-CA204443](https://reporter.nih.gov/project-details/10982160), with the author,
314+
Dr. Qianqian Fang, serving as the principle investigator on both awards.

iso2mesh/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
barycentricgrid,
138138
)
139139

140-
__version__ = "0.3.3"
140+
__version__ = "0.3.4"
141141
__all__ = [
142142
"advancefront",
143143
"barycentricgrid",

iso2mesh/plot.py

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def plotsurf(node, face, *args, **kwargs):
3838

3939
sc = np.random.rand(10, 3)
4040

41-
ax = _createaxis(*args, **kwargs)
41+
ax = plt.gca()
4242

4343
h = {"fig": [], "ax": [], "obj": []}
4444
h["fig"].append(plt.gcf())
@@ -180,7 +180,7 @@ def plottetra(node, elem, *args, **kwargs):
180180

181181
np.random.seed(randseed)
182182

183-
ax = _createaxis(*args, **kwargs)
183+
ax = plt.gca()
184184

185185
h = {"fig": [], "ax": [], "obj": []}
186186
h["fig"].append(plt.gcf())
@@ -264,7 +264,7 @@ def plotedges(node, edges, *args, **kwargs):
264264
edlen = edges.shape[0]
265265
rng_state = np.random.get_state()
266266

267-
ax = _createaxis(*args, **kwargs)
267+
ax = plt.gca()
268268

269269
hh = {"fig": [], "ax": [], "obj": []}
270270
hh["fig"].append(plt.gcf())
@@ -307,14 +307,6 @@ def plotedges(node, edges, *args, **kwargs):
307307
edges = edges.astype(int) - 1 # 1-based to 0-based
308308

309309
if node.shape[1] >= 3:
310-
ax = plt.gca()
311-
312-
if ax.name != "3d":
313-
plt.figure() # Create a new figure
314-
ax = plt.gcf().add_subplot(
315-
projection="3d"
316-
) # Add 3D axes to the current figure
317-
318310
segments = [[node[start], node[end]] for start, end in edges]
319311
h = Line3DCollection(segments, **kwargs)
320312
ax.add_collection3d(h)
@@ -404,9 +396,6 @@ def plotmesh(node, *args, **kwargs):
404396
(h,) = ax.plot(x[idx], y[idx], z[idx], *opt, **kwargs)
405397
handles["obj"].append(h)
406398
_autoscale_3d(ax, node)
407-
if not "hold" in extraarg or not extraarg["hold"] or extraarg["hold"] == "off":
408-
plt.show(block=False)
409-
return handles
410399

411400
# Plot surface mesh
412401
if face is not None:
@@ -440,6 +429,7 @@ def plotmesh(node, *args, **kwargs):
440429
handles = plottetra(node, elem[idx, :], opt, *args, **kwargs)
441430

442431
if not "hold" in extraarg or not extraarg["hold"] or extraarg["hold"] == "off":
432+
plt.draw()
443433
plt.show(block=False)
444434

445435
return handles
@@ -456,23 +446,29 @@ def _autoscale_3d(ax, points):
456446

457447
def _createaxis(*args, **kwargs):
458448
subplotid = kwargs.get("subplot", 111)
459-
docreate = False if len(args) == 0 else args[0]
449+
docreate = True
450+
fig = None
460451

461452
if "parent" in kwargs:
462-
ax = kwargs["parent"]
463-
if isinstance(ax, dict):
464-
ax = ax["ax"][-1]
465-
elif isinstance(ax, list):
466-
ax = ax[-1]
453+
hh = kwargs["parent"]
454+
if isinstance(hh, dict):
455+
fig = hh["fig"][0]
456+
ax = hh["ax"][-1]
457+
elif isinstance(hh, list):
458+
ax = hh[-1]
459+
if "subplot" in kwargs and fig:
460+
ax = fig.add_subplot(subplotid, projection="3d")
467461
elif not docreate and len(plt.get_fignums()) > 0 and len(plt.gcf().axes) > 0:
468-
ax = plt.gcf().axes[-1]
462+
if not fig:
463+
fig = plt.gcf()
464+
ax = fig.axes[-1]
469465
else:
470-
if docreate:
471-
plt.figure()
472-
ax = plt.gcf().add_subplot(subplotid, projection="3d")
466+
if docreate and not fig:
467+
fig = plt.figure()
468+
ax = fig.add_subplot(subplotid, projection="3d")
473469

474470
if ax.name != "3d":
475-
plt.figure() # Create a new figure
476-
ax = plt.gcf().add_subplot(subplotid, projection="3d")
471+
fig = plt.figure() # Create a new figure
472+
ax = fig.add_subplot(subplotid, projection="3d")
477473

478474
return ax

iso2mesh/trait.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def nodevolume(node, elem, evol=None):
347347

348348
# Loop through each element and accumulate the volumes
349349
for i in range(elemnum):
350-
nodevol[elem[i, :dim]] += evol[i]
350+
nodevol[elem[i, :dim] - 1] += evol[i]
351351

352352
# Divide by the dimensionality to get the final node volumes
353353
nodevol /= dim

setup.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
setup(
77
name="iso2mesh",
88
packages=["iso2mesh"],
9-
version="0.3.3",
10-
license='GPLv3+',
11-
description="Image-based 3D Surface and Volumetric Mesh Generator",
9+
version="0.3.4",
10+
license="GPLv3+",
11+
description="One-liner 3D Surface and Tetrahedral Mesh Generation Toolbox",
1212
long_description=readme,
1313
long_description_content_type="text/markdown",
1414
author="Qianqian Fang",
@@ -18,12 +18,20 @@
1818
keywords=[
1919
"Iso2Mesh",
2020
"Mesh generation",
21+
"Finite element method",
2122
"FEM",
23+
"FEA",
2224
"MATLAB",
2325
"Mesh-based Monte Carlo",
2426
"CGAL",
2527
"Tetgen",
2628
"CSG",
29+
"Boolean",
30+
"Surface mesh",
31+
"Tetrahedral mesh",
32+
"Modeling",
33+
"Multiphysics",
34+
"Image processing",
2735
],
2836
platforms="any",
2937
install_requires=["numpy>=1.8.0", "matplotlib"],

test/run_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,58 @@ def test_elemvolume(self):
577577
result = np.unique(elemvolume(self.no, self.el).round(decimals=6)).tolist()
578578
self.assertEqual(result, expected)
579579

580+
def test_nodevolume(self):
581+
expected = np.sum(elemvolume(self.no, self.el))
582+
result = np.sum(nodevolume(self.no, self.el))
583+
self.assertAlmostEqual(result, expected, 7)
584+
580585
def test_surfvolume(self):
581586
self.assertEqual(surfvolume(self.no, self.fc), 1)
582587

588+
def test_surfacenorm(self):
589+
snorm = surfacenorm(self.no, self.fc)
590+
expected = [
591+
[0.0, 0.0, -1.0],
592+
[0.0, -1.0, 0.0],
593+
[0.0, 0.0, -1.0],
594+
[-1.0, 0.0, 0.0],
595+
[0.0, -1.0, 0.0],
596+
[-1.0, 0.0, 0.0],
597+
[1.0, 0.0, 0.0],
598+
[1.0, 0.0, 0.0],
599+
[0.0, 1.0, 0.0],
600+
[0.0, 1.0, 0.0],
601+
[0.0, -1.0, 0.0],
602+
[-1.0, 0.0, 0.0],
603+
[0.0, -1.0, 0.0],
604+
[-1.0, 0.0, 0.0],
605+
[1.0, 0.0, 0.0],
606+
[1.0, 0.0, 0.0],
607+
[0.0, 1.0, 0.0],
608+
[0.0, 1.0, 0.0],
609+
[0.0, 0.0, 1.0],
610+
[0.0, 0.0, 1.0],
611+
]
612+
self.assertEqual(snorm.tolist(), expected)
613+
614+
def test_nodesurfnorm(self):
615+
nnorm = nodesurfnorm(self.no, self.fc)
616+
expected = [
617+
[-0.57735, -0.57735, -0.57735],
618+
[0.816497, -0.408248, -0.408248],
619+
[-0.408248, 0.816497, -0.408248],
620+
[0.408248, 0.408248, -0.816497],
621+
[-0.707107, -0.707107, 0.0],
622+
[0.707107, -0.707107, 0.0],
623+
[-0.707107, 0.707107, 0.0],
624+
[0.707107, 0.707107, 0.0],
625+
[-0.408248, -0.408248, 0.816497],
626+
[0.408248, -0.816497, 0.408248],
627+
[-0.816497, 0.408248, 0.408248],
628+
[0.57735, 0.57735, 0.57735],
629+
]
630+
self.assertEqual(nnorm.round(6).tolist(), expected)
631+
583632
def test_insurface(self):
584633
pts = np.array([[1.5, -0.9, 2.1], [1, 0, 2], [-1, 0, 2], [1.2, 0, 2.5]])
585634
expected = [1, 1, 0, 1]

0 commit comments

Comments
 (0)