Module sverchok.utils.sv_obj_helper

Expand source code
# ##### 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 random
import string

import bmesh
import bpy
from bpy.props import (BoolProperty, StringProperty, FloatProperty, IntProperty, BoolVectorProperty)
from mathutils import Matrix

from sverchok.data_structure import updateNode
from sverchok.utils.sv_viewer_utils import (
    matrix_sanitizer, natural_plus_one, greek_alphabet)

sv_caps = set(string.ascii_uppercase)

def enum_from_list(*item_list):
    """
    usage:   var = enum_from_list('TOP_BASELINE', 'TOP')
    produces:  [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 1)]
    """
    return [(item, item, "", idx) for idx, item in enumerate(item_list)]

def enum_from_list_idx(*item_list):
    """
    usage:   var = enum_from_list_idx('0:TOP_BASELINE', '7:TOP')
    produces:  [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 7)]

    """
    return [(n, n, "", int(i)) for i, n in [item.split(':') for item in item_list]]



common_ops = ['object_hide_viewport', 'object_hide_select', 'object_hide_render']
CALLBACK_OP = 'node.sv_callback_svobjects_helper'

def get_random_init_v3():
    """ it's not random """
    idx = bpy.context.scene.SvGreekAlphabet_index
    if idx <= 23:
        name = greek_alphabet[idx]
        bpy.context.scene.SvGreekAlphabet_index += 1
    else:
        name = ''.join(random.sample(sv_caps, 6))

    return name


def tracked_operator(node, layout_element, fn_name='', text='', icon=None):
    """
    this is a wrapper around the layout.operator(CALLBACK_OP....), it allows
    us to track the nodetree and nodename origins of the callback.

    // Without treename and nodename it's not possible to tell where the button press comes from
    // and now you can just press the button, without first making a node selected or active.

    """
    operator_props = dict(text=text)
    if icon:
        operator_props['icon'] = icon

    button = layout_element.operator(CALLBACK_OP, **operator_props)
    button.fn_name = fn_name
    button.node_name = node.name
    button.tree_name = node.id_data.name


class SvObjectsHelperCallback(bpy.types.Operator):

    bl_idname = CALLBACK_OP
    bl_label = "Sverchok objects helper"
    bl_options = {'REGISTER', 'UNDO'}

    fn_name: StringProperty(default='')

    # The imformation of "which node this button was pressed on"
    # is not communicated unless you do it explicitly.
    tree_name: StringProperty(default='')
    node_name: StringProperty(default='')

    def execute(self, context):
        type_op = self.fn_name

        if self.tree_name and self.node_name:
            n = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
        else:
            n = context.node

        objs = n.get_children()

        if type_op in {'object_hide_viewport', 'object_hide_render', 'object_hide_select'}:
            for obj in objs:
                stripped_op_name = type_op.replace("object_", '')
                setattr(obj, stripped_op_name, getattr(n, type_op))
            setattr(n, type_op, not getattr(n, type_op))

        elif type_op == "object_select":
            for obj in objs:
                obj.select_set(n.object_select)
            n.object_select = not n.object_select

        elif type_op == 'random_basedata_name':   # random_data_name  ?
            n.basedata_name = get_random_init_v3()

        elif type_op == 'add_material':
            if hasattr(n, type_op):
                # some nodes will define their own add_material..
                getattr(n, type_op)()
            else:
                # this is the simplest automatic material generator.
                mat = bpy.data.materials.new('sv_material')
                mat.use_nodes = True
                n.material = mat.name

        return {'FINISHED'}


