From d9e7d63454b2400e43cf8dd7f4099032b4d83cf5 Mon Sep 17 00:00:00 2001 From: Victor Doval <10011941+vicdoval@users.noreply.github.com> Date: Sun, 4 Apr 2021 00:20:48 +0200 Subject: [PATCH 1/2] Extrude Edges Upgrade --- .../modifier_change/extrude_edges_mk2.rst | 4 +- nodes/modifier_change/extrude_edges_mk2.py | 113 ++++----------- utils/mesh/extrude_edges.py | 129 ++++++++++++++++++ utils/nodes_mixins/recursive_nodes.py | 17 ++- 4 files changed, 172 insertions(+), 91 deletions(-) create mode 100644 utils/mesh/extrude_edges.py diff --git a/docs/nodes/modifier_change/extrude_edges_mk2.rst b/docs/nodes/modifier_change/extrude_edges_mk2.rst index 55c416ed06..f55c344437 100644 --- a/docs/nodes/modifier_change/extrude_edges_mk2.rst +++ b/docs/nodes/modifier_change/extrude_edges_mk2.rst @@ -26,7 +26,8 @@ This node has the following inputs: Parameters ---------- -This node does not have parameters. +Implementation: (in N-panel) Offers Numpy (Faster) and Bmesh (Legacy. Slower) +List Match: (in N-panel) Chose how list length should be matched Outputs ------- @@ -68,4 +69,3 @@ Extrude only top edges of the cube: Extrude only boundary edges of the plane; this also is an example of FaceData socket usage: .. image:: https://user-images.githubusercontent.com/284644/71553528-ca5c4f00-2a32-11ea-95c4-80c1d85129f1.png - diff --git a/nodes/modifier_change/extrude_edges_mk2.py b/nodes/modifier_change/extrude_edges_mk2.py index 7fe532e32e..4120e91cc0 100644 --- a/nodes/modifier_change/extrude_edges_mk2.py +++ b/nodes/modifier_change/extrude_edges_mk2.py @@ -16,20 +16,14 @@ # # ##### END GPL LICENSE BLOCK ##### -from mathutils import Matrix, Vector - import bpy -from bpy.props import IntProperty, FloatProperty -import bmesh.ops from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, match_long_repeat, Matrix_generate -from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh, bmesh_edges_from_edge_mask - -def is_matrix(lst): - return len(lst) == 4 and len(lst[0]) == 4 +from sverchok.data_structure import updateNode +from sverchok.utils.mesh.extrude_edges import extrude_edges, extrude_edges_bmesh +from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode -class SvExtrudeEdgesNodeMk2(bpy.types.Node, SverchCustomTreeNode): +class SvExtrudeEdgesNodeMk2(bpy.types.Node, SverchCustomTreeNode, SvRecursiveNode): ''' Triggers: Extrude edges Tooltip: Extrude some edges of the mesh @@ -38,6 +32,18 @@ class SvExtrudeEdgesNodeMk2(bpy.types.Node, SverchCustomTreeNode): bl_label = 'Extrude Edges Mk2' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_EXTRUDE_EDGES' + implentation_items = [ + ('BMESH', 'Bmesh', 'Slower (Legacy. Face data is not transfered identically)', 0), + ('NUMPY', 'Numpy', 'Faster', 1)] + implentation: bpy.props.EnumProperty( + name='Implementation', + items=implentation_items, + default='NUMPY', + update=updateNode + ) + def draw_buttons_ext(self, context, layout): + layout.prop(self, 'implentation') + layout.prop(self, 'list_match') def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Vertices") @@ -55,83 +61,24 @@ def sv_init(self, context): self.outputs.new('SvStringsSocket', 'NewFaces') self.outputs.new('SvStringsSocket', 'FaceData') - def process(self): - if not (self.inputs['Vertices'].is_linked): - return - - if not any(output.is_linked for output in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - edges_s = self.inputs['Edges'].sv_get(default=[[]]) - faces_s = self.inputs['Faces'].sv_get(default=[[]]) - matrices_s = self.inputs['Matrices'].sv_get(default=[[]]) - if is_matrix(matrices_s[0]): - matrices_s = [Matrix_generate(matrices_s)] - else: - matrices_s = [Matrix_generate(matrices) for matrices in matrices_s] - edge_masks_s = self.inputs['EdgeMask'].sv_get(default=[[]]) - face_data_s = self.inputs['FaceData'].sv_get(default=[[]]) - - result_vertices = [] - result_edges = [] - result_faces = [] - result_face_data = [] - result_ext_vertices = [] - result_ext_edges = [] - result_ext_faces = [] - - meshes = match_long_repeat([vertices_s, edges_s, faces_s, edge_masks_s, face_data_s, matrices_s]) - - for vertices, edges, faces, edge_mask, face_data, matrices in zip(*meshes): - if not matrices: - matrices = [Matrix()] - if face_data: - face_data_matched = repeat_last_for_length(face_data, len(faces)) - - bm = bmesh_from_pydata(vertices, edges, faces, markup_face_data=True, markup_edge_data=True) - if edge_mask: - b_edges = bmesh_edges_from_edge_mask(bm, edge_mask) - else: - b_edges = bm.edges - - new_geom = bmesh.ops.extrude_edge_only(bm, edges=b_edges, use_select_history=False)['geom'] - - extruded_verts = [v for v in new_geom if isinstance(v, bmesh.types.BMVert)] - - for vertex, matrix in zip(*match_long_repeat([extruded_verts, matrices])): - bmesh.ops.transform(bm, verts=[vertex], matrix=matrix, space=Matrix()) - - extruded_verts = [tuple(v.co) for v in extruded_verts] - - extruded_edges = [e for e in new_geom if isinstance(e, bmesh.types.BMEdge)] - extruded_edges = [tuple(v.index for v in edge.verts) for edge in extruded_edges] + def pre_setup(self): + self.inputs[0].is_mandatory = True + self.inputs[1].nesting_level = 3 + self.inputs[2].nesting_level = 3 + self.inputs[5].nesting_level = 2 + self.inputs[5].default_mode = 'MATRIX' - extruded_faces = [f for f in new_geom if isinstance(f, bmesh.types.BMFace)] - extruded_faces = [[v.index for v in edge.verts] for edge in extruded_faces] + def process_data(self, params): - if face_data: - new_vertices, new_edges, new_faces, new_face_data = pydata_from_bmesh(bm, face_data_matched) - else: - new_vertices, new_edges, new_faces = pydata_from_bmesh(bm) - new_face_data = [] - bm.free() + output_data = [[] for s in self.outputs] + extrude = extrude_edges if self.implentation == 'NUMPY' else extrude_edges_bmesh + for vertices, edges, faces, edge_mask, face_data, matrices in zip(*params): + res = extrude(vertices, edges, faces, edge_mask, face_data, matrices) + for o, r in zip(output_data, res): + o.append(r) - result_vertices.append(new_vertices) - result_edges.append(new_edges) - result_faces.append(new_faces) - result_face_data.append(new_face_data) - result_ext_vertices.append(extruded_verts) - result_ext_edges.append(extruded_edges) - result_ext_faces.append(extruded_faces) + return output_data - self.outputs['Vertices'].sv_set(result_vertices) - self.outputs['Edges'].sv_set(result_edges) - self.outputs['Faces'].sv_set(result_faces) - self.outputs['FaceData'].sv_set(result_face_data) - self.outputs['NewVertices'].sv_set(result_ext_vertices) - self.outputs['NewEdges'].sv_set(result_ext_edges) - self.outputs['NewFaces'].sv_set(result_ext_faces) def register(): diff --git a/utils/mesh/extrude_edges.py b/utils/mesh/extrude_edges.py new file mode 100644 index 0000000000..b988ed78c1 --- /dev/null +++ b/utils/mesh/extrude_edges.py @@ -0,0 +1,129 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE + +from itertools import cycle +from mathutils import Matrix, Vector +import bmesh.ops +from numpy import( + array as np_array, + zeros as np_zeros, + unique as np_unique, + concatenate as np_concatenate, + ndarray as np_ndarray, + transpose as np_transpose +) +from sverchok.data_structure import match_long_repeat, repeat_last_for_length +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh, bmesh_edges_from_edge_mask +from sverchok.utils.modules.matrix_utils import matrix_apply_np +from sverchok.utils.bvh_tree import bvh_tree_from_polygons + + +def extrude_edges(vertices, edges, faces, edge_mask, face_data, matrices): + if not matrices: + matrices = [Matrix()] + if face_data: + face_data_matched = repeat_last_for_length(face_data, len(faces)) + if edge_mask: + edge_mask_matched = repeat_last_for_length(edge_mask, len(edges)) + + if isinstance(edges, np_ndarray): + if edge_mask: + np_edges = edges[edge_mask_matched] + else: + np_edges = edges + else: + if edge_mask: + np_edges = np_array(edges)[edge_mask_matched] + else: + np_edges = np_array(edges) + if isinstance(vertices, np_ndarray): + np_verts = vertices + else: + np_verts = np_array(vertices) + + affeced_verts_idx = np_unique(np_edges) + if len(matrices) == 1: + extruded_verts = matrix_apply_np(np_verts[affeced_verts_idx], matrices[0]) + new_vertices = np_concatenate([np_verts, extruded_verts]).tolist() + else: + extruded_verts = [m @ Vector(v) + for v, m in zip(np_verts[affeced_verts_idx].tolist(), cycle(matrices))] + new_vertices = vertices + extruded_verts + + top_edges = np_edges + len(vertices) + mid_edges = np_zeros((len(affeced_verts_idx), 2), dtype=int) + mid_edges[:, 0] = affeced_verts_idx + mid_edges[:, 1] = affeced_verts_idx + len(vertices) + extruded_edges_py = (np_concatenate([top_edges, mid_edges])).tolist() + extruded_faces = np_zeros((len(np_edges), 4), dtype=int) + extruded_faces[:, : 2] = np_edges + extruded_faces[:, 2] = top_edges[:, 1] + extruded_faces[:, 3] = top_edges[:, 0] + extruded_faces_py = extruded_faces.tolist() + if isinstance(edges, np_ndarray): + new_edges = np_concatenate([edges, top_edges, mid_edges]).tolist() + else: + new_edges = edges + extruded_edges_py + + if faces and faces[0]: + if isinstance(faces, np_ndarray): + new_faces = np_concatenate([faces, extruded_faces]).tolist() + else: + new_faces = faces + extruded_faces_py + else: + new_faces = extruded_faces_py + + if face_data: + bvh = bvh_tree_from_polygons(vertices, faces, all_triangles=False, epsilon=0.0, safe_check=True) + mid_points = (np_verts[np_edges[:, 1]] + np_verts[np_edges[:, 0]])/2 + face_idx = [bvh.find_nearest(P)[2] for P in mid_points.tolist()] + new_face_data = face_data_matched+ [face_data_matched[p] for p in face_idx] + else: + new_face_data = [] + + return (new_vertices, new_edges, new_faces, + extruded_verts, extruded_edges_py, extruded_faces_py, + new_face_data) + +def extrude_edges_bmesh(vertices, edges, faces, edge_mask, face_data, matrices): + if not matrices: + matrices = [Matrix()] + if face_data: + face_data_matched = repeat_last_for_length(face_data, len(faces)) + + bm = bmesh_from_pydata(vertices, edges, faces, markup_face_data=True, markup_edge_data=True) + if edge_mask: + b_edges = bmesh_edges_from_edge_mask(bm, edge_mask) + else: + b_edges = bm.edges + + new_geom = bmesh.ops.extrude_edge_only(bm, edges=b_edges, use_select_history=False)['geom'] + + extruded_verts = [v for v in new_geom if isinstance(v, bmesh.types.BMVert)] + if len(matrices) == 1: + bmesh.ops.transform(bm, verts=extruded_verts, matrix=matrices[0], space=Matrix()) + else: + for vertex, matrix in zip(*match_long_repeat([extruded_verts, matrices])): + bmesh.ops.transform(bm, verts=[vertex], matrix=matrix, space=Matrix()) + + extruded_verts = [tuple(v.co) for v in extruded_verts] + + extruded_edges = [e for e in new_geom if isinstance(e, bmesh.types.BMEdge)] + extruded_edges = [tuple(v.index for v in edge.verts) for edge in extruded_edges] + + extruded_faces = [f for f in new_geom if isinstance(f, bmesh.types.BMFace)] + extruded_faces = [[v.index for v in edge.verts] for edge in extruded_faces] + + if face_data: + new_vertices, new_edges, new_faces, new_face_data = pydata_from_bmesh(bm, face_data_matched) + else: + new_vertices, new_edges, new_faces = pydata_from_bmesh(bm) + new_face_data = [] + bm.free() + return (new_vertices, new_edges, new_faces, + extruded_verts, extruded_edges, extruded_faces, + new_face_data) diff --git a/utils/nodes_mixins/recursive_nodes.py b/utils/nodes_mixins/recursive_nodes.py index 46eacf2ea3..f4ba942911 100644 --- a/utils/nodes_mixins/recursive_nodes.py +++ b/utils/nodes_mixins/recursive_nodes.py @@ -29,13 +29,18 @@ def one_item_list(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]) + bmesh_list = [] + for p in zip(*params): + if len(params) == 2: + if len(p[1][0]) == 2: + bmesh_list.append(bmesh_from_pydata(verts=p[0], edges=p[1])) - return bmesh_from_pydata(verts=params[0], faces=params[1]) + else: + bmesh_list.append(bmesh_from_pydata(verts=p[0], faces=p[1])) + else: - return bmesh_from_pydata(*params) + bmesh_list.append(bmesh_from_pydata(*p)) + return bmesh_list class SvRecursiveNode(): ''' This mixin is used to vectorize any node. @@ -122,7 +127,7 @@ 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], + [3 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]] From d9319e0f468e1ad2e00dd9fdee340605a886e23c Mon Sep 17 00:00:00 2001 From: Victor Doval <10011941+vicdoval@users.noreply.github.com> Date: Sun, 4 Apr 2021 00:22:25 +0200 Subject: [PATCH 2/2] Correct name --- nodes/modifier_change/extrude_edges_mk2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes/modifier_change/extrude_edges_mk2.py b/nodes/modifier_change/extrude_edges_mk2.py index 4120e91cc0..e77e755aae 100644 --- a/nodes/modifier_change/extrude_edges_mk2.py +++ b/nodes/modifier_change/extrude_edges_mk2.py @@ -29,7 +29,7 @@ class SvExtrudeEdgesNodeMk2(bpy.types.Node, SverchCustomTreeNode, SvRecursiveNod Tooltip: Extrude some edges of the mesh ''' bl_idname = 'SvExtrudeEdgesNodeMk2' - bl_label = 'Extrude Edges Mk2' + bl_label = 'Extrude Edges' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_EXTRUDE_EDGES' implentation_items = [