Skip to content

Commit

Permalink
Component analyzer upgrade (#4033)
Browse files Browse the repository at this point in the history
* Adding numpy routines 2x average boost

* pydata_to_bmesh nesting safe_check

* recursive back-end

* adjacent elements index routine

* docs update
  • Loading branch information
vicdoval authored Apr 10, 2021
1 parent 56db66e commit 97b32c9
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 127 deletions.
11 changes: 10 additions & 1 deletion data_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,10 @@ def repeat_last_for_length(lst, count, deepcopy=False):
repeat_last_for_length([], n) = []
repeat_last_for_length([1,2], 4) = [1, 2, 2, 2]
"""
if not lst or len(lst) >= count:
if not lst:
return lst
if len(lst) >= count:
return lst[:count]
n = len(lst)
x = lst[-1]
result = lst[:]
Expand Down Expand Up @@ -1073,6 +1075,13 @@ def matrixdef(orig, loc, scale, rot, angle, vec_angle=[[]]):
#### random stuff
####

def has_element(pol_edge):
if pol_edge is None:
return False
if len(pol_edge) > 0 and hasattr(pol_edge[0], '__len__') and len(pol_edge[0]) > 0:
return True
return False

def no_space(s):
return s.replace(' ', '_')

Expand Down
14 changes: 12 additions & 2 deletions docs/nodes/analyzer/component_analyzer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ This node has the following parameters:
* **Adjacent Faces**: Adjacent faces.
* **Adjacent Edges num**: Number of Adjacent edges.
* **Adjacent Faces num**: Number of adjacent faces.
* **Adjacent Edges Idx**: Index of Adjacent edges.
* **Adjacent Faces Idx**: Index of adjacent faces.
* **Is Boundary**: Is Vertex on mesh borders.
* **Is Interior**: Is Vertex on mesh interior.
* **Is Manifold**: Is Vertex part of the Manifold.
Expand All @@ -54,6 +56,10 @@ This node has the following parameters:
* **Inverted**. Reversed edges.
* **Adjacent faces**. Adjacent faces.
* **Adjacent faces Num**. Adjacent faces number.
* **Adjacent faces Idx**. Adjacent faces Index.
* **Connected edges**. Edges connected to each edge.
* **Connected edges Num**. Connected edges number.
* **Connected edges Idx**. Connected edges Index.
* **Is Boundary**. Is Edge on mesh borders.
* **Is Contiguous**. Is Edge contiguous.
* **Is Convex**. Is Edge Convex.
Expand All @@ -78,13 +84,15 @@ This node has the following parameters:
* **Area**. Area of faces
* **Perimeter**. Perimeter of faces
* **Sides**. Sides of faces
* **Adjacent Faces**. Faces that share a edge with face.
* **Neighbor Faces**. Faces that share a vertex with face.
* **Neighbor Faces Num**. Number of Faces that share a edge with face
* **Adjacent Faces Num**. Number of Faces that share a vertex with face.
* **Neighbor Faces Idx**. Index of Faces that share a edge with face
* **Adjacent Faces Idx**. Index of Faces that share a vertex with face.
* **Sharpness**. Average of curvature of mesh in faces vertices
* **Inverse**. Reversed Polygons (Flipped).
* **Edges**. Face Edges.
* **Adjacent Faces**. Faces that share a edge with face.
* **Neighbor Faces**. Faces that share a vertex with face.
* **Is Boundary**. Is the face boundary or interior


Expand All @@ -93,6 +101,8 @@ This node has the following parameters:
* **Split Output**. Split the result to get one object per result *[[0, 1], [2]] --> [[0], [1], [2]]*
* **Wrap Output**. Keeps original data shape *[Matrix, Matrix, Matrix] --> [[Matrix, Matrix], [Matrix]]*

- Some routines offer *Output Numpy* property to output numpy arrays in stead of regular python lists (making the node faster)


Example of usage
----------------
Expand Down
86 changes: 62 additions & 24 deletions nodes/analyzer/component_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, match_long_repeat,throttle_and_update_node
from sverchok.utils.listutils import lists_flat

from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode
from sverchok.utils.modules.polygon_utils import faces_modes_dict, pols_origin_modes_dict, tangent_modes_dict

from sverchok.utils.modules.edge_utils import edges_modes_dict
Expand All @@ -45,7 +45,7 @@
"s":"SvStringsSocket",
"m": "SvMatrixSocket"
}
op_dict = {
op_dict = { #signature : (prop_name, e for enum and b for boolean)
'c': ('center_mode', 'e'),
's': ('sum_items', 'b'),
'o': ('origin_mode', 'e'),
Expand All @@ -54,10 +54,11 @@
'u': ('matrix_normal_up', 'e'),
'n': ('matrix_normal', 'e'),
't': ('tangent_mode', 'e'),
'a': ('output_numpy', 'b'),
}


class SvComponentAnalyzerNode(bpy.types.Node, SverchCustomTreeNode):
class SvComponentAnalyzerNode(bpy.types.Node, SverchCustomTreeNode, SvRecursiveNode):
"""
Triggers: Center/Matrix/Length
Tooltip: Data from vertices/edges/faces as Orientation, Location, Length, Normal, Center...
Expand Down Expand Up @@ -210,6 +211,10 @@ def update_mode(self, context):
items=matrix_normal_modes,
default="X",
update=update_mode)
output_numpy: BoolProperty(
name='Output NumPy',
description='Output NumPy arrays',
default=False, update=updateNode)