class SvObjHelper():

    # hints found at ba.org/forum/showthread.php?290106
    # - this will not allow objects on multiple layers, yet.
    def g(self):
        self['lp'] = self.get('lp', [False] * 20)
        return self['lp']

    def s(self, value):
        val = []
        for b in zip(self['lp'], value):
            val.append(b[0] != b[1])
        self['lp'] = val

    def layer_updateNode(self, context):
        '''will update in place without geometry updates'''
        for obj in self.get_children():
            obj.layers = self.layer_choice[:]

    def get_children(self):
        # criteria: basedata_name must be in object.keys and the value must be self.basedata_name
        objects = bpy.data.objects
        objs = [obj for obj in objects if obj.type == self.data_kind]
        return [o for o in objs if o.get('basedata_name') == self.basedata_name]

    def group_state_update_handler(self, context):
        """
        since this is technically a scene/admin code controlling hierarchy, pressing
        the button should result in asymmetric behaviour depending on the new state of
        "self.grouping".

        + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        | state | desired behaviour                                             |
        + ----- + --------------------------------------------------------------+
        | True  | add all objects associated with the node to the collection    |
        + ----- + --------------------------------------------------------------+
        | False | remove collection, if present, and association with object    |
        + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        """
        if self.grouping:
            updateNode(self, context)
        else:
            self.clear_collection()

    def to_collection(self, objs):
        collections = bpy.data.collections
        named = self.custom_collection_name or self.basedata_name

        # alias collection, or generate new collection and alias that
        collection = collections.get(named)
        if not collection:
            collection = collections.new(named)
            bpy.context.scene.collection.children.link(collection)

        for obj in objs:
            if obj.name not in collection.objects:
                collection.objects.link(obj)
            if obj.name in bpy.context.scene.collection.objects:
                bpy.context.scene.collection.objects.unlink(obj)

    def clear_collection(self):
        collections = bpy.data.collections
        named = self.custom_collection_name or self.basedata_name

        # alias collection, or generate new collection and alias that
        collection = collections.get(named)
        if not collection:
            """ seems the collection is already gone, this is a no op """
            return
        else:
            for obj in collection.objects:
                bpy.context.scene.collection.objects.link(obj)
            collections.remove(collection)

    def ensure_parent(self):
        if self.parent_to_empty:
            self.parent_name = 'Empty_' + self.basedata_name
            collection = bpy.context.scene.collection
            scene = bpy.context.scene
            if not self.parent_name in bpy.data.objects:
                empty = bpy.data.objects.new(self.parent_name, None)
                collection.objects.link(empty)
                scene.update()

    def to_parent(self, objs):
        for obj in objs:
            if self.parent_to_empty:
                obj.parent = bpy.data.objects[self.parent_name]
            elif obj.parent:
                obj.parent = None

    layer_choice: BoolVectorProperty(
        subtype='LAYER', size=20, name="Layer Choice",
        update=layer_updateNode,
        description="This sets which layer objects are placed on",
        get=g, set=s)

    activate: BoolProperty(
        name='activate',
        description="When enabled this will process incoming data",
        default=True,
        update=updateNode)

    basedata_name: StringProperty(
        name='basedata name',
        default='Alpha',
        description="which base name the object and data will use",
        update=updateNode
    )

    # most importantly, what kind of base data are we making?
    data_kind: StringProperty(name='data kind', default='MESH')

    # to be used if the node has no material input.
    material: StringProperty(name='material', default='')
    material_pointer: bpy.props.PointerProperty(
        type=bpy.types.Material, poll=lambda s, o: True, update=updateNode)

    # to be used as standard toggles for object attributes of same name
    object_hide_viewport: BoolProperty(name='object hide viewport', default=True)
    object_hide_render: BoolProperty(name='object hide render', default=True)
    object_hide_select: BoolProperty(name='object hide select', default=False)

    object_select: BoolProperty(name='object select', default=True)

    show_wire: BoolProperty(name='show wire', update=updateNode)
    use_smooth: BoolProperty(name='use smooth', default=True, update=updateNode)

    parent_to_empty: BoolProperty(name='parent to empty', default=False, update=updateNode)
    parent_name: StringProperty(name='parent name')  # calling updateNode would recurse.

    custom_collection_name: StringProperty(
        name='collection name', update=updateNode,
        description='custom collection name, will default to the basename of the node first')

    def sv_init_helper_basedata_name(self):
        """
        this is to be used in sv_init, at the top
        """
        dname = bpy.context.scene.sv_object_names.get_available_name()
        self.basedata_name = dname
        self.use_custom_color = True


    def icons(self, TYPE):
        NAMED_ICON = {
            'object_hide_viewport': 'RESTRICT_VIEW',
            'object_hide_render': 'RESTRICT_RENDER',
            'object_hide_select': 'RESTRICT_SELECT'}.get(TYPE)
        if not NAMED_ICON:
            return 'ERROR'
        return NAMED_ICON + ['_ON', '_OFF'][getattr(self, TYPE)]

    def draw_live_and_outliner(self, context, layout):
        col = layout.column(align=True)
        row = col.row(align=True)
        row.column().prop(self, "activate", text="LIVE", toggle=True)

        for op_name in common_ops:
            tracked_operator(self, row, fn_name=op_name, icon=self.icons(op_name))

    def draw_object_buttons(self, context, layout):

        col = layout.column(align=True)
        if col:
            row = col.row(align=True)
            row.scale_y = 1
            row.prop(self, "basedata_name", text="", icon=self.bl_icon)

            row = col.row(align=True)
            row.scale_y = 2
            tracked_operator(self, row, fn_name='object_select', text='Select / Deselect')

            row = col.row(align=True)
            row.scale_y = 1
            row.prop_search(self, 'material_pointer', bpy.data, 'materials', text='', icon='MATERIAL_DATA')
            tracked_operator(self, row, fn_name='add_material', icon="ZOOM_IN")

    def draw_ext_object_buttons(self, context, layout):
        layout.separator()
        row = layout.row(align=True)
        tracked_operator(self, row, fn_name='random_basedata_name', text='Rnd Name')
        tracked_operator(self, row, fn_name='add_material', text='+Material', icon="ZOOM_IN")

    def set_corresponding_materials(self):
        if self.material_pointer:
            for obj in self.get_children():
                obj.active_material = self.material_pointer

    def remove_non_updated_objects(self, obj_index):
        objs = self.get_children()
        obj_names = [obj.name for obj in objs if obj['idx'] > obj_index]
        if not obj_names:
            return

        if self.data_kind == 'MESH':
            kinds = bpy.data.meshes
        elif self.data_kind == 'CURVE':
            kinds = bpy.data.curves
        elif self.data_kind == 'META':
            kinds = bpy.data.metaballs
        elif self.data_kind == 'FONT':
            kinds = bpy.data.curves


        objects = bpy.data.objects
        collection = bpy.context.scene.collection

        if hasattr(self, "grouping") and self.grouping:
            # if a node employs "grouping" it must also provide
            # - self.custom_collection_name (even if not directly used)
            # - self.basedata_name
            named = self.custom_collection_name or self.basedata_name
            collection = bpy.data.collections.get(named)

        # remove excess objects
        for object_name in obj_names:
            obj = objects[object_name]
            obj.hide_select = False
            collection.objects.unlink(obj)
            objects.remove(obj, do_unlink=True)

        # delete associated meshes/curves etc
        for object_name in obj_names:
            kinds.remove(kinds[object_name])

    def create_object(self, object_name, obj_index, data):
        """
        Create a new object and link it into collection.
        """
        obj = bpy.data.objects.new(object_name, data)
        obj['basedata_name'] = self.basedata_name
        obj['madeby'] = self.name
        obj['idx'] = obj_index
        bpy.context.scene.collection.objects.link(obj)
        return obj

    def get_or_create_object(self, object_name, obj_index, data):
        """
        Return existing Object or create new one.
        : if object reference exists, pick it up else make a new one
        """
        obj = bpy.data.objects.get(object_name)
        if not obj:
            obj = self.create_object(object_name, obj_index, data)
        return obj

    def get_obj_curve(self, obj_index):
        curves = bpy.data.curves
        objects = bpy.data.objects
        collection = bpy.context.scene.collection

        curve_name = f'{self.basedata_name}.{obj_index:04d}'

        # if curve data exists, pick it up else make new curve
        cu = curves.get(curve_name)
        if not cu:
            cu = curves.new(name=curve_name, type='CURVE')
        obj = self.get_or_create_object(curve_name, obj_index, cu)

        # break down existing splines entirely.
        if cu.splines:
            cu.splines.clear()

        return obj, cu


    def clear_current_mesh(self, data):
        bm = bmesh.new()
        bm.to_mesh(data)
        bm.free()
        data.update()

    def get_alphabet(self):
        return greek_alphabet


    def push_custom_matrix_if_present(self, sv_object, matrix):
        if matrix:
            # matrix = matrix_sanitizer(matrix)
            sv_object.matrix_local = matrix
        else:
            sv_object.matrix_local = Matrix.Identity(4)


