Module sverchok.utils.nodes_mixins.generating_objects

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

import random
import string
from itertools import cycle
from typing import List, Union

import numpy as np

import bpy
from bpy.props import StringProperty
from mathutils import Matrix

from sverchok.data_structure import updateNode, update_with_kwargs, numpy_full_list, repeat_last
from sverchok.utils.handle_blender_data import correct_collection_length, delete_data_block
from sverchok.utils.sv_bmesh_utils import add_mesh_to_bmesh, bmesh_from_edit_mesh, EmptyBmesh


class SvObjectData(bpy.types.PropertyGroup):
    obj: bpy.props.PointerProperty(type=bpy.types.Object)

    # Object have not information about in which collection it is located
    # Keep here information about collection for performance reasons
    # Now object can be only in on collection
    collection: bpy.props.PointerProperty(type=bpy.types.Collection)

    def ensure_object(self, data_block, name: str, object_template: bpy.types.Object = None):
        """Add object if it does not exist, if object_template is given new object will be copied from it"""
        if not self.obj:
            # it looks like it means only that the property group item was created newly
            if object_template:
                self.obj = object_template.copy()
                self.obj.data = data_block
            else:
                self.obj = bpy.data.objects.new(name=name, object_data=data_block)
        else:
            # in case if data block was changed
            if self.obj.data != data_block:
                self.obj.data = data_block  # EXPENSIVE

    def select(self):
        """Just select the object"""
        if self.obj:
            self.obj.select_set(True)

    def ensure_link_to_collection(self, collection: bpy.types.Collection = None):
        """Links object to scene or given collection, unlink from previous collection"""
        try:
            if collection:
                collection.objects.link(self.obj)
            else:
                # default collection
                bpy.context.scene.collection.objects.link(self.obj)
        except RuntimeError:
            # then the object already added, it looks like more faster way to ensure object is in the scene
            pass

        if self.collection != collection:
            # new collection was given, object should be removed from previous one
            if self.collection is None:
                # it means that it is scene default collection
                # from other hand if item only was created it also will be None but object is not in any collection yet
                try:
                    bpy.context.scene.collection.objects.unlink(self.obj)
                except RuntimeError:
                    pass
            else:
                try:
                    self.collection.objects.unlink(self.obj)
                except RuntimeError:
                    # collection was already unliked by user or another node
                    pass

            self.collection = collection

    def check_object_name(self, name: str) -> None:
        """If base name of an object was changed names of all instances also should be changed"""
        real_name = self.obj.name.rsplit('.', 1)[0]
        if real_name != name:
            self.obj.name = name

    def check_object_show_state(self, to_show: bool):
        # hide_viewport is faster than hide_set and hide_viewport is stable
        # when objects are assigned to a collection
        hide = not to_show
        if self.obj.hide_viewport != hide:
            self.obj.hide_viewport = hide

    def recreate_object(self, object_template: bpy.types.Object = None):
        """
        Object will be replaced by new object recreated from scratch or copied from given object_template if given
        Previous object will be removed, data block remains unchanged
        """
        # in case recreated object should have a chance to get the same name of previous object
        # previous object should be deleted first
        data_block = self.obj.data
        obj_name = self.obj.name
        bpy.data.objects.remove(self.obj)
        if object_template:
            new_obj = object_template.copy()
            new_obj.data = data_block
        else:
            new_obj = bpy.data.objects.new(name=obj_name, object_data=data_block)
        self.obj = new_obj

    def copy(self) -> bpy.types.Object:
        """Return copy of object which is assigned to a collection"""
        obj = self.obj.copy()
        for collection in self.obj.users_collection:
            collection.objects.link(obj)
        return obj

    def remove_data(self):
        """Should be called before removing item"""
        if self.obj:
            delete_data_block(self.obj)


