diff --git a/docs/nodes/list_struct/levels.rst b/docs/nodes/list_struct/levels.rst index c4bae6be09..6592849b9c 100644 --- a/docs/nodes/list_struct/levels.rst +++ b/docs/nodes/list_struct/levels.rst @@ -29,24 +29,18 @@ Each row of the table describes one nesting level of input data, and defines what do you want to do with data at this nesting level. The table has the following columns: -* **Depth**. This shows the nesting depth of this level, i.e. how deeply nested +* **Level**. This shows the nesting depth of this level, i.e. how deeply nested this data is, counting from the outermost list. Outermost list always has - depth of 0, one that is nested in it has depth of 1, and so on. -* **Nesting**. This shows how many nesting levels are inside each item of data - at this level. At the innermost nesting level, each item of the list is an - "atomic object", for example it can be integer number, floating-point number, - surface or curve, and so on, but not a list or tuple. So, the innermost data - level has nesting level equal to 0 (zero). A list which consists of atomic - objects has nesting level of 1, and so on. + depth of 1, one that is nested in it has depth of 2, and so on. * **Shape**. This describes the shape of data at this level. For lists or tuples, it shows whether this is a list or tuple, and also the number of items in it, in square brackets. For atomic objects, it shows the type of the data ("float", or "int", or "SvSurface", and so on). * **Flatten**. This column contains a checkbox. If checked, the node will concatenate all lists contained in list at this nesting level. Obviously, - atomic objects (nesting of 0) do not contain any nested objects, so for the + atomic objects (integers, floats etc.) do not contain any nested objects, so for the innermost level this checkbox is not available. For lists that contain atomic - objects (nesting of 1), this checkbox is not available either, as there are + objects, this checkbox is not available either, as there are no nested lists too. This checkbox does transform data only at one level, it does not "go deeper" automatically. So, if you check this checkbox, you always decrease nesting level of whole data by 1. To give some examples, @@ -72,33 +66,33 @@ Examples of Usage By default, all checkboxes are disabled, so the node does nothing: -.. image:: https://user-images.githubusercontent.com/9460236/129511959-8e17fe20-ce5e-4127-a88b-bb1dc84b916e.png +.. image:: https://user-images.githubusercontent.com/28003269/187598033-b1489f12-a949-4a14-842c-b77b4d1a94c0.png Let's wrap each number into a separate list (this is what "Graft" option of output socket menus does as well): -.. image:: https://user-images.githubusercontent.com/9460236/129511970-6767616b-7f4f-4672-85d8-2ecf96cc4111.png +.. image:: https://user-images.githubusercontent.com/28003269/187598129-4cd1cb55-4122-43dd-b175-d5ed36b353d9.png By enabling "Wrap" at the next level, we put each vertex into a separate list: -.. image:: https://user-images.githubusercontent.com/9460236/129513146-33f5e2c7-345b-414e-bbf6-561b3dacabd7.png +.. image:: https://user-images.githubusercontent.com/28003269/187598191-b9da1499-c19b-46b4-8564-6e548ca2a2a0.png The next level - put each list of vertices (object) into a separate list: -.. image:: https://user-images.githubusercontent.com/9460236/129511986-c4bf1bac-f8a6-44a9-b187-b532210f89f8.png +.. image:: https://user-images.githubusercontent.com/28003269/187598252-75720f20-48a9-4760-8c97-661867e9843a.png And the outermost level - put the whole data structure into additional pair of square brackets: -.. image:: https://user-images.githubusercontent.com/9460236/129511989-bf1b69d4-b916-4771-a289-30d0761cf60c.png +.. image:: https://user-images.githubusercontent.com/28003269/187598332-9e6ef1a8-80de-4ca4-9991-659c24c6fdc9.png By enabling "Flatten" at the deepest available level, we concatenate vertices data into lists of numbers: -.. image:: https://user-images.githubusercontent.com/9460236/129511997-5fe4d9bd-ce06-40cc-811e-f41de1ec3378.png +.. image:: https://user-images.githubusercontent.com/28003269/187598388-c978e176-e697-4535-ba5b-c7e7612182d4.png By flattening at the outermost level, we concatenate lists of vertices into a single list of vertices: -.. image:: https://user-images.githubusercontent.com/9460236/129512002-d194d402-d80f-4a4f-a3e6-4afb281fd191.png +.. image:: https://user-images.githubusercontent.com/28003269/187598453-09121868-9fc0-4078-90f9-21d5dc50a40c.png If we enable both Flatten flags, we concatenate lists of vertices into lists of numbers, AND we concatenate lists of numbers into a single list of numbers: -.. image:: https://user-images.githubusercontent.com/9460236/129512012-38d5314a-d799-4fb5-ad50-b6792e2907e4.png +.. image:: https://user-images.githubusercontent.com/28003269/187598519-c849fde8-352a-43a5-b638-787e0e9d425c.png diff --git a/index.md b/index.md index 2f6305a928..f27d836573 100644 --- a/index.md +++ b/index.md @@ -475,7 +475,7 @@ ListShuffleNode SvListSortNode ListFlipNode - SvListLevelsNode + SvListLevelsNodeMK2 ## Dictionary SvDictionaryIn diff --git a/nodes/list_struct/levels.py b/nodes/list_struct/levels.py index cd71d1368d..65088cbe85 100644 --- a/nodes/list_struct/levels.py +++ b/nodes/list_struct/levels.py @@ -17,26 +17,29 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy.props import BoolProperty, IntProperty, StringProperty, CollectionProperty, BoolVectorProperty +from bpy.props import BoolProperty, IntProperty, StringProperty, CollectionProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, describe_data_shape_by_level, list_levels_adjust, SIMPLE_DATA_TYPES,\ - changable_sockets +from sverchok.data_structure import (updateNode, describe_data_shape_by_level, list_levels_adjust, SIMPLE_DATA_TYPES, + changable_sockets) from sverchok.utils.curve.core import SvCurve from sverchok.utils.surface.core import SvSurface from sverchok.dependencies import FreeCAD -from sverchok.utils.logging import info +from sverchok.utils.logging import debug +from sverchok.utils.handle_blender_data import correct_collection_length + ALL_TYPES = SIMPLE_DATA_TYPES + (SvCurve, SvSurface) if FreeCAD is not None: import Part ALL_TYPES = ALL_TYPES + (Part.Shape,) -class SvNestingLevelEntry(bpy.types.PropertyGroup): + +class SvNestingLevelEntryMK2(bpy.types.PropertyGroup): def update_entry(self, context): if hasattr(context, 'node'): updateNode(context.node, context) else: - info("Node is not defined in this context, so will not update the node.") + debug("Node is not defined in this context, so will not update the node.") description : StringProperty(options = {'SKIP_SAVE'}, default="?") flatten : BoolProperty( @@ -50,105 +53,74 @@ def update_entry(self, context): default=False, update=update_entry) -class SvListLevelsNode(bpy.types.Node, SverchCustomTreeNode): + +class SvListLevelsNodeMK2(SverchCustomTreeNode, bpy.types.Node): ''' Triggers: List Levels Tooltip: List nesting levels manipulation ''' - bl_idname = 'SvListLevelsNode' + bl_idname = 'SvListLevelsNodeMK2' bl_label = 'List Levels' bl_icon = 'OUTLINER' - levels_config : CollectionProperty(type=SvNestingLevelEntry) - prev_nesting_level : IntProperty(default = 0, options = {'SKIP_SAVE'}) - flatten_mem: BoolVectorProperty(size=32, default=[False for i in range(32)]) - wrap_mem: BoolVectorProperty(size=32, default=[False for i in range(32)]) + levels_config : CollectionProperty(type=SvNestingLevelEntryMK2) + nesting: IntProperty(description="How much nested levels should be shown") def draw_buttons(self, context, layout): - n = len(self.levels_config) - if not n: + if not self.nesting: layout.label(text="No data passed") return - grid = layout.grid_flow(row_major=True, columns=5, align=True) - - grid.label(text='Depth') - grid.label(text='Nesting') - grid.label(text='Shape') - grid.label(text='Flatten') - grid.label(text='Wrap') - - for i, entry in enumerate(self.levels_config): - nesting = n-i-1 - level_str = str(i) - if i == 0: - level_str += " (outermost)" - elif nesting == 0: - level_str += " (innermost)" - grid.label(text=level_str) - grid.label(text=str(nesting)) - grid.label(text=entry.description) + + # https://blender.stackexchange.com/questions/51256/how-to-create-uilist-with-auto-aligned-three-columns/51263#51263 + lvl_split = 0.08 + shape_split = 0.6 + flat_split = 0.5 + + col = layout.column() + col.label(text='Lvl|Shape|Flatten|Wrap') + + for i, entry in zip(range(self.nesting), self.levels_config): + nesting = self.nesting - i - 1 + row = col.split(factor=lvl_split) + row.label(text=f"{i+1}") + row = row.split(factor=shape_split) + row.label(text=entry.description) + row = row.split(factor=flat_split) if nesting < 2: - grid.label(icon='X', text='') + row.label(icon='X', text='') else: - grid.prop(entry, 'flatten', text='') - grid.prop(entry, 'wrap', text='') + row.prop(entry, 'flatten', text='') + row.prop(entry, 'wrap', text='') + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'Data') + self.outputs.new('SvStringsSocket', 'Data') def sv_update(self): - self.update_buttons(False) - - def update_buttons(self, update_during_process): - try: - data = self.inputs['Data'].sv_get(default=[]) - except LookupError: - data = [] - if not data: - self.prev_nesting_level = 0 - for i, l in enumerate(self.levels_config): - self.flatten_mem[i] = l.flatten - self.wrap_mem[i] = l.wrap - - self.levels_config.clear() - return + changable_sockets(self, 'Data', ['Data', ]) + def process(self): + data = self.inputs['Data'].sv_get(default=[], deepcopy=False) - changable_sockets(self, 'Data', ['Data', ]) nesting, descriptions = describe_data_shape_by_level(data, include_numpy_nesting=False) - rebuild_list = self.prev_nesting_level != nesting - self.prev_nesting_level = nesting + nesting += 1 + self.nesting = nesting if data else 0 + if len(self.levels_config) < nesting: + correct_collection_length(self.levels_config, nesting) - if rebuild_list: - self.levels_config.clear() - for i, descr in enumerate(descriptions): - l = self.levels_config.add() - l.description = descr - l.flatten = self.flatten_mem[i] - l.wrap = self.wrap_mem[i] + for entry, description in zip(self.levels_config, descriptions): + entry.description = description + if data: + result = list_levels_adjust(data, self.levels_config, data_types=ALL_TYPES) else: - for entry, descr in zip(self.levels_config, descriptions): - entry.description = descr - - def sv_init(self, context): - self.width = 300 - self.inputs.new('SvStringsSocket', 'Data') - self.outputs.new('SvStringsSocket', 'Data') - - def sv_copy(self, original): - self.prev_nesting_level = 0 + result = [] - def process(self): - if not self.inputs['Data'].is_linked: - return - self.update_buttons(True) - if not self.outputs['Data'].is_linked: - return + self.outputs['Data'].sv_set(result) - data = self.inputs['Data'].sv_get(default=[], deepcopy=False) - result = list_levels_adjust(data, self.levels_config, data_types=ALL_TYPES) - self.outputs['Data'].sv_set(result) +classes = [SvNestingLevelEntryMK2, SvListLevelsNodeMK2] -classes = [SvNestingLevelEntry, SvListLevelsNode] def register(): for name in classes: diff --git a/old_nodes/levels.py b/old_nodes/levels.py new file mode 100644 index 0000000000..b54fdc9fbf --- /dev/null +++ b/old_nodes/levels.py @@ -0,0 +1,160 @@ +# ##### 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 ##### + +import bpy +from bpy.props import BoolProperty, IntProperty, StringProperty, CollectionProperty, BoolVectorProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, describe_data_shape_by_level, list_levels_adjust, SIMPLE_DATA_TYPES,\ + changable_sockets +from sverchok.utils.curve.core import SvCurve +from sverchok.utils.surface.core import SvSurface +from sverchok.dependencies import FreeCAD +from sverchok.utils.logging import info +ALL_TYPES = SIMPLE_DATA_TYPES + (SvCurve, SvSurface) +if FreeCAD is not None: + import Part + ALL_TYPES = ALL_TYPES + (Part.Shape,) + +class SvNestingLevelEntry(bpy.types.PropertyGroup): + def update_entry(self, context): + if hasattr(context, 'node'): + updateNode(context.node, context) + else: + info("Node is not defined in this context, so will not update the node.") + + description : StringProperty(options = {'SKIP_SAVE'}, default="?") + flatten : BoolProperty( + name = "Flatten", + description = "Concatenate all child lists into one list", + default=False, + update=update_entry) + wrap : BoolProperty( + name = "Wrap", + description = "Wrap data into additional pair of square brackets []", + default=False, + update=update_entry) + +class SvListLevelsNode(bpy.types.Node, SverchCustomTreeNode): + ''' + Triggers: List Levels + Tooltip: List nesting levels manipulation + ''' + bl_idname = 'SvListLevelsNode' + bl_label = 'List Levels' + bl_icon = 'OUTLINER' + replacement_nodes = [('SvListLevelsNodeMK2', None, None)] + + levels_config : CollectionProperty(type=SvNestingLevelEntry) + prev_nesting_level : IntProperty(default = 0, options = {'SKIP_SAVE'}) + flatten_mem: BoolVectorProperty(size=32, default=[False for i in range(32)]) + wrap_mem: BoolVectorProperty(size=32, default=[False for i in range(32)]) + + def draw_buttons(self, context, layout): + n = len(self.levels_config) + if not n: + layout.label(text="No data passed") + return + grid = layout.grid_flow(row_major=True, columns=5, align=True) + + grid.label(text='Depth') + grid.label(text='Nesting') + grid.label(text='Shape') + grid.label(text='Flatten') + grid.label(text='Wrap') + + for i, entry in enumerate(self.levels_config): + nesting = n-i-1 + level_str = str(i) + if i == 0: + level_str += " (outermost)" + elif nesting == 0: + level_str += " (innermost)" + grid.label(text=level_str) + grid.label(text=str(nesting)) + grid.label(text=entry.description) + if nesting < 2: + grid.label(icon='X', text='') + else: + grid.prop(entry, 'flatten', text='') + grid.prop(entry, 'wrap', text='') + + def sv_update(self): + self.update_buttons(False) + + def update_buttons(self, update_during_process): + try: + data = self.inputs['Data'].sv_get(default=[]) + except LookupError: + data = [] + if not data: + self.prev_nesting_level = 0 + for i, l in enumerate(self.levels_config): + self.flatten_mem[i] = l.flatten + self.wrap_mem[i] = l.wrap + + self.levels_config.clear() + return + + + changable_sockets(self, 'Data', ['Data', ]) + nesting, descriptions = describe_data_shape_by_level(data, include_numpy_nesting=False) + rebuild_list = self.prev_nesting_level != nesting + self.prev_nesting_level = nesting + + if rebuild_list: + self.levels_config.clear() + for i, descr in enumerate(descriptions): + l = self.levels_config.add() + l.description = descr + l.flatten = self.flatten_mem[i] + l.wrap = self.wrap_mem[i] + + else: + for entry, descr in zip(self.levels_config, descriptions): + entry.description = descr + + def sv_init(self, context): + self.width = 300 + self.inputs.new('SvStringsSocket', 'Data') + self.outputs.new('SvStringsSocket', 'Data') + + def sv_copy(self, original): + self.prev_nesting_level = 0 + + def process(self): + if not self.inputs['Data'].is_linked: + return + self.update_buttons(True) + if not self.outputs['Data'].is_linked: + return + + data = self.inputs['Data'].sv_get(default=[], deepcopy=False) + result = list_levels_adjust(data, self.levels_config, data_types=ALL_TYPES) + + self.outputs['Data'].sv_set(result) + +classes = [SvNestingLevelEntry, SvListLevelsNode] + +def register(): + for name in classes: + bpy.utils.register_class(name) + +def unregister(): + for name in reversed(classes): + bpy.utils.unregister_class(name)