def register():
    bpy.utils.register_class(SvObjectsHelperCallback)


def unregister():
    bpy.utils.unregister_class(SvObjectsHelperCallback)

Functions

def enum_from_list(*item_list)

usage: var = enum_from_list('TOP_BASELINE', 'TOP') produces: [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 1)]

Expand source code
def enum_from_list(*item_list):
    """
    usage:   var = enum_from_list('TOP_BASELINE', 'TOP')
    produces:  [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 1)]
    """
    return [(item, item, "", idx) for idx, item in enumerate(item_list)]
def enum_from_list_idx(*item_list)

usage: var = enum_from_list_idx('0:TOP_BASELINE', '7:TOP') produces: [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 7)]

Expand source code
def enum_from_list_idx(*item_list):
    """
    usage:   var = enum_from_list_idx('0:TOP_BASELINE', '7:TOP')
    produces:  [('TOP_BASELINE', 'TOP_BASELINE', '', 0), ('TOP', 'TOP', '', 7)]

    """
    return [(n, n, "", int(i)) for i, n in [item.split(':') for item in item_list]]
def get_random_init_v3()

it's not random

Expand source code
def get_random_init_v3():
    """ it's not random """
    idx = bpy.context.scene.SvGreekAlphabet_index
    if idx <= 23:
        name = greek_alphabet[idx]
        bpy.context.scene.SvGreekAlphabet_index += 1
    else:
        name = ''.join(random.sample(sv_caps, 6))

    return name
def register()
Expand source code
def register():
    bpy.utils.register_class(SvObjectsHelperCallback)
def tracked_operator(node, layout_element, fn_name='', text='', icon=None)

this is a wrapper around the layout.operator(CALLBACK_OP....), it allows us to track the nodetree and nodename origins of the callback.

// Without treename and nodename it's not possible to tell where the button press comes from // and now you can just press the button, without first making a node selected or active.

Expand source code
def tracked_operator(node, layout_element, fn_name='', text='', icon=None):
    """
    this is a wrapper around the layout.operator(CALLBACK_OP....), it allows
    us to track the nodetree and nodename origins of the callback.

    // Without treename and nodename it's not possible to tell where the button press comes from
    // and now you can just press the button, without first making a node selected or active.

    """
    operator_props = dict(text=text)
    if icon:
        operator_props['icon'] = icon

    button = layout_element.operator(CALLBACK_OP, **operator_props)
    button.fn_name = fn_name
    button.node_name = node.name
    button.tree_name = node.id_data.name
