This commit is contained in:
Nicolas Predella 2026-09-29 14:37:44 +03:00
parent 7a8506e34c
commit 8ba3ea16c4
6 changed files with 287 additions and 72 deletions

View file

@ -62,6 +62,13 @@ wheels = [
"./wheels/shapely_polyskel-0.1.2-py3-none-any.whl", "./wheels/shapely_polyskel-0.1.2-py3-none-any.whl",
"./wheels/euclid3-0.1-py3-none-any.whl", "./wheels/euclid3-0.1-py3-none-any.whl",
"./wheels/pyransac3d-0.6.0-py3-none-any.whl", "./wheels/pyransac3d-0.6.0-py3-none-any.whl",
"./wheels/trimesh-5.1.0-py3-none-any.whl",
"./wheels/manifold3d-3.5.4-cp311-cp311-win_amd64.whl",
"./wheels/manifold3d-3.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",
"./wheels/manifold3d-3.5.4-cp311-cp311-macosx_11_0_arm64.whl",
"./wheels/manifold3d-3.5.4-cp313-cp313-win_amd64.whl",
"./wheels/manifold3d-3.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",
"./wheels/manifold3d-3.5.4-cp313-cp313-macosx_11_0_arm64.whl",
] ]
# Optional: add-ons can list which resources they will require: # Optional: add-ons can list which resources they will require:

View file