class BlenderObjects:
    """Should be used for generating list of objects"""
    def show_objects_update(self, context, to_show: bool = None):
        """Hide / show objects. It should be only place to hide objects"""
        to_show = to_show if to_show is not None else self.show_objects
        [setattr(prop.obj, 'hide_viewport', not to_show) for prop in self.object_data]

    def selectable_objects_update(self, context):
        [setattr(prop.obj, 'hide_select', False if self.selectable_objects else True) for prop in self.object_data]

    def render_objects_update(self, context):
        [setattr(prop.obj, 'hide_render', False if self.render_objects else True) for prop in self.object_data]

    object_data: bpy.props.CollectionProperty(type=SvObjectData, options={'SKIP_SAVE'})

    show_objects: bpy.props.BoolProperty(
        default=True,
        description="Show / hide objects in viewport",
        update=update_with_kwargs(show_objects_update))

    selectable_objects: bpy.props.BoolProperty(
        default=True,
        description="Make objects selectable / unselectable",
        update=selectable_objects_update)

    render_objects: bpy.props.BoolProperty(
        default=True,
        description="Show / hide objects for render engines",
        update=render_objects_update)

    def regenerate_objects(self,
                           object_names: List[str],
                           data_blocks,
                           collections: List[bpy.types.Collection] = None,
                           object_template: List[bpy.types.Object] = None,
                           to_show: list[bool] = None,
                           ):
        """
        It will generate new or remove old objects, number of generated objects will be equal to given data_blocks
        Object_names list can contain one name. In this case Blender will add suffix to next objects (.001, .002,...)
        :param object_template: optionally, object which properties should be grabbed for instanced object
        :param collections: objects will be putted into collections if given, only one in list can be given
        :param data_blocks: nearly any data blocks - mesh, curves, lights ...
        :param object_names: usually equal to name of data block
        :param data_blocks: for now it is support only be bpy.types.Mesh
        :param to_show: whether to show objects in viewport.
        """
        if collections is None:
            collections = [None]
        if object_template is None:
            object_template = [None]
        if to_show is None:
            to_show = [True]

        correct_collection_length(self.object_data, len(data_blocks))
        prop_group: SvObjectData
        input_data = zip(self.object_data,
                         data_blocks,
                         cycle(object_names),
                         cycle(collections),
                         cycle(object_template),
                         cycle(to_show),
                         )
        for prop_group, data_block, name, collection, template, show in input_data:
            prop_group.ensure_object(data_block, name, template)
            prop_group.ensure_link_to_collection(collection)
            prop_group.check_object_name(name)
            prop_group.check_object_show_state(show)

    def draw_object_properties(self, layout):
        """Should be used for adding hide, select, render objects properties"""
        layout.prop(self, 'show_objects', toggle=True, text='',
                    icon=f"RESTRICT_VIEW_{'OFF' if self.show_objects else 'ON'}")
        layout.prop(self, 'selectable_objects', toggle=True, text='',
                    icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
        layout.prop(self, 'render_objects', toggle=True, text='',
                    icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")


class SvMeshData(bpy.types.PropertyGroup):
    mesh: bpy.props.PointerProperty(type=bpy.types.Mesh, options={'SKIP_SAVE'})

    def regenerate_mesh(self, mesh_name: str, verts, edges=None, faces=None, matrix: Matrix = None,
                        make_changes_test=True):
        """
        It takes vertices, edges and faces and updates mesh data block
        If it assume that topology is unchanged only position of vertices will be changed
        In this case it will be more efficient if vertices are given in np.array float32 format
        Can apply matrix to mesh optionally
        """
        if edges is None:
            edges = []
        if faces is None:
            faces = []

        if not self.mesh:
            # new mesh should be created
            self.mesh = bpy.data.meshes.new(name=mesh_name)

        if not make_changes_test or self.is_topology_changed(verts, edges, faces):

            if self.mesh.is_editmode:
                with bmesh_from_edit_mesh(self.mesh) as bm:
                    bm.clear()
                    add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                    bm.normal_update()
                    if matrix:
                        bm.transform(matrix)
            else:
                with EmptyBmesh(False) as bm:
                    add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                    if matrix:
                        bm.transform(matrix)
                    bm.to_mesh(self.mesh)

        else:

            if self.mesh.is_editmode:
                with bmesh_from_edit_mesh(self.mesh) as bm:
                    for bv, v in zip(bm.verts, verts):
                        bv.co = v
                    if matrix:
                        bm.transform(matrix)
            else:
                self.update_vertices(verts)
                if matrix:
                    self.mesh.transform(matrix)

        self.mesh.update()

    def set_smooth(self, is_smooth_mesh):
        """Make mesh smooth or flat"""
        if is_smooth_mesh:
            is_smooth = np.ones(len(self.mesh.polygons), dtype=bool)
        else:
            is_smooth = np.zeros(len(self.mesh.polygons), dtype=bool)
        self.mesh.polygons.foreach_set('use_smooth', is_smooth)

    def is_topology_changed(self, verts: list, edges: list, faces: list) -> bool:
        """
        Simple and fast test but not 100% robust.
        If number of vertices and faces are unchanged it assumes that topology is not changed
        This test is useful if mesh just changed its location.
        It is much faster just set new coordinate for each vector then recreate whole object
        """
        if not faces:
            # edges can be take in account if mesh does not have polygons
            # because Sverchok edges can exclude edges within polygons
            return len(self.mesh.vertices) != len(verts) or len(self.mesh.edges) != len(edges)
        else:
            number_is_changed = len(self.mesh.vertices) != len(verts) or len(self.mesh.polygons) != len(faces)
            # check several polygons indexes
            are_polygons_changed = any([list(p.vertices) != f for _, p, f in zip(range(5), self.mesh.polygons, faces)])
            return number_is_changed or are_polygons_changed

    def update_vertices(self, verts: Union[list, np.ndarray]):
        """
        Just update position of mesh vertices, order and number of given vertices should be the same as mesh
        numpy array with float32 type will be 10 times faster than any other input data
        """
        verts = np.array(verts, dtype=np.float32)  # todo will be this fast if it is already array float 32?
        self.mesh.vertices.foreach_set('co', np.ravel(verts))

    def copy(self) -> bpy.types.Mesh:
        return self.mesh.copy()

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.mesh:
            delete_data_block(self.mesh)


class SvLightData(bpy.types.PropertyGroup):
    light: bpy.props.PointerProperty(type=bpy.types.Light)

    def regenerate_light(self, light_name: str, light_type: str):
        if not self.light:
            # new mesh should be created
            self.light = bpy.data.lights.new(name=light_name, type=light_type)
        elif self.light.type != light_type:
            # in case if type was changed
            self.light.type = light_type

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.light:
            delete_data_block(self.light)


class SvCurveData(bpy.types.PropertyGroup):
    """For now it is supporting only one spline per curve"""
    curve: bpy.props.PointerProperty(type=bpy.types.Curve)

    def regenerate_curve(self,
                         curve_name: str,
                         vertices: Union[List[list], List[np.ndarray]],
                         spline_type: str = 'POLY',
                         vertices_radius: Union[List[list], List[np.ndarray]] = None,
                         close_spline: Union[List[bool], List[int]] = None,
                         use_smooth: bool = True,
                         tilt: Union[List[list], List[np.ndarray]] = None):
        # Be aware that curve consists multiple splines
        if not self.curve:
            self.curve = bpy.data.curves.new(name=curve_name, type='CURVE')  # type ['CURVE', 'SURFACE', 'FONT']
        if len(self.curve.splines) != len(vertices) \
                or any(len(s.points) != len(v) for s, v in zip(self.curve.splines, vertices)):
            # if at least on spline has wrong number of points whole list of splines should be recreated
            # thanks to Blender API
            self.curve.splines.clear()
            [self.curve.splines.new(spline_type) for _ in range(len(vertices))]
            [s.points.add(len(v) - 1) for s, v in zip(self.curve.splines, vertices)]

        for s, v, r, t, c in zip(self.curve.splines, vertices,
                              repeat_last(vertices_radius or [None]),
                              repeat_last(tilt or [None]),
                              repeat_last(close_spline)):
            v = np.asarray(v, dtype=np.float32)
            if r is None:
                r = np.ones(len(v), dtype=np.float32)
            r = np.asarray(r, dtype=np.float32)
            self._regenerate_spline(s, v, spline_type, r, t, c, use_smooth)

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.curve:
            delete_data_block(self.curve)

    @staticmethod
    def _regenerate_spline(spline: bpy.types.Spline,
                           vertices: np.ndarray,
                           spline_type: str = 'POLY',
                           vertices_radius: np.ndarray = None,
                           tilt: np.ndarray = None,
                           close_spline: bool = False,
                           use_smooth: bool = True):
        spline.type = spline_type
        spline.use_cyclic_u = close_spline
        spline.use_smooth = use_smooth

        # flatten vertices array and add W component (X, Y, Z, W), W is responsible for drawing NURBS curves
        w_vertices = np.concatenate((vertices, np.ones((len(vertices), 1), dtype=np.float32)), axis=1)
        flatten_vertices = np.ravel(w_vertices)
        spline.points.foreach_set('co', flatten_vertices)
        if vertices_radius is not None:
            spline.points.foreach_set('radius', numpy_full_list(vertices_radius, len(vertices)))
        if tilt is not None:
            spline.points.foreach_set('tilt', numpy_full_list(tilt, len(vertices)))


class SvViewerNode(BlenderObjects):
    """
    Mixin for all nodes which displays any objects in viewport
    """

    is_active: bpy.props.BoolProperty(name='Live', default=True, update=updateNode,
                                      description="When enabled this will process incoming data",)

    base_data_name: bpy.props.StringProperty(
        default='Alpha',
        description='stores the mesh name found in the object, this mesh is instanced',
        update=updateNode)

    collection: bpy.props.PointerProperty(type=bpy.types.Collection, update=updateNode,
                                          description="Collection where to put objects")

    def draw_viewer_properties(self, layout):
        col = layout.column(align=True)
        row = col.row(align=True)
        row.column().prop(self, 'is_active', toggle=True)

        self.draw_object_properties(row)  # hide, selectable, render

        col = layout.column(align=True)
        row = col.row(align=True)
        row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
        row.operator('node.sv_generate_random_object_name', text='', icon='FILE_REFRESH')

        row = col.row(align=True)
        row.scale_y = 2
        row.operator('node.sv_select_objects', text="Select")

        col.prop_search(self, 'collection', bpy.data, 'collections', text='', icon='GROUP')

    def init_viewer(self):
        """Should be called from descendant class"""
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        self.use_custom_color = True

        self.outputs.new('SvObjectSocket', "Objects")

    def sv_copy(self, other):
        """
        Regenerate object names, and clean data
        Use super().sv_copy(other) to override this method
        """
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        # object and mesh lists should be clear other wise two nodes would have links to the same objects
        self.object_data.clear()

    def show_viewport(self, is_show: bool):
        """It should be called by node tree to show/hide objects"""
        if not self.show_objects:
            # just ignore request
            pass
        else:
            self.show_objects_update(None, is_show)

    def load_from_json(self, node_data: dict, import_version: float):
        """
        Manually serialization node properties
        Should be overridden in zis way: super().storage_get_data(storage); self.my_prop = storage['my_prop']
        """
        if import_version <= 0.08:
            collection_name = node_data['collection']
            if collection_name:
                collection = (bpy.data.collections.get(collection_name))
                if not collection:
                    collection = bpy.data.collections.new(collection_name)
                    bpy.context.collection.children.link(collection)
                self.collection = collection

    def draw_label(self):
        if self.hide:
            return f"{self.bl_label[:2]}V {self.base_data_name}"
        else:
            return self.bl_label


class SvViewerLightNode(BlenderObjects):
    """
    Mixin for all nodes which displays any objects in viewport
    """

    is_active: bpy.props.BoolProperty(
        name='Live',
        default=True,
        update=updateNode,
        description="When enabled this will process incoming data")

    base_data_name: bpy.props.StringProperty(
        default='Alpha',
        description='stores the mesh name found in the object, this mesh is instanced',
        update=updateNode)

    def draw_viewer_properties(self, layout):
        col = layout.column(align=True)
        row = col.row()

        row_show = row.row(align=True)
        row_show.prop(self, 'is_active', toggle=True)
        row_show.prop(self, 'show_objects', toggle=True, text='',
                      icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')

        row.operator(SvShowFlyPanelOpeartor.bl_idname, text="Options")

    def draw_buttons_fly(self, layout):
        col = layout.column()
        row = col.row()

        row_show = row.row(align=True)

        row_show.prop(self, 'is_active', toggle=True)
        row_show.prop(self, 'show_objects', toggle=True, text='',
                      icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')
        row = row.row(align=True)
        row.prop(self, 'selectable_objects', toggle=True, text='',
                    icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
        row.prop(self, 'render_objects', toggle=True, text='',
                    icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")

        row = col.row(align=True)
        row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
        op = row.operator(SvGenerateRandomObjectName.bl_idname, text='', icon='FILE_REFRESH')
        op.node_name = self.name
        op.tree_name = self.id_data.name
        row = col.row(align=True)
        row.scale_y = 2
        op = row.operator('node.sv_select_objects', text="Select")
        op.node_name = self.name
        op.tree_name = self.id_data.name
        op = row.operator(SvBakeObjectsOperator.bl_idname, text="Bake")
        op.node_name = self.name
        op.tree_name = self.id_data.name

    def init_viewer(self):
        """Should be called from descendant class"""
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        self.use_custom_color = True

        self.outputs.new('SvObjectSocket', "Objects")

    def sv_copy(self, other):
        """
        Regenerate object names, and clean data
        Use super().sv_copy(other) to override this method
        """
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        # object and mesh lists should be clear other wise two nodes would have links to the same objects
        self.object_data.clear()

    def sv_free(self):
        for data in self.object_data:
            data.remove_data()

    def bake(self):
        raise NotImplemented

    def show_viewport(self, is_show: bool):
        """It should be called by node tree to show/hide objects"""
        if not self.show_objects:
            # just ignore request
            pass
        else:
            self.show_objects_update(None, is_show)

    def draw_label(self):
        if self.hide:
            return f"{self.bl_label[:2]}V {self.base_data_name}"
        else:
            return self.bl_label


class SvObjectNames(bpy.types.PropertyGroup):
    available_name_number: bpy.props.IntProperty(default=0, min=0, max=24)
    greek_alphabet = [
        'Alpha', 'Beta', 'Gamma', 'Delta',
        'Epsilon', 'Zeta', 'Eta', 'Theta',
        'Iota', 'Kappa', 'Lamda', 'Mu',
        'Nu', 'Xi', 'Omicron', 'Pi',
        'Rho', 'Sigma', 'Tau', 'Upsilon',
        'Phi', 'Chi', 'Psi', 'Omega']

    def get_available_name(self):
        """It returns name from greek alphabet, if all names was used it returns random letters"""
        if self.available_name_number <= 23:
            name = self.greek_alphabet[self.available_name_number]
            self.available_name_number += 1
        else:
            name = self.get_random_name()

        return name

    @staticmethod
    def get_random_name():
        """Generate random name from random letters"""
        return ''.join(random.sample(set(string.ascii_uppercase), 6))


class SearchNode:
    node_name: StringProperty()
    tree_name: StringProperty()

    @property
    def node(self):
        if not hasattr(self, '_node'):
            self._node = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
        return self._node

    def invoke(self, context, event):
        if hasattr(context, 'node'):
            self._node = context.node
        return self.execute(context)


class SvShowFlyPanelOpeartor(bpy.types.Operator):
    """Shows extra node options"""
    bl_idname = 'node.sv_show_fly_panel'
    bl_label = "Node Options"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        self.node = context.node
        wm = context.window_manager
        return wm.invoke_popup(self, width=200)

    def draw(self, context):
        self.node.draw_buttons_fly(self.layout)


class SvSelectObjects(SearchNode, bpy.types.Operator):
    """It calls `select` method of every item in `object_data` collection of node"""
    bl_idname = 'node.sv_select_objects'
    bl_label = "Select objects"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Select generated objects"

    def execute(self, context):
        prop: SvObjectData
        for prop in self.node.object_data:
            prop.select()
        return {'FINISHED'}


class SvGenerateRandomObjectName(SearchNode, bpy.types.Operator):
    """
    It calls get_random_name fo sv_object_names property in scene
    and assign it to base_data_name property of node
    """
    bl_idname = 'node.sv_generate_random_object_name'
    bl_label = "Random name"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Generate random name"

    def execute(self, context):
        self.node.base_data_name = bpy.context.scene.sv_object_names.get_random_name()
        return {'FINISHED'}


class SvCreateMaterial(bpy.types.Operator):
    """It creates and add new material to a node"""
    bl_idname = 'node.sv_create_material'
    bl_label = "Create material"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Create new material"

    def execute(self, context):
        mat = bpy.data.materials.new('sv_material')
        mat.use_nodes = True
        context.node.material = mat
        return {'FINISHED'}

    @classmethod
    def poll(cls, context):
        return hasattr(context.node, 'material')


class SvBakeObjectsOperator(SearchNode, bpy.types.Operator):
    """Create object copies independent of the node"""
    bl_idname = 'node.sv_bake_objects'
    bl_label = "Bake objects"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    def execute(self, context):
        self.node.bake()
        return {'FINISHED'}


module_classes = [
    SvObjectData,
    SvMeshData,
    SvSelectObjects,
    SvObjectNames,
    SvGenerateRandomObjectName,
    SvLightData,
    SvCurveData,
    SvCreateMaterial,
    SvShowFlyPanelOpeartor,
    SvBakeObjectsOperator,
]


def register():
    [bpy.utils.register_class(cls) for cls in module_classes]
    bpy.types.Scene.sv_object_names = bpy.props.PointerProperty(type=SvObjectNames)


def unregister():
    [bpy.utils.unregister_class(cls) for cls in module_classes[::-1]]

Functions

def register()
Expand source code
def register():
    [bpy.utils.register_class(cls) for cls in module_classes]
    bpy.types.Scene.sv_object_names = bpy.props.PointerProperty(type=SvObjectNames)
def unregister()
Expand source code
def unregister():
    [bpy.utils.unregister_class(cls) for cls in module_classes[::-1]]

Classes

class BlenderObjects

Should be used for generating list of objects

Expand source code
class BlenderObjects:
    """Should be used for generating list of objects"""
    def show_objects_update(self, context, to_show: bool = None):
        """Hide / show objects. It should be only place to hide objects"""
        to_show = to_show if to_show is not None else self.show_objects
        [setattr(prop.obj, 'hide_viewport', not to_show) for prop in self.object_data]

    def selectable_objects_update(self, context):
        [setattr(prop.obj, 'hide_select', False if self.selectable_objects else True) for prop in self.object_data]

    def render_objects_update(self, context):
        [setattr(prop.obj, 'hide_render', False if self.render_objects else True) for prop in self.object_data]

    object_data: bpy.props.CollectionProperty(type=SvObjectData, options={'SKIP_SAVE'})

    show_objects: bpy.props.BoolProperty(
        default=True,
        description="Show / hide objects in viewport",
        update=update_with_kwargs(show_objects_update))

    selectable_objects: bpy.props.BoolProperty(
        default=True,
        description="Make objects selectable / unselectable",
        update=selectable_objects_update)

    render_objects: bpy.props.BoolProperty(
        default=True,
        description="Show / hide objects for render engines",
        update=render_objects_update)

    def regenerate_objects(self,
                           object_names: List[str],
                           data_blocks,
                           collections: List[bpy.types.Collection] = None,
                           object_template: List[bpy.types.Object] = None,
                           to_show: list[bool] = None,
                           ):
        """
        It will generate new or remove old objects, number of generated objects will be equal to given data_blocks
        Object_names list can contain one name. In this case Blender will add suffix to next objects (.001, .002,...)
        :param object_template: optionally, object which properties should be grabbed for instanced object
        :param collections: objects will be putted into collections if given, only one in list can be given
        :param data_blocks: nearly any data blocks - mesh, curves, lights ...
        :param object_names: usually equal to name of data block
        :param data_blocks: for now it is support only be bpy.types.Mesh
        :param to_show: whether to show objects in viewport.
        """
        if collections is None:
            collections = [None]
        if object_template is None:
            object_template = [None]
        if to_show is None:
            to_show = [True]

        correct_collection_length(self.object_data, len(data_blocks))
        prop_group: SvObjectData
        input_data = zip(self.object_data,
                         data_blocks,
                         cycle(object_names),
                         cycle(collections),
                         cycle(object_template),
                         cycle(to_show),
                         )
        for prop_group, data_block, name, collection, template, show in input_data:
            prop_group.ensure_object(data_block, name, template)
            prop_group.ensure_link_to_collection(collection)
            prop_group.check_object_name(name)
            prop_group.check_object_show_state(show)

    def draw_object_properties(self, layout):
        """Should be used for adding hide, select, render objects properties"""
        layout.prop(self, 'show_objects', toggle=True, text='',
                    icon=f"RESTRICT_VIEW_{'OFF' if self.show_objects else 'ON'}")
        layout.prop(self, 'selectable_objects', toggle=True, text='',
                    icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
        layout.prop(self, 'render_objects', toggle=True, text='',
                    icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")

Subclasses

Class variables

var object_data : <_PropertyDeferred, , {'type': SvObjectData'>, 'options': {'SKIP_SAVE'}, 'attr': 'object_data'}>
var render_objects : <_PropertyDeferred, , {'default': True, 'description': 'Show / hide objects for render engines', 'update': BlenderObjects.render_objects_update() at 0x7f2f1b0f5b80>, 'attr': 'render_objects'}>
var selectable_objects : <_PropertyDeferred, , {'default': True, 'description': 'Make objects selectable / unselectable', 'update': BlenderObjects.selectable_objects_update() at 0x7f2f1b0f5af0>, 'attr': 'selectable_objects'}>
var show_objects : <_PropertyDeferred, , {'default': True, 'description': 'Show / hide objects in viewport', 'update': BlenderObjects.show_objects_update() at 0x7f2f1b0f5c10>, 'attr': 'show_objects'}>

Methods

def draw_object_properties(self, layout)

Should be used for adding hide, select, render objects properties

Expand source code
def draw_object_properties(self, layout):
    """Should be used for adding hide, select, render objects properties"""
    layout.prop(self, 'show_objects', toggle=True, text='',
                icon=f"RESTRICT_VIEW_{'OFF' if self.show_objects else 'ON'}")
    layout.prop(self, 'selectable_objects', toggle=True, text='',
                icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
    layout.prop(self, 'render_objects', toggle=True, text='',
                icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")
def regenerate_objects(self, object_names: List[str], data_blocks, collections: List[bpy_types.Collection] = None, object_template: List[bpy_types.Object] = None, to_show: list = None)

It will generate new or remove old objects, number of generated objects will be equal to given data_blocks Object_names list can contain one name. In this case Blender will add suffix to next objects (.001, .002,…) :param object_template: optionally, object which properties should be grabbed for instanced object :param collections: objects will be putted into collections if given, only one in list can be given :param data_blocks: nearly any data blocks - mesh, curves, lights … :param object_names: usually equal to name of data block :param data_blocks: for now it is support only be bpy.types.Mesh :param to_show: whether to show objects in viewport.

Expand source code
def regenerate_objects(self,
                       object_names: List[str],
                       data_blocks,
                       collections: List[bpy.types.Collection] = None,
                       object_template: List[bpy.types.Object] = None,
                       to_show: list[bool] = None,
                       ):
    """
    It will generate new or remove old objects, number of generated objects will be equal to given data_blocks
    Object_names list can contain one name. In this case Blender will add suffix to next objects (.001, .002,...)
    :param object_template: optionally, object which properties should be grabbed for instanced object
    :param collections: objects will be putted into collections if given, only one in list can be given
    :param data_blocks: nearly any data blocks - mesh, curves, lights ...
    :param object_names: usually equal to name of data block
    :param data_blocks: for now it is support only be bpy.types.Mesh
    :param to_show: whether to show objects in viewport.
    """
    if collections is None:
        collections = [None]
    if object_template is None:
        object_template = [None]
    if to_show is None:
        to_show = [True]

    correct_collection_length(self.object_data, len(data_blocks))
    prop_group: SvObjectData
    input_data = zip(self.object_data,
                     data_blocks,
                     cycle(object_names),
                     cycle(collections),
                     cycle(object_template),
                     cycle(to_show),
                     )
    for prop_group, data_block, name, collection, template, show in input_data:
        prop_group.ensure_object(data_block, name, template)
        prop_group.ensure_link_to_collection(collection)
        prop_group.check_object_name(name)
        prop_group.check_object_show_state(show)
def render_objects_update(self, context)
Expand source code
def render_objects_update(self, context):
    [setattr(prop.obj, 'hide_render', False if self.render_objects else True) for prop in self.object_data]
def selectable_objects_update(self, context)
Expand source code
def selectable_objects_update(self, context):
    [setattr(prop.obj, 'hide_select', False if self.selectable_objects else True) for prop in self.object_data]
def show_objects_update(self, context, to_show: bool = None)

Hide / show objects. It should be only place to hide objects

Expand source code
def show_objects_update(self, context, to_show: bool = None):
    """Hide / show objects. It should be only place to hide objects"""
    to_show = to_show if to_show is not None else self.show_objects
    [setattr(prop.obj, 'hide_viewport', not to_show) for prop in self.object_data]
class SearchNode
Expand source code
class SearchNode:
    node_name: StringProperty()
    tree_name: StringProperty()

    @property
    def node(self):
        if not hasattr(self, '_node'):
            self._node = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
        return self._node

    def invoke(self, context, event):
        if hasattr(context, 'node'):
            self._node = context.node
        return self.execute(context)

Subclasses

Class variables

var node_name : <_PropertyDeferred, , {'attr': 'node_name'}>
var tree_name : <_PropertyDeferred, , {'attr': 'tree_name'}>

Instance variables

var node
Expand source code
@property
def node(self):
    if not hasattr(self, '_node'):
        self._node = bpy.data.node_groups[self.tree_name].nodes[self.node_name]
    return self._node

Methods

def invoke(self, context, event)
Expand source code
def invoke(self, context, event):
    if hasattr(context, 'node'):
        self._node = context.node
    return self.execute(context)
class SvBakeObjectsOperator (...)

Create object copies independent of the node

Expand source code
class SvBakeObjectsOperator(SearchNode, bpy.types.Operator):
    """Create object copies independent of the node"""
    bl_idname = 'node.sv_bake_objects'
    bl_label = "Bake objects"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    def execute(self, context):
        self.node.bake()
        return {'FINISHED'}

Ancestors

  • SearchNode
  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

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

Methods

def execute(self, context)
Expand source code
def execute(self, context):
    self.node.bake()
    return {'FINISHED'}
class SvCreateMaterial (...)

It creates and add new material to a node

Expand source code
class SvCreateMaterial(bpy.types.Operator):
    """It creates and add new material to a node"""
    bl_idname = 'node.sv_create_material'
    bl_label = "Create material"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Create new material"

    def execute(self, context):
        mat = bpy.data.materials.new('sv_material')
        mat.use_nodes = True
        context.node.material = mat
        return {'FINISHED'}

    @classmethod
    def poll(cls, context):
        return hasattr(context.node, 'material')

Ancestors

  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

var bl_idname
var bl_label
var bl_options
var bl_rna

Static methods

def description(context, properties)
Expand source code
@classmethod
def description(cls, context, properties):
    return "Create new material"
def poll(context)
Expand source code
@classmethod
def poll(cls, context):
    return hasattr(context.node, 'material')

Methods

def execute(self, context)
Expand source code
def execute(self, context):
    mat = bpy.data.materials.new('sv_material')
    mat.use_nodes = True
    context.node.material = mat
    return {'FINISHED'}
class SvCurveData (...)

For now it is supporting only one spline per curve

Expand source code
class SvCurveData(bpy.types.PropertyGroup):
    """For now it is supporting only one spline per curve"""
    curve: bpy.props.PointerProperty(type=bpy.types.Curve)

    def regenerate_curve(self,
                         curve_name: str,
                         vertices: Union[List[list], List[np.ndarray]],
                         spline_type: str = 'POLY',
                         vertices_radius: Union[List[list], List[np.ndarray]] = None,
                         close_spline: Union[List[bool], List[int]] = None,
                         use_smooth: bool = True,
                         tilt: Union[List[list], List[np.ndarray]] = None):
        # Be aware that curve consists multiple splines
        if not self.curve:
            self.curve = bpy.data.curves.new(name=curve_name, type='CURVE')  # type ['CURVE', 'SURFACE', 'FONT']
        if len(self.curve.splines) != len(vertices) \
                or any(len(s.points) != len(v) for s, v in zip(self.curve.splines, vertices)):
            # if at least on spline has wrong number of points whole list of splines should be recreated
            # thanks to Blender API
            self.curve.splines.clear()
            [self.curve.splines.new(spline_type) for _ in range(len(vertices))]
            [s.points.add(len(v) - 1) for s, v in zip(self.curve.splines, vertices)]

        for s, v, r, t, c in zip(self.curve.splines, vertices,
                              repeat_last(vertices_radius or [None]),
                              repeat_last(tilt or [None]),
                              repeat_last(close_spline)):
            v = np.asarray(v, dtype=np.float32)
            if r is None:
                r = np.ones(len(v), dtype=np.float32)
            r = np.asarray(r, dtype=np.float32)
            self._regenerate_spline(s, v, spline_type, r, t, c, use_smooth)

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.curve:
            delete_data_block(self.curve)

    @staticmethod
    def _regenerate_spline(spline: bpy.types.Spline,
                           vertices: np.ndarray,
                           spline_type: str = 'POLY',
                           vertices_radius: np.ndarray = None,
                           tilt: np.ndarray = None,
                           close_spline: bool = False,
                           use_smooth: bool = True):
        spline.type = spline_type
        spline.use_cyclic_u = close_spline
        spline.use_smooth = use_smooth

        # flatten vertices array and add W component (X, Y, Z, W), W is responsible for drawing NURBS curves
        w_vertices = np.concatenate((vertices, np.ones((len(vertices), 1), dtype=np.float32)), axis=1)
        flatten_vertices = np.ravel(w_vertices)
        spline.points.foreach_set('co', flatten_vertices)
        if vertices_radius is not None:
            spline.points.foreach_set('radius', numpy_full_list(vertices_radius, len(vertices)))
        if tilt is not None:
            spline.points.foreach_set('tilt', numpy_full_list(tilt, len(vertices)))

Ancestors

  • bpy_types.PropertyGroup
  • builtins.bpy_struct

Class variables

var bl_rna
var curve : <_PropertyDeferred, , {'type': , 'attr': 'curve'}>

Methods

def regenerate_curve(self, curve_name: str, vertices: Union[List[list], List[numpy.ndarray]], spline_type: str = 'POLY', vertices_radius: Union[List[list], List[numpy.ndarray]] = None, close_spline: Union[List[bool], List[int]] = None, use_smooth: bool = True, tilt: Union[List[list], List[numpy.ndarray]] = None)
Expand source code
def regenerate_curve(self,
                     curve_name: str,
                     vertices: Union[List[list], List[np.ndarray]],
                     spline_type: str = 'POLY',
                     vertices_radius: Union[List[list], List[np.ndarray]] = None,
                     close_spline: Union[List[bool], List[int]] = None,
                     use_smooth: bool = True,
                     tilt: Union[List[list], List[np.ndarray]] = None):
    # Be aware that curve consists multiple splines
    if not self.curve:
        self.curve = bpy.data.curves.new(name=curve_name, type='CURVE')  # type ['CURVE', 'SURFACE', 'FONT']
    if len(self.curve.splines) != len(vertices) \
            or any(len(s.points) != len(v) for s, v in zip(self.curve.splines, vertices)):
        # if at least on spline has wrong number of points whole list of splines should be recreated
        # thanks to Blender API
        self.curve.splines.clear()
        [self.curve.splines.new(spline_type) for _ in range(len(vertices))]
        [s.points.add(len(v) - 1) for s, v in zip(self.curve.splines, vertices)]

    for s, v, r, t, c in zip(self.curve.splines, vertices,
                          repeat_last(vertices_radius or [None]),
                          repeat_last(tilt or [None]),
                          repeat_last(close_spline)):
        v = np.asarray(v, dtype=np.float32)
        if r is None:
            r = np.ones(len(v), dtype=np.float32)
        r = np.asarray(r, dtype=np.float32)
        self._regenerate_spline(s, v, spline_type, r, t, c, use_smooth)
def remove_data(self)

This method should be called before deleting the property The mesh is belonged only to this property and should be deleted with it

Expand source code
def remove_data(self):
    """
    This method should be called before deleting the property
    The mesh is belonged only to this property and should be deleted with it
    """
    if self.curve:
        delete_data_block(self.curve)
class SvGenerateRandomObjectName (...)

It calls get_random_name fo sv_object_names property in scene and assign it to base_data_name property of node

Expand source code
class SvGenerateRandomObjectName(SearchNode, bpy.types.Operator):
    """
    It calls get_random_name fo sv_object_names property in scene
    and assign it to base_data_name property of node
    """
    bl_idname = 'node.sv_generate_random_object_name'
    bl_label = "Random name"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Generate random name"

    def execute(self, context):
        self.node.base_data_name = bpy.context.scene.sv_object_names.get_random_name()
        return {'FINISHED'}

Ancestors

  • SearchNode
  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

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

Static methods

def description(context, properties)
Expand source code
@classmethod
def description(cls, context, properties):
    return "Generate random name"

Methods

def execute(self, context)
Expand source code
def execute(self, context):
    self.node.base_data_name = bpy.context.scene.sv_object_names.get_random_name()
    return {'FINISHED'}
class SvLightData (...)
Expand source code
class SvLightData(bpy.types.PropertyGroup):
    light: bpy.props.PointerProperty(type=bpy.types.Light)

    def regenerate_light(self, light_name: str, light_type: str):
        if not self.light:
            # new mesh should be created
            self.light = bpy.data.lights.new(name=light_name, type=light_type)
        elif self.light.type != light_type:
            # in case if type was changed
            self.light.type = light_type

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.light:
            delete_data_block(self.light)

Ancestors

  • bpy_types.PropertyGroup
  • builtins.bpy_struct

Class variables

var bl_rna
var light : <_PropertyDeferred, , {'type': , 'attr': 'light'}>

Methods

def regenerate_light(self, light_name: str, light_type: str)
Expand source code
def regenerate_light(self, light_name: str, light_type: str):
    if not self.light:
        # new mesh should be created
        self.light = bpy.data.lights.new(name=light_name, type=light_type)
    elif self.light.type != light_type:
        # in case if type was changed
        self.light.type = light_type
def remove_data(self)

This method should be called before deleting the property The mesh is belonged only to this property and should be deleted with it

Expand source code
def remove_data(self):
    """
    This method should be called before deleting the property
    The mesh is belonged only to this property and should be deleted with it
    """
    if self.light:
        delete_data_block(self.light)
class SvMeshData (...)
Expand source code
class SvMeshData(bpy.types.PropertyGroup):
    mesh: bpy.props.PointerProperty(type=bpy.types.Mesh, options={'SKIP_SAVE'})

    def regenerate_mesh(self, mesh_name: str, verts, edges=None, faces=None, matrix: Matrix = None,
                        make_changes_test=True):
        """
        It takes vertices, edges and faces and updates mesh data block
        If it assume that topology is unchanged only position of vertices will be changed
        In this case it will be more efficient if vertices are given in np.array float32 format
        Can apply matrix to mesh optionally
        """
        if edges is None:
            edges = []
        if faces is None:
            faces = []

        if not self.mesh:
            # new mesh should be created
            self.mesh = bpy.data.meshes.new(name=mesh_name)

        if not make_changes_test or self.is_topology_changed(verts, edges, faces):

            if self.mesh.is_editmode:
                with bmesh_from_edit_mesh(self.mesh) as bm:
                    bm.clear()
                    add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                    bm.normal_update()
                    if matrix:
                        bm.transform(matrix)
            else:
                with EmptyBmesh(False) as bm:
                    add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                    if matrix:
                        bm.transform(matrix)
                    bm.to_mesh(self.mesh)

        else:

            if self.mesh.is_editmode:
                with bmesh_from_edit_mesh(self.mesh) as bm:
                    for bv, v in zip(bm.verts, verts):
                        bv.co = v
                    if matrix:
                        bm.transform(matrix)
            else:
                self.update_vertices(verts)
                if matrix:
                    self.mesh.transform(matrix)

        self.mesh.update()

    def set_smooth(self, is_smooth_mesh):
        """Make mesh smooth or flat"""
        if is_smooth_mesh:
            is_smooth = np.ones(len(self.mesh.polygons), dtype=bool)
        else:
            is_smooth = np.zeros(len(self.mesh.polygons), dtype=bool)
        self.mesh.polygons.foreach_set('use_smooth', is_smooth)

    def is_topology_changed(self, verts: list, edges: list, faces: list) -> bool:
        """
        Simple and fast test but not 100% robust.
        If number of vertices and faces are unchanged it assumes that topology is not changed
        This test is useful if mesh just changed its location.
        It is much faster just set new coordinate for each vector then recreate whole object
        """
        if not faces:
            # edges can be take in account if mesh does not have polygons
            # because Sverchok edges can exclude edges within polygons
            return len(self.mesh.vertices) != len(verts) or len(self.mesh.edges) != len(edges)
        else:
            number_is_changed = len(self.mesh.vertices) != len(verts) or len(self.mesh.polygons) != len(faces)
            # check several polygons indexes
            are_polygons_changed = any([list(p.vertices) != f for _, p, f in zip(range(5), self.mesh.polygons, faces)])
            return number_is_changed or are_polygons_changed

    def update_vertices(self, verts: Union[list, np.ndarray]):
        """
        Just update position of mesh vertices, order and number of given vertices should be the same as mesh
        numpy array with float32 type will be 10 times faster than any other input data
        """
        verts = np.array(verts, dtype=np.float32)  # todo will be this fast if it is already array float 32?
        self.mesh.vertices.foreach_set('co', np.ravel(verts))

    def copy(self) -> bpy.types.Mesh:
        return self.mesh.copy()

    def remove_data(self):
        """
        This method should be called before deleting the property
        The mesh is belonged only to this property and should be deleted with it
        """
        if self.mesh:
            delete_data_block(self.mesh)

Ancestors

  • bpy_types.PropertyGroup
  • builtins.bpy_struct

Class variables

var bl_rna
var mesh : <_PropertyDeferred, , {'type': , 'options': {'SKIP_SAVE'}, 'attr': 'mesh'}>

Methods

def copy(self) ‑> bpy_types.Mesh
Expand source code
def copy(self) -> bpy.types.Mesh:
    return self.mesh.copy()
def is_topology_changed(self, verts: list, edges: list, faces: list) ‑> bool

Simple and fast test but not 100% robust. If number of vertices and faces are unchanged it assumes that topology is not changed This test is useful if mesh just changed its location. It is much faster just set new coordinate for each vector then recreate whole object

Expand source code
def is_topology_changed(self, verts: list, edges: list, faces: list) -> bool:
    """
    Simple and fast test but not 100% robust.
    If number of vertices and faces are unchanged it assumes that topology is not changed
    This test is useful if mesh just changed its location.
    It is much faster just set new coordinate for each vector then recreate whole object
    """
    if not faces:
        # edges can be take in account if mesh does not have polygons
        # because Sverchok edges can exclude edges within polygons
        return len(self.mesh.vertices) != len(verts) or len(self.mesh.edges) != len(edges)
    else:
        number_is_changed = len(self.mesh.vertices) != len(verts) or len(self.mesh.polygons) != len(faces)
        # check several polygons indexes
        are_polygons_changed = any([list(p.vertices) != f for _, p, f in zip(range(5), self.mesh.polygons, faces)])
        return number_is_changed or are_polygons_changed
def regenerate_mesh(self, mesh_name: str, verts, edges=None, faces=None, matrix: Matrix = None, make_changes_test=True)

It takes vertices, edges and faces and updates mesh data block If it assume that topology is unchanged only position of vertices will be changed In this case it will be more efficient if vertices are given in np.array float32 format Can apply matrix to mesh optionally

Expand source code
def regenerate_mesh(self, mesh_name: str, verts, edges=None, faces=None, matrix: Matrix = None,
                    make_changes_test=True):
    """
    It takes vertices, edges and faces and updates mesh data block
    If it assume that topology is unchanged only position of vertices will be changed
    In this case it will be more efficient if vertices are given in np.array float32 format
    Can apply matrix to mesh optionally
    """
    if edges is None:
        edges = []
    if faces is None:
        faces = []

    if not self.mesh:
        # new mesh should be created
        self.mesh = bpy.data.meshes.new(name=mesh_name)

    if not make_changes_test or self.is_topology_changed(verts, edges, faces):

        if self.mesh.is_editmode:
            with bmesh_from_edit_mesh(self.mesh) as bm:
                bm.clear()
                add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                bm.normal_update()
                if matrix:
                    bm.transform(matrix)
        else:
            with EmptyBmesh(False) as bm:
                add_mesh_to_bmesh(bm, verts, edges, faces, update_indexes=False, update_normals=False)
                if matrix:
                    bm.transform(matrix)
                bm.to_mesh(self.mesh)

    else:

        if self.mesh.is_editmode:
            with bmesh_from_edit_mesh(self.mesh) as bm:
                for bv, v in zip(bm.verts, verts):
                    bv.co = v
                if matrix:
                    bm.transform(matrix)
        else:
            self.update_vertices(verts)
            if matrix:
                self.mesh.transform(matrix)

    self.mesh.update()
def remove_data(self)

This method should be called before deleting the property The mesh is belonged only to this property and should be deleted with it

Expand source code
def remove_data(self):
    """
    This method should be called before deleting the property
    The mesh is belonged only to this property and should be deleted with it
    """
    if self.mesh:
        delete_data_block(self.mesh)
def set_smooth(self, is_smooth_mesh)

Make mesh smooth or flat

Expand source code
def set_smooth(self, is_smooth_mesh):
    """Make mesh smooth or flat"""
    if is_smooth_mesh:
        is_smooth = np.ones(len(self.mesh.polygons), dtype=bool)
    else:
        is_smooth = np.zeros(len(self.mesh.polygons), dtype=bool)
    self.mesh.polygons.foreach_set('use_smooth', is_smooth)
def update_vertices(self, verts: Union[list, numpy.ndarray])

Just update position of mesh vertices, order and number of given vertices should be the same as mesh numpy array with float32 type will be 10 times faster than any other input data

Expand source code
def update_vertices(self, verts: Union[list, np.ndarray]):
    """
    Just update position of mesh vertices, order and number of given vertices should be the same as mesh
    numpy array with float32 type will be 10 times faster than any other input data
    """
    verts = np.array(verts, dtype=np.float32)  # todo will be this fast if it is already array float 32?
    self.mesh.vertices.foreach_set('co', np.ravel(verts))
class SvObjectData (...)
Expand source code
class SvObjectData(bpy.types.PropertyGroup):
    obj: bpy.props.PointerProperty(type=bpy.types.Object)

    # Object have not information about in which collection it is located
    # Keep here information about collection for performance reasons
    # Now object can be only in on collection
    collection: bpy.props.PointerProperty(type=bpy.types.Collection)

    def ensure_object(self, data_block, name: str, object_template: bpy.types.Object = None):
        """Add object if it does not exist, if object_template is given new object will be copied from it"""
        if not self.obj:
            # it looks like it means only that the property group item was created newly
            if object_template:
                self.obj = object_template.copy()
                self.obj.data = data_block
            else:
                self.obj = bpy.data.objects.new(name=name, object_data=data_block)
        else:
            # in case if data block was changed
            if self.obj.data != data_block:
                self.obj.data = data_block  # EXPENSIVE

    def select(self):
        """Just select the object"""
        if self.obj:
            self.obj.select_set(True)

    def ensure_link_to_collection(self, collection: bpy.types.Collection = None):
        """Links object to scene or given collection, unlink from previous collection"""
        try:
            if collection:
                collection.objects.link(self.obj)
            else:
                # default collection
                bpy.context.scene.collection.objects.link(self.obj)
        except RuntimeError:
            # then the object already added, it looks like more faster way to ensure object is in the scene
            pass

        if self.collection != collection:
            # new collection was given, object should be removed from previous one
            if self.collection is None:
                # it means that it is scene default collection
                # from other hand if item only was created it also will be None but object is not in any collection yet
                try:
                    bpy.context.scene.collection.objects.unlink(self.obj)
                except RuntimeError:
                    pass
            else:
                try:
                    self.collection.objects.unlink(self.obj)
                except RuntimeError:
                    # collection was already unliked by user or another node
                    pass

            self.collection = collection

    def check_object_name(self, name: str) -> None:
        """If base name of an object was changed names of all instances also should be changed"""
        real_name = self.obj.name.rsplit('.', 1)[0]
        if real_name != name:
            self.obj.name = name

    def check_object_show_state(self, to_show: bool):
        # hide_viewport is faster than hide_set and hide_viewport is stable
        # when objects are assigned to a collection
        hide = not to_show
        if self.obj.hide_viewport != hide:
            self.obj.hide_viewport = hide

    def recreate_object(self, object_template: bpy.types.Object = None):
        """
        Object will be replaced by new object recreated from scratch or copied from given object_template if given
        Previous object will be removed, data block remains unchanged
        """
        # in case recreated object should have a chance to get the same name of previous object
        # previous object should be deleted first
        data_block = self.obj.data
        obj_name = self.obj.name
        bpy.data.objects.remove(self.obj)
        if object_template:
            new_obj = object_template.copy()
            new_obj.data = data_block
        else:
            new_obj = bpy.data.objects.new(name=obj_name, object_data=data_block)
        self.obj = new_obj

    def copy(self) -> bpy.types.Object:
        """Return copy of object which is assigned to a collection"""
        obj = self.obj.copy()
        for collection in self.obj.users_collection:
            collection.objects.link(obj)
        return obj

    def remove_data(self):
        """Should be called before removing item"""
        if self.obj:
            delete_data_block(self.obj)

Ancestors

  • bpy_types.PropertyGroup
  • builtins.bpy_struct

Class variables

var bl_rna
var collection : <_PropertyDeferred, , {'type': , 'attr': 'collection'}>
var obj : <_PropertyDeferred, , {'type': , 'attr': 'obj'}>

Methods

def check_object_name(self, name: str) ‑> None

If base name of an object was changed names of all instances also should be changed

Expand source code
def check_object_name(self, name: str) -> None:
    """If base name of an object was changed names of all instances also should be changed"""
    real_name = self.obj.name.rsplit('.', 1)[0]
    if real_name != name:
        self.obj.name = name
def check_object_show_state(self, to_show: bool)
Expand source code
def check_object_show_state(self, to_show: bool):
    # hide_viewport is faster than hide_set and hide_viewport is stable
    # when objects are assigned to a collection
    hide = not to_show
    if self.obj.hide_viewport != hide:
        self.obj.hide_viewport = hide
def copy(self) ‑> bpy_types.Object

Return copy of object which is assigned to a collection

Expand source code
def copy(self) -> bpy.types.Object:
    """Return copy of object which is assigned to a collection"""
    obj = self.obj.copy()
    for collection in self.obj.users_collection:
        collection.objects.link(obj)
    return obj

Links object to scene or given collection, unlink from previous collection

Expand source code
def ensure_link_to_collection(self, collection: bpy.types.Collection = None):
    """Links object to scene or given collection, unlink from previous collection"""
    try:
        if collection:
            collection.objects.link(self.obj)
        else:
            # default collection
            bpy.context.scene.collection.objects.link(self.obj)
    except RuntimeError:
        # then the object already added, it looks like more faster way to ensure object is in the scene
        pass

    if self.collection != collection:
        # new collection was given, object should be removed from previous one
        if self.collection is None:
            # it means that it is scene default collection
            # from other hand if item only was created it also will be None but object is not in any collection yet
            try:
                bpy.context.scene.collection.objects.unlink(self.obj)
            except RuntimeError:
                pass
        else:
            try:
                self.collection.objects.unlink(self.obj)
            except RuntimeError:
                # collection was already unliked by user or another node
                pass

        self.collection = collection
def ensure_object(self, data_block, name: str, object_template: bpy_types.Object = None)

Add object if it does not exist, if object_template is given new object will be copied from it

Expand source code
def ensure_object(self, data_block, name: str, object_template: bpy.types.Object = None):
    """Add object if it does not exist, if object_template is given new object will be copied from it"""
    if not self.obj:
        # it looks like it means only that the property group item was created newly
        if object_template:
            self.obj = object_template.copy()
            self.obj.data = data_block
        else:
            self.obj = bpy.data.objects.new(name=name, object_data=data_block)
    else:
        # in case if data block was changed
        if self.obj.data != data_block:
            self.obj.data = data_block  # EXPENSIVE
def recreate_object(self, object_template: bpy_types.Object = None)

Object will be replaced by new object recreated from scratch or copied from given object_template if given Previous object will be removed, data block remains unchanged

Expand source code
def recreate_object(self, object_template: bpy.types.Object = None):
    """
    Object will be replaced by new object recreated from scratch or copied from given object_template if given
    Previous object will be removed, data block remains unchanged
    """
    # in case recreated object should have a chance to get the same name of previous object
    # previous object should be deleted first
    data_block = self.obj.data
    obj_name = self.obj.name
    bpy.data.objects.remove(self.obj)
    if object_template:
        new_obj = object_template.copy()
        new_obj.data = data_block
    else:
        new_obj = bpy.data.objects.new(name=obj_name, object_data=data_block)
    self.obj = new_obj
def remove_data(self)

Should be called before removing item

Expand source code
def remove_data(self):
    """Should be called before removing item"""
    if self.obj:
        delete_data_block(self.obj)
def select(self)

Just select the object

Expand source code
def select(self):
    """Just select the object"""
    if self.obj:
        self.obj.select_set(True)
class SvObjectNames (...)
Expand source code
class SvObjectNames(bpy.types.PropertyGroup):
    available_name_number: bpy.props.IntProperty(default=0, min=0, max=24)
    greek_alphabet = [
        'Alpha', 'Beta', 'Gamma', 'Delta',
        'Epsilon', 'Zeta', 'Eta', 'Theta',
        'Iota', 'Kappa', 'Lamda', 'Mu',
        'Nu', 'Xi', 'Omicron', 'Pi',
        'Rho', 'Sigma', 'Tau', 'Upsilon',
        'Phi', 'Chi', 'Psi', 'Omega']

    def get_available_name(self):
        """It returns name from greek alphabet, if all names was used it returns random letters"""
        if self.available_name_number <= 23:
            name = self.greek_alphabet[self.available_name_number]
            self.available_name_number += 1
        else:
            name = self.get_random_name()

        return name

    @staticmethod
    def get_random_name():
        """Generate random name from random letters"""
        return ''.join(random.sample(set(string.ascii_uppercase), 6))

Ancestors

  • bpy_types.PropertyGroup
  • builtins.bpy_struct

Class variables

var available_name_number : <_PropertyDeferred, , {'default': 0, 'min': 0, 'max': 24, 'attr': 'available_name_number'}>
var bl_rna
var greek_alphabet

Static methods

def get_random_name()

Generate random name from random letters

Expand source code
@staticmethod
def get_random_name():
    """Generate random name from random letters"""
    return ''.join(random.sample(set(string.ascii_uppercase), 6))

Methods

def get_available_name(self)

It returns name from greek alphabet, if all names was used it returns random letters

Expand source code
def get_available_name(self):
    """It returns name from greek alphabet, if all names was used it returns random letters"""
    if self.available_name_number <= 23:
        name = self.greek_alphabet[self.available_name_number]
        self.available_name_number += 1
    else:
        name = self.get_random_name()

    return name
class SvSelectObjects (...)

It calls select method of every item in object_data collection of node

Expand source code
class SvSelectObjects(SearchNode, bpy.types.Operator):
    """It calls `select` method of every item in `object_data` collection of node"""
    bl_idname = 'node.sv_select_objects'
    bl_label = "Select objects"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def description(cls, context, properties):
        return "Select generated objects"

    def execute(self, context):
        prop: SvObjectData
        for prop in self.node.object_data:
            prop.select()
        return {'FINISHED'}

Ancestors

  • SearchNode
  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

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

Static methods

def description(context, properties)
Expand source code
@classmethod
def description(cls, context, properties):
    return "Select generated objects"

Methods

def execute(self, context)
Expand source code
def execute(self, context):
    prop: SvObjectData
    for prop in self.node.object_data:
        prop.select()
    return {'FINISHED'}
class SvShowFlyPanelOpeartor (...)

Shows extra node options

Expand source code
class SvShowFlyPanelOpeartor(bpy.types.Operator):
    """Shows extra node options"""
    bl_idname = 'node.sv_show_fly_panel'
    bl_label = "Node Options"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        self.node = context.node
        wm = context.window_manager
        return wm.invoke_popup(self, width=200)

    def draw(self, context):
        self.node.draw_buttons_fly(self.layout)

Ancestors

  • bpy_types.Operator
  • builtins.bpy_struct

Class variables

var bl_idname
var bl_label
var bl_options
var bl_rna

Methods

def draw(self, context)
Expand source code
def draw(self, context):
    self.node.draw_buttons_fly(self.layout)
def execute(self, context)
Expand source code
def execute(self, context):
    return {'FINISHED'}
def invoke(self, context, event)
Expand source code
def invoke(self, context, event):
    self.node = context.node
    wm = context.window_manager
    return wm.invoke_popup(self, width=200)
class SvViewerLightNode

Mixin for all nodes which displays any objects in viewport

Expand source code
class SvViewerLightNode(BlenderObjects):
    """
    Mixin for all nodes which displays any objects in viewport
    """

    is_active: bpy.props.BoolProperty(
        name='Live',
        default=True,
        update=updateNode,
        description="When enabled this will process incoming data")

    base_data_name: bpy.props.StringProperty(
        default='Alpha',
        description='stores the mesh name found in the object, this mesh is instanced',
        update=updateNode)

    def draw_viewer_properties(self, layout):
        col = layout.column(align=True)
        row = col.row()

        row_show = row.row(align=True)
        row_show.prop(self, 'is_active', toggle=True)
        row_show.prop(self, 'show_objects', toggle=True, text='',
                      icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')

        row.operator(SvShowFlyPanelOpeartor.bl_idname, text="Options")

    def draw_buttons_fly(self, layout):
        col = layout.column()
        row = col.row()

        row_show = row.row(align=True)

        row_show.prop(self, 'is_active', toggle=True)
        row_show.prop(self, 'show_objects', toggle=True, text='',
                      icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')
        row = row.row(align=True)
        row.prop(self, 'selectable_objects', toggle=True, text='',
                    icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
        row.prop(self, 'render_objects', toggle=True, text='',
                    icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")

        row = col.row(align=True)
        row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
        op = row.operator(SvGenerateRandomObjectName.bl_idname, text='', icon='FILE_REFRESH')
        op.node_name = self.name
        op.tree_name = self.id_data.name
        row = col.row(align=True)
        row.scale_y = 2
        op = row.operator('node.sv_select_objects', text="Select")
        op.node_name = self.name
        op.tree_name = self.id_data.name
        op = row.operator(SvBakeObjectsOperator.bl_idname, text="Bake")
        op.node_name = self.name
        op.tree_name = self.id_data.name

    def init_viewer(self):
        """Should be called from descendant class"""
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        self.use_custom_color = True

        self.outputs.new('SvObjectSocket', "Objects")

    def sv_copy(self, other):
        """
        Regenerate object names, and clean data
        Use super().sv_copy(other) to override this method
        """
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        # object and mesh lists should be clear other wise two nodes would have links to the same objects
        self.object_data.clear()

    def sv_free(self):
        for data in self.object_data:
            data.remove_data()

    def bake(self):
        raise NotImplemented

    def show_viewport(self, is_show: bool):
        """It should be called by node tree to show/hide objects"""
        if not self.show_objects:
            # just ignore request
            pass
        else:
            self.show_objects_update(None, is_show)

    def draw_label(self):
        if self.hide:
            return f"{self.bl_label[:2]}V {self.base_data_name}"
        else:
            return self.bl_label

Ancestors

Subclasses

  • sverchok.nodes.viz.geo_nodes_viewer.SvGeoNodesViewerNode

Class variables

var base_data_name : <_PropertyDeferred, , {'default': 'Alpha', 'description': 'stores the mesh name found in the object, this mesh is instanced', 'update': , 'attr': 'base_data_name'}>
var is_active : <_PropertyDeferred, , {'name': 'Live', 'default': True, 'update': , 'description': 'When enabled this will process incoming data', 'attr': 'is_active'}>

Methods

def bake(self)
Expand source code
def bake(self):
    raise NotImplemented
def draw_buttons_fly(self, layout)
Expand source code
def draw_buttons_fly(self, layout):
    col = layout.column()
    row = col.row()

    row_show = row.row(align=True)

    row_show.prop(self, 'is_active', toggle=True)
    row_show.prop(self, 'show_objects', toggle=True, text='',
                  icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')
    row = row.row(align=True)
    row.prop(self, 'selectable_objects', toggle=True, text='',
                icon=f"RESTRICT_SELECT_{'OFF' if self.selectable_objects else 'ON'}")
    row.prop(self, 'render_objects', toggle=True, text='',
                icon=f"RESTRICT_RENDER_{'OFF' if self.render_objects else 'ON'}")

    row = col.row(align=True)
    row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
    op = row.operator(SvGenerateRandomObjectName.bl_idname, text='', icon='FILE_REFRESH')
    op.node_name = self.name
    op.tree_name = self.id_data.name
    row = col.row(align=True)
    row.scale_y = 2
    op = row.operator('node.sv_select_objects', text="Select")
    op.node_name = self.name
    op.tree_name = self.id_data.name
    op = row.operator(SvBakeObjectsOperator.bl_idname, text="Bake")
    op.node_name = self.name
    op.tree_name = self.id_data.name
def draw_label(self)
Expand source code
def draw_label(self):
    if self.hide:
        return f"{self.bl_label[:2]}V {self.base_data_name}"
    else:
        return self.bl_label
def draw_viewer_properties(self, layout)
Expand source code
def draw_viewer_properties(self, layout):
    col = layout.column(align=True)
    row = col.row()

    row_show = row.row(align=True)
    row_show.prop(self, 'is_active', toggle=True)
    row_show.prop(self, 'show_objects', toggle=True, text='',
                  icon=f'HIDE_{"OFF" if self.show_objects else "ON"}')

    row.operator(SvShowFlyPanelOpeartor.bl_idname, text="Options")
def init_viewer(self)

Should be called from descendant class

Expand source code
def init_viewer(self):
    """Should be called from descendant class"""
    self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
    self.use_custom_color = True

    self.outputs.new('SvObjectSocket', "Objects")
def show_viewport(self, is_show: bool)

It should be called by node tree to show/hide objects

Expand source code
def show_viewport(self, is_show: bool):
    """It should be called by node tree to show/hide objects"""
    if not self.show_objects:
        # just ignore request
        pass
    else:
        self.show_objects_update(None, is_show)
def sv_copy(self, other)

Regenerate object names, and clean data Use super().sv_copy(other) to override this method

Expand source code
def sv_copy(self, other):
    """
    Regenerate object names, and clean data
    Use super().sv_copy(other) to override this method
    """
    self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
    # object and mesh lists should be clear other wise two nodes would have links to the same objects
    self.object_data.clear()
def sv_free(self)
Expand source code
def sv_free(self):
    for data in self.object_data:
        data.remove_data()

Inherited members

class SvViewerNode

Mixin for all nodes which displays any objects in viewport

Expand source code
class SvViewerNode(BlenderObjects):
    """
    Mixin for all nodes which displays any objects in viewport
    """

    is_active: bpy.props.BoolProperty(name='Live', default=True, update=updateNode,
                                      description="When enabled this will process incoming data",)

    base_data_name: bpy.props.StringProperty(
        default='Alpha',
        description='stores the mesh name found in the object, this mesh is instanced',
        update=updateNode)

    collection: bpy.props.PointerProperty(type=bpy.types.Collection, update=updateNode,
                                          description="Collection where to put objects")

    def draw_viewer_properties(self, layout):
        col = layout.column(align=True)
        row = col.row(align=True)
        row.column().prop(self, 'is_active', toggle=True)

        self.draw_object_properties(row)  # hide, selectable, render

        col = layout.column(align=True)
        row = col.row(align=True)
        row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
        row.operator('node.sv_generate_random_object_name', text='', icon='FILE_REFRESH')

        row = col.row(align=True)
        row.scale_y = 2
        row.operator('node.sv_select_objects', text="Select")

        col.prop_search(self, 'collection', bpy.data, 'collections', text='', icon='GROUP')

    def init_viewer(self):
        """Should be called from descendant class"""
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        self.use_custom_color = True

        self.outputs.new('SvObjectSocket', "Objects")

    def sv_copy(self, other):
        """
        Regenerate object names, and clean data
        Use super().sv_copy(other) to override this method
        """
        self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
        # object and mesh lists should be clear other wise two nodes would have links to the same objects
        self.object_data.clear()

    def show_viewport(self, is_show: bool):
        """It should be called by node tree to show/hide objects"""
        if not self.show_objects:
            # just ignore request
            pass
        else:
            self.show_objects_update(None, is_show)

    def load_from_json(self, node_data: dict, import_version: float):
        """
        Manually serialization node properties
        Should be overridden in zis way: super().storage_get_data(storage); self.my_prop = storage['my_prop']
        """
        if import_version <= 0.08:
            collection_name = node_data['collection']
            if collection_name:
                collection = (bpy.data.collections.get(collection_name))
                if not collection:
                    collection = bpy.data.collections.new(collection_name)
                    bpy.context.collection.children.link(collection)
                self.collection = collection

    def draw_label(self):
        if self.hide:
            return f"{self.bl_label[:2]}V {self.base_data_name}"
        else:
            return self.bl_label

Ancestors

Subclasses

  • sverchok.nodes.viz.dupli_instances_lite.SvDupliInstancesLite
  • sverchok.nodes.viz.dupli_instances_mk5.SvDupliInstancesMK5
  • sverchok.nodes.viz.instancer.SvInstancerNodeMK3
  • sverchok.nodes.viz.light_viewer.SvLightViewerNode
  • sverchok.nodes.viz.mesh_viewer.SvMeshViewer
  • sverchok.nodes.viz.polyline_viewer.SvPolylineViewerNode

Class variables

var base_data_name : <_PropertyDeferred, , {'default': 'Alpha', 'description': 'stores the mesh name found in the object, this mesh is instanced', 'update': , 'attr': 'base_data_name'}>
var collection : <_PropertyDeferred, , {'type': , 'update': , 'description': 'Collection where to put objects', 'attr': 'collection'}>
var is_active : <_PropertyDeferred, , {'name': 'Live', 'default': True, 'update': , 'description': 'When enabled this will process incoming data', 'attr': 'is_active'}>

Methods

def draw_label(self)
Expand source code
def draw_label(self):
    if self.hide:
        return f"{self.bl_label[:2]}V {self.base_data_name}"
    else:
        return self.bl_label
def draw_viewer_properties(self, layout)
Expand source code
def draw_viewer_properties(self, layout):
    col = layout.column(align=True)
    row = col.row(align=True)
    row.column().prop(self, 'is_active', toggle=True)

    self.draw_object_properties(row)  # hide, selectable, render

    col = layout.column(align=True)
    row = col.row(align=True)
    row.prop(self, "base_data_name", text="", icon='OUTLINER_OB_MESH')
    row.operator('node.sv_generate_random_object_name', text='', icon='FILE_REFRESH')

    row = col.row(align=True)
    row.scale_y = 2
    row.operator('node.sv_select_objects', text="Select")

    col.prop_search(self, 'collection', bpy.data, 'collections', text='', icon='GROUP')
def init_viewer(self)

Should be called from descendant class

Expand source code
def init_viewer(self):
    """Should be called from descendant class"""
    self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
    self.use_custom_color = True

    self.outputs.new('SvObjectSocket', "Objects")
def load_from_json(self, node_data: dict, import_version: float)

Manually serialization node properties Should be overridden in zis way: super().storage_get_data(storage); self.my_prop = storage['my_prop']

Expand source code
def load_from_json(self, node_data: dict, import_version: float):
    """
    Manually serialization node properties
    Should be overridden in zis way: super().storage_get_data(storage); self.my_prop = storage['my_prop']
    """
    if import_version <= 0.08:
        collection_name = node_data['collection']
        if collection_name:
            collection = (bpy.data.collections.get(collection_name))
            if not collection:
                collection = bpy.data.collections.new(collection_name)
                bpy.context.collection.children.link(collection)
            self.collection = collection
def show_viewport(self, is_show: bool)

It should be called by node tree to show/hide objects

Expand source code
def show_viewport(self, is_show: bool):
    """It should be called by node tree to show/hide objects"""
    if not self.show_objects:
        # just ignore request
        pass
    else:
        self.show_objects_update(None, is_show)
def sv_copy(self, other)

Regenerate object names, and clean data Use super().sv_copy(other) to override this method

Expand source code
def sv_copy(self, other):
    """
    Regenerate object names, and clean data
    Use super().sv_copy(other) to override this method
    """
    self.base_data_name = bpy.context.scene.sv_object_names.get_available_name()
    # object and mesh lists should be clear other wise two nodes would have links to the same objects
    self.object_data.clear()

Inherited members