def unregister()
Expand source code
def unregister():
    bpy.utils.unregister_class(SvObjectsHelperCallback)

Classes

class SvObjHelper
Expand source code
class SvObjHelper():

    # hints found at ba.org/forum/showthread.php?290106
    # - this will not allow objects on multiple layers, yet.
    def g(self):
        self['lp'] = self.get('lp', [False] * 20)
        return self['lp']

    def s(self, value):
        val = []
        for b in zip(self['lp'], value):
            val.append(b[0] != b[1])
        self['lp'] = val

    def layer_updateNode(self, context):
        '''will update in place without geometry updates'''
        for obj in self.get_children():
            obj.layers = self.layer_choice[:]

    def get_children(self):
        # criteria: basedata_name must be in object.keys and the value must be self.basedata_name
        objects = bpy.data.objects
        objs = [obj for obj in objects if obj.type == self.data_kind]
        return [o for o in objs if o.get('basedata_name') == self.basedata_name]

    def group_state_update_handler(self, context):
        """
        since this is technically a scene/admin code controlling hierarchy, pressing
        the button should result in asymmetric behaviour depending on the new state of
        "self.grouping".

        + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        | state | desired behaviour                                             |
        + ----- + --------------------------------------------------------------+
        | True  | add all objects associated with the node to the collection    |
        + ----- + --------------------------------------------------------------+
        | False | remove collection, if present, and association with object    |
        + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        """
        if self.grouping:
            updateNode(self, context)
        else:
            self.clear_collection()

    def to_collection(self, objs):
        collections = bpy.data.collections
        named = self.custom_collection_name or self.basedata_name

        # alias collection, or generate new collection and alias that
        collection = collections.get(named)
        if not collection:
            collection = collections.new(named)
            bpy.context.scene.collection.children.link(collection)

        for obj in objs:
            if obj.name not in collection.objects:
                collection.objects.link(obj)
            if obj.name in bpy.context.scene.collection.objects:
                bpy.context.scene.collection.objects.unlink(obj)

    def clear_collection(self):
        collections = bpy.data.collections
        named = self.custom_collection_name or self.basedata_name

        # alias collection, or generate new collection and alias that
        collection = collections.get(named)
        if not collection:
            """ seems the collection is already gone, this is a no op """
            return
        else:
            for obj in collection.objects:
                bpy.context.scene.collection.objects.link(obj)
            collections.remove(collection)

    def ensure_parent(self):
        if self.parent_to_empty:
            self.parent_name = 'Empty_' + self.basedata_name
            collection = bpy.context.scene.collection
            scene = bpy.context.scene
            if not self.parent_name in bpy.data.objects:
                empty = bpy.data.objects.new(self.parent_name, None)
                collection.objects.link(empty)
                scene.update()

    def to_parent(self, objs):
        for obj in objs:
            if self.parent_to_empty:
                obj.parent = bpy.data.objects[self.parent_name]
            elif obj.parent:
                obj.parent = None

    layer_choice: BoolVectorProperty(
        subtype='LAYER', size=20, name="Layer Choice",
        update=layer_updateNode,
        description="This sets which layer objects are placed on",
        get=g, set=s)

    activate: BoolProperty(
        name='activate',
        description="When enabled this will process incoming data",
        default=True,
        update=updateNode)

    basedata_name: StringProperty(
        name='basedata name',
        default='Alpha',
        description="which base name the object and data will use",
        update=updateNode
    )

    # most importantly, what kind of base data are we making?
    data_kind: StringProperty(name='data kind', default='MESH')

    # to be used if the node has no material input.
    material: StringProperty(name='material', default='')
    material_pointer: bpy.props.PointerProperty(
        type=bpy.types.Material, poll=lambda s, o: True, update=updateNode)

    # to be used as standard toggles for object attributes of same name
    object_hide_viewport: BoolProperty(name='object hide viewport', default=True)
    object_hide_render: BoolProperty(name='object hide render', default=True)
    object_hide_select: BoolProperty(name='object hide select', default=False)

    object_select: BoolProperty(name='object select', default=True)

    show_wire: BoolProperty(name='show wire', update=updateNode)
    use_smooth: BoolProperty(name='use smooth', default=True, update=updateNode)

    parent_to_empty: BoolProperty(name='parent to empty', default=False, update=updateNode)
    parent_name: StringProperty(name='parent name')  # calling updateNode would recurse.

    custom_collection_name: StringProperty(
        name='collection name', update=updateNode,
        description='custom collection name, will default to the basename of the node first')

    def sv_init_helper_basedata_name(self):
        """
        this is to be used in sv_init, at the top
        """
        dname = bpy.context.scene.sv_object_names.get_available_name()
        self.basedata_name = dname
        self.use_custom_color = True


    def icons(self, TYPE):
        NAMED_ICON = {
            'object_hide_viewport': 'RESTRICT_VIEW',
            'object_hide_render': 'RESTRICT_RENDER',
            'object_hide_select': 'RESTRICT_SELECT'}.get(TYPE)
        if not NAMED_ICON:
            return 'ERROR'
        return NAMED_ICON + ['_ON', '_OFF'][getattr(self, TYPE)]

    def draw_live_and_outliner(self, context, layout):
        col = layout.column(align=True)
        row = col.row(align=True)
        row.column().prop(self, "activate", text="LIVE", toggle=True)

        for op_name in common_ops:
            tracked_operator(self, row, fn_name=op_name, icon=self.icons(op_name))

    def draw_object_buttons(self, context, layout):

        col = layout.column(align=True)
        if col:
            row = col.row(align=True)
            row.scale_y = 1
            row.prop(self, "basedata_name", text="", icon=self.bl_icon)

            row = col.row(align=True)
            row.scale_y = 2
            tracked_operator(self, row, fn_name='object_select', text='Select / Deselect')

            row = col.row(align=True)
            row.scale_y = 1
            row.prop_search(self, 'material_pointer', bpy.data, 'materials', text='', icon='MATERIAL_DATA')
            tracked_operator(self, row, fn_name='add_material', icon="ZOOM_IN")

    def draw_ext_object_buttons(self, context, layout):
        layout.separator()
        row = layout.row(align=True)
        tracked_operator(self, row, fn_name='random_basedata_name', text='Rnd Name')
        tracked_operator(self, row, fn_name='add_material', text='+Material', icon="ZOOM_IN")

    def set_corresponding_materials(self):
        if self.material_pointer:
            for obj in self.get_children():
                obj.active_material = self.material_pointer

    def remove_non_updated_objects(self, obj_index):
        objs = self.get_children()
        obj_names = [obj.name for obj in objs if obj['idx'] > obj_index]
        if not obj_names:
            return

        if self.data_kind == 'MESH':
            kinds = bpy.data.meshes
        elif self.data_kind == 'CURVE':
            kinds = bpy.data.curves
        elif self.data_kind == 'META':
            kinds = bpy.data.metaballs
        elif self.data_kind == 'FONT':
            kinds = bpy.data.curves


        objects = bpy.data.objects
        collection = bpy.context.scene.collection

        if hasattr(self, "grouping") and self.grouping:
            # if a node employs "grouping" it must also provide
            # - self.custom_collection_name (even if not directly used)
            # - self.basedata_name
            named = self.custom_collection_name or self.basedata_name
            collection = bpy.data.collections.get(named)

        # remove excess objects
        for object_name in obj_names:
            obj = objects[object_name]
            obj.hide_select = False
            collection.objects.unlink(obj)
            objects.remove(obj, do_unlink=True)

        # delete associated meshes/curves etc
        for object_name in obj_names:
            kinds.remove(kinds[object_name])

    def create_object(self, object_name, obj_index, data):
        """
        Create a new object and link it into collection.
        """
        obj = bpy.data.objects.new(object_name, data)
        obj['basedata_name'] = self.basedata_name
        obj['madeby'] = self.name
        obj['idx'] = obj_index
        bpy.context.scene.collection.objects.link(obj)
        return obj

    def get_or_create_object(self, object_name, obj_index, data):
        """
        Return existing Object or create new one.
        : if object reference exists, pick it up else make a new one
        """
        obj = bpy.data.objects.get(object_name)
        if not obj:
            obj = self.create_object(object_name, obj_index, data)
        return obj

    def get_obj_curve(self, obj_index):
        curves = bpy.data.curves
        objects = bpy.data.objects
        collection = bpy.context.scene.collection

        curve_name = f'{self.basedata_name}.{obj_index:04d}'

        # if curve data exists, pick it up else make new curve
        cu = curves.get(curve_name)
        if not cu:
            cu = curves.new(name=curve_name, type='CURVE')
        obj = self.get_or_create_object(curve_name, obj_index, cu)

        # break down existing splines entirely.
        if cu.splines:
            cu.splines.clear()

        return obj, cu


    def clear_current_mesh(self, data):
        bm = bmesh.new()
        bm.to_mesh(data)
        bm.free()
        data.update()

    def get_alphabet(self):
        return greek_alphabet


    def push_custom_matrix_if_present(self, sv_object, matrix):
        if matrix:
            # matrix = matrix_sanitizer(matrix)
            sv_object.matrix_local = matrix
        else:
            sv_object.matrix_local = Matrix.Identity(4)