@ -6,6 +6,8 @@ import pyclipper
from typing import Any, Literal, cast from typing import Any, Literal, cast
import copy import copy
from shapely.geometry import Polygon, MultiPolygon, LineString, LinearRing, MultiLineString, GeometryCollection, Point from shapely.geometry import Polygon, MultiPolygon, LineString, LinearRing, MultiLineString, GeometryCollection, Point
import manifold3d
import trimesh
from ..infra.blender.blender_mesh_capture import PdfProperties from ..infra.blender.blender_mesh_capture import PdfProperties
@ -655,81 +657,116 @@ class SlicePaths(list[np.ndarray]):
return self.copy_with(new_paths, extrusion_rate=new_ext, feedrate=new_feed, extra_attributes=new_extra) return self.copy_with(new_paths, extrusion_rate=new_ext, feedrate=new_feed, extra_attributes=new_extra)
def slice_at_range(models, z_values, ignore_holes: bool = False) -> list[list[np.ndarray]]: def _slice_single_mesh(mesh_array: np.ndarray, z_values: np.ndarray | list[float], ignore_holes: bool = False) -> list[np.ndarray]:
"""
Slices a single 3D triangle mesh (shape (F, 4, 3) or (F, 3, 3)) across multiple Z heights.
Uses manifold3d for robust solid slicing with exact coplanar/topological resolution,
and falls back to trimesh for open/non-manifold surface meshes.
"""
if len(mesh_array) == 0:
return []
z_arr = np.asarray(z_values, dtype=np.float64)
if len(z_arr) == 0:
return []
tris = mesh_array[:, :3, :] # Shape (F, 3, 3)
flat = np.ascontiguousarray(tris.reshape(-1, 3), dtype=np.float32)
void_dtype = np.dtype((np.void, flat.dtype.itemsize * flat.shape[1]))
flat_void = flat.view(void_dtype).ravel()
unique_voids, inv = np.unique(flat_void, return_inverse=True)
unique_verts = unique_voids.view(flat.dtype).reshape(-1, 3)
faces = inv.reshape(-1, 3).astype(np.uint32)
all_paths: list[np.ndarray] = []
# 1. Primary: manifold3d for solid meshes (fastest, guaranteed watertight topological output)
try:
mesh_m = manifold3d.Mesh(
vert_properties=np.ascontiguousarray(unique_verts),
tri_verts=np.ascontiguousarray(faces)
)
m = manifold3d.Manifold(mesh_m)
# If mesh has inverted normals (negative volume), flip triangle winding
if m.status() == manifold3d.Error.NoError and m.volume() < 0:
faces_flipped = faces[:, [0, 2, 1]]
mesh_m_flipped = manifold3d.Mesh(
vert_properties=np.ascontiguousarray(unique_verts),
tri_verts=np.ascontiguousarray(faces_flipped)
)
m = manifold3d.Manifold(mesh_m_flipped)
if m.status() == manifold3d.Error.NoError and not m.is_empty():
min_z = float(np.min(unique_verts[:, 2]))
max_z = float(np.max(unique_verts[:, 2]))
for z in z_arr:
zf = float(z)
if zf < min_z - 1e-4 or zf > max_z + 1e-4:
continue
# If slicing at boundary face, apply micro-epsilon so boundary is cleanly captured
slice_z = zf - 1e-5 if abs(zf - max_z) < 1e-5 else (zf + 1e-5 if abs(zf - min_z) < 1e-5 else zf)
cs = m.slice(slice_z)
polys = cs.to_polygons()
for poly in polys:
if len(poly) < 3:
continue
pts_scaled = (poly * SCALE).round().astype(np.int64).tolist()
if ignore_holes and not pyclipper.Orientation(pts_scaled):
continue
p3d = np.column_stack([poly.astype(np.float64), np.full(len(poly), zf, dtype=np.float64)])
p3d = np.vstack([p3d, p3d[0]])
p3d = align_path_seam_sharpest(p3d)
all_paths.append(p3d)
return all_paths
except Exception:
pass
# 2. Fallback: trimesh multiplane slicing for open sheets / non-manifold meshes
try:
min_z = float(np.min(unique_verts[:, 2]))
max_z = float(np.max(unique_verts[:, 2]))
slice_heights = np.where(
np.abs(z_arr - max_z) < 1e-5,
z_arr - 1e-5,
np.where(np.abs(z_arr - min_z) < 1e-5, z_arr + 1e-5, z_arr)
)
tm = trimesh.Trimesh(vertices=unique_verts, faces=faces, process=False)
lines, _, _ = trimesh.intersections.mesh_multiplane(
mesh=tm,
plane_origin=[0.0, 0.0, 0.0],
plane_normal=[0.0, 0.0, 1.0],
heights=slice_heights
)
for z, line_segs in zip(z_arr, lines):
if line_segs is None or len(line_segs) == 0:
continue
zf = float(z)
seg2d = np.asarray(line_segs, dtype=np.float64)
z_col = np.full((len(seg2d), 2, 1), zf, dtype=np.float64)
seg3d = np.concatenate([seg2d, z_col], axis=-1)
slice_paths = segments_to_slice(seg3d, ignore_holes=ignore_holes)
all_paths.extend(slice_paths)
except Exception:
pass
return all_paths
def slice_at_range(models: list[np.ndarray], z_values: np.ndarray | list[float], ignore_holes: bool = False) -> list[list[np.ndarray]]:
"""
Top-level slicing function that slices multiple model meshes across given Z heights.
Returns a list of path lists (one per model).
"""
if not models: if not models:
return [] return []
mmps: list[list[np.ndarray]] = [] mmps: list[list[np.ndarray]] = []
for model in models: for model in models:
model_edges: np.ndarray = edges_from_model(model) model_paths = _slice_single_mesh(model, z_values, ignore_holes=ignore_holes)
layered_edges: np.ndarray = layered_model(model_edges, z_values) mmps.append(model_paths)
sliced_edges: list[np.ndarray] = slice_at_z(layered_edges)
all_model_paths: list[np.ndarray] = []
for z, e in zip(z_values, sliced_edges):
if e is None or len(e) == 0:
continue
zf = float(z)
# Build 3D segments directly
seg3d = e.copy()
seg3d[..., 2] = zf
# Build geometry for this slice
slice_paths = segments_to_slice(seg3d, ignore_holes=ignore_holes)
all_model_paths.extend(slice_paths)
mmps.append(all_model_paths)
return mmps return mmps
def edges_from_model(model): # returns a numpy: (facet, 3, 2, xyz)
pairs_indices = np.array([[0, 1], [1, 2], [2, 0]]) # Create an array of indices to select the pairs
return model[:, pairs_indices, :] # Use advanced indexing to create the array
def layered_model(models: np.ndarray, z_values: np.ndarray) -> np.ndarray:
models_array: np.ndarray = np.array(models)[None, :] # (1, F, 3, 2, 3)
m = len(z_values)
# array of zeros with shape (m, 1, 1, 1, 3)
z_offsets_full = np.zeros((m, 1, 1, 1, 3))
z_offsets_full[..., 2] = z_values[:, None, None, None]
layered = models_array - z_offsets_full # (m, F, 3, 2, 3)
return layered
def slice_at_z(edges, normal_vector=np.array([0,0,1]), tolerance=1e-15):
edges_offs = edges # Shape (m, n, 3, 2, 3)
# Obtain intersection points
line_vector = edges_offs[..., 1, :] - edges_offs[..., 0, :]
numerator = np.sum(normal_vector * -edges_offs[..., 0, :], axis=-1)
denominator = np.sum(normal_vector[np.newaxis, np.newaxis, :] * line_vector, axis=-1)
# Avoid division by zero
with np.errstate(divide='ignore', invalid='ignore'):
t = numerator / denominator
t[denominator == 0] = np.nan # Assign NaN where there's no valid intersection
t[(t < 0) | (t > 1)] = np.nan
intersections = edges_offs[..., 0, :] + t[..., np.newaxis] * line_vector
intersections[np.abs(intersections) < tolerance] = 0 # Zero out small values
all_segments = []
for itx in intersections:
mask = ~np.any(np.isnan(itx), axis=-1)
filtered_arr = itx[mask]
segment_count = filtered_arr.shape[-2] // 2
segments = filtered_arr.reshape(segment_count, 2, 3)
# Quantize to micron resolution
segments = np.round(segments * SCALE) / SCALE
all_segments.append(segments)
return all_segments
def link_segments(segments: np.ndarray) -> list[np.ndarray]: def link_segments(segments: np.ndarray) -> list[np.ndarray]:
"""Links segments (N, 2, 3) into continuous paths using 1-micron integer grid.""" """Links segments (N, 2, 3) into continuous paths using 1-micron integer grid."""
if len(segments) == 0: return [] if len(segments) == 0: return []
@ -825,6 +862,8 @@ def segments_to_slice(segments: np.ndarray, ignore_holes: bool = False) -> list[
path_pts = p[:, :2] path_pts = p[:, :2]
path_pts = path_pts[:-1] path_pts = path_pts[:-1]
scaled = (path_pts * SCALE).round().astype(np.int64).tolist() scaled = (path_pts * SCALE).round().astype(np.int64).tolist()
if abs(pyclipper.Area(scaled)) < 1:
continue
pc.AddPath(scaled, pyclipper.PT_SUBJECT, closed = True) # pyright: ignore[reportAttributeAccessIssue] pc.AddPath(scaled, pyclipper.PT_SUBJECT, closed = True) # pyright: ignore[reportAttributeAccessIssue]
if ignore_holes: if ignore_holes:

View file

@ -26,8 +26,8 @@ This file provides a mapping of modules to their primary responsibilities and ke
## `/core` (Geometry & Slicing) ## `/core` (Geometry & Slicing)
- `slice.py`: - `slice.py`:
- `slice_at_range()`: Top-level slicing function that iterates through Z-heights (supports `ignore_holes` option). - `slice_at_range()`: Top-level slicing function that iterates through Z-heights using `manifold3d` for guaranteed watertight solid slicing (resolving coplanar and floating-point degeneracies) with a fallback to `trimesh` multiplane slicing for open sheets (supports `ignore_holes` option).
- `slice_at_z()`: Vectorized plane-mesh intersection using NumPy. - `_slice_single_mesh()`: Mesh slicer implementing `manifold3d` solid slicing and `trimesh` multiplane intersection with micro-epsilon perturbation at boundary faces.
- `align_path_seam_sharpest()`: High-performance vectorized heuristic that positions the start vertex (seam) of closed polygon loops at the sharpest concave angle (inner corner), with fallback to sharpest angle overall. - `align_path_seam_sharpest()`: High-performance vectorized heuristic that positions the start vertex (seam) of closed polygon loops at the sharpest concave angle (inner corner), with fallback to sharpest angle overall.
- `SlicePaths.align_seams()`: Method on `SlicePaths` to align path seams to sharpest concave angles while preserving per-vertex attributes and metadata. - `SlicePaths.align_seams()`: Method on `SlicePaths` to align path seams to sharpest concave angles while preserving per-vertex attributes and metadata.
- `Pather`: Class that converts toolpaths into G-code strings. Supports per-segment extrusion rates, feedrates, and custom extra attributes. - `Pather`: Class that converts toolpaths into G-code strings. Supports per-segment extrusion rates, feedrates, and custom extra attributes.

View file

@ -13,4 +13,6 @@ dependencies = [
"shapely-polyskel>=0.1.2", "shapely-polyskel>=0.1.2",
"euclid3>=0.1", "euclid3>=0.1",
"pyransac3d>=0.6.0", "pyransac3d>=0.6.0",
"manifold3d>=3.5.4",
"trimesh>=5.1.0",
] ]

