diff --git a/docs/nodes/list_main/list_main.rst b/docs/nodes/list_main/list_main.rst index 5c3507c02e785ad79f8a4e6b07effd47c624e1aa..491e7fbcb8e108e4e855f41bbe14012e7e598145 100644 --- a/docs/nodes/list_main/list_main.rst +++ b/docs/nodes/list_main/list_main.rst @@ -15,3 +15,4 @@ List Main list_mask_in list_match list_math + list_modifier diff --git a/docs/nodes/list_main/list_modifier.rst b/docs/nodes/list_main/list_modifier.rst new file mode 100644 index 0000000000000000000000000000000000000000..d381b74a24e6c930c1ba609d34e9aa31c214dee1 --- /dev/null +++ b/docs/nodes/list_main/list_modifier.rst @@ -0,0 +1,101 @@ +List Modifier +~~~~~~~~~~~~~ + +This node offers an assortment of list modification functions. The node has both Unary and Binary modes. + +- In Unary mode it will use the input of either sockets, it will use data1 first, then check data2 +- If both are linked data1 is used. +- The node will draw the name of the current mode into the node header, useful for minimized nodes. + +Behaviour +~~~~~~~~~ + ++----------------------+----------+--------------------------------------------------------------------------+ +| Modes | inputs | Behaviour Description | ++======================+==========+==========================================================================+ +| Set | unary | turns the valid input into a set :: | +| | | | +| | | input = [0,0,0,1,1,1,3,3,3,5,5,5,6,7,8,4,4,4,6,6,6,7,7,7,8] | +| | | output = [set(input)] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Ordered Set by input | unary | only unique numbers but ordered by the original input sequence :: | +| | | | +| | | input = [0,0,0,1,1,1,3,3,3,5,5,5,6,7,8,4,4,4,6,6,6,7,7,7,8] | +| | | output = [0,1,3,5,6,7,8,4] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Unique Consecutives | unary | no consecutive repeats :: | +| | | | +| | | input = [0,0,0,1,1,1,3,3,3,5,5,5,6,7,8,4,4,4,6,6,6,7,7,7,8] | +| | | output = [0,1,3,5,6,7,8,4,6,7,8] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Sequential Set | unary | unique input values, ordered by their value :: | +| | | | +| | | input = [0,0,0,1,1,1,3,3,3,5,5,5,6,7,8,4,4,4,6,6,6,7,7,7,8] | +| | | output = [0,1,3,4,5,6,7,8] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Sequential Set Rev | unary | unique input values, ordered by their value, reversed :: | +| | | | +| | | input = [0,0,0,1,1,1,3,3,3,5,5,5,6,7,8,4,4,4,6,6,6,7,7,7,8] | +| | | output = [8,7,6,5,4,3,1,0] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Normalize | unary | scales down the values in the list to the range ``-1.0 .. 1.0`` | ++----------------------+----------+--------------------------------------------------------------------------+ +| Accumulating Sum | unary | see ``itertools.accumulate`` :: | +| | | | +| | | input = list(accumulate(range(10))) | +| | | output = [0,1,3,6,10,15,21,28,36,45] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Mask Subset | binary | generates a mask to indicate for each value in A whether it appears in B | +| | | :: | +| | | | +| | | A = [0,1,2,3,4,5,6,7] | +| | | B = [2,3,4,5] | +| | | output = [False, False, True, True, True, True, False, False] | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Intersection | binary | returns the set of items that appear in both A and B | +| | | | +| | | |image1| | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Union | binary | returns the set of items A joined with B | +| | | | +| | | |image2| | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Difference | binary | returns the set of items from A that don’t appear in B | +| | | | +| | | |image3| | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ +| Symmetric Diff | binary | returns the set of elements of A and B that don’t appear in Both | +| | | | +| | | |image4| | +| | | | ++----------------------+----------+--------------------------------------------------------------------------+ + +*output as list* + +The boolean switch to *output as list* will be on by default, +essentially it will wrap the output as a list because true sets don’t +have a defined order (which we do need most of the time). + +Example +------- + +See the pullrequest for details : https://github.com/nortikin/sverchok/pull/884 + +also see the original thread : https://github.com/nortikin/sverchok/issues/865 + + + +.. |image1| image:: https://cloud.githubusercontent.com/assets/619340/18662881/733c219c-7f1c-11e6-85fc-fcfc1ea7768d.png +.. |image2| image:: https://cloud.githubusercontent.com/assets/619340/18662921/a24aac7e-7f1c-11e6-80c1-684e513607a2.png +.. |image3| image:: https://cloud.githubusercontent.com/assets/619340/18663232/ec821d80-7f1d-11e6-83bc-3fd64ff037b4.png +.. |image4| image:: https://cloud.githubusercontent.com/assets/619340/18662983/f252aeba-7f1c-11e6-963b-e2b7d7111e17.png \ No newline at end of file diff --git a/index.md b/index.md index 9a861653788e10459caf8a2ff6feed16ef78fb49..dfe1a8d166d79588f354b3f6967bcb8377d781ef 100644 --- a/index.md +++ b/index.md @@ -1,3 +1,4 @@ +> ### This file is parsed by menu.py > > The following strict rules apply to editing this file: > @@ -95,6 +96,9 @@ MaskListNode SvMaskJoinNode +## List Mutators + SvListModifierNode + ## List main ListJoinNode ZipNode diff --git a/menu.py b/menu.py index 6198efb88682df4b5aed4802a5d1cfc8ad942c04..cd796d966cc76ffbcc3dbe95c85877b5fba678be 100644 --- a/menu.py +++ b/menu.py @@ -18,7 +18,6 @@ # ##### END GPL LICENSE BLOCK ##### import os -import pprint from os.path import dirname from collections import OrderedDict @@ -37,14 +36,6 @@ def make_node_cats(): ''' this loads the index.md file and converts it to an OrderedDict of node categories. - ## category name - bl_idname shortname - ... ... ... - - becomes - - node_cats[category name] = [['bl_idname', 'shortname'],...... ] - ''' index_path = os.path.join(dirname(__file__), 'index.md') @@ -70,7 +61,6 @@ def make_node_cats(): # final append node_cats[category] = temp_list - # pprint.pprint(node_cats) return node_cats @@ -93,8 +83,9 @@ def juggle_and_join(node_cats): node_cats['Beta Nodes'].extend(alpha) # put masks into list main - masks = node_cats.pop("List Masks") - node_cats["List main"].extend(masks) + for ltype in ["List Masks", "List Mutators"]: + node_refs = node_cats.pop(ltype) + node_cats["List main"].extend(node_refs) # add extended gens to Gens menu gen_ext = node_cats.pop("Extended Generators") diff --git a/nodes/__init__.py b/nodes/__init__.py index 7f093b5c47269f97f0bbd265365bb886924e86e3..2bb5aec26572dc33b2e0f09fa5a2b36bca8d5dbc 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -39,4 +39,3 @@ def automatic_collection(): return nodes_dict automatic_collection() - diff --git a/nodes/basic_debug/3dview_props.py b/nodes/basic_debug/3dview_props.py index 6b9d66b4f8b56be6a052a76ed274303468766999..24cb7cdb43ebe5a5c10306b448d3e0dad867aa01 100644 --- a/nodes/basic_debug/3dview_props.py +++ b/nodes/basic_debug/3dview_props.py @@ -26,7 +26,7 @@ class Sv3DviewPropsNode(bpy.types.Node, SverchCustomTreeNode): ''' Sv 3Dview Props Node ''' bl_idname = 'Sv3DviewPropsNode' bl_label = '3dview Props' - bl_icon = 'OUTLINER_OB_EMPTY' + bl_icon = 'SETTINGS' def draw_buttons(self, context, layout): context = bpy.context diff --git a/nodes/list_basic/modifier.py b/nodes/list_basic/modifier.py new file mode 100644 index 0000000000000000000000000000000000000000..0a1af8e07924524b7293f9c1278e6d7b643e72d8 --- /dev/null +++ b/nodes/list_basic/modifier.py @@ -0,0 +1,173 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +from itertools import accumulate + +import bpy +from bpy.props import EnumProperty, IntProperty, BoolProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +def normalize(a): + # or numpy version.. + max_value = max(a) + return [n/max_value for n in a] + + +def ordered_set(a): + seen = set() + b = [] + for x in a: + if not x in seen: + b.append(x) + seen.add(x) + return b + + +def unique_consecutives(a): + prev = '' + b = [] + for x in a: + if not x == prev: + b.append(x) + prev = x + return b + + +def mask_subset(a, b): + return [(_ in b) for _ in a] + + +SET = 'Set' +INTX = 'Intersection' +UNION = 'Union' +DIFF = 'Difference' +SYMDIFF = 'Symmetric Diff' +SET_OPS = [SET, INTX, UNION, DIFF, SYMDIFF] + +# using a purposely broad indexing value range incase other functions get into this.. +node_item_list = [ + (1, 1, SET, set), + (1, 10, "Ordered Set by input", ordered_set), + (1, 20, "Unique Consecutives", unique_consecutives), + (1, 30, "Sequential Set", lambda a: sorted(set(a))), + (1, 40, "Sequential Set Rev", lambda a: sorted(set(a), reverse=True)), + (1, 50, "Normalize", normalize), + (1, 60, "Accumulating Sum", lambda a: list(accumulate(a))), + (2, 69, "Mask subset (A in B)", mask_subset), + (2, 70, INTX, lambda a, b: set(a) & set(b)), + (2, 80, UNION, lambda a, b: set(a) | set(b)), + (2, 90, DIFF, lambda a, b: set(a) - set(b)), + (2, 100, SYMDIFF, lambda a, b: set(a) ^ set(b)) +] + +func_dict = {k: v for _, _, k, v in node_item_list} +num_inputs = {k: v for v, _, k, _ in node_item_list} + + +class SvListModifierNode(bpy.types.Node, SverchCustomTreeNode): + ''' List Modifier''' + bl_idname = 'SvListModifierNode' + bl_label = 'List Modifier' + bl_icon = 'MODIFIER' + + mode_items = [(name, name, "", idx) for _, idx, name, _ in node_item_list] + + func_ = EnumProperty( + name="Modes", + description="Mode Choices", + default=SET, items=mode_items, + update=updateNode + ) + + listify = BoolProperty( + default=True, + description='Output lists or proper sets', + update=updateNode + ) + + + def draw_buttons(self, context, layout): + layout.prop(self, "func_", text='') + layout.prop(self, "listify", text='output as list') + + def sv_init(self, context): + self.inputs.new('StringsSocket', "Data1") + self.inputs.new('StringsSocket', "Data2") + self.outputs.new('StringsSocket', "Result") + + def draw_label(self): + return self.func_ + + def process(self): + inputs = self.inputs + outputs = self.outputs + + if not outputs[0].is_linked: + return + + unary = (num_inputs[self.func_] == 1) + f = self.get_f(unary) + + if unary: + if inputs['Data1'].is_linked: + data1 = inputs['Data1'].sv_get() + elif inputs['Data2'].is_linked: + data1 = inputs['Data2'].sv_get() + else: + return + out = f(data1) + else: + data1 = inputs['Data1'].sv_get() + data2 = inputs['Data2'].sv_get() + out = f(data1, data2) + # params = match_long_repeat([data1, data2]) + # out = f(*params) + + outputs[0].sv_set(out) + + def get_f(self, unary): + operation = func_dict[self.func_] + + do_post = (self.func_ in SET_OPS) and self.listify + post_process = list if do_post else lambda x: x # identity function + + if unary: + def f(d): + if isinstance(d[0], (int, float)): + return post_process(operation(d)) + else: + return [f(x) for x in d] + else: + def f(a, b): + if isinstance(a[0], (int, float)): + return post_process(operation(a, b)) + else: + return [f(*_) for _ in zip(a, b)] + + return f + + +def register(): + bpy.utils.register_class(SvListModifierNode) + + +def unregister(): + bpy.utils.unregister_class(SvListModifierNode) diff --git a/ui/nodeview_space_menu.py b/ui/nodeview_space_menu.py index fc94f76e26964c0d464d2aa02c704af67b9d88c4..66b69a8a38a14ea2c7de3bc9b4396efeb1e9240d 100644 --- a/ui/nodeview_space_menu.py +++ b/ui/nodeview_space_menu.py @@ -30,9 +30,7 @@ import sverchok from sverchok.menu import make_node_cats node_cats = make_node_cats() - addon_name = sverchok.__name__ - menu_prefs = {} @@ -46,6 +44,20 @@ def get_icon_switch(): def layout_draw_categories(layout, node_details): show_icons = menu_prefs.get('show_icons') + def icon(display_icon): + '''returns empty dict if show_icons is False, else the icon passed''' + return {'icon': display_icon for i in [1] if show_icons and display_icon} + + def get_icon(node_ref): + # some nodes don't declare a bl_icon, but most do so try/except is fine. + try: + _icon = getattr(node_ref, 'bl_icon') + if _icon == 'OUTLINER_OB_EMPTY': + _icon = None + except: + _icon = None + return _icon + add_n_grab = 'node.add_node' for node_info in node_details: @@ -56,20 +68,8 @@ def layout_draw_categories(layout, node_details): bl_idname = node_info[0] node_ref = getattr(bpy.types, bl_idname) - if show_icons: - - # some nodes don't declare a bl_icon, but most do so try/except is fine. - try: - icon = getattr(node_ref, 'bl_icon') - except: - icon = None - - if icon and (not icon == 'OUTLINER_OB_EMPTY'): - layout_params = dict(text=node_ref.bl_label, icon=icon) - else: - layout_params = dict(text=node_ref.bl_label) - else: - layout_params = dict(text=node_ref.bl_label) + display_icon = get_icon(node_ref) + layout_params = dict(text=node_ref.bl_label, **icon(display_icon)) node_op = layout.operator(add_n_grab, **layout_params) node_op.type = bl_idname @@ -106,42 +106,37 @@ class NODEVIEW_MT_Dynamic_Menu(bpy.types.Menu): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - s = layout.operator("node.add_search", text="Search", icon='VIEWZOOM') + s = layout.operator("node.add_search", text="Search", icon='OUTLINER_DATA_FONT') s.use_transform = True show_icons = menu_prefs.get('show_icons') + def icon(display_icon): + '''returns empty dict if show_icons is False, else the icon passed''' + return {'icon': display_icon for i in [1] if show_icons} + layout.separator() - if show_icons: - layout.menu("NODEVIEW_MT_AddGenerators", icon='OBJECT_DATAMODE') - layout.menu("NODEVIEW_MT_AddTransforms", icon='MANIPUL') - layout.menu("NODEVIEW_MT_AddAnalyzers", icon='BORDERMOVE') - layout.menu("NODEVIEW_MT_AddModifiers", icon='MODIFIER') - else: - layout.menu("NODEVIEW_MT_AddGenerators") - layout.menu("NODEVIEW_MT_AddTransforms") - layout.menu("NODEVIEW_MT_AddAnalyzers") - layout.menu("NODEVIEW_MT_AddModifiers") + + layout.menu("NODEVIEW_MT_AddGenerators", **icon('OBJECT_DATAMODE')) + layout.menu("NODEVIEW_MT_AddTransforms", **icon('MANIPUL')) + layout.menu("NODEVIEW_MT_AddAnalyzers", **icon('VIEWZOOM')) + layout.menu("NODEVIEW_MT_AddModifiers", **icon('MODIFIER')) layout.separator() layout.menu("NODEVIEW_MT_AddNumber") layout.menu("NODEVIEW_MT_AddVector") layout.menu("NODEVIEW_MT_AddMatrix") layout.menu("NODEVIEW_MT_AddLogic") - layout.menu("NODEVIEW_MT_AddListOps") + layout.menu("NODEVIEW_MT_AddListOps", **icon('NLA')) layout.separator() - layout.menu("NODEVIEW_MT_AddViz") + layout.menu("NODEVIEW_MT_AddViz", **icon('RESTRICT_VIEW_OFF')) layout.menu("NODEVIEW_MT_AddText") layout.menu("NODEVIEW_MT_AddScene") layout.menu("NODEVIEW_MT_AddLayout") layout.separator() layout.menu("NODEVIEW_MT_AddNetwork") - if show_icons: - layout.menu("NODEVIEW_MT_AddBetas", icon='OUTLINER_DATA_POSE') - layout.menu("NODEVIEW_MT_AddAlphas", icon='ERROR') - else: - layout.menu("NODEVIEW_MT_AddBetas") - layout.menu("NODEVIEW_MT_AddAlphas") + layout.menu("NODEVIEW_MT_AddBetas", **icon('OUTLINER_DATA_POSE')) + layout.menu("NODEVIEW_MT_AddAlphas", **icon('ERROR')) class NODEVIEW_MT_AddGenerators(bpy.types.Menu): @@ -173,6 +168,7 @@ class NODEVIEW_MT_AddListOps(bpy.types.Menu): layout.menu("NODEVIEW_MT_AddListmain") layout.menu("NODEVIEW_MT_AddListstruct") layout_draw_categories(self.layout, node_cats["List Masks"]) + layout_draw_categories(self.layout, node_cats["List Mutators"]) classes = [