This commit is contained in:
Nicolas Predella 2026-06-10 16:05:51 +03:00
parent 21fa166bfd
commit f20e11fff6
4 changed files with 39 additions and 10 deletions

View file

@ -33,7 +33,7 @@ NodeSlicer is a high-performance Blender extension that provides a custom node-b
4. **Process**: 4. **Process**:
* Add perimeters with **Buffer Shells**. * Add perimeters with **Buffer Shells**.
* Add internal structure with the **Infill** node. * Add internal structure with the **Infill** node.
* Combine or modify paths using **Join Paths** or **Join Slices**. * Combine or modify paths using **Join Paths** or **Boolean Paths**.
5. **Generate**: Convert paths to G-code using **Paths to Gcode**. 5. **Generate**: Convert paths to G-code using **Paths to Gcode**.
6. **Export & Preview**: Use **Gcode To File** to save the result, and **Gcode to Preview Mesh** to visualize the final toolpaths in the viewport. 6. **Export & Preview**: Use **Gcode To File** to save the result, and **Gcode to Preview Mesh** to visualize the final toolpaths in the viewport.

View file

@ -234,7 +234,7 @@ class SLICENODE_Infill(SliceNode):
@register_class @register_class
class SLICENODE_JoinSlices(SliceNodeJoiner): class SLICENODE_JoinSlices(SliceNodeJoiner):
bl_label = "Join Slices" bl_label = "Boolean Paths"
socket_type = "SLICESOCK_Paths" socket_type = "SLICESOCK_Paths"
operation: NfEnumProperty( operation: NfEnumProperty(
@ -270,6 +270,8 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
# We return a new SlicePaths from the result. # We return a new SlicePaths from the result.
accumulated = accumulated.boolean(current, self.operation) accumulated = accumulated.boolean(current, self.operation)
pass
return accumulated return accumulated
@register_class @register_class

View file

@ -103,10 +103,7 @@ class SlicePaths(list[np.ndarray]):
return new_paths return new_paths
def boolean(self, other: SlicePaths, operation: str = 'UNION') -> SlicePaths: def boolean(self, other: SlicePaths, operation: str = 'UNION') -> SlicePaths:
"""Performs a 2D boolean operation with another set of paths.""" return boolean_slice(self, other, operation)
res = boolean_slice(self, other, operation)
# Boolean operations merge paths across layers, so we don't preserve original grouping as precisely.
return self.copy_with(res)
def extend_with_groups(self, other: SlicePaths | list[np.ndarray]): def extend_with_groups(self, other: SlicePaths | list[np.ndarray]):
if isinstance(other, SlicePaths): if isinstance(other, SlicePaths):
@ -315,7 +312,7 @@ def segments_to_slice(segments: np.ndarray) -> list[np.ndarray]:
return res return res
@ndarray_lru_cache(maxsize=128) @ndarray_lru_cache(maxsize=128)
def boolean_slice(subject: list[np.ndarray], object: list[np.ndarray], operation: str = 'UNION') -> list[np.ndarray]: def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNION') -> SlicePaths:
all_z_keys: set[int] = set() all_z_keys: set[int] = set()
subject_dict: dict[int, list[np.ndarray]] = {} subject_dict: dict[int, list[np.ndarray]] = {}
object_dict: dict[int, list[np.ndarray]] = {} object_dict: dict[int, list[np.ndarray]] = {}
@ -330,9 +327,10 @@ def boolean_slice(subject: list[np.ndarray], object: list[np.ndarray], operation
all_z_keys.add(z) all_z_keys.add(z)
object_dict.setdefault(z, []).append(s) object_dict.setdefault(z, []).append(s)
results: list[np.ndarray] = [] results: SlicePaths = SlicePaths()
for k in all_z_keys: group = 0
for k in sorted(all_z_keys):
subject_slices = subject_dict.get(k) subject_slices = subject_dict.get(k)
object_slices = object_dict.get(k) object_slices = object_dict.get(k)
@ -372,11 +370,17 @@ def boolean_slice(subject: list[np.ndarray], object: list[np.ndarray], operation
solution = pc.Execute(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # pyright: ignore[reportAttributeAccessIssue] solution = pc.Execute(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # pyright: ignore[reportAttributeAccessIssue]
for path in solution: for path in solution:
if not path: continue if not path:
continue
fpath = np.array(path) / SCALE fpath = np.array(path) / SCALE
fpath_3d = np.column_stack([fpath, np.full(len(fpath), z)]) fpath_3d = np.column_stack([fpath, np.full(len(fpath), z)])
fpath_3d = np.vstack([fpath_3d, fpath_3d[0]]) fpath_3d = np.vstack([fpath_3d, fpath_3d[0]])
results.append(fpath_3d) results.append(fpath_3d)
results.groups += [group] * len(solution)
group +=1
pass
return results return results

23
uv.lock generated
View file

@ -6,6 +6,12 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
[[package]]
name = "euclid3"
version = "0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/d2/80730bee6b51f2a0faacaec51abb919f144c8b1fff5907fe019ec0e95698/euclid3-0.01.tar.gz", hash = "sha256:25b827a57adbfd9a3fa8625e43abc3e907f61de622343e7e538482ef9b46fd0b", size = 13201, upload-time = "2014-05-14T10:51:19.907Z" }
[[package]] [[package]]
name = "fake-bpy-module" name = "fake-bpy-module"
version = "20260317" version = "20260317"
@ -20,21 +26,25 @@ name = "nodeslicer"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "euclid3" },
{ name = "fake-bpy-module" }, { name = "fake-bpy-module" },
{ 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" },
{ name = "python-libnest2d" }, { name = "python-libnest2d" },
{ name = "shapely" }, { name = "shapely" },
{ name = "shapely-polyskel" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "euclid3", specifier = ">=0.1" },
{ name = "fake-bpy-module", specifier = ">=20260317" }, { name = "fake-bpy-module", specifier = ">=20260317" },
{ 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 = "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" },
] ]
[[package]] [[package]]
@ -309,3 +319,16 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" },
{ url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" },
] ]
[[package]]
name = "shapely-polyskel"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "euclid3" },
{ name = "shapely" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/d4/c88f0e2b1919ec1109a0ca2b06bbc6b5183cda46dfced90829c60b7f5331/shapely_polyskel-0.1.2.tar.gz", hash = "sha256:a615f5d7caa16e25be0e0dd609ece9ddde025fd737570286d0f00a7f9470d3bd", size = 17637, upload-time = "2025-01-05T19:43:39.121Z" }
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" },
]