diff --git a/README.md b/README.md index 5009ab8..b93f318 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ NodeSlicer is a high-performance Blender extension that provides a custom node-b 4. **Process**: * Add perimeters with **Buffer Shells**. * 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**. 6. **Export & Preview**: Use **Gcode To File** to save the result, and **Gcode to Preview Mesh** to visualize the final toolpaths in the viewport. diff --git a/core/nodeslicer/nodes/path.py b/core/nodeslicer/nodes/path.py index cf31ca0..ddd0a53 100644 --- a/core/nodeslicer/nodes/path.py +++ b/core/nodeslicer/nodes/path.py @@ -234,7 +234,7 @@ class SLICENODE_Infill(SliceNode): @register_class class SLICENODE_JoinSlices(SliceNodeJoiner): - bl_label = "Join Slices" + bl_label = "Boolean Paths" socket_type = "SLICESOCK_Paths" operation: NfEnumProperty( @@ -270,6 +270,8 @@ class SLICENODE_JoinSlices(SliceNodeJoiner): # We return a new SlicePaths from the result. accumulated = accumulated.boolean(current, self.operation) + pass + return accumulated @register_class diff --git a/core/slice.py b/core/slice.py index e7e4d22..e2e6f4a 100644 --- a/core/slice.py +++ b/core/slice.py @@ -103,10 +103,7 @@ class SlicePaths(list[np.ndarray]): return new_paths def boolean(self, other: SlicePaths, operation: str = 'UNION') -> SlicePaths: - """Performs a 2D boolean operation with another set of paths.""" - 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) + return boolean_slice(self, other, operation) def extend_with_groups(self, other: SlicePaths | list[np.ndarray]): if isinstance(other, SlicePaths): @@ -315,7 +312,7 @@ def segments_to_slice(segments: np.ndarray) -> list[np.ndarray]: return res @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() subject_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) 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) 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] for path in solution: - if not path: continue + if not path: + continue fpath = np.array(path) / SCALE fpath_3d = np.column_stack([fpath, np.full(len(fpath), z)]) fpath_3d = np.vstack([fpath_3d, fpath_3d[0]]) results.append(fpath_3d) + + results.groups += [group] * len(solution) + group +=1 + + pass return results diff --git a/uv.lock b/uv.lock index d038379..19d71b0 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,12 @@ resolution-markers = [ "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]] name = "fake-bpy-module" version = "20260317" @@ -20,21 +26,25 @@ name = "nodeslicer" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "euclid3" }, { 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.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyclipper" }, { name = "python-libnest2d" }, { name = "shapely" }, + { name = "shapely-polyskel" }, ] [package.metadata] requires-dist = [ + { name = "euclid3", specifier = ">=0.1" }, { name = "fake-bpy-module", specifier = ">=20260317" }, { name = "numpy", specifier = ">=2.2.6" }, { name = "pyclipper", specifier = ">=1.4.0" }, { name = "python-libnest2d", specifier = ">=0.1.3" }, { name = "shapely", specifier = ">=2.1.2" }, + { name = "shapely-polyskel", specifier = ">=0.1.2" }, ] [[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/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" }, +]