View file

@ -1872,6 +1872,102 @@ class TestCodeReviewBugFixes(unittest.TestCase):
gname = get_gcode_name_from_metadata(gc) gname = get_gcode_name_from_metadata(gc)
self.assertEqual(gname, "BracketA-PLA-MK4") self.assertEqual(gname, "BracketA-PLA-MK4")
def test_robust_mesh_slicing_coplanar_cube(self):
from NodeSlicer.core.slice import slice_at_range, is_closed_path
# 10x10x10 cube
verts = np.array([
[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0],
[0, 0, 10], [10, 0, 10], [10, 10, 10], [0, 10, 10]
], dtype=float)
faces = [
[0, 2, 1], [0, 3, 2], [4, 5, 6], [4, 6, 7],
[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5],
[2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]
]
triangles = np.zeros((len(faces), 4, 3))
for i, f in enumerate(faces):
triangles[i, 0] = verts[f[0]]
triangles[i, 1] = verts[f[1]]
triangles[i, 2] = verts[f[2]]
# Slice at flat bottom (z=0.0), middle (z=5.0), and flat top (z=10.0)
res = slice_at_range([triangles], [0.0, 5.0, 10.0])
self.assertEqual(len(res), 1)
paths = res[0]
self.assertEqual(len(paths), 3)
for p in paths:
self.assertTrue(is_closed_path(p))
self.assertTrue(len(p) >= 4)
def test_robust_mesh_slicing_octahedron_coplanar_vertices_and_edges(self):
from NodeSlicer.core.slice import slice_at_range, is_closed_path
# Octahedron centered at origin with 4 vertices lying exactly at z=0.0
verts = np.array([
[5, 0, 0], [-5, 0, 0], [0, 5, 0], [0, -5, 0], [0, 0, 5], [0, 0, -5]
], dtype=float)
faces = [
[0, 2, 4], [2, 1, 4], [1, 3, 4], [3, 0, 4],
[2, 0, 5], [1, 2, 5], [3, 1, 5], [0, 3, 5],
]
triangles = np.zeros((len(faces), 4, 3))
for i, f in enumerate(faces):
triangles[i, 0] = verts[f[0]]
triangles[i, 1] = verts[f[1]]
triangles[i, 2] = verts[f[2]]
res = slice_at_range([triangles], [0.0])
self.assertEqual(len(res), 1)
paths = res[0]
self.assertEqual(len(paths), 1)
self.assertTrue(is_closed_path(paths[0]))
def test_robust_mesh_slicing_staircase_step(self):
from NodeSlicer.core.slice import slice_at_range, is_closed_path
# Stepped mesh with a plateau at z=5.0
verts = np.array([
[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0],
[20, 0, 0], [20, 10, 0],
[10, 0, 5], [20, 0, 5], [20, 10, 5], [10, 10, 5],
[0, 0, 10], [10, 0, 10], [10, 10, 10], [0, 10, 10]
], dtype=float)
faces = [
[0, 2, 1], [0, 3, 2], [1, 5, 4], [1, 2, 5],
[10, 11, 12], [10, 12, 13],
[6, 7, 8], [6, 8, 9],
[0, 1, 6], [0, 6, 10], [10, 6, 11],
[1, 4, 7], [1, 7, 6],
[4, 5, 8], [4, 8, 7],
[5, 2, 9], [5, 9, 8],
[2, 3, 13], [2, 13, 9], [9, 13, 12],
[3, 0, 10], [3, 10, 13],
[11, 6, 9], [11, 9, 12]
]
triangles = np.zeros((len(faces), 4, 3))
for i, f in enumerate(faces):
triangles[i, 0] = verts[f[0]]
triangles[i, 1] = verts[f[1]]
triangles[i, 2] = verts[f[2]]
res = slice_at_range([triangles], [0.0, 2.5, 5.0, 7.5])
self.assertEqual(len(res), 1)
paths = res[0]
self.assertEqual(len(paths), 4)
for p in paths:
self.assertTrue(is_closed_path(p))
def test_robust_mesh_slicing_open_sheet(self):
from NodeSlicer.core.slice import slice_at_range
# Open triangle in 3D
open_tri = np.zeros((1, 4, 3))
open_tri[0, 0] = [0, 0, 0]
open_tri[0, 1] = [10, 0, 10]
open_tri[0, 2] = [0, 10, 5]
res = slice_at_range([open_tri], [3.0])
self.assertEqual(len(res), 1)
self.assertEqual(len(res[0]), 1)
self.assertEqual(len(res[0][0]), 2) # 1 line segment with 2 endpoints
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