Subclasses

  • sverchok.nodes.viz.viewer_bezier_curve.SvBezierCurveOutNode
  • sverchok.nodes.viz.viewer_curves.SvCurveViewerNodeV28
  • sverchok.nodes.viz.viewer_metaball.SvMetaballOutNode
  • sverchok.nodes.viz.viewer_nurbs_curve.SvNurbsCurveOutNode
  • sverchok.nodes.viz.viewer_nurbs_surface.SvNurbsSurfaceOutNode
  • sverchok.nodes.viz.viewer_skin.SvSkinViewerNodeV28
  • sverchok.nodes.viz.viewer_typography.SvTypeViewerNodeV28

Class variables

var activate : <_PropertyDeferred, , {'name': 'activate', 'description': 'When enabled this will process incoming data', 'default': True, 'update': , 'attr': 'activate'}>
var basedata_name : <_PropertyDeferred, , {'name': 'basedata name', 'default': 'Alpha', 'description': 'which base name the object and data will use', 'update': , 'attr': 'basedata_name'}>
var custom_collection_name : <_PropertyDeferred, , {'name': 'collection name', 'update': , 'description': 'custom collection name, will default to the basename of the node first', 'attr': 'custom_collection_name'}>
var data_kind : <_PropertyDeferred, , {'name': 'data kind', 'default': 'MESH', 'attr': 'data_kind'}>
var layer_choice : <_PropertyDeferred, , {'subtype': 'LAYER', 'size': 20, 'name': 'Layer Choice', 'update': SvObjHelper.layer_updateNode() at 0x7f2f1b0e4dc0>, 'description': 'This sets which layer objects are placed on', 'get': SvObjHelper.g() at 0x7f2f1b0e4ca0>, 'set': SvObjHelper.s() at 0x7f2f1b0e4d30>, 'attr': 'layer_choice'}>
var material : <_PropertyDeferred, , {'name': 'material', 'default': '', 'attr': 'material'}>
var material_pointer : <_PropertyDeferred, , {'type': , 'poll': SvObjHelper. at 0x7f2f1b0c21f0>, 'update': , 'attr': 'material_pointer'}>
var object_hide_render : <_PropertyDeferred, , {'name': 'object hide render', 'default': True, 'attr': 'object_hide_render'}>
var object_hide_select : <_PropertyDeferred, , {'name': 'object hide select', 'default': False, 'attr': 'object_hide_select'}>
var object_hide_viewport : <_PropertyDeferred, , {'name': 'object hide viewport', 'default': True, 'attr': 'object_hide_viewport'}>
var object_select : <_PropertyDeferred, , {'name': 'object select', 'default': True, 'attr': 'object_select'}>
var parent_name : <_PropertyDeferred, , {'name': 'parent name', 'attr': 'parent_name'}>
var parent_to_empty : <_PropertyDeferred, , {'name': 'parent to empty', 'default': False, 'update': , 'attr': 'parent_to_empty'}>
var show_wire : <_PropertyDeferred, , {'name': 'show wire', 'update': , 'attr': 'show_wire'}>
var use_smooth : <_PropertyDeferred, , {'name': 'use smooth', 'default': True, 'update': , 'attr': 'use_smooth'}>

