diff --git a/data_structure.py b/data_structure.py index da97b8fd8b..327efbf385 100755 --- a/data_structure.py +++ b/data_structure.py @@ -486,7 +486,7 @@ def levels_of_list_or_np(lst): return level return 0 -SIMPLE_DATA_TYPES = (float, int, float64, int32, int64, str) +SIMPLE_DATA_TYPES = (float, int, float64, int32, int64, str, Matrix) def get_data_nesting_level(data, data_types=SIMPLE_DATA_TYPES): diff --git a/docs/nodes/modifier_make/bisect.rst b/docs/nodes/modifier_make/bisect.rst index ce07c512c3..2e5f7eadb8 100644 --- a/docs/nodes/modifier_make/bisect.rst +++ b/docs/nodes/modifier_make/bisect.rst @@ -7,7 +7,7 @@ Functionality This can give the cross section of an object shape from any angle. The implementation is from ``bmesh.ops.bisect_plane``. It can also provide either side of the cut, separate or joined. -Inputs +Inputs ------ *Vertices*, *PolyEdges* and *Matrix* @@ -16,20 +16,35 @@ Inputs Parameters ---------- -+-------------+------+---------------------------------------------------+ -| Parameter | Type | Description | -+=============+======+===================================================+ -| Clear Inner | bool | don't include the negative side of the Matrix cut | -+-------------+------+---------------------------------------------------+ -| Clear Outer | bool | don't include the positive side of the Matrix cut | -+-------------+------+---------------------------------------------------+ -| Fill cuts | bool | generates a polygon from the bisections | -+-------------+------+---------------------------------------------------+ ++-------------+------+-----------------------------------------------------+ +| Parameter | Type | Description | ++=============+======+=====================================================+ +| Clear Inner | bool | don't include the negative side of the Matrix cut | ++-------------+------+-----------------------------------------------------+ +| Clear Outer | bool | don't include the positive side of the Matrix cut | ++-------------+------+-----------------------------------------------------+ +| Fill cuts | bool | generates a polygon from the bisections | ++-------------+------+-----------------------------------------------------+ +| Per Object | bool | One matrix per mesh or multiple matrixes per object | ++-------------+------+-----------------------------------------------------+ + +Advanced Parameters +------------------- + +In the N-Panel (and on the right-click menu) you can find: + +**Simplify Output**: Method to keep output data suitable for most of the rest of the Sverchok nodes + - None: Do not perform any change on the data. Only for advanced users + - Join: The node will join the deepest level of bisections in one object + - Flat: It will flat the output to keep the one bisection per object (default) + +**Match List Global**: Define how list with different lengths should be matched. Refers to the matching of groups (level 1) + Outputs ------- -*Vertices*, *Edges*, and *Polygons*. +*Vertices*, *Edges*, and *Polygons*. @@ -44,4 +59,3 @@ Examples Notes ----- - diff --git a/nodes/modifier_make/bisect.py b/nodes/modifier_make/bisect.py index 0fd7ddb9fe..3f4f184223 100644 --- a/nodes/modifier_make/bisect.py +++ b/nodes/modifier_make/bisect.py @@ -17,40 +17,22 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy.props import BoolProperty +from bpy.props import BoolProperty, IntVectorProperty import bmesh from mathutils import Vector, Matrix from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, Matrix_generate, Vector_generate - +from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode +from sverchok.utils.mesh_functions import mesh_join +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh # based on CrossSectionNode # but using python bmesh code for driving -# by Linus Yng / edits+upgrades Dealga McArdle - +# by Linus Yng / edits+upgrades Dealga McArdle and Victor Doval -def bisect(cut_me_vertices, cut_me_edges, pp, pno, outer, inner, fill): +def bisect_bmesh(bm, pp, pno, outer, inner, fill): - if not cut_me_edges or not cut_me_vertices: - return False - - cut_me_polygons = [] - if len(cut_me_edges[0]) > 2: - cut_me_polygons = cut_me_edges.copy() - cut_me_edges = [] - - bm = bmesh.new() - new_vert = bm.verts.new - new_edge = bm.edges.new - new_face = bm.faces.new - bm_verts = [new_vert(v) for v in cut_me_vertices] - if cut_me_edges: - for edge in cut_me_edges: - new_edge((bm_verts[edge[0]], bm_verts[edge[1]])) - else: - for face in cut_me_polygons: - new_face([bm_verts[i] for i in face]) geom_in = bm.verts[:] + bm.edges[:] + bm.faces[:] res = bmesh.ops.bisect_plane( @@ -70,26 +52,37 @@ def bisect(cut_me_vertices, cut_me_edges, pp, pno, outer, inner, fill): bm.verts.index_update() bm.edges.index_update() bm.faces.index_update() - - for edge in bm.edges[:]: - edges.append([v.index for v in edge.verts[:]]) - verts = [vert.co[:] for vert in bm.verts[:]] - for face in bm.faces: - faces.append([v.index for v in face.verts[:]]) + verts, edges, faces = pydata_from_bmesh(bm) bm.clear() bm.free() return (verts, edges, faces) +def bisect(cut_me_vertices, cut_me_edges, pp, pno, outer, inner, fill): -class SvBisectNode(bpy.types.Node, SverchCustomTreeNode): + if not cut_me_edges or not cut_me_vertices: + return False + + if len(cut_me_edges[0]) > 2: + bm = bmesh_from_pydata(cut_me_vertices, [], cut_me_edges) + else: + bm = bmesh_from_pydata(cut_me_vertices, cut_me_edges, []) + + return bisect_bmesh(bm, pp, pno, outer, inner, fill) + + + +class SvBisectNode(bpy.types.Node, SverchCustomTreeNode, SvRecursiveNode): ''' Matrix Cuts geometry''' bl_idname = 'SvBisectNode' bl_label = 'Bisect' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_BISECT' + build_bmesh = True + bmesh_inputs = [0, 1] + inner: BoolProperty( name='inner', description='clear inner', default=False, update=updateNode) @@ -106,6 +99,25 @@ class SvBisectNode(bpy.types.Node, SverchCustomTreeNode): name="Per Object", update=updateNode, default=False, description="slice each object with all matrices, or match object and matrices individually") + slice_mode: BoolProperty( + name="Per Object", update=updateNode, default=False, + description="slice each object with all matrices, or match object and matrices individually") + + remove_empty: BoolProperty( + name="Clean Output", update=updateNode, default=False, + description="Remove empty objects from output") + + correct_output_modes = [ + ('NONE', 'None', 'Leave at multi-object level (Advanced)', 0), + ('JOIN', 'Join', 'Join (mesh join) last level of objects', 1), + ('FLAT', 'Flat Output', 'Flat to object level', 2), + ] + correct_output: bpy.props.EnumProperty( + name="Simplify Output", + description="Behavior on different list lengths, object level", + items=correct_output_modes, default="FLAT", + update=updateNode) + def sv_init(self, context): self.inputs.new('SvVerticesSocket', 'vertices') self.inputs.new('SvStringsSocket', 'edg_pol') @@ -116,62 +128,96 @@ def sv_init(self, context): self.outputs.new('SvStringsSocket', 'polygons') def draw_buttons(self, context, layout): - row = layout.row(align=True) + col = layout.column(align=True) + col.label(text='Remove:') + row = col.row(align=True) row.prop(self, 'inner', text="Inner", toggle=True) row.prop(self, 'outer', text="Outer", toggle=True) row = layout.row(align=True) row.prop(self, 'fill', text="Fill", toggle=True) - if hasattr(self, 'slice_mode'): - row.prop(self, 'slice_mode', toggle=True) + layout.prop(self, 'remove_empty') - def process(self): + row.prop(self, 'slice_mode', toggle=True) - if not all([s.is_linked for s in self.inputs[:2]]): - return + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'correct_output') + layout.prop(self, 'list_match') - if not self.outputs['vertices'].is_linked: - return + def rclick_menu(self, context, layout): + layout.prop_menu_enum(self, "list_match", text="List Match") + if not self.slice_mode: + layout.prop_menu_enum(self, 'correct_output') - verts_ob = Vector_generate(self.inputs['vertices'].sv_get()) - edg_pols = self.inputs['edg_pol'].sv_get() - cut_mats = self.inputs['cut_matrix'].sv_get(default=[Matrix()]) - verts_out = [] - edges_out = [] - polys_out = [] + def pre_setup(self): + self.inputs['vertices'].is_mandatory = True + self.inputs['edg_pol'].is_mandatory = True - if not hasattr(self, 'slice_mode') or not self.slice_mode: + if self.slice_mode: + self.inputs['cut_matrix'].nesting_level = 1 + else: + self.inputs['cut_matrix'].nesting_level = 2 - for cut_mat in cut_mats: - pp = cut_mat.to_translation() - pno = Vector((0.0, 0.0, 1.0)) @ cut_mat.to_3x3().transposed() - for obj in zip(verts_ob, edg_pols): - res = bisect(obj[0], obj[1], pp, pno, self.outer, self.inner, self.fill) - if not res: - return - verts_out.append(res[0]) - edges_out.append(res[1]) - polys_out.append(res[2]) + self.inputs['cut_matrix'].default_mode = 'MATRIX' - else: - for idx, (obj) in enumerate(zip(verts_ob, edg_pols)): + def process_data(self, params): - cut_mat = cut_mats[idx if idx < len(cut_mats) else -1] + verts_out = [] + edges_out = [] + polys_out = [] + if self.slice_mode: + bms, cut_mats = params + for cut_mat, bm in zip(cut_mats, bms): pp = cut_mat.to_translation() pno = Vector((0.0, 0.0, 1.0)) @ cut_mat.to_3x3().transposed() - res = bisect(obj[0], obj[1], pp, pno, self.outer, self.inner, self.fill) + res = bisect_bmesh(bm.copy(), pp, pno, self.outer, self.inner, self.fill) if not res: return + if self.remove_empty: + if not res[0]: + continue verts_out.append(res[0]) edges_out.append(res[1]) polys_out.append(res[2]) + else: + bms, cut_mats_s = params + for cut_mats, bm in zip(cut_mats_s, bms): + vs, es, ps = [], [], [] + for cut_mat in cut_mats: + pp = cut_mat.to_translation() + pno = Vector((0.0, 0.0, 1.0)) @ cut_mat.to_3x3().transposed() + res = bisect_bmesh(bm.copy(), pp, pno, self.outer, self.inner, self.fill) + if not res: + return + if self.remove_empty: + if not res[0]: + continue + if self.correct_output == 'FLAT': + verts_out.append(res[0]) + edges_out.append(res[1]) + polys_out.append(res[2]) + else: + vs.append(res[0]) + es.append(res[1]) + ps.append(res[2]) + + if self.correct_output == 'NONE': + verts_out.append(vs) + edges_out.append(es) + polys_out.append(ps) + elif self.correct_output == 'JOIN': + r = mesh_join(vs, es, ps) + verts_out.extend(r[0]) + edges_out.extend(r[1]) + polys_out.extend(r[2]) + + + + return verts_out, edges_out, polys_out - self.outputs['vertices'].sv_set(verts_out) - self.outputs['edges'].sv_set(edges_out) - self.outputs['polygons'].sv_set(polys_out) - def register(): bpy.utils.register_class(SvBisectNode) diff --git a/utils/mesh_functions.py b/utils/mesh_functions.py index 647b780463..1f9d19653d 100644 --- a/utils/mesh_functions.py +++ b/utils/mesh_functions.py @@ -125,3 +125,15 @@ def repeat_meshes(meshes: Iterator[Mesh], number: int = -1) -> Iterator[Mesh]: def apply_matrix_to_vertices_py(vertices: List[Vertex], matrix: Matrix) -> List[Vertex]: return [(matrix @ Vector(v)).to_tuple() for v in vertices] + +def mesh_join(vertices: List[List[Vertex]], + edges: List[List[Edge]], + polygons: List[List[Polygon]]) -> Tuple[List[List[Vertex]], + List[List[Edge]], + List[List[Polygon]]]: + is_py_input = isinstance(vertices[0], (list, tuple)) + meshes = (meshes_py if is_py_input else meshes_np)(vertices, edges, polygons) + meshes = join_meshes(meshes) + out_vertices, out_edges, out_polygons = to_elements(meshes) + + return out_vertices, out_edges, out_polygons diff --git a/utils/nodes_mixins/recursive_nodes.py b/utils/nodes_mixins/recursive_nodes.py index c97782daac..46eacf2ea3 100644 --- a/utils/nodes_mixins/recursive_nodes.py +++ b/utils/nodes_mixins/recursive_nodes.py @@ -6,15 +6,18 @@ # License-Filename: LICENSE from bpy.props import EnumProperty from mathutils import Matrix -from bpy.props import BoolProperty +from bpy.props import BoolProperty, IntVectorProperty from sverchok.utils.sv_itertools import process_matched from sverchok.core.socket_data import sentinel -from sverchok.data_structure import updateNode, list_match_func, numpy_list_match_modes, ensure_nesting_level, ensure_min_nesting +from sverchok.data_structure import (updateNode, + list_match_func, numpy_list_match_modes, + ensure_nesting_level, ensure_min_nesting) +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata DEFAULT_TYPES = { 'NONE': sentinel, 'EMPTY_LIST': [[]], - 'MATRIX': Matrix(), + 'MATRIX': [Matrix()], 'MASK': [[True]] } def one_item_list(data): @@ -25,7 +28,14 @@ def one_item_list(data): return data return [d[0] for d in data] +def create_bms(params): + if len(params) ==2: + if len(params[1][0]) ==2: + return bmesh_from_pydata(verts=params[0], edges=params[1]) + return bmesh_from_pydata(verts=params[0], faces=params[1]) + + return bmesh_from_pydata(*params) class SvRecursiveNode(): ''' This mixin is used to vectorize any node. @@ -77,8 +87,7 @@ def process_data(self, params): res1, res2 = awesome_func(param1) return res1, res2 - this mixing also adds the list_match property to let the user choose among repeat_last, cycle and match short and so on - + This mixing also adds the list_match property to let the user choose among repeat_last, cycle and match short and so on to add this property to the layout: def draw_buttons_ext(self, context, layout): layout.prop(self, 'list_match') @@ -86,6 +95,18 @@ def draw_buttons_ext(self, context, layout): def rclick_menu(self, context, layout): layout.prop_menu_enum(self, "list_match", text="List Match") + in case of needing to generate bmesh geometry you can + + set self.build_bmesh = True + define base sockets indices (verts, edges and faces) or (verts, edg_pol) in self.bmesh_inputs + + then process_data will recive a list with bmesh mesh as first item + def process_data(self, params) + bmesh_list, other_param1, other_param2 = params + + creating the bmesh_list before matching improves performace a lot, but if + you are modifiying the bm in your function do it over a copy -> bm.copy() + ''' list_match: EnumProperty( @@ -94,9 +115,29 @@ def rclick_menu(self, context, layout): items=numpy_list_match_modes, default="REPEAT", update=updateNode) + build_bmesh = False + bmesh_inputs = [0, 1, 2] + + def update_params_to_bmesh(self, params, input_nesting): + bms = process_matched([p for i, p in enumerate(params) if i in self.bmesh_inputs], + create_bms, + self.list_match, + [2 for n in self.bmesh_inputs], + 1) + params = [bms, *[p for i, p in enumerate(params) if i not in self.bmesh_inputs]] + input_nesting = [1, *[n for i, n in enumerate(input_nesting) if i not in self.bmesh_inputs]] + return params, input_nesting + + def pre_setup(self): + ''' + function to be overriden in the node in case something has to change + before getting input data + ''' + # pass + def process(self): - if hasattr(self, 'pre_setup'): - self.pre_setup() + + self.pre_setup() if not all([s.is_linked for s in self.inputs if s.is_mandatory]): return @@ -112,16 +153,15 @@ def process(self): if s.pre_processing == 'ONE_ITEM': p = one_item_list(ensure_min_nesting(s.sv_get(deepcopy=False, default=default), 2)) else: - if s.is_linked: - p = ensure_min_nesting(s.sv_get(deepcopy=False, default=default), s.nesting_level) - else: - p = s.sv_get(deepcopy=False, default=default) - # params.append(ensure_nesting_level(p, s.nesting_level)) - params.append(p) + p = ensure_min_nesting(s.sv_get(deepcopy=False, default=default), s.nesting_level) + params.append(p) one_output = len(self.outputs) == 1 + if self.build_bmesh: + params, input_nesting = self.update_params_to_bmesh(params, input_nesting) + result = process_matched(params, self.process_data, self.list_match, input_nesting, len(self.outputs)) if one_output: diff --git a/utils/sv_itertools.py b/utils/sv_itertools.py index 2e97c848da..13ece96f2a 100644 --- a/utils/sv_itertools.py +++ b/utils/sv_itertools.py @@ -135,7 +135,7 @@ def recurse_f_level_control(params, constant, main_func, matching_f, desired_lev p_temp.append(p) params = matching_f(p_temp) for g in zip(*params): - result_add(recurse_f_level_control(matching_f(g), constant, main_func, matching_f, desired_levels, concatenate=concatenate)) + result_add(recurse_f_level_control(g, constant, main_func, matching_f, desired_levels, concatenate=concatenate)) else: result = main_func(params, constant, matching_f) return result @@ -158,7 +158,7 @@ def process_matched(params, main_func, matching_mode, input_nesting, outputs_num if any(over_levels): p_temp = [] for p, lv, dl in zip(params, input_levels, input_nesting): - print(lv,dl) + if lv <= dl: p_temp.append([p]) else: