Skip to content

Commit

Permalink
Extrude Edges Upgrade (#4017)
Browse files Browse the repository at this point in the history
* Extrude Edges Upgrade

* Correct name
  • Loading branch information
vicdoval authored Apr 3, 2021
1 parent bf9612d commit d838609
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 92 deletions.
4 changes: 2 additions & 2 deletions docs/nodes/modifier_change/extrude_edges_mk2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------
Expand Down Expand Up @@ -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

115 changes: 31 additions & 84 deletions nodes/modifier_change/extrude_edges_mk2.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,34 @@
#
# ##### 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
'''
bl_idname = 'SvExtrudeEdgesNodeMk2'
bl_label = 'Extrude Edges Mk2'
bl_label = 'Extrude Edges'
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')

This comment has been minimized.

Copy link
@zeffii

zeffii Apr 4, 2021

Collaborator

typo here

layout.prop(self, 'list_match')

def sv_init(self, context):
self.inputs.new('SvVerticesSocket', "Vertices")
Expand All @@ -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():
Expand Down
129 changes: 129 additions & 0 deletions utils/mesh/extrude_edges.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 11 additions & 6 deletions utils/nodes_mixins/recursive_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]]
Expand Down

0 comments on commit d838609

Please sign in to comment.