Methods

def clear_collection(self)
Expand source code
def clear_collection(self):
    collections = bpy.data.collections
    named = self.custom_collection_name or self.basedata_name

    # alias collection, or generate new collection and alias that
    collection = collections.get(named)
    if not collection:
        """ seems the collection is already gone, this is a no op """
        return
    else:
        for obj in collection.objects:
            bpy.context.scene.collection.objects.link(obj)
        collections.remove(collection)
def clear_current_mesh(self, data)
Expand source code
def clear_current_mesh(self, data):
    bm = bmesh.new()
    bm.to_mesh(data)
    bm.free()
    data.update()
def create_object(self, object_name, obj_index, data)

Create a new object and link it into collection.

Expand source code
def create_object(self, object_name, obj_index, data):
    """
    Create a new object and link it into collection.
    """
    obj = bpy.data.objects.new(object_name, data)
    obj['basedata_name'] = self.basedata_name
    obj['madeby'] = self.name
    obj['idx'] = obj_index
    bpy.context.scene.collection.objects.link(obj)
    return obj
def draw_ext_object_buttons(self, context, layout)
Expand source code
def draw_ext_object_buttons(self, context, layout):
    layout.separator()
    row = layout.row(align=True)
    tracked_operator(self, row, fn_name='random_basedata_name', text='Rnd Name')
    tracked_operator(self, row, fn_name='add_material', text='+Material', icon="ZOOM_IN")
def draw_live_and_outliner(self, context, layout)
Expand source code
def draw_live_and_outliner(self, context, layout):
    col = layout.column(align=True)
    row = col.row(align=True)
    row.column().prop(self, "activate", text="LIVE", toggle=True)

    for op_name in common_ops:
        tracked_operator(self, row, fn_name=op_name, icon=self.icons(op_name))
def draw_object_buttons(self, context, layout)
Expand source code
def draw_object_buttons(self, context, layout):

    col = layout.column(align=True)
    if col:
        row = col.row(align=True)
        row.scale_y = 1
        row.prop(self, "basedata_name", text="", icon=self.bl_icon)

        row = col.row(align=True)
        row.scale_y = 2
        tracked_operator(self, row, fn_name='object_select', text='Select / Deselect')

        row = col.row(align=True)
        row.scale_y = 1
        row.prop_search(self, 'material_pointer', bpy.data, 'materials', text='', icon='MATERIAL_DATA')
        tracked_operator(self, row, fn_name='add_material', icon="ZOOM_IN")