75
uv.lock generated
View file

@ -2,7 +2,8 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.10" requires-python = ">=3.10"
resolution-markers = [ resolution-markers = [
"python_full_version >= '3.11'", "python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
@ -21,6 +22,58 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/73/2f8c20f53ee3a9692cab552d02c7178afd15e1b15bb335b5d90feb943bbe/fake_bpy_module-20260317-py3-none-any.whl", hash = "sha256:399ac3f8479c236a023f632733df6bbb40af8340cc3c3c05b41f3b63bb732127", size = 1013802, upload-time = "2026-03-17T06:53:22.489Z" }, { url = "https://files.pythonhosted.org/packages/77/73/2f8c20f53ee3a9692cab552d02c7178afd15e1b15bb335b5d90feb943bbe/fake_bpy_module-20260317-py3-none-any.whl", hash = "sha256:399ac3f8479c236a023f632733df6bbb40af8340cc3c3c05b41f3b63bb732127", size = 1013802, upload-time = "2026-03-17T06:53:22.489Z" },
] ]
[[package]]
name = "manifold3d"
version = "3.5.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/0f/1f7e51a8f9a6f25e837159c5bf7c95a3ae948aa12e1c5fdeee0fd90ca5f9/manifold3d-3.5.4.tar.gz", hash = "sha256:5bd88c482c6fd7fc01aa7b715b3e31c2585ee98343d5559d286ba30db0d0b6f0", size = 307695, upload-time = "2026-09-25T10:46:09.083Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/43/cd66885a0814c5c0288f6926d5a57cef817370f948a09e2ac532151b8b51/manifold3d-3.5.4-cp310-cp310-macosx_10_14_universal2.whl", hash = "sha256:7e43cdeeab20fd58bf3cb513de18ea04db194f4f64f6f1b4d8080590c4206389", size = 1829635, upload-time = "2026-09-25T10:45:07.23Z" },
{ url = "https://files.pythonhosted.org/packages/fc/5a/43625c7dbefed9d68985847fb4edef7005931779ffdbc56e8b986785948c/manifold3d-3.5.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:afe19a01f74b0223525ca3511b24cc119d8cbfa51a141fe2727464860fc54c5b", size = 991040, upload-time = "2026-09-25T10:45:08.821Z" },
{ url = "https://files.pythonhosted.org/packages/a2/b5/1e81f1937afb60e1bb2495dab78cc1ec784d8795e7a030b89114e22049ab/manifold3d-3.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0d8e25a2ea72ecba7c543c624e665c5c5078a38247e979f3c968b6480feebf97", size = 871023, upload-time = "2026-09-25T10:45:10.257Z" },
{ url = "https://files.pythonhosted.org/packages/b8/ab/e846ef088ca8183833d0249b3cc129c136b6a131c317576c1d33ec1ecbe1/manifold3d-3.5.4-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d4a0e3a982bdce4b97877164f6a1d5e2b1efc2a9144ffd7b6c3d0eb714ce850", size = 1303309, upload-time = "2026-09-25T10:45:11.709Z" },
{ url = "https://files.pythonhosted.org/packages/9b/97/1c7a5b11768c47f09073bc996b0fc87778fd8b4ca0c1cea5722b89532268/manifold3d-3.5.4-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b1eb3c3c2de01c048244fd9e8b21e45dc37043e5bc47106df35e564377ef85a9", size = 1411677, upload-time = "2026-09-25T10:45:13.405Z" },
{ url = "https://files.pythonhosted.org/packages/41/3a/e85795e0bf86b72e5d46868cca46cd763ae5eeb926254101dc15a5fff487/manifold3d-3.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:06ee0b2ebec889e4ab447b1b4e9e845dd2e1ee05d02454e838fb63723848c579", size = 1022593, upload-time = "2026-09-25T10:45:15.198Z" },
{ url = "https://files.pythonhosted.org/packages/5a/c9/eae4830cc72c6cc1f8846795628cb4eea57bda450a138bc4d32597f669cf/manifold3d-3.5.4-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:1b948c0dab18195d1ea22e57cf89b13757c2b062e8f64d68ef55cd952a4f5a32", size = 1843741, upload-time = "2026-09-25T10:45:16.781Z" },
{ url = "https://files.pythonhosted.org/packages/ce/13/50980f98bbd90539bbe3efddba61819f077ea3a50aa24f8e5b4da227bad1/manifold3d-3.5.4-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:b4b2a9ed95c51b6dbb6f22dbc01ec7d696c10041fd1d29a7cc31ae75a33943d3", size = 1005422, upload-time = "2026-09-25T10:45:18.148Z" },
{ url = "https://files.pythonhosted.org/packages/2e/7f/4af914c508e3bc8532efeb5bef1767281e6605da82db9f2dac58ab90711f/manifold3d-3.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b9eedfdd50868b553e5bd5c83b6d99f8d2d70007cba3bf849ae90722a57ecfc", size = 885164, upload-time = "2026-09-25T10:45:19.393Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a4/331345b949affcf279de06c1d98e9f944c2b2a0c1f3be098e051bd2849c5/manifold3d-3.5.4-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2498cc4c2e9b623e5b557d57ef91af9e5881dc1b592e720f74e004bc145a73c", size = 1317453, upload-time = "2026-09-25T10:45:20.886Z" },
{ url = "https://files.pythonhosted.org/packages/0c/f1/28ee6afc0deef7ad65cb1ee2f757d9c2d50518bd64b08bdfcf5e8e0ff278/manifold3d-3.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d021c4096aa0e40748c0720b8386002de349b788b3ad7d92a3dba214110d0f0e", size = 1426317, upload-time = "2026-09-25T10:45:22.864Z" },
{ url = "https://files.pythonhosted.org/packages/b7/42/8dc2e031840b8d2a8491df31ae0ea19d95e4da70d33711ff798055180f8f/manifold3d-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:34ef0b1b70b6d153eb12a72706e128e9b629c562cd4ab103083afd17ca071fbd", size = 1037097, upload-time = "2026-09-25T10:45:24.316Z" },
{ url = "https://files.pythonhosted.org/packages/5c/62/cc6328443b8122183760cc26ccb0b239f8531492c7d0ad1cedc5c15f41ad/manifold3d-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:df86fafec8268e6b164a7338b1dd9eb77139870ed5fa0e0d343a7d9870f3a833", size = 1146339, upload-time = "2026-09-29T05:41:53.512Z" },
{ url = "https://files.pythonhosted.org/packages/ad/f4/3bde89d5e37eb223ef7493b9ba782462dd8aea5af07d0370e6ce8a847186/manifold3d-3.5.4-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:d4b47d7b0aba1ba1b575e7d549be8c9ba2d552b03e4043e3022454fdaad13447", size = 1843283, upload-time = "2026-09-25T10:45:25.725Z" },
{ url = "https://files.pythonhosted.org/packages/1f/95/3b2e63c154d3b2e70cb8e52fc72a061eaf1bc82e826c955b324381162dc7/manifold3d-3.5.4-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:b1ae587cf7cfa489fb50bba84a0e048d9daadd105b6a047a312e45a7602163fa", size = 1005349, upload-time = "2026-09-25T10:45:27.09Z" },
{ url = "https://files.pythonhosted.org/packages/66/28/6ec4f751be1b5868964eb92925da789c985efdaff5f0a5f85b47cbd87428/manifold3d-3.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d440e37d1e9888f90f42c70b4b3da98655be54105616e7739d84692bf7bda671", size = 884727, upload-time = "2026-09-25T10:45:28.554Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f2/15ab84b2a80dcdc91ef53cde5fafdeb63cc3989646f17da7542011522b58/manifold3d-3.5.4-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f4e8ca1c59d68047b262dbdb6e917ac4abff2942a65e7e469f4a398ce1f0b9b", size = 1317923, upload-time = "2026-09-25T10:45:30.034Z" },
{ url = "https://files.pythonhosted.org/packages/9e/04/91323ab10f23d185931b572622589673ec561a8106fa1ae0f2fc876fb960/manifold3d-3.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a985892699268bbf07192b78b5e5569034c41ce74bf8183495123e8095af4fe", size = 1427177, upload-time = "2026-09-25T10:45:31.537Z" },
{ url = "https://files.pythonhosted.org/packages/a9/35/6b67f3af439f209b6f4ba653a5f3500f56464c99274fb4aa21323886c762/manifold3d-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:5be1025860df8a83ed3c4fd585d87376b075e23ea45e833578fe77eacc49bee1", size = 1036128, upload-time = "2026-09-25T10:45:32.819Z" },
{ url = "https://files.pythonhosted.org/packages/79/55/aacf4b263a8bef710108cdcf1469156b53e44a63af574b435262b0e46820/manifold3d-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:559b6cd07976a6ab3edf9806e5f1757432c397589069e01a0045e78d326ff510", size = 1145058, upload-time = "2026-09-29T05:41:55.705Z" },
{ url = "https://files.pythonhosted.org/packages/56/a7/12b1585a876eca4a1cf4985c45ce165a9c13583dd7b74f0c1f19cbf7c23e/manifold3d-3.5.4-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:f26933565a268f91e222a494570bebe494bde204277134b3e32e0d6ac686a9ad", size = 1843251, upload-time = "2026-09-25T10:45:34.177Z" },
{ url = "https://files.pythonhosted.org/packages/b5/3b/4f030178617d1309883a857318e2764ca9ddb1080e9d9be66f326ce30286/manifold3d-3.5.4-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:95b512975ae13d5ecc57a9d388cf84be4cc717add5173d7d5fa408e6d6a07677", size = 1005296, upload-time = "2026-09-25T10:45:35.763Z" },
{ url = "https://files.pythonhosted.org/packages/5e/55/6f434b0b4750565ac57dd5dcd3b2770b7de408988f0d9a044bf587863e7d/manifold3d-3.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed0736f472c3362b8084867f10a724a9a483927bdb7cbfceb564acc52a7f4273", size = 884711, upload-time = "2026-09-25T10:45:37.364Z" },
{ url = "https://files.pythonhosted.org/packages/5c/91/f5ccf45874cd667005107b654390e6379a2d970ed5fe6e5989bf24f4c7d6/manifold3d-3.5.4-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b46d2f19017fc2f3095d71cc111b8574e92afd5515d70529720324da5b1c0ed", size = 1317898, upload-time = "2026-09-25T10:45:38.768Z" },
{ url = "https://files.pythonhosted.org/packages/ac/9e/993cc841e4d88e2c8e6b1c5dfc52d4d0e8f78a46fbeaa98ff9bd539e3248/manifold3d-3.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a5469641713f1bc291fea873cb134c435704df36828d134190fc2475b304259", size = 1427121, upload-time = "2026-09-25T10:45:40.199Z" },
{ url = "https://files.pythonhosted.org/packages/ea/7a/316e60dea127721fcd523210c2e6c641ab2fa5f6c957eb8a83222f0d7ee4/manifold3d-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:76975aa4ebd18dcb0481e43dac3ed70d78066e9ad047e73a5f7dbc0df1553cf4", size = 1036021, upload-time = "2026-09-25T10:45:41.554Z" },
{ url = "https://files.pythonhosted.org/packages/d1/e7/a96928beac5287485cc0f61ac34c3b2ee42f57221d279a7d3bb176739a4d/manifold3d-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:552ffb1f79ef77f1c7e05d638ee43e7fe88c652b1f9b042a88aab1fef85fc28c", size = 1144955, upload-time = "2026-09-29T05:41:57.75Z" },
{ url = "https://files.pythonhosted.org/packages/b8/20/86f3ec8c2481a002b378aeb28341f613eb8c10a2dec6945be7c1a4db0647/manifold3d-3.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c96a28ef7ed7a34e37cad775db95831051f03ca9e188f572b0a08b3561f4763", size = 1843034, upload-time = "2026-09-25T10:45:43.258Z" },
{ url = "https://files.pythonhosted.org/packages/ce/de/f57e7aebf0092bca0a6a3435d45105043a1f902cc89289264ab68823b07e/manifold3d-3.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:487b351a9b064353c9a8cb86b9f004ec0064511d121355a0fe738e96a5ca5306", size = 1004990, upload-time = "2026-09-25T10:45:44.55Z" },
{ url = "https://files.pythonhosted.org/packages/43/83/6d168952066c22749fdc570a67ba580271057b3da9da299d9cac32e3b374/manifold3d-3.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3d6a81f79f4812dd9e88c8093588c31c6e863c410f61a274656f9cdff76b80c0", size = 884652, upload-time = "2026-09-25T10:45:46.168Z" },
{ url = "https://files.pythonhosted.org/packages/00/cd/475da5e3d38586edd0c487e116a77896d28dfc0f90fdbf9bb1f1a64057c9/manifold3d-3.5.4-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7869f140b830faf93a76da3705d814b8e1652f3f68aab39351bfc6f05d93e01", size = 1319035, upload-time = "2026-09-25T10:45:47.572Z" },
{ url = "https://files.pythonhosted.org/packages/75/9f/d91de303052c3b6d851c14cc60c4ff9aabcdb4ed79e6a6122fde5c668aa9/manifold3d-3.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fc18f0712b5ff10fdc5307f9abc8a40affe3a5f8559a19b9c03e3a82e4513b", size = 1427465, upload-time = "2026-09-25T10:45:48.848Z" },
{ url = "https://files.pythonhosted.org/packages/17/a6/396f72fbcab489cb2678c066468fe2e33d7ce577351d11f948f0a7527d21/manifold3d-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:a04a30eb686e913e1872dce6f537947d64a01c5b4a882af0be47beef2309b074", size = 1068405, upload-time = "2026-09-25T10:45:50.573Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e8/f6751f97187a8e7fddc446ac5d73f5000cf4dcc033dec5683fea3fe906ff/manifold3d-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:8c309c698837fdbca297ad7387f5348526e6635e49ce28476e2c9dcad30a947e", size = 1181481, upload-time = "2026-09-29T05:41:59.352Z" },
{ url = "https://files.pythonhosted.org/packages/93/00/5f7651472bab7378cc57b6a15edba155d056542b4c0c117b743f5cdc8a44/manifold3d-3.5.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ee967a8f1b526194ea8a598e04d2fd6920a9a78e9d465a7b78e2a6827585b8f3", size = 1853599, upload-time = "2026-09-25T10:45:51.941Z" },
{ url = "https://files.pythonhosted.org/packages/f8/b4/63fa966d1138716a78c4e856c7846088f74a6797cc18346e6dbc8601f82f/manifold3d-3.5.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a55945fccc1ad20c958bedf61397a12ea5329f897a29f2237c70e12bdf7bd51d", size = 1011076, upload-time = "2026-09-25T10:45:53.499Z" },
{ url = "https://files.pythonhosted.org/packages/28/2f/e200a5f4204d935435df2c6d58b35da8f2bfaaab61839365b2c1bac614e9/manifold3d-3.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5284a8a14fc86798d1698b37729e519516ba6c0574624ae16542032764adf189", size = 890394, upload-time = "2026-09-25T10:45:55.033Z" },
{ url = "https://files.pythonhosted.org/packages/19/8d/3856eefc139ab1f18e928abcc4531515e75d4a43db31a6180583c33d90f0/manifold3d-3.5.4-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd68576b104f563a8d2a9629f7de565c5ac704f276394f9ffdb9f0c5a05c20d8", size = 1326941, upload-time = "2026-09-25T10:45:56.43Z" },
{ url = "https://files.pythonhosted.org/packages/0f/24/8c9d993e89cf758c3dbbd4470dcf3efc5efef11dcd8f89e208539fd11b21/manifold3d-3.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae80814e5c6f85f4c53f632b71009fdfa185645a1d68c673d0d7a6171c29f78b", size = 1433600, upload-time = "2026-09-25T10:45:57.904Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f0/fac6f8eaff4617eeab741c87bfae8a6afd1057648b0551f206c4247b0407/manifold3d-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:d034b1d74df1aad9377f30b2228c33b3887bf5057c7efd98a86892604edd86aa", size = 1070681, upload-time = "2026-09-25T10:45:59.412Z" },
]
[[package]] [[package]]
name = "nodeslicer" name = "nodeslicer"
version = "0.1.0" version = "0.1.0"
@ -28,6 +81,7 @@ source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "euclid3" }, { name = "euclid3" },
{ name = "fake-bpy-module" }, { name = "fake-bpy-module" },
{ name = "manifold3d" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "pyclipper" }, { name = "pyclipper" },
@ -35,18 +89,21 @@ dependencies = [
{ name = "python-libnest2d" }, { name = "python-libnest2d" },
{ name = "shapely" }, { name = "shapely" },
{ name = "shapely-polyskel" }, { name = "shapely-polyskel" },
{ name = "trimesh" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "euclid3", specifier = ">=0.1" }, { name = "euclid3", specifier = ">=0.1" },
{ name = "fake-bpy-module", specifier = ">=20260317" }, { name = "fake-bpy-module", specifier = ">=20260317" },
{ name = "manifold3d", specifier = ">=3.5.4" },
{ name = "numpy", specifier = ">=2.2.6" }, { name = "numpy", specifier = ">=2.2.6" },
{ name = "pyclipper", specifier = ">=1.4.0" }, { name = "pyclipper", specifier = ">=1.4.0" },
{ name = "pyransac3d", specifier = ">=0.6.0" }, { name = "pyransac3d", specifier = ">=0.6.0" },
{ name = "python-libnest2d", specifier = ">=0.1.3" }, { name = "python-libnest2d", specifier = ">=0.1.3" },
{ name = "shapely", specifier = ">=2.1.2" }, { name = "shapely", specifier = ">=2.1.2" },
{ name = "shapely-polyskel", specifier = ">=0.1.2" }, { name = "shapely-polyskel", specifier = ">=0.1.2" },
{ name = "trimesh", specifier = ">=5.1.0" },
] ]
[[package]] [[package]]
@ -119,7 +176,8 @@ name = "numpy"
version = "2.4.3" version = "2.4.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
resolution-markers = [ resolution-markers = [
"python_full_version >= '3.11'", "python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
] ]
sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" }
wheels = [ wheels = [
@ -343,3 +401,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/d3/d4/c88f0e2b1919ec110
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/01/90d3f3f93990aed34e49fa5071a842f76a5dfc2fdcb026f44a5115c9d969/shapely_polyskel-0.1.2-py3-none-any.whl", hash = "sha256:827400d0d733a41ca7cea40141aa2563d4c709f53feb42d54ccfd436b5cab1bc", size = 14418, upload-time = "2025-01-05T19:43:35.529Z" }, { url = "https://files.pythonhosted.org/packages/b5/01/90d3f3f93990aed34e49fa5071a842f76a5dfc2fdcb026f44a5115c9d969/shapely_polyskel-0.1.2-py3-none-any.whl", hash = "sha256:827400d0d733a41ca7cea40141aa2563d4c709f53feb42d54ccfd436b5cab1bc", size = 14418, upload-time = "2025-01-05T19:43:35.529Z" },
] ]
[[package]]
name = "trimesh"
version = "5.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/3f/a13d797349fdd00a8586b60d6b71df849e1766e2002729697a265b27a773/trimesh-5.1.0.tar.gz", hash = "sha256:927aa8748af8b84cb969c8f53270d929e546f2beada77a2bc234b605fddf1454", size = 870501, upload-time = "2026-08-31T18:28:55.464Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/c8/5ede3b786ef58e0076e9ca748b2c1787183f85e5ad8133373715c31248f9/trimesh-5.1.0-py3-none-any.whl", hash = "sha256:0fa85d1d131e321b683c00747c20090200b92071298b905ea588f609eb204c89", size = 750004, upload-time = "2026-08-31T18:28:53.746Z" },
]