def actual_mode(self):
if self.mode == 'Verts':
Expand All @@ -221,8 +226,11 @@ def actual_mode(self):
return component_mode

def draw_label(self):
text = "CA: " + self.mode + " "+ self.actual_mode()
return text
if self.hide:
text = "CA: " + self.mode + " "+ self.actual_mode()
return self.label if self.label else text

return self.label if self.label else self.name

def draw_buttons(self, context, layout):
layout.prop(self, "mode", expand=True)
Expand All @@ -239,14 +247,25 @@ def draw_buttons(self, context, layout):

for option in local_ops:
if option in op_dict:
layout.prop(self, op_dict[option][0])
if option != 'a':
layout.prop(self, op_dict[option][0])

if not 'u' in out_ops:
layout.prop(self, 'split')
else:
layout.prop(self, 'wrap')

def draw_buttons_ext(self, context, layout):
layout.prop(self, 'list_match')
self.draw_buttons(context, layout)
info = modes_dicts[self.mode][self.actual_mode().replace("_", " ")]
local_ops = info[2]
if 'a' in local_ops:
layout.prop(self, 'output_numpy')


def rclick_menu(self, context, layout):
layout.prop_menu_enum(self, "list_match", text="List Match")
layout.prop_menu_enum(self, "mode", text=self.mode)
if self.mode == 'Verts':
layout.prop_menu_enum(self, "vertex_mode", text=self.vertex_mode)
Expand Down Expand Up @@ -282,6 +301,15 @@ def sv_init(self, context):

self.update_mode(context)

def post_process(self, result_vals, unwrap):
if unwrap:
if not self.wrap:
return [v for r in result_vals for v in r]
else:
if self.split:
return [[v] for r in result_vals for v in r]
return result_vals

def output(self, result_vals, socket, unwrap):
if unwrap:
if not self.wrap:
Expand All @@ -291,24 +319,36 @@ def output(self, result_vals, socket, unwrap):
result_vals = [[v] for r in result_vals for v in r]

socket.sv_set(result_vals)
def pre_setup(self):
for s in self.inputs:
s.nesting_level = 3
s.is_mandatory = False
modes_dict = modes_dicts[self.mode]
component_mode = self.actual_mode().replace("_", " ")
func_inputs = modes_dict[component_mode][1]
if "v" in func_inputs:
self.inputs[0].is_mandatory = True
if "e" in func_inputs:
self.inputs[1].is_mandatory = True
if "p" in func_inputs:
self.inputs[2].is_mandatory = True

def process_data(self, params):
verts, edges, pols = params

def process(self):
if not any(output.is_linked for output in self.outputs):
return
modes_dict = modes_dicts[self.mode]
component_mode = self.actual_mode().replace("_", " ")
func_inputs, local_ops, output_ops, func, output_sockets = modes_dict[component_mode][1:6]
params = []
if "v" in func_inputs:
params.append(self.inputs['Vertices'].sv_get(deepcopy=False))
params.append(verts)
if "e" in func_inputs:
params.append(self.inputs['Edges'].sv_get(default=[[]], deepcopy=False))
params.append(edges)
if "p" in func_inputs:
params.append(self.inputs['Faces'].sv_get(default=[[]], deepcopy=False))
params.append(pols)

result_vals = []

meshes = match_long_repeat(params)

special = False
if local_ops:
Expand All @@ -322,30 +362,28 @@ def process(self):
'u': self.matrix_normal_up,
'n': self.matrix_normal,
't': self.tangent_mode,
'a': self.output_numpy
}
special_op = []
for option in local_ops:
option_val = options_dict[option]
special_op.append(option_val if type(option_val) == bool else option_val.replace("_", " "))
special = True
if len(local_ops) == 1:
special_op = special_op[0]

for param in zip(*meshes):
for param in zip(*params):
if special:
vals = func(*param, special_op)
vals = func(*param, *special_op)
else:
vals = func(*param)
result_vals.append(vals)

result_vals.append(vals)
unwrap = 'u' in output_ops
if len(output_sockets) > 1:
results = list(zip(*result_vals))
for res, socket in zip(results, self.outputs):
self.output(res, socket, unwrap)
else:
self.output(result_vals, self.outputs[0], unwrap)
if len(output_sockets) == 1:
return self.post_process(result_vals, unwrap), [], []
if len(output_sockets) == 2:
return (*[self.post_process(l, unwrap) for l in zip(*result_vals)], [])

return [self.post_process(l, unwrap) for l in zip(*result_vals)]


def register():
Expand Down
1 change: 1 addition & 0 deletions utils/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ def np_normalize_vectors(vecs):
norms = np.linalg.norm(vecs, axis=1)
nonzero = (norms > 0)
vecs[nonzero] = vecs[nonzero] / norms[nonzero][:,np.newaxis]
return vecs

def weighted_center(verts, field=None):
if field is None:
Expand Down
Loading

0 comments on commit 97b32c9

Please sign in to comment.