def ensure_parent(self)
Expand source code
def ensure_parent(self):
    if self.parent_to_empty:
        self.parent_name = 'Empty_' + self.basedata_name
        collection = bpy.context.scene.collection
        scene = bpy.context.scene
        if not self.parent_name in bpy.data.objects:
            empty = bpy.data.objects.new(self.parent_name, None)
            collection.objects.link(empty)
            scene.update()
def g(self)
Expand source code
def g(self):
    self['lp'] = self.get('lp', [False] * 20)
    return self['lp']
def get_alphabet(self)
Expand source code
def get_alphabet(self):
    return greek_alphabet
def get_children(self)
Expand source code
def get_children(self):
    # criteria: basedata_name must be in object.keys and the value must be self.basedata_name
    objects = bpy.data.objects
    objs = [obj for obj in objects if obj.type == self.data_kind]
    return [o for o in objs if o.get('basedata_name') == self.basedata_name]
def get_obj_curve(self, obj_index)
Expand source code
def get_obj_curve(self, obj_index):
    curves = bpy.data.curves
    objects = bpy.data.objects
    collection = bpy.context.scene.collection

    curve_name = f'{self.basedata_name}.{obj_index:04d}'

    # if curve data exists, pick it up else make new curve
    cu = curves.get(curve_name)
    if not cu:
        cu = curves.new(name=curve_name, type='CURVE')
    obj = self.get_or_create_object(curve_name, obj_index, cu)

    # break down existing splines entirely.
    if cu.splines:
        cu.splines.clear()

    return obj, cu
def get_or_create_object(self, object_name, obj_index, data)
Return existing Object or create new one.
if object reference exists, pick it up else make a new one
Expand source code
def get_or_create_object(self, object_name, obj_index, data):
    """
    Return existing Object or create new one.
    : if object reference exists, pick it up else make a new one
    """
    obj = bpy.data.objects.get(object_name)
    if not obj:
        obj = self.create_object(object_name, obj_index, data)
    return obj
def group_state_update_handler(self, context)

since this is technically a scene/admin code controlling hierarchy, pressing the button should result in asymmetric behaviour depending on the new state of "self.grouping".

                                                                        • + | state | desired behaviour |
  • ----- + --------------------------------------------------------------+ | True | add all objects associated with the node to the collection |
  • ----- + --------------------------------------------------------------+ | False | remove collection, if present, and association with object |
                                                                        • +
Expand source code
def group_state_update_handler(self, context):
    """
    since this is technically a scene/admin code controlling hierarchy, pressing
    the button should result in asymmetric behaviour depending on the new state of
    "self.grouping".

    + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    | state | desired behaviour                                             |
    + ----- + --------------------------------------------------------------+
    | True  | add all objects associated with the node to the collection    |
    + ----- + --------------------------------------------------------------+
    | False | remove collection, if present, and association with object    |
    + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    """
    if self.grouping:
        updateNode(self, context)
    else:
        self.clear_collection()
def icons(self, TYPE)
Expand source code
def icons(self, TYPE):
    NAMED_ICON = {
        'object_hide_viewport': 'RESTRICT_VIEW',
        'object_hide_render': 'RESTRICT_RENDER',
        'object_hide_select': 'RESTRICT_SELECT'}.get(TYPE)
    if not NAMED_ICON:
        return 'ERROR'
    return NAMED_ICON + ['_ON', '_OFF'][getattr(self, TYPE)]
def layer_updateNode(self, context)

will update in place without geometry updates

Expand source code
def layer_updateNode(self, context):
    '''will update in place without geometry updates'''
    for obj in self.get_children():
        obj.layers = self.layer_choice[:]
def push_custom_matrix_if_present(self, sv_object, matrix)
Expand source code
def push_custom_matrix_if_present(self, sv_object, matrix):
    if matrix:
        # matrix = matrix_sanitizer(matrix)
        sv_object.matrix_local = matrix
    else:
        sv_object.matrix_local = Matrix.Identity(4)
def remove_non_updated_objects(self, obj_index)
Expand source code
def remove_non_updated_objects(self, obj_index):
    objs = self.get_children()
    obj_names = [obj.name for obj in objs if obj['idx'] > obj_index]
    if not obj_names:
        return

    if self.data_kind == 'MESH':
        kinds = bpy.data.meshes
    elif self.data_kind == 'CURVE':
        kinds = bpy.data.curves
    elif self.data_kind == 'META':
        kinds = bpy.data.metaballs
    elif self.data_kind == 'FONT':
        kinds = bpy.data.curves


    objects = bpy.data.objects
    collection = bpy.context.scene.collection

    if hasattr(self, "grouping") and self.grouping:
        # if a node employs "grouping" it must also provide
        # - self.custom_collection_name (even if not directly used)
        # - self.basedata_name
        named = self.custom_collection_name or self.basedata_name
        collection = bpy.data.collections.get(named)

    # remove excess objects
    for object_name in obj_names:
        obj = objects[object_name]
        obj.hide_select = False
        collection.objects.unlink(obj)
        objects.remove(obj, do_unlink=True)

    # delete associated meshes/curves etc
    for object_name in obj_names:
        kinds.remove(kinds[object_name])
def s(self, value)
Expand source code
def s(self, value):
    val = []
    for b in zip(self['lp'], value):
        val.append(b[0] != b[1])
    self['lp'] = val
def set_corresponding_materials(self)
Expand source code
def set_corresponding_materials(self):
    if self.material_pointer:
        for obj in self.get_children():
            obj.active_material = self.material_pointer
def sv_init_helper_basedata_name(self)

this is to be used in sv_init, at the top

Expand source code
def sv_init_helper_basedata_name(self):
    """
    this is to be used in sv_init, at the top
    """
    dname = bpy.context.scene.sv_object_names.get_available_name()
    self.basedata_name = dname
    self.use_custom_color = True
def to_collection(self, objs)
Expand source code
def to_collection(self, objs):
    collections = bpy.data.collections
    named = self.custom_collection_name or self.basedata_name

    # alias collection, or generate new collection and alias that
    collection = collections.get(named)
    if not collection:
        collection = collections.new(named)
        bpy.context.scene.collection.children.link(collection)

    for obj in objs:
        if obj.name not in collection.objects:
            collection.objects.link(obj)
        if obj.name in bpy.context.scene.collection.objects:
            bpy.context.scene.collection.objects.unlink(obj)
def to_parent(self, objs)
Expand source code
def to_parent(self, objs):
    for obj in objs:
        if self.parent_to_empty:
            obj.parent = bpy.data.objects[self.parent_name]
        elif obj.parent:
            obj.parent = None
class SvObjectsHelperCallback (...)
Expand source code
class SvObjectsHelperCallback(bpy.types.Operator):

    bl_idname = CALLBACK_OP
    bl_label = "Sverchok objects helper"
    bl_options = {'REGISTER', 'UNDO'}

    fn_name: StringProperty(default='')

    # The imformation of "which node this button was pressed on"
    # is not communicated unless you do it explicitly.
    tree_name: StringProperty(default='')
    node_name: StringProperty(default='')

    def execute(self, context):
        type_op = self.fn_name

        if self.tree_name and self.node_name:
            n = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
        else:
            n = context.node

        objs = n.get_children()

        if type_op in {'object_hide_viewport', 'object_hide_render', 'object_hide_select'}:
            for obj in objs:
                stripped_op_name = type_op.replace("object_", '')
                setattr(obj, stripped_op_name, getattr(n, type_op))
            setattr(n, type_op, not getattr(n, type_op))

        elif type_op == "object_select":
            for obj in objs:
                obj.select_set(n.object_select)
            n.object_select = not n.object_select

        elif type_op == 'random_basedata_name':   # random_data_name  ?
            n.basedata_name = get_random_init_v3()

        elif type_op == 'add_material':
            if hasattr(n, type_op):
                # some nodes will define their own add_material..
                getattr(n, type_op)()
            else:
                # this is the simplest automatic material generator.
                mat = bpy.data.materials.new('sv_material')
                mat.use_nodes = True
                n.material = mat.name

        return {'FINISHED'}

Ancestors

  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

var bl_idname
var bl_label
var bl_options
var bl_rna
var fn_name : <_PropertyDeferred, , {'default': '', 'attr': 'fn_name'}>
var node_name : <_PropertyDeferred, , {'default': '', 'attr': 'node_name'}>
var tree_name : <_PropertyDeferred, , {'default': '', 'attr': 'tree_name'}>

Methods

def execute(self, context)
Expand source code
def execute(self, context):
    type_op = self.fn_name

    if self.tree_name and self.node_name:
        n = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
    else:
        n = context.node

    objs = n.get_children()

    if type_op in {'object_hide_viewport', 'object_hide_render', 'object_hide_select'}:
        for obj in objs:
            stripped_op_name = type_op.replace("object_", '')
            setattr(obj, stripped_op_name, getattr(n, type_op))
        setattr(n, type_op, not getattr(n, type_op))

    elif type_op == "object_select":
        for obj in objs:
            obj.select_set(n.object_select)
        n.object_select = not n.object_select

    elif type_op == 'random_basedata_name':   # random_data_name  ?
        n.basedata_name = get_random_init_v3()

    elif type_op == 'add_material':
        if hasattr(n, type_op):
            # some nodes will define their own add_material..
            getattr(n, type_op)()
        else:
            # this is the simplest automatic material generator.
            mat = bpy.data.materials.new('sv_material')
            mat.use_nodes = True
            n.material = mat.name

    return {'FINISHED'}