Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extrude Edges Upgrade #4017

Merged
merged 2 commits into from
Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
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