refactor zip + drop geometry
This commit is contained in:
parent
d556c61ee5
commit
7d53f29566
7 changed files with 132 additions and 38 deletions
|
|
@ -465,7 +465,7 @@ class SLICENODE_TransformGeometry(SliceNode):
|
||||||
if any(r != 0.0 for r in r_arr):
|
if any(r != 0.0 for r in r_arr):
|
||||||
col.rotate(r_arr)
|
col.rotate(r_arr)
|
||||||
if any(t != 0.0 for t in t_arr):
|
if any(t != 0.0 for t in t_arr):
|
||||||
col.translate(t_arr)
|
col.offset(t_arr)
|
||||||
# Also offset wipe tower location
|
# Also offset wipe tower location
|
||||||
if any(t != 0.0 for t in t_arr):
|
if any(t != 0.0 for t in t_arr):
|
||||||
copied_data.wipe_tower_xy = copied_data.wipe_tower_xy + t_arr[0:2]
|
copied_data.wipe_tower_xy = copied_data.wipe_tower_xy + t_arr[0:2]
|
||||||
|
|
@ -485,8 +485,32 @@ class SLICENODE_TransformGeometry(SliceNode):
|
||||||
if any(r != 0.0 for r in r_arr):
|
if any(r != 0.0 for r in r_arr):
|
||||||
copied_data.rotate(r_arr)
|
copied_data.rotate(r_arr)
|
||||||
if any(t != 0.0 for t in t_arr):
|
if any(t != 0.0 for t in t_arr):
|
||||||
copied_data.translate(t_arr)
|
copied_data.offset(t_arr)
|
||||||
|
|
||||||
return copied_data
|
return copied_data
|
||||||
|
|
||||||
|
|
||||||
|
@register_class
|
||||||
|
class SLICENODE_DropGeometry(SliceNode):
|
||||||
|
bl_label = "Drop Geometry"
|
||||||
|
|
||||||
|
def init(self, context):
|
||||||
|
self.add_input("SLICESOCK_Mesh", "Mesh")
|
||||||
|
self.add_input("SLICESOCK_Float", "Z")
|
||||||
|
self.add_output("SLICESOCK_Mesh", "Mesh")
|
||||||
|
|
||||||
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | SlicingGroup | None:
|
||||||
|
|
||||||
|
z=self.resolve('Z')
|
||||||
|
if socket_name != "Mesh":
|
||||||
|
return None
|
||||||
|
|
||||||
|
mesh_data: SlicingCollection | SlicingGroup | None = self.resolve("Mesh")
|
||||||
|
if not mesh_data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert mesh_data.min_z is not None
|
||||||
|
min_z = round(mesh_data.min_z , 6)
|
||||||
|
mesh_data.offset(np.array([0.0, 0.0, z-min_z], dtype=np.float64))
|
||||||
|
|
||||||
|
return mesh_data
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,11 @@ class SLICENODE_SliceShells(SliceNode):
|
||||||
min_z = floor(min_z / layer_height) * layer_height
|
min_z = floor(min_z / layer_height) * layer_height
|
||||||
max_z = merged_object.mesh[:,0:3,2:3].max()
|
max_z = merged_object.mesh[:,0:3,2:3].max()
|
||||||
|
|
||||||
z_values = np.arange(min_z, max_z + layer_height, layer_height)
|
z_values = np.arange(
|
||||||
|
min_z + layer_height/2,
|
||||||
|
max_z,
|
||||||
|
layer_height
|
||||||
|
)
|
||||||
|
|
||||||
slices_group: list[list[np.ndarray]] | None = slice_at_range([merged_object.mesh], z_values)
|
slices_group: list[list[np.ndarray]] | None = slice_at_range([merged_object.mesh], z_values)
|
||||||
assert slices_group
|
assert slices_group
|
||||||
|
|
@ -331,10 +335,12 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
|
||||||
from ....infra.blender.blender_slice_nodes_generic import resolve_input
|
from ....infra.blender.blender_slice_nodes_generic import resolve_input
|
||||||
|
|
||||||
inputs_data: list[SlicePaths] = []
|
inputs_data: list[SlicePaths] = []
|
||||||
for input in self.inputs:
|
for input in self.inputs:
|
||||||
paths = resolve_input(input)
|
paths: SlicePaths = resolve_input(input)
|
||||||
if paths:
|
if paths:
|
||||||
|
# paths.normalize_groups()
|
||||||
inputs_data.append(paths)
|
inputs_data.append(paths)
|
||||||
|
|
||||||
if not inputs_data:
|
if not inputs_data:
|
||||||
|
|
@ -344,6 +350,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||||
merged = SlicePaths()
|
merged = SlicePaths()
|
||||||
for paths in inputs_data:
|
for paths in inputs_data:
|
||||||
merged.extend_with_groups(paths)
|
merged.extend_with_groups(paths)
|
||||||
|
|
||||||
return merged
|
return merged
|
||||||
|
|
||||||
elif self.mode == 'ZIP':
|
elif self.mode == 'ZIP':
|
||||||
|
|
@ -358,7 +365,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||||
d.setdefault(g, []).append(p_list[i])
|
d.setdefault(g, []).append(p_list[i])
|
||||||
grouped_inputs.append(d)
|
grouped_inputs.append(d)
|
||||||
|
|
||||||
sorted_groups = sorted(list(all_groups))
|
sorted_groups = sorted(all_groups)
|
||||||
|
|
||||||
merged = SlicePaths()
|
merged = SlicePaths()
|
||||||
for g in sorted_groups:
|
for g in sorted_groups:
|
||||||
|
|
@ -366,6 +373,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||||
group_paths = d.get(g, [])
|
group_paths = d.get(g, [])
|
||||||
for p in group_paths:
|
for p in group_paths:
|
||||||
merged.append_with_group(p, g)
|
merged.append_with_group(p, g)
|
||||||
|
|
||||||
return merged
|
return merged
|
||||||
|
|
||||||
return SlicePaths()
|
return SlicePaths()
|
||||||
|
|
|
||||||
|
|
@ -497,6 +497,7 @@ class SLICENODE_MT_Geometry(Menu):
|
||||||
assert layout
|
assert layout
|
||||||
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh"
|
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh"
|
||||||
layout.operator("node.add_node", text="Transform Geometry").type = "SLICENODE_TransformGeometry"
|
layout.operator("node.add_node", text="Transform Geometry").type = "SLICENODE_TransformGeometry"
|
||||||
|
layout.operator("node.add_node", text="Drop Geometry").type = "SLICENODE_DropGeometry"
|
||||||
layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh"
|
layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh"
|
||||||
layout.operator("node.add_node", text="Separate Mesh by Range").type = "SLICENODE_SeparateMeshByRange"
|
layout.operator("node.add_node", text="Separate Mesh by Range").type = "SLICENODE_SeparateMeshByRange"
|
||||||
layout.operator("node.add_node", text="Arrange Slicing Group").type = "SLICENODE_ArrangeMesh"
|
layout.operator("node.add_node", text="Arrange Slicing Group").type = "SLICENODE_ArrangeMesh"
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,9 @@ class SlicePaths(list[np.ndarray]):
|
||||||
if k not in extra_attributes:
|
if k not in extra_attributes:
|
||||||
self.extra_attributes[k].append(np.zeros(len(path)))
|
self.extra_attributes[k].append(np.zeros(len(path)))
|
||||||
|
|
||||||
|
def normalize_groups(self) -> None:
|
||||||
|
self.groups = [m.setdefault(x, len(m)) for m in [{}] for x in self.groups]
|
||||||
|
|
||||||
def slice_at_range(models, z_values) -> list[list[np.ndarray]]:
|
def slice_at_range(models, z_values) -> list[list[np.ndarray]]:
|
||||||
if not models:
|
if not models:
|
||||||
return []
|
return []
|
||||||
|
|
@ -484,26 +487,26 @@ def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNI
|
||||||
if not connect_along_boundary or is_closed:
|
if not connect_along_boundary or is_closed:
|
||||||
pc.Clear()
|
pc.Clear()
|
||||||
if is_closed: path_pts = path_pts[:-1]
|
if is_closed: path_pts = path_pts[:-1]
|
||||||
pc.AddPath((path_pts * SCALE).astype(np.int64).tolist(), pyclipper.PT_SUBJECT, is_closed)
|
pc.AddPath((path_pts * SCALE).astype(np.int64).tolist(), pyclipper.PT_SUBJECT, is_closed) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
for c in clips_scaled:
|
for c in clips_scaled:
|
||||||
pc.AddPath(c, pyclipper.PT_CLIP, True)
|
pc.AddPath(c, pyclipper.PT_CLIP, True) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)
|
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
for path in pyclipper.ClosedPathsFromPolyTree(solution):
|
for path in pyclipper.ClosedPathsFromPolyTree(solution): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
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)])
|
||||||
layer_results.append(np.vstack([fpath_3d, fpath_3d[0]]))
|
layer_results.append(np.vstack([fpath_3d, fpath_3d[0]]))
|
||||||
for path in pyclipper.OpenPathsFromPolyTree(solution):
|
for path in pyclipper.OpenPathsFromPolyTree(solution): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
fpath = np.array(path) / SCALE
|
fpath = np.array(path) / SCALE
|
||||||
layer_results.append(np.column_stack([fpath, np.full(len(fpath), z)]))
|
layer_results.append(np.column_stack([fpath, np.full(len(fpath), z)]))
|
||||||
else:
|
else:
|
||||||
pc.Clear()
|
pc.Clear()
|
||||||
pc.AddPath((path_pts * SCALE).astype(np.int64).tolist(), pyclipper.PT_SUBJECT, False)
|
pc.AddPath((path_pts * SCALE).astype(np.int64).tolist(), pyclipper.PT_SUBJECT, False) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
for c in clips_scaled:
|
for c in clips_scaled:
|
||||||
pc.AddPath(c, pyclipper.PT_CLIP, True)
|
pc.AddPath(c, pyclipper.PT_CLIP, True) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)
|
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
open_results = pyclipper.OpenPathsFromPolyTree(solution)
|
open_results = pyclipper.OpenPathsFromPolyTree(solution) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
if not open_results: continue
|
if not open_results: continue
|
||||||
|
|
||||||
def get_p(pt):
|
def get_p(pt):
|
||||||
|
|
@ -555,24 +558,24 @@ def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNI
|
||||||
if operation == 'INTERSECTION':
|
if operation == 'INTERSECTION':
|
||||||
obj_closed_pts = [s[:, :2] for s in object_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
obj_closed_pts = [s[:, :2] for s in object_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
||||||
obj_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in obj_closed_pts]
|
obj_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in obj_closed_pts]
|
||||||
process_op(subject_slices, obj_closed_scaled, obj_closed_pts, pyclipper.CT_INTERSECTION)
|
process_op(subject_slices, obj_closed_scaled, obj_closed_pts, pyclipper.CT_INTERSECTION) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
# Pass 2 for B_open ∩ A_closed
|
# Pass 2 for B_open ∩ A_closed
|
||||||
b_open = [s for s in object_slices if not (len(s) > 2 and np.allclose(s[0], s[-1]))]
|
b_open = [s for s in object_slices if not (len(s) > 2 and np.allclose(s[0], s[-1]))]
|
||||||
if b_open:
|
if b_open:
|
||||||
sub_closed_pts = [s[:, :2] for s in subject_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
sub_closed_pts = [s[:, :2] for s in subject_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
||||||
sub_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in sub_closed_pts]
|
sub_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in sub_closed_pts]
|
||||||
process_op(b_open, sub_closed_scaled, sub_closed_pts, pyclipper.CT_INTERSECTION)
|
process_op(b_open, sub_closed_scaled, sub_closed_pts, pyclipper.CT_INTERSECTION) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
elif operation == 'DIFFERENCE':
|
elif operation == 'DIFFERENCE':
|
||||||
obj_closed_pts = [s[:, :2] for s in object_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
obj_closed_pts = [s[:, :2] for s in object_slices if len(s) > 2 and np.allclose(s[0], s[-1])]
|
||||||
obj_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in obj_closed_pts]
|
obj_closed_scaled = [(pts[:-1] * SCALE).astype(np.int64).tolist() for pts in obj_closed_pts]
|
||||||
process_op(subject_slices, obj_closed_scaled, obj_closed_pts, pyclipper.CT_DIFFERENCE)
|
process_op(subject_slices, obj_closed_scaled, obj_closed_pts, pyclipper.CT_DIFFERENCE) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
# DIFFERENCE is not symmetric for open paths in the same way, usually A - B means A is the main focus.
|
# DIFFERENCE is not symmetric for open paths in the same way, usually A - B means A is the main focus.
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# UNION, XOR
|
# UNION, XOR
|
||||||
add_paths_to_clipper(subject_slices, pyclipper.PT_SUBJECT)
|
add_paths_to_clipper(subject_slices, pyclipper.PT_SUBJECT) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
obj_closed = []
|
obj_closed = []
|
||||||
obj_open = []
|
obj_open = []
|
||||||
for s in object_slices:
|
for s in object_slices:
|
||||||
|
|
@ -580,23 +583,23 @@ def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNI
|
||||||
else: obj_open.append(s)
|
else: obj_open.append(s)
|
||||||
|
|
||||||
if operation in ['UNION', 'XOR']:
|
if operation in ['UNION', 'XOR']:
|
||||||
add_paths_to_clipper(obj_open, pyclipper.PT_SUBJECT)
|
add_paths_to_clipper(obj_open, pyclipper.PT_SUBJECT) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
for s in obj_closed:
|
for s in obj_closed:
|
||||||
path_pts = s[:, :2]
|
path_pts = s[:, :2]
|
||||||
scaled = (path_pts[:-1] * SCALE).astype(np.int64).tolist()
|
scaled = (path_pts[:-1] * SCALE).astype(np.int64).tolist()
|
||||||
pc.AddPath(scaled, pyclipper.PT_CLIP, True)
|
pc.AddPath(scaled, pyclipper.PT_CLIP, True) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
op_type = pyclipper.CT_UNION
|
op_type = pyclipper.CT_UNION # pyright: ignore[reportAttributeAccessIssue]
|
||||||
if operation == 'UNION': op_type = pyclipper.CT_UNION
|
if operation == 'UNION': op_type = pyclipper.CT_UNION # pyright: ignore[reportAttributeAccessIssue]
|
||||||
elif operation == 'XOR': op_type = pyclipper.CT_XOR
|
elif operation == 'XOR': op_type = pyclipper.CT_XOR # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)
|
solution = pc.Execute2(op_type, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
for path in pyclipper.ClosedPathsFromPolyTree(solution):
|
for path in pyclipper.ClosedPathsFromPolyTree(solution): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
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)])
|
||||||
layer_results.append(np.vstack([fpath_3d, fpath_3d[0]]))
|
layer_results.append(np.vstack([fpath_3d, fpath_3d[0]]))
|
||||||
for path in pyclipper.OpenPathsFromPolyTree(solution):
|
for path in pyclipper.OpenPathsFromPolyTree(solution): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
fpath = np.array(path) / SCALE
|
fpath = np.array(path) / SCALE
|
||||||
layer_results.append(np.column_stack([fpath, np.full(len(fpath), z)]))
|
layer_results.append(np.column_stack([fpath, np.full(len(fpath), z)]))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **Translation**: 3D vector specifying the translation offset.
|
- **Translation**: 3D vector specifying the translation offset.
|
||||||
- **Rotation**: 3D vector/Euler rotation values.
|
- **Rotation**: 3D vector/Euler rotation values.
|
||||||
- **Scale**: 3D vector specifying scaling factors along the X, Y, and Z axes (defaults to `1.0, 1.0, 1.0`).
|
- **Scale**: 3D vector specifying scaling factors along the X, Y, and Z axes (defaults to `1.0, 1.0, 1.0`).
|
||||||
|
- **Drop Geometry**: Moves a `SlicingCollection` or `SlicingGroup` along Z so that its minimum Z sits at the specified value.
|
||||||
|
- **Mesh**: Input mesh data.
|
||||||
|
- **Z**: Target minimum Z coordinate (defaults to `0.0`).
|
||||||
- **Get Paths**: Extracts existing edges from a mesh as paths.
|
- **Get Paths**: Extracts existing edges from a mesh as paths.
|
||||||
- **Extrusion Multiplier Attribute**: Mesh attribute to use for per-segment extrusion rates.
|
- **Extrusion Multiplier Attribute**: Mesh attribute to use for per-segment extrusion rates.
|
||||||
- **Feedrate Attribute**: Mesh attribute to use for per-segment feedrates.
|
- **Feedrate Attribute**: Mesh attribute to use for per-segment feedrates.
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,6 @@ class SlicingObject():
|
||||||
def offset(self, offset: NDArray):
|
def offset(self, offset: NDArray):
|
||||||
self.mesh[:, :3, :] += offset
|
self.mesh[:, :3, :] += offset
|
||||||
|
|
||||||
def translate(self, translation: NDArray):
|
|
||||||
self.offset(translation)
|
|
||||||
|
|
||||||
def scale(self, factor: float | NDArray):
|
def scale(self, factor: float | NDArray):
|
||||||
self.mesh[:, :3, :] *= factor
|
self.mesh[:, :3, :] *= factor
|
||||||
if isinstance(factor, (list, tuple, np.ndarray)):
|
if isinstance(factor, (list, tuple, np.ndarray)):
|
||||||
|
|
@ -210,10 +207,6 @@ class SlicingCollection():
|
||||||
for so in self.objects: so.offset(offset)
|
for so in self.objects: so.offset(offset)
|
||||||
self._clear_cache()
|
self._clear_cache()
|
||||||
|
|
||||||
def translate(self, translation: NDArray):
|
|
||||||
for so in self.objects: so.translate(translation)
|
|
||||||
self._clear_cache()
|
|
||||||
|
|
||||||
def scale(self, factor: float | NDArray):
|
def scale(self, factor: float | NDArray):
|
||||||
for so in self.objects: so.scale(factor)
|
for so in self.objects: so.scale(factor)
|
||||||
self._clear_cache()
|
self._clear_cache()
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,10 @@ sys.modules['bpy.utils.previews'] = MagicMock()
|
||||||
sys.modules['bpy.app'] = MagicMock()
|
sys.modules['bpy.app'] = MagicMock()
|
||||||
sys.modules['bpy.app.handlers'] = MagicMock()
|
sys.modules['bpy.app.handlers'] = MagicMock()
|
||||||
|
|
||||||
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject
|
from ..infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject
|
||||||
|
|
||||||
class TestTransform(unittest.TestCase):
|
class TestTransform(unittest.TestCase):
|
||||||
def test_translate(self):
|
def test_offset(self):
|
||||||
# Create a SlicingObject with a single triangle and a normal pointing up (+Z)
|
# Create a SlicingObject with a single triangle and a normal pointing up (+Z)
|
||||||
so = SlicingObject()
|
so = SlicingObject()
|
||||||
so.mesh = np.array([[[1.0, 2.0, 3.0],
|
so.mesh = np.array([[[1.0, 2.0, 3.0],
|
||||||
|
|
@ -58,8 +58,8 @@ class TestTransform(unittest.TestCase):
|
||||||
[7.0, 8.0, 9.0],
|
[7.0, 8.0, 9.0],
|
||||||
[0.0, 0.0, 1.0]]], dtype=np.float64)
|
[0.0, 0.0, 1.0]]], dtype=np.float64)
|
||||||
|
|
||||||
# Translate
|
# Offset
|
||||||
so.translate(np.array([10.0, 20.0, 30.0], dtype=np.float64))
|
so.offset(np.array([10.0, 20.0, 30.0], dtype=np.float64))
|
||||||
|
|
||||||
# Normals should remain unchanged, vertices shifted
|
# Normals should remain unchanged, vertices shifted
|
||||||
np.testing.assert_allclose(so.mesh[0, 0], [11.0, 22.0, 33.0])
|
np.testing.assert_allclose(so.mesh[0, 0], [11.0, 22.0, 33.0])
|
||||||
|
|
@ -138,7 +138,7 @@ class TestTransform(unittest.TestCase):
|
||||||
self.assertIn("unique_verts", sc.__dict__)
|
self.assertIn("unique_verts", sc.__dict__)
|
||||||
|
|
||||||
# Apply transformation
|
# Apply transformation
|
||||||
sc.translate(np.array([10, 0, 0]))
|
sc.offset(np.array([10, 0, 0]))
|
||||||
|
|
||||||
# Verify cache was cleared
|
# Verify cache was cleared
|
||||||
self.assertNotIn("all_verts", sc.__dict__)
|
self.assertNotIn("all_verts", sc.__dict__)
|
||||||
|
|
@ -197,6 +197,68 @@ class TestTransform(unittest.TestCase):
|
||||||
# v0 becomes [6.0, 22.0, 36.0]
|
# v0 becomes [6.0, 22.0, 36.0]
|
||||||
np.testing.assert_allclose(result_sc.objects[0].mesh[0, 0], [6.0, 22.0, 36.0], atol=1e-7)
|
np.testing.assert_allclose(result_sc.objects[0].mesh[0, 0], [6.0, 22.0, 36.0], atol=1e-7)
|
||||||
|
|
||||||
|
def test_drop_geometry_node(self):
|
||||||
|
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_DropGeometry
|
||||||
|
|
||||||
|
# Create a SlicingObject with a single triangle having min_z = 3.0
|
||||||
|
so = SlicingObject()
|
||||||
|
so.mesh = np.array([[[1.0, 2.0, 3.0],
|
||||||
|
[4.0, 5.0, 6.0],
|
||||||
|
[7.0, 8.0, 9.0],
|
||||||
|
[0.0, 0.0, 1.0]]], dtype=np.float64)
|
||||||
|
|
||||||
|
sc = SlicingCollection([], "coll")
|
||||||
|
sc.objects = [so]
|
||||||
|
|
||||||
|
# Init node
|
||||||
|
node = SLICENODE_DropGeometry()
|
||||||
|
|
||||||
|
# Test case 1: Drop to Z = 0.0
|
||||||
|
node.z = 0.0
|
||||||
|
node.inputs = {}
|
||||||
|
# Mock resolve to return our inputs
|
||||||
|
def mock_resolve_no_socket(fieldname):
|
||||||
|
if fieldname == "Mesh":
|
||||||
|
return sc
|
||||||
|
elif fieldname == "Z":
|
||||||
|
return 0.0
|
||||||
|
return None
|
||||||
|
node.resolve = mock_resolve_no_socket
|
||||||
|
|
||||||
|
result_sc_1 = node.evaluate_output("Mesh")
|
||||||
|
self.assertIsNotNone(result_sc_1)
|
||||||
|
self.assertEqual(result_sc_1.min_z, 0.0)
|
||||||
|
np.testing.assert_allclose(result_sc_1.objects[0].mesh[0, 0], [1.0, 2.0, 0.0])
|
||||||
|
|
||||||
|
# Test case 2: Drop to Z = 10.0
|
||||||
|
so2 = SlicingObject()
|
||||||
|
so2.mesh = np.array([[[1.0, 2.0, 3.0],
|
||||||
|
[4.0, 5.0, 6.0],
|
||||||
|
[7.0, 8.0, 9.0],
|
||||||
|
[0.0, 0.0, 1.0]]], dtype=np.float64)
|
||||||
|
sc2 = SlicingCollection([], "coll2")
|
||||||
|
sc2.objects = [so2]
|
||||||
|
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket.is_linked = False
|
||||||
|
mock_socket.value = 10.0
|
||||||
|
node.inputs = {"Z": mock_socket}
|
||||||
|
node.z = 0.0
|
||||||
|
|
||||||
|
def mock_resolve_with_socket(fieldname):
|
||||||
|
if fieldname == "Mesh":
|
||||||
|
return sc2
|
||||||
|
elif fieldname == "Z":
|
||||||
|
return 10.0
|
||||||
|
return None
|
||||||
|
node.resolve = mock_resolve_with_socket
|
||||||
|
|
||||||
|
result_sc_2 = node.evaluate_output("Mesh")
|
||||||
|
self.assertIsNotNone(result_sc_2)
|
||||||
|
self.assertEqual(result_sc_2.min_z, 10.0)
|
||||||
|
np.testing.assert_allclose(result_sc_2.objects[0].mesh[0, 0], [1.0, 2.0, 10.0])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue