diff --git a/data_structure.py b/data_structure.py index 7c7c42b847a33e4195d89301070c8ff90513734d..5768a115692c08b6ed1460c0da7855fe403aaf74 100755 --- a/data_structure.py +++ b/data_structure.py @@ -248,7 +248,7 @@ def levelsOflist(lst): return level return 0 -def get_data_nesting_level(data, data_types=(float, int, np.float64)): +def get_data_nesting_level(data, data_types=(float, int, np.float64, str)): """ data: number, or list of numbers, or list of lists, etc. data_types: list or tuple of types. @@ -284,7 +284,7 @@ def get_data_nesting_level(data, data_types=(float, int, np.float64)): return helper(data, 0) -def ensure_nesting_level(data, target_level, data_types=(float, int, np.float64)): +def ensure_nesting_level(data, target_level, data_types=(float, int, np.float64, str)): """ data: number, or list of numbers, or list of lists, etc. target_level: data nesting level required for further processing. diff --git a/index.md b/index.md index b8c38ebfeaa4578d7a86da3961c9ab01a0bdc4bf..d1bb3dc19a9c1d873e234cd27a27b7c7450231b3 100644 --- a/index.md +++ b/index.md @@ -219,11 +219,16 @@ ## Viz ViewerNode2 - SvBmeshViewerNodeMK2 IndexViewerNode SvMetaballOutNode SvTextureViewerNode Sv3DviewPropsNode + --- + SvCurveViewerNodeMK2 + SvPolylineViewerNodeMK2 + SvBmeshViewerNodeMK3 + SvTypeViewerNodeMK1 + SvSkinViewerNodeMK2 ## Text ViewerNodeTextMK3 @@ -254,7 +259,6 @@ SvObjInLite SvObjEdit SvFrameInfoNodeMK2 - SvEmptyOutNode SvInstancerNode SvDupliInstancesMK4 @@ -290,11 +294,6 @@ SvSpiralNode ## Alpha Nodes - SvCurveViewerNode - SvCurveViewerNodeAlt - SvPolylineViewerNodeMK1 - SvTypeViewerNode - SvSkinViewerNodeMK1b SvMatrixViewer --- SvBMVertsNode diff --git a/nodes/viz/metaball_out.py b/nodes/viz/metaball_out.py index 6d19b9109253ea001f18ac9d07d63cc02ec0e6f7..6588bcbf9a4165d1d0edde04287267b54ac95c5a 100644 --- a/nodes/viz/metaball_out.py +++ b/nodes/viz/metaball_out.py @@ -23,62 +23,15 @@ from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import SvGetSocketAnyType, node_id, Matrix_generate, match_long_repeat, updateNode from sverchok.utils.sv_viewer_utils import greek_alphabet +from sverchok.utils.sv_obj_helper import SvObjHelper -class SvMetaballOperator(bpy.types.Operator): - - bl_idname = "node.sv_callback_metaball" - bl_label = "Sverchok metaball general callback" - bl_options = {'REGISTER', 'UNDO'} - - fn_name = StringProperty(default='') - - def hide_unhide(self, context, type_op): - node = context.node - metaball = node.find_metaball() - - if type_op in {'hide', 'hide_render', 'hide_select'}: - op_value = getattr(node, type_op) - setattr(metaball, type_op, op_value) - setattr(node, type_op, not op_value) - - elif type_op == 'mesh_select': - metaball.select = node.select_state - node.select_state = not node.select_state - - def execute(self, context): - self.hide_unhide(context, self.fn_name) - return {'FINISHED'} - - -class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): +class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): '''Create Blender's metaball object''' bl_idname = 'SvMetaballOutNode' bl_label = 'Metaball' bl_icon = 'META_BALL' - def rename_metaball(self, context): - meta = self.find_metaball() - if meta: - meta.name = self.metaball_ref_name - self.label = meta.name - - n_id = StringProperty(default='') - metaball_ref_name = StringProperty(default='') - - activate = BoolProperty( - name='Activate', - default=True, - description='When enabled this will process incoming data', - update=updateNode) - - hide = BoolProperty(default=True) - hide_render = BoolProperty(default=True) - hide_select = BoolProperty(default=True) - select_state = BoolProperty(default=False) - - meta_name = StringProperty(default='SvMetaBall', name="Base name", - description="Base name of metaball", - update=rename_metaball) + data_kind = StringProperty(default='META') meta_types = [ ("BALL", "Ball", "Ball", "META_BALL", 1), @@ -94,8 +47,6 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): description = "Meta object type", items = meta_types, update=updateNode) - material = StringProperty(name="Material", default='', update=updateNode) - radius = FloatProperty( name='Radius', description='Metaball radius', @@ -121,49 +72,30 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): description='Influence of meta elements', default=0.6, min=0.0, max=5.0, update=updateNode) + def get_metaball_name(self, idx): + if idx == 1: + return self.basedata_name + else: + return self.basedata_name + '.' + str("%04d" % idx) + def create_metaball(self): - n_id = node_id(self) scene = bpy.context.scene objects = bpy.data.objects - metaball_data = bpy.data.metaballs.new("MetaBall") - metaball_object = bpy.data.objects.new(self.meta_name, metaball_data) - scene.objects.link(metaball_object) - scene.update() - - metaball_object["SVERCHOK_REF"] = n_id - self.metaball_ref_name = metaball_object.name + object_name = self.get_metaball_name(1) + metaball_data = bpy.data.metaballs.new(object_name) + metaball_object = self.get_or_create_object(object_name, 1, metaball_data) return metaball_object def find_metaball(self): - n_id = node_id(self) - - def check_metaball(obj): - """ Check that it is the correct metaball """ - if obj.type == 'META': - return "SVERCHOK_REF" in obj and obj["SVERCHOK_REF"] == n_id - return False - - objects = bpy.data.objects - if self.metaball_ref_name in objects: - obj = objects[self.metaball_ref_name] - if check_metaball(obj): - return obj - for obj in objects: - if check_metaball(obj): - self.metaball_ref_name = obj.name - return obj - return None - - def get_next_name(self): - gai = bpy.context.scene.SvGreekAlphabet_index - bpy.context.scene.SvGreekAlphabet_index += 1 - return 'Meta_' + greek_alphabet[gai] + children = self.get_children() + if children: + return children[0] + else: + return None def sv_init(self, context): - self.meta_name = self.get_next_name() - self.create_metaball() self.inputs.new('StringsSocket', 'Types').prop_name = "meta_type" self.inputs.new('MatrixSocket', 'Origins') self.inputs.new('StringsSocket', "Radius").prop_name = "radius" @@ -172,47 +104,16 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): self.outputs.new('SvObjectSocket', "Objects") def draw_buttons(self, context, layout): - view_icon = 'BLENDER' if self.activate else 'ERROR' - show_hide = 'node.sv_callback_metaball' - - def icons(TYPE): - NAMED_ICON = { - 'hide': 'RESTRICT_VIEW', - 'hide_render': 'RESTRICT_RENDER', - 'hide_select': 'RESTRICT_SELECT'}.get(TYPE) - if not NAMED_ICON: - return 'WARNING' - return NAMED_ICON + ['_ON', '_OFF'][getattr(self, TYPE)] - - row = layout.row(align=True) - row.column().prop(self, "activate", text="UPD", toggle=True, icon=view_icon) - row.separator() - - row.operator(show_hide, text='', icon=icons('hide')).fn_name = 'hide' - row.operator(show_hide, text='', icon=icons('hide_select')).fn_name = 'hide_select' - row.operator(show_hide, text='', icon=icons('hide_render')).fn_name = 'hide_render' - - col = layout.column(align=True) - col.prop(self, "meta_name", text='', icon='OUTLINER_OB_META') - col.operator(show_hide, text='Select Toggle').fn_name = 'mesh_select' - - layout.prop_search( - self, 'material', bpy.data, 'materials', - text='', - icon='MATERIAL_DATA') + self.draw_live_and_outliner(context, layout) + self.draw_object_buttons(context, layout) layout.prop(self, "threshold") def draw_buttons_ext(self, context, layout): self.draw_buttons(context, layout) + self.draw_ext_object_buttons(context, layout) layout.prop(self, "view_resolution") layout.prop(self, "render_resolution") - def copy(self, node): - self.n_id = '' - self.meta_name = self.get_next_name() - meta = self.create_metaball() - self.label = meta.name - def update_material(self): if bpy.data.materials.get(self.material): metaball_object = self.find_metaball() @@ -231,8 +132,6 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): metaball_object = self.create_metaball() self.debug("Created new metaball") - self.update_material() - metaball_object.data.resolution = self.view_resolution metaball_object.data.render_resolution = self.render_resolution metaball_object.data.threshold = self.threshold @@ -278,6 +177,8 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): for item in items: element = metaball_object.data.elements.new() setup_element(element, item) + + self.set_corresponding_materials() if 'Objects' in self.outputs: self.outputs['Objects'].sv_set([metaball_object]) @@ -285,10 +186,7 @@ class SvMetaballOutNode(bpy.types.Node, SverchCustomTreeNode): def register(): bpy.utils.register_class(SvMetaballOutNode) - bpy.utils.register_class(SvMetaballOperator) - def unregister(): - bpy.utils.unregister_class(SvMetaballOperator) bpy.utils.unregister_class(SvMetaballOutNode) diff --git a/nodes/viz/viewer_bmesh_mk3.py b/nodes/viz/viewer_bmesh_mk3.py new file mode 100644 index 0000000000000000000000000000000000000000..fa79e0eaffffffa17d16617a813725d1606ecdd1 --- /dev/null +++ b/nodes/viz/viewer_bmesh_mk3.py @@ -0,0 +1,343 @@ +# ##### 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 ##### + +# MK2 + +import itertools + +import bpy +from bpy.props import BoolProperty, StringProperty, BoolVectorProperty +from mathutils import Matrix, Vector + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import dataCorrect, fullList, updateNode +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata +from sverchok.utils.sv_viewer_utils import ( + matrix_sanitizer, + natural_plus_one, + greek_alphabet +) +from sverchok.utils.sv_obj_helper import SvObjHelper, CALLBACK_OP, get_random_init_v2 + + +def default_mesh(name): + verts, faces = [(1, 1, -1), (1, -1, -1), (-1, -1, -1)], [(0, 1, 2)] + mesh_data = bpy.data.meshes.new(name) + mesh_data.from_pydata(verts, [], faces) + mesh_data.update() + return mesh_data + + +def make_bmesh_geometry(node, obj_index, context, verts, *topology): + scene = context.scene + meshes = bpy.data.meshes + objects = bpy.data.objects + + edges, faces, matrix = topology + name = node.basedata_name + '.' + str("%04d" % obj_index) + + if name in objects: + sv_object = objects[name] + else: + temp_mesh = default_mesh(name) + sv_object = objects.new(name, temp_mesh) + scene.objects.link(sv_object) + + # book-keeping via ID-props!? even this is can be broken by renames + sv_object['idx'] = obj_index + sv_object['madeby'] = node.name + sv_object['basedata_name'] = node.basedata_name + + mesh = sv_object.data + current_count = len(mesh.vertices) + propose_count = len(verts) + difference = (propose_count - current_count) + + ''' With this mode you make a massive assumption about the + constant state of geometry. Assumes the count of verts + edges/faces stays the same, and only updates the locations + + node.fixed_verts is not suitable for initial object creation + but if over time you find that the only change is going to be + vertices, this mode can be switched to to increase efficiency + ''' + if node.fixed_verts and difference == 0: + f_v = list(itertools.chain.from_iterable(verts)) + mesh.vertices.foreach_set('co', f_v) + mesh.update() + else: + + ''' get bmesh, write bmesh to obj, free bmesh''' + bm = bmesh_from_pydata(verts, edges, faces, normal_update=node.calc_normals) + bm.to_mesh(sv_object.data) + bm.free() + + sv_object.hide_select = False + + if matrix: + matrix = matrix_sanitizer(matrix) + if node.extended_matrix: + sv_object.data.transform(matrix) + sv_object.matrix_local = Matrix.Identity(4) + else: + sv_object.matrix_local = matrix + else: + sv_object.matrix_local = Matrix.Identity(4) + + +def make_bmesh_geometry_merged(node, obj_index, context, yielder_object): + scene = context.scene + meshes = bpy.data.meshes + objects = bpy.data.objects + name = node.basedata_name + '.' + str("%04d" % obj_index) + + if name in objects: + sv_object = objects[name] + else: + temp_mesh = default_mesh(name) + sv_object = objects.new(name, temp_mesh) + scene.objects.link(sv_object) + + # book-keeping via ID-props! + sv_object['idx'] = obj_index + sv_object['madeby'] = node.name + sv_object['basedata_name'] = node.basedata_name + + vert_count = 0 + big_verts = [] + big_edges = [] + big_faces = [] + + for result in yielder_object: + + verts, topology = result + edges, faces, matrix = topology + + if matrix: + matrix = matrix_sanitizer(matrix) + verts = [matrix * Vector(v) for v in verts] + + big_verts.extend(verts) + big_edges.extend([[a + vert_count, b + vert_count] for a, b in edges]) + big_faces.extend([[j + vert_count for j in f] for f in faces]) + + vert_count += len(verts) + + + if node.fixed_verts and len(sv_object.data.vertices) == len(big_verts): + mesh = sv_object.data + f_v = list(itertools.chain.from_iterable(big_verts)) + mesh.vertices.foreach_set('co', f_v) + mesh.update() + else: + ''' get bmesh, write bmesh to obj, free bmesh''' + bm = bmesh_from_pydata(big_verts, big_edges, big_faces, normal_update=node.calc_normals) + bm.to_mesh(sv_object.data) + bm.free() + + sv_object.hide_select = False + sv_object.matrix_local = Matrix.Identity(4) + + +class SvBmeshViewerNodeMK3(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + """ bmv Generate Live geom """ + + bl_idname = 'SvBmeshViewerNodeMK3' + bl_label = 'Viewer BMesh 3' + bl_icon = 'OUTLINER_OB_MESH' + + grouping = BoolProperty(default=False) + merge = BoolProperty(default=False, update=updateNode) + + calc_normals = BoolProperty(default=False, update=updateNode) + + fixed_verts = BoolProperty( + default=False, + description="Use only with unchanging topology") + + autosmooth = BoolProperty( + default=False, + update=updateNode, + description="This auto sets all faces to smooth shade") + + extended_matrix = BoolProperty( + default=False, + description='Allows mesh.transform(matrix) operation, quite fast!') + + def sv_init(self, context): + self.sv_init_helper_basedata_name() + + self.inputs.new('VerticesSocket', 'vertices', 'vertices') + self.inputs.new('StringsSocket', 'edges', 'edges') + self.inputs.new('StringsSocket', 'faces', 'faces') + self.inputs.new('MatrixSocket', 'matrix', 'matrix') + + self.outputs.new('SvObjectSocket', "Objects") + + def draw_buttons(self, context, layout): + self.draw_live_and_outliner(context, layout) + + # additional UI options. + col = layout.column(align=True) + row = col.row(align=True) + row.prop(self, "grouping", text="Group", toggle=True) + row.prop(self, "merge", text="Merge", toggle=True) + + self.draw_object_buttons(context, layout) + + + def draw_buttons_ext(self, context, layout): + # self.draw_buttons(context, layout) + self.draw_ext_object_buttons(context, layout) + + col = layout.column(align=True) + box = col.box() + if box: + box.label(text="Beta options") + box.prop(self, "extended_matrix", text="Extended Matrix") + box.prop(self, "fixed_verts", text="Fixed vert count") + box.prop(self, 'autosmooth', text='smooth shade') + box.prop(self, 'calc_normals', text='calculate normals') + box.prop(self, 'layer_choice', text='layer') + + def get_geometry_from_sockets(self): + + def get(socket_name): + data = self.inputs[socket_name].sv_get(default=[]) # , deepcopy=False) # can't because of fulllist. + return dataCorrect(data) + + mverts = get('vertices') + medges = get('edges') + mfaces = get('faces') + mmtrix = get('matrix') + return mverts, medges, mfaces, mmtrix + + def get_structure(self, stype, sindex): + if not stype: + return [] + + try: + j = stype[sindex] + except IndexError: + j = [] + finally: + return j + + def process(self): + + if not self.activate: + return + + mverts, *mrest = self.get_geometry_from_sockets() + + def get_edges_faces_matrices(obj_index): + for geom in mrest: + yield self.get_structure(geom, obj_index) + + # extend all non empty lists to longest of mverts or *mrest + maxlen = max(len(mverts), *(map(len, mrest))) + fullList(mverts, maxlen) + for idx in range(3): + if mrest[idx]: + fullList(mrest[idx], maxlen) + + if self.merge: + obj_index = 0 + + def keep_yielding(): + # this will yield all in one go. + for idx, Verts in enumerate(mverts): + if not Verts: + continue + + data = get_edges_faces_matrices(idx) + yield (Verts, data) + + yielder_object = keep_yielding() + make_bmesh_geometry_merged(self, obj_index, bpy.context, yielder_object) + + else: + for obj_index, Verts in enumerate(mverts): + if not Verts: + continue + + data = get_edges_faces_matrices(obj_index) + make_bmesh_geometry(self, obj_index, bpy.context, Verts, *data) + + last_index = (len(mverts) - 1) if not self.merge else 0 + self.remove_non_updated_objects(last_index) + + objs = self.get_children() + + if self.grouping: + self.to_group(objs) + + # truthy if self.material is in .materials + if bpy.data.materials.get(self.material): + self.set_corresponding_materials() + + if self.autosmooth: + self.set_autosmooth(objs) + + if self.outputs[0].is_linked: + self.outputs[0].sv_set(objs) + + + def set_autosmooth(self, objs): + for obj in objs: + mesh = obj.data + smooth_states = [True] * len(mesh.polygons) + mesh.polygons.foreach_set('use_smooth', smooth_states) + mesh.update() + + + def update_socket(self, context): + self.update() + + + def add_material(self): + + mat = bpy.data.materials.new('sv_material') + mat.use_nodes = True + mat.use_fake_user = True + + nodes = mat.node_tree.nodes + self.material = mat.name + + if bpy.context.scene.render.engine == 'CYCLES': + # add attr node to the left of diffuse BSDF + connect it + diffuse_node = nodes['Diffuse BSDF'] + attr_node = nodes.new('ShaderNodeAttribute') + attr_node.location = (-170, 300) + attr_node.attribute_name = 'SvCol' + + links = mat.node_tree.links + links.new(attr_node.outputs[0], diffuse_node.inputs[0]) + + +def register(): + bpy.utils.register_class(SvBmeshViewerNodeMK3) + + + +def unregister(): + bpy.utils.unregister_class(SvBmeshViewerNodeMK3) + + +# if __name__ == '__main__': +# register() diff --git a/nodes/viz/viewer_curves_mk1.py b/nodes/viz/viewer_curves_mk1.py new file mode 100644 index 0000000000000000000000000000000000000000..4628238f3f9d2c614081b23c677466f31666a93c --- /dev/null +++ b/nodes/viz/viewer_curves_mk1.py @@ -0,0 +1,362 @@ +# ##### 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 itertools + +import bpy +from bpy.props import (BoolProperty, StringProperty, FloatProperty, IntProperty) + +from mathutils import Vector + +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import ( + dataCorrect, + fullList, + updateNode) + +from sverchok.utils.sv_viewer_utils import matrix_sanitizer +from sverchok.utils.sv_obj_helper import SvObjHelper + + +def tuple_to_enumdata(*iterable): + return [(k, k, '', i) for i, k in enumerate(iterable)] + +dimension_modes = tuple_to_enumdata("3D", "2D") +fill_modes_2d = tuple_to_enumdata('NONE', 'BACK', 'FRONT', 'BOTH') +fill_modes_3d = tuple_to_enumdata('HALF', 'FRONT', 'BACK', 'FULL') +mode_options = tuple_to_enumdata("Merge", "Duplicate", "Unique") + +def set_curve_props(node, cu): + cu.bevel_depth = node.depth + cu.bevel_resolution = node.resolution + cu.dimensions = node.curve_dimensions + cu.fill_mode = node.set_fill_mode() + + +# -- DUPLICATES -- +def make_duplicates_live_curve(node, object_index, verts, edges, matrices): + curves = bpy.data.curves + objects = bpy.data.objects + scene = bpy.context.scene + + # if curve data exists, pick it up else make new curve + # this curve is then used as a data.curve for all objects generated by this node.. + # objects still have slow creation time, but storage is very good due to + # reuse of curve data and applying matrices to objects instead. + + # only make the curve data. + curve_name = node.basedata_name + '.cloner.' + str("%04d" % obj_index) + cu = curves.get(curve_name) + if not cu: + cu = curves.new(name=curve_name, type='CURVE') + + # wipe! + if cu.splines: + cu.splines.clear() + + set_curve_props(node, cu) + + # rebuild! + for edge in edges: + v0, v1 = verts[edge[0]], verts[edge[1]] + full_flat = [v0[0], v0[1], v0[2], 0.0, v1[0], v1[1], v1[2], 0.0] + + # each spline has a default first coordinate but we need two. + segment = cu.splines.new('POLY') + segment.points.add(1) + segment.points.foreach_set('co', full_flat) + + # if object reference exists, pick it up else make a new one + # assign the same curve to all Objects. + for obj_index, matrix in enumerate(matrices): + m = matrix_sanitizer(matrix) + + # get object to clone the Curve data into. + obj_name = node.basedata_name + '.' + str("%04d" % obj_index) + + obj = objects.get(obj_name) + if not obj: + obj = objects.new(obj_name, cu) + scene.objects.link(obj) + + # make sure this object is known by its origin + obj['basedata_name'] = node.basedata_name + obj['madeby'] = node.name + obj['idx'] = obj_index + obj.matrix_local = m + + +# -- MERGE -- +def make_merged_live_curve(node, obj_index, verts, edges, matrices): + + obj, cu = node.get_obj_curve(obj_index) + set_curve_props(node, cu) + + for matrix in matrices: + m = matrix_sanitizer(matrix) + + # and rebuild + for edge in edges: + v0, v1 = m * Vector(verts[edge[0]]), m * Vector(verts[edge[1]]) + + full_flat = [v0[0], v0[1], v0[2], 0.0, v1[0], v1[1], v1[2], 0.0] + + # each spline has a default first coordinate but we need two. + segment = cu.splines.new('POLY') + segment.points.add(1) + segment.points.foreach_set('co', full_flat) + + +# -- UNIQUE -- +def live_curve(obj_index, verts, edges, matrix, node): + + obj, cu = node.get_obj_curve(obj_index) + set_curve_props(node, cu) + + # and rebuild + + if node.curve_dimensions == '3D': + + for edge in edges: + v0, v1 = verts[edge[0]], verts[edge[1]] + full_flat = [v0[0], v0[1], v0[2], 0.0, v1[0], v1[1], v1[2], 0.0] + + # each spline has a default first coordinate but we need two. + segment = cu.splines.new('POLY') + segment.points.add(1) + segment.points.foreach_set('co', full_flat) + else: + + for v_obj, e_obj in zip(verts, edges): + segment = cu.splines.new('POLY') + #v1, v2, v3 = v_obj[e_obj[0][0]] + #points = [v1, v2, v3, 0.0] + points = [] + prev = [] + if len(v_obj) == len(e_obj): + e_obj.pop(-1) + e_obj.sort() + segment.points.add(len(e_obj)) + for edge in e_obj: + for e in edge: + if e not in prev: + prev.append(e) + v1 = v_obj[e] + points.extend([v1[0], v1[1], v1[2], 0.0]) + segment.points.foreach_set('co', points) + segment.use_cyclic_u = True + + return obj + + +def make_curve_geometry(node, context, obj_index, verts, *topology): + edges, matrix = topology + + sv_object = live_curve(obj_index, verts, edges, matrix, node) + sv_object.hide_select = False + node.push_custom_matrix_if_present(sv_object, matrix) + + +class SvCurveViewerNodeMK2(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + """ + Triggers: CV object curves + Tooltip: Advanced 2d/3d curve outputting into scene + """ + + bl_idname = 'SvCurveViewerNodeMK2' + bl_label = 'Curve Viewer mk2' + bl_icon = 'MOD_CURVE' + + selected_mode = bpy.props.EnumProperty( + items=mode_options, + description="merge or use duplicates", + default="Unique", + update=updateNode) + + curve_dimensions = bpy.props.EnumProperty( + items=dimension_modes, update=updateNode, + description="2D or 3D curves", default="3D") + + fill_2D = bpy.props.EnumProperty( + items=fill_modes_2d, description="offers fill more for 2d Curve data", + default=fill_modes_2d[2][0], update=updateNode) + + fill_3D = bpy.props.EnumProperty( + items=fill_modes_3d, description="offers fill more for 3d Curve data", + default=fill_modes_3d[3][0], update=updateNode) + + data_kind = StringProperty(default='CURVE') + grouping = BoolProperty(default=False) + + depth = FloatProperty(min=0.0, default=0.2, update=updateNode) + resolution = IntProperty(min=0, default=3, update=updateNode) + + def sv_init(self, context): + self.sv_init_helper_basedata_name() + self.inputs.new('VerticesSocket', 'vertices') + self.inputs.new('StringsSocket', 'edges') + self.inputs.new('MatrixSocket', 'matrix') + + def draw_buttons(self, context, layout): + + self.draw_live_and_outliner(context, layout) + + col = layout.column(align=True) + row = col.row(align=True) + row.prop(self, "grouping", text="Group", toggle=True) + row.separator() + row.prop(self, "selected_mode", expand=True) + + self.draw_object_buttons(context, layout) + + layout.row().prop(self, 'curve_dimensions', expand=True) + col = layout.column(align=True) + col.prop(self, 'depth', text='depth radius') + col.prop(self, 'resolution', text='surface resolution') + + col.separator() + row = col.row() + row.prop(self, 'fill_' + self.curve_dimensions, expand=True) + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + self.draw_ext_object_buttons(context, layout) + + def set_fill_mode(self): + return getattr(self, "fill_" + self.curve_dimensions) + + def remove_cloner_curve(self): + # opportunity to remove the .cloner. + if self.selected_mode == 'Duplicate': + curve_name = self.basedata_name + '.cloner.' + str("%04d" % obj_index) + cu = bpy.data.curves.get(curve_name) + if cu: + bpy.data.curves.remove(cu) + + def output_dupe_or_merged_geometry(self, TYPE, mverts, *mrest): + ''' + this should probably be shared in the main process function but + for prototyping convenience and logistics i will keep this separate + for the time-being. Upon further consideration, i might suggest keeping this + entirely separate to keep function length shorter. + ''' + verts = mverts[0] + edges = mrest[0][0] + matrices = mrest[1] + + # object index = 0 because these modes will output only one object. + if TYPE == 'Merge': + make_merged_live_curve(self, 0, verts, edges, matrices) + elif TYPE == 'Duplicate': + make_duplicates_live_curve(self, 0, verts, edges, matrices) + + def get_geometry_from_sockets(self): + + def get(socket_name): + data = self.inputs[socket_name].sv_get(default=[]) + return dataCorrect(data) + + mverts = get('vertices') + medges = get('edges') + mmtrix = get('matrix') + return mverts, medges, mmtrix + + def get_structure(self, stype, sindex): + if not stype: + return [] + + try: + j = stype[sindex] + except IndexError: + j = [] + finally: + return j + + def process(self): + + if not self.activate: + return + + if not (self.inputs['vertices'].is_linked and self.inputs['edges'].is_linked): + # possible remove any potential existing geometry here too + return + + # maybe if edges is not linked that the vertices can be assumed to be + # sequential and auto generated.. maybe... maybe. + + mverts, *mrest = self.get_geometry_from_sockets() + + mode = self.selected_mode + single_set = (len(mverts) == 1) and (len(mrest[-1]) > 1) + has_matrices = self.inputs['matrix'].is_linked + + if single_set and (mode in {'Merge', 'Duplicate'}) and has_matrices: + obj_index = 0 + self.output_dupe_or_merged_geometry(mode, mverts, *mrest) + + if mode == "Duplicate": + obj_index = len(mrest[1]) - 1 + + else: + + def get_edges_matrices(obj_index): + for geom in mrest: + yield self.get_structure(geom, obj_index) + + # extend all non empty lists to longest of mverts or *mrest + maxlen = max(len(mverts), *(map(len, mrest))) + fullList(mverts, maxlen) + for idx in range(2): + if mrest[idx]: + fullList(mrest[idx], maxlen) + + if self.curve_dimensions == '3D': + + for obj_index, Verts in enumerate(mverts): + if not Verts: + continue + + data = get_edges_matrices(obj_index) + make_curve_geometry(self, bpy.context, obj_index, Verts, *data) + + # we must be explicit + obj_index = len(mverts) - 1 + + else: + obj_index = 0 + make_curve_geometry(self, bpy.context, obj_index, mverts, *mrest) + + + + self.remove_non_updated_objects(obj_index) + objs = self.get_children() + + if self.grouping: + self.to_group(objs) + + self.set_corresponding_materials() + self.remove_cloner_curve() + + +def register(): + bpy.utils.register_class(SvCurveViewerNodeMK2) + + +def unregister(): + bpy.utils.unregister_class(SvCurveViewerNodeMK2) diff --git a/nodes/viz/viewer_polyline_mk2.py b/nodes/viz/viewer_polyline_mk2.py new file mode 100644 index 0000000000000000000000000000000000000000..30aab6f9f9d5a3ece70f22a8e939eeddb485ee04 --- /dev/null +++ b/nodes/viz/viewer_polyline_mk2.py @@ -0,0 +1,243 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from bpy.props import (BoolProperty, StringProperty, FloatProperty, IntProperty) + +from sverchok.utils.sv_obj_helper import SvObjHelper +from sverchok.utils.geom import multiply_vectors +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import dataCorrect, fullList, updateNode + + +def set_bevel_object(node, cu, obj_index): + # use bevel object if provided + bevel_objs = node.inputs['bevel object'].sv_get(default=[]) + if bevel_objs: + obj_ref = bevel_objs[obj_index] if obj_index < len(bevel_objs) else bevel_objs[-1] + if obj_ref.type == 'CURVE': + cu.bevel_object = obj_ref + cu.use_fill_caps = node.caps + else: + cu.bevel_object = None + cu.use_fill_caps = False + + +# -- POLYLINE -- +def live_curve(obj_index, node, verts, radii, twist): + + obj, cu = node.get_obj_curve(obj_index) + + obj.show_wire = node.show_wire + cu.bevel_depth = node.depth + cu.bevel_resolution = node.resolution + cu.dimensions = node.curve_dimensions + if cu.dimensions == '2D': + cu.fill_mode = 'FRONT' + else: + cu.fill_mode = 'FULL' + + set_bevel_object(node, cu, obj_index) + + kind = ["POLY", "NURBS"][bool(node.bspline)] + + if node.selected_mode == 'Multi': + verts = [verts] + radii = [radii] + twist = [twist] + + for idx, (VERTS, RADII, TWIST) in enumerate(zip(verts, radii, twist)): + + full_flat = [] + for v in VERTS: + full_flat.extend([v[0], v[1], v[2], 1.0]) + + polyline = cu.splines.new(kind) + polyline.points.add(len(VERTS)-1) + polyline.points.foreach_set('co', full_flat) + + if RADII: + if len(VERTS) < len(RADII): + RADII = RADII[:len(VERTS)] + elif len(VERTS) > len(RADII): + fullList(RADII, len(VERTS)) + polyline.points.foreach_set('radius', RADII) + + if TWIST: + if len(VERTS) < len(TWIST): + TWIST = TWIST[:len(VERTS)] + elif len(VERTS) > len(TWIST): + fullList(TWIST, len(VERTS)) + polyline.points.foreach_set('tilt', TWIST) + + if node.close: + cu.splines[idx].use_cyclic_u = True + + if node.bspline: + polyline.order_u = len(polyline.points)-1 + + polyline.use_smooth = node.use_smooth + + return obj + + + +def make_curve_geometry(obj_index, node, verts, matrix, radii, twist): + sv_object = live_curve(obj_index, node, verts, radii, twist) + sv_object.hide_select = False + node.push_custom_matrix_if_present(sv_object, matrix) + return sv_object + + +class SvPolylineViewerNodeMK2(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + + bl_idname = 'SvPolylineViewerNodeMK2' + bl_label = 'Polyline Viewer MK2' + bl_icon = 'MOD_CURVE' + + mode_options = [(k, k, '', i) for i, k in enumerate(["Multi", "Single"])] + selected_mode = bpy.props.EnumProperty( + items=mode_options, + description="offers joined of unique curves", + default="Multi", update=updateNode + ) + + dimension_modes = [(k, k, '', i) for i, k in enumerate(["3D", "2D"])] + + curve_dimensions = bpy.props.EnumProperty( + items=dimension_modes, update=updateNode, + description="2D or 3D curves", default="3D" + ) + + depth = FloatProperty(min=0.0, default=0.2, update=updateNode) + resolution = IntProperty(min=0, default=3, update=updateNode) + bspline = BoolProperty(default=False, update=updateNode) + close = BoolProperty(default=False, update=updateNode) + + radii = FloatProperty(min=0, default=0.2, update=updateNode) + twist = FloatProperty(default=0.0, update=updateNode) + caps = BoolProperty(update=updateNode) + + data_kind = StringProperty(default='CURVE') + + def sv_init(self, context): + self.sv_init_helper_basedata_name() + + self.inputs.new('VerticesSocket', 'vertices') + self.inputs.new('MatrixSocket', 'matrix') + self.inputs.new('StringsSocket', 'radii').prop_name = 'radii' + self.inputs.new('StringsSocket', 'twist').prop_name = 'twist' + self.inputs.new('SvObjectSocket', 'bevel object') + self.outputs.new('SvObjectSocket', "object") + + + def draw_buttons(self, context, layout): + + self.draw_live_and_outliner(context, layout) + self.draw_object_buttons(context, layout) + + layout.row().prop(self, 'curve_dimensions', expand=True) + + col = layout.column() + if self.curve_dimensions == '3D': + r1 = col.row(align=True) + r1.prop(self, 'depth', text='radius') + r1.prop(self, 'resolution', text='subdiv') + row = col.row(align=True) + row.prop(self, 'bspline', text='bspline', toggle=True) + row.prop(self, 'close', text='close', toggle=True) + if self.inputs['bevel object'].sv_get(default=[]): + row.prop(self, 'caps', text='caps', toggle=True) + row = col.row(align=True) + row.prop(self, 'show_wire', text='wire', toggle=True) + row.prop(self, 'use_smooth', text='smooth', toggle=True) + row.separator() + row.prop(self, 'selected_mode', expand=True) + + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + self.draw_ext_object_buttons(context, layout) + + + def get_geometry_from_sockets(self, has_matrices): + + def get(socket_name): + data = self.inputs[socket_name].sv_get(default=[]) + return dataCorrect(data) + + mverts = get('vertices') + mradii = self.inputs['radii'].sv_get(deepcopy=False) + mtwist = self.inputs['twist'].sv_get(deepcopy=False) + mmtrix = get('matrix') + + # extend all non empty lists to longest of these + maxlen = max(len(mverts), len(mmtrix)) + if has_matrices: + fullList(mverts, maxlen) + fullList(mmtrix, maxlen) + + if mradii: + fullList(mradii, maxlen) + if mtwist: + fullList(mtwist, maxlen) + + return mverts, mradii, mtwist, mmtrix + + + def process(self): + if not self.activate: + return + + if not self.inputs['vertices'].is_linked: + return + + has_matrices = self.inputs['matrix'].is_linked + mverts, mradii, mtwist, mmatrices = self.get_geometry_from_sockets(has_matrices) + + out_objects = [] + for obj_index, Verts in enumerate(mverts): + if not Verts: + continue + + matrix = mmatrices[obj_index] if has_matrices else [] + + if self.selected_mode == 'Multi': + curve_args = obj_index, self, Verts, matrix, mradii[obj_index], mtwist[obj_index] + new_obj = make_curve_geometry(*curve_args) + out_objects.append(new_obj) + else: + if matrix: + mverts = [multiply_vectors(*mv) for mv in zip(mmatrices, mverts)] + new_obj = make_curve_geometry(0, self, mverts, [], mradii, mtwist) + out_objects.append(new_obj) + break + + last_index = len(mverts) - 1 + self.remove_non_updated_objects(last_index) + self.set_corresponding_materials() + + self.outputs['object'].sv_set(out_objects) + + +def register(): + bpy.utils.register_class(SvPolylineViewerNodeMK2) + + +def unregister(): + bpy.utils.unregister_class(SvPolylineViewerNodeMK2) diff --git a/nodes/viz/viewer_skin_mk2.py b/nodes/viz/viewer_skin_mk2.py new file mode 100644 index 0000000000000000000000000000000000000000..6e21973c1073747e810a061f545b6f0ac8ef5e9e --- /dev/null +++ b/nodes/viz/viewer_skin_mk2.py @@ -0,0 +1,295 @@ +# ##### 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 itertools +from collections import defaultdict + +import bmesh +import bpy +from bpy.props import (BoolProperty, FloatProperty, IntProperty) + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat, fullList +from sverchok.utils.sv_obj_helper import SvObjHelper +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh + + +def process_mesh_into_features(skin_vertices, edge_keys, assume_unique=True): + """ + be under no illusion that this function is an attempt at an optimized tip/junction finder. + primarily it exists to test an assumption about foreach_set and obj.data.skin_verts edge keys. + + # this works for a edgenet mesh (polyline) that has no disjoint elements + # but I think all it does it set the last vertex as the root. + obj.data.skin_vertices[0].data.foreach_set('use_root', all_yes) + + disjoint elements each need 1 vertex set to root + """ + + # need a set of sorted keys + if not assume_unique: + try: + edge_keys = set(edge_keys) + except: + # this will be slower..but it should catch most input + edge_keys = set(tuple(sorted(key)) for key in edge_keys) + else: + edge_keys = set(edge_keys) + + # iterate and accumulate + ndA = defaultdict(set) + for key in edge_keys: + lowest, highest = key + ndA[lowest].add(highest) + ndA[highest].add(lowest) + + ndB = defaultdict(set) + ndC = {k: len(ndA[k]) for k in sorted(ndA.keys()) if len(ndA[k]) == 1 or len(ndA[k]) >=3} + for k, v in ndC.items(): + ndB[v].add(k) + + # in heavily branching input, there will be a lot of redundant use_root pushing. + for k in sorted(ndB.keys()): + for index in ndB[k]: + skin_vertices[index].use_root = True + + +def set_data_for_layer(bm, data, layer): + for i in range(len(bm.verts)): + bm.verts[i][data] = layer[i] or 0.1 + + +def shrink_geometry(bm, dist, layers): + vlayers = bm.verts.layers + made_layers = [] + for idx, layer in enumerate(layers): + first_element = layer[0] or 0.2 + if isinstance(first_element, float): + data_layer = vlayers.float.new('float_layer' + str(idx)) + + made_layers.append(data_layer) + bm.verts.ensure_lookup_table() + set_data_for_layer(bm, data_layer, layer) + + bmesh.ops.remove_doubles(bm, verts=bm.verts[:], dist=dist) + bm.verts.ensure_lookup_table() + + verts, edges, faces = pydata_from_bmesh(bm) + data_out = [verts, edges] + for layer_name in made_layers: + data_out.append([bm.verts[i][layer_name] for i in range(len(bm.verts))]) + + return data_out + + +def force_pydata(mesh, verts, edges): + + mesh.vertices.add(len(verts)) + f_v = list(itertools.chain.from_iterable(verts)) + mesh.vertices.foreach_set('co', f_v) + mesh.update() + + mesh.edges.add(len(edges)) + f_e = list(itertools.chain.from_iterable(edges)) + mesh.edges.foreach_set('vertices', f_e) + mesh.update(calc_edges=True) + + +def make_bmesh_geometry(node, context, geometry, idx, layers): + scene = context.scene + meshes = bpy.data.meshes + objects = bpy.data.objects + verts, edges, matrix, _, _ = geometry + name = node.basedata_name + '.' + str("%04d" % idx) + + if name in objects: + obj = objects.get(name) + node.clear_current_mesh(obj.data) + else: + # this is only executed once, upon the first run. + mesh = meshes.new(name) + obj = objects.new(name, mesh) + scene.objects.link(obj) + + # at this point the mesh is always fresh and empty + obj['idx'] = idx + obj['basedata_name'] = node.basedata_name + + data_layers = None + if node.distance_doubles > 0.0: + bm = bmesh_from_pydata(verts, edges, []) + verts, edges, _, *data_layers = shrink_geometry(bm, node.distance_doubles, layers) + + force_pydata(obj.data, verts, edges) + obj.update_tag(refresh={'OBJECT', 'DATA'}) + + if node.activate: + + if 'sv_skin' in obj.modifiers: + sk = obj.modifiers['sv_skin'] + obj.modifiers.remove(sk) + + if 'sv_subsurf' in obj.modifiers: + sd = obj.modifiers['sv_subsurf'] + obj.modifiers.remove(sd) + + _ = obj.modifiers.new(type='SKIN', name='sv_skin') + b = obj.modifiers.new(type='SUBSURF', name='sv_subsurf') + b.levels = node.levels + b.render_levels = node.render_levels + + node.push_custom_matrix_if_present(obj, matrix) + + return obj, data_layers + + +class SvSkinViewerNodeMK2(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + + bl_idname = 'SvSkinViewerNodeMK2' + bl_label = 'Skin Mesher mk2' + bl_icon = 'MOD_SKIN' + + general_radius_x = FloatProperty( + name='general_radius_x', + default=0.25, + description='value used to uniformly set the radii of skin vertices x', + min=0.0001, step=0.05, + update=updateNode) + + general_radius_y = FloatProperty( + name='general_radius_y', + default=0.25, + description='value used to uniformly set the radii of skin vertices y', + min=0.0001, step=0.05, + update=updateNode) + + levels = IntProperty(min=0, default=1, max=3, update=updateNode) + render_levels = IntProperty(min=0, default=1, max=3, update=updateNode) + + distance_doubles = FloatProperty( + default=0.0, min=0.0, + name='doubles distance', + description="removes coinciding verts, also aims to remove double radii data", + update=updateNode) + + use_root = BoolProperty(default=True, update=updateNode) + use_slow_root = BoolProperty(default=False, update=updateNode) + + + def sv_init(self, context): + self.sv_init_helper_basedata_name() + + self.inputs.new('VerticesSocket', 'vertices') + self.inputs.new('StringsSocket', 'edges') + self.inputs.new('MatrixSocket', 'matrix') + self.inputs.new('StringsSocket', 'radii_x').prop_name = "general_radius_x" + self.inputs.new('StringsSocket', 'radii_y').prop_name = "general_radius_y" + + + def draw_buttons(self, context, layout): + self.draw_live_and_outliner(context, layout) + self.draw_object_buttons(context, layout) + + r1 = layout.column(align=True) + r1.prop(self, 'levels', text="div View") + r1.prop(self, 'render_levels', text="div Render") + r1.prop(self, 'distance_doubles', text='doubles distance') + + + def draw_buttons_ext(self, context, layout): + k = layout.box() + r = k.row(align=True) + r.label("setting roots") + r = k.row(align=True) + r.prop(self, "use_root", text="mark all", toggle=True) + r.prop(self, "use_slow_root", text="mark some", toggle=True) + + + def get_geometry_from_sockets(self): + i = self.inputs + mverts = i['vertices'].sv_get(default=[]) + medges = i['edges'].sv_get(default=[]) + mmtrix = i['matrix'].sv_get(default=[None]) + mradiix = i['radii_x'].sv_get() + mradiiy = i['radii_y'].sv_get() + return mverts, medges, mmtrix, mradiix, mradiiy + + + def process(self): + if not self.activate: + return + + # only interested in the first + geometry_full = self.get_geometry_from_sockets() + + # pad all input to longest + maxlen = max(*(map(len, geometry_full))) + fullList(geometry_full[0], maxlen) + fullList(geometry_full[1], maxlen) + fullList(geometry_full[2], maxlen) + fullList(geometry_full[3], maxlen) + fullList(geometry_full[4], maxlen) + + catch_idx = 0 + for idx, (geometry) in enumerate(zip(*geometry_full)): + catch_idx = idx + self.unit_generator(idx, geometry) + + self.remove_non_updated_objects(catch_idx) + self.set_corresponding_materials() + + + def unit_generator(self, idx, geometry): + verts, _, _, radiix, radiiy = geometry + ntimes = len(verts) + radiix, _ = match_long_repeat([radiix, verts]) + radiiy, _ = match_long_repeat([radiiy, verts]) + + # assign radii after creation + obj, data_layers = make_bmesh_geometry(self, bpy.context, geometry, idx, [radiix, radiiy]) + + if data_layers and self.distance_doubles > 0.0: + # This sets the modified geometry with radius x and radius y. + f_r = list(itertools.chain(*zip(data_layers[0], data_layers[1]))) + f_r = [abs(f) for f in f_r] + obj.data.skin_vertices[0].data.foreach_set('radius', f_r) + all_yes = list(itertools.repeat(True, len(obj.data.vertices))) + obj.data.skin_vertices[0].data.foreach_set('use_root', all_yes) + + elif len(radiix) == len(verts): + f_r = list(itertools.chain(*zip(radiix, radiiy))) + f_r = [abs(f) for f in f_r] + obj.data.skin_vertices[0].data.foreach_set('radius', f_r) + + if self.use_root: + # set all to root + all_yes = list(itertools.repeat(True, ntimes)) + obj.data.skin_vertices[0].data.foreach_set('use_root', all_yes) + elif self.use_slow_root: + process_mesh_into_features(obj.data.skin_vertices[0].data, obj.data.edge_keys) + + + def flip_roots_or_junctions_only(self, data): + pass + + +def register(): + bpy.utils.register_class(SvSkinViewerNodeMK2) + +def unregister(): + bpy.utils.unregister_class(SvSkinViewerNodeMK2) diff --git a/nodes/viz/viewer_typography_mk1.py b/nodes/viz/viewer_typography_mk1.py new file mode 100644 index 0000000000000000000000000000000000000000..4c9dc5654c119a7b6a1965d07ffdbb66f4dd4d2e --- /dev/null +++ b/nodes/viz/viewer_typography_mk1.py @@ -0,0 +1,272 @@ +# ##### 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 ##### + +# pylint: disable=E1121 + + +import bpy +from bpy.props import ( + BoolProperty, + StringProperty, + BoolVectorProperty, + FloatProperty, + IntProperty +) + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import dataCorrect, fullList, updateNode +from sverchok.utils.sv_obj_helper import SvObjHelper, enum_from_list + +mode_options_x = enum_from_list('LEFT', 'CENTER', 'RIGHT', 'JUSTIFY', 'FLUSH') +mode_options_y = enum_from_list('TOP_BASELINE', 'TOP', 'CENTER', 'BOTTOM') + +def get_font(node): + fonts = bpy.data.fonts + default = fonts.get('Bfont') + return fonts.get(node.fontname, default) + +def font_set_props(f, node, txt): + + f.body = txt + f.size = node.fsize + f.font = get_font(node) + + f.space_character = node.space_character + f.space_word = node.space_word + f.space_line = node.space_line + f.offset_x = node.xoffset + f.offset_y = node.yoffset + f.offset = node.offset + f.extrude = node.extrude + f.bevel_depth = node.bevel_depth + f.bevel_resolution = node.bevel_resolution + + f.align_x = node.align_x + f.align_y = node.align_y + +def get_obj_and_fontcurve(context, name): + scene = context.scene + curves = bpy.data.curves + objects = bpy.data.objects + + # CURVES + if not name in curves: + f = curves.new(name, 'FONT') + else: + f = curves[name] + + # CONTAINER OBJECTS + if name in objects: + sv_object = objects[name] + else: + sv_object = objects.new(name, f) + scene.objects.link(sv_object) + + return sv_object, f + + +def make_text_object(node, idx, context, data): + txt, matrix = data + name = node.basedata_name + '.' + str("%04d" % idx) + + sv_object, f = get_obj_and_fontcurve(context, name) + font_set_props(f, node, txt) + sv_object['idx'] = idx + sv_object['madeby'] = node.name + sv_object['basedata_name'] = node.basemesh_name + sv_object.hide_select = False + node.push_custom_matrix_if_present(sv_object, matrix) + + +class SvFontFileImporterOpMK1(bpy.types.Operator): + + bl_idname = "node.sv_fontfile_importer_mk1" + bl_label = "sv FontFile Importer" + + filepath = StringProperty( + name="File Path", + description="Filepath used for importing the font file", + maxlen=1024, default="", subtype='FILE_PATH') + + def execute(self, context): + n = self.node + t = bpy.data.fonts.load(self.filepath) + n.fontname = t.name + return {'FINISHED'} + + def invoke(self, context, event): + self.node = context.node + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class SvTypeViewerNodeMK1(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + + bl_idname = 'SvTypeViewerNodeMK1' + bl_label = 'Typography Viewer mk1' + bl_icon = 'OUTLINER_OB_FONT' + + grouping = BoolProperty(default=False) + data_kind = StringProperty(name='data kind', default='FONT') + + show_options = BoolProperty(default=0) + fontname = StringProperty(default='', update=updateNode) + fsize = FloatProperty(default=1.0, update=updateNode) + + # space + space_character = FloatProperty(default=1.0, update=updateNode) + space_word = FloatProperty(default=1.0, update=updateNode) + space_line = FloatProperty(default=1.0, update=updateNode) + yoffset = FloatProperty(default=0.0, update=updateNode) + xoffset = FloatProperty(default=0.0, update=updateNode) + + # modifications + offset = FloatProperty(default=0.0, update=updateNode) + extrude = FloatProperty(default=0.0, update=updateNode) + + # bevel + bevel_depth = FloatProperty(default=0.0, update=updateNode) + bevel_resolution = IntProperty(default=0, update=updateNode) + + # orientation x | y + align_x = bpy.props.EnumProperty( + items=mode_options_x, description="Horizontal Alignment", + default="LEFT", update=updateNode) + + align_y = bpy.props.EnumProperty( + items=mode_options_y, description="Vertical Alignment", + default="TOP_BASELINE", update=updateNode) + + + def sv_init(self, context): + self.sv_init_helper_basedata_name() + self.inputs.new('StringsSocket', 'text') + self.inputs.new('MatrixSocket', 'matrix') + + def draw_buttons(self, context, layout): + self.draw_live_and_outliner(context, layout) + self.draw_object_buttons(context, layout) + + col = layout.column(align=True) + if col: + row = col.row(align=True) + row.prop(self, "grouping", text="Group", toggle=True) + + col = layout.column(align=True) + col.prop(self, 'fsize') + col.prop(self, 'show_options', toggle=True) + if self.show_options: + col.label('position') + row = col.row(align=True) + if row: + row.prop(self, 'xoffset', text='XOFF') + row.prop(self, 'yoffset', text='YOFF') + split = col.split() + col1 = split.column() + col1.prop(self, 'space_character', text='CH') + col1.prop(self, 'space_word', text='W') + col1.prop(self, 'space_line', text='L') + + col.label('modifications') + col.prop(self, 'offset') + col.prop(self, 'extrude') + col.label('bevel') + col.prop(self, 'bevel_depth') + col.prop(self, 'bevel_resolution') + + col.label("alignment") + row = col.row(align=True) + row.prop(self, 'align_x', text="") + row.prop(self, 'align_y', text="") + col.separator() + + + def draw_buttons_ext(self, context, layout): + shf = 'node.sv_fontfile_importer_mk1' + + self.draw_buttons(context, layout) + self.draw_ext_object_buttons(context, layout) + + col = layout.column(align=True) + row = col.row(align=True) + row.prop_search(self, 'fontname', bpy.data, 'fonts', text='', icon='FONT_DATA') + row.operator(shf, text='', icon='ZOOMIN') + + box = col.box() + if box: + box.label(text="Beta options") + box.prop(self, 'layer_choice', text='layer') + + row = layout.row() + row.prop(self, 'parent_to_empty', text='parented') + if self.parent_to_empty: + row.label(self.parent_name) + + def process(self): + + if (not self.activate) or (not self.inputs['text'].is_linked): + return + + # no autorepeat yet. + text = self.inputs['text'].sv_get(default=[['sv_text']])[0] + matrices = self.inputs['matrix'].sv_get(default=[[]]) + + if self.parent_to_empty: + mtname = 'Empty_' + self.basemesh_name + self.parent_name = mtname + scene = bpy.context.scene + if not mtname in bpy.data.objects: + empty = bpy.data.objects.new(mtname, None) + scene.objects.link(empty) + scene.update() + + last_index = 0 + for obj_index, txt_content in enumerate(text): + matrix = matrices[obj_index] + if isinstance(txt_content, list) and (len(txt_content) == 1): + txt_content = txt_content[0] + else: + txt_content = str(txt_content) + + make_text_object(self, obj_index, bpy.context, (txt_content, matrix)) + last_index = obj_index + + self.remove_non_updated_objects(last_index) + objs = self.get_children() + + if self.grouping: + self.to_group(objs) + + self.set_corresponding_materials() + + for obj in objs: + if self.parent_to_empty: + obj.parent = bpy.data.objects[mtname] + elif obj.parent: + obj.parent = None + + +def register(): + bpy.utils.register_class(SvTypeViewerNodeMK1) + bpy.utils.register_class(SvFontFileImporterOpMK1) + + +def unregister(): + bpy.utils.unregister_class(SvFontFileImporterOpMK1) + bpy.utils.unregister_class(SvTypeViewerNodeMK1) diff --git a/nodes/viz/empty_out.py b/old_nodes/empty_out.py similarity index 100% rename from nodes/viz/empty_out.py rename to old_nodes/empty_out.py diff --git a/nodes/viz/viewer_bmesh_mk2.py b/old_nodes/viewer_bmesh_mk2.py similarity index 100% rename from nodes/viz/viewer_bmesh_mk2.py rename to old_nodes/viewer_bmesh_mk2.py diff --git a/nodes/viz/viewer_curves.py b/old_nodes/viewer_curves.py similarity index 100% rename from nodes/viz/viewer_curves.py rename to old_nodes/viewer_curves.py diff --git a/nodes/viz/viewer_curves_2d.py b/old_nodes/viewer_curves_2d.py similarity index 100% rename from nodes/viz/viewer_curves_2d.py rename to old_nodes/viewer_curves_2d.py diff --git a/nodes/viz/viewer_polyline_mk1.py b/old_nodes/viewer_polyline_mk1.py similarity index 100% rename from nodes/viz/viewer_polyline_mk1.py rename to old_nodes/viewer_polyline_mk1.py diff --git a/nodes/viz/viewer_skin_mk1.py b/old_nodes/viewer_skin_mk1.py similarity index 100% rename from nodes/viz/viewer_skin_mk1.py rename to old_nodes/viewer_skin_mk1.py diff --git a/nodes/viz/viewer_typography.py b/old_nodes/viewer_typography.py similarity index 100% rename from nodes/viz/viewer_typography.py rename to old_nodes/viewer_typography.py diff --git a/utils/sv_obj_helper.py b/utils/sv_obj_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..cb562b9b8db74ebba312556fd305eb31f10bd29e --- /dev/null +++ b/utils/sv_obj_helper.py @@ -0,0 +1,385 @@ +# ##### 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 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) + + +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', 'object_hide_select', 'object_hide_render'] +CALLBACK_OP = 'node.sv_callback_svobjects_helper' + +def get_random_init_v2(): + objects = bpy.data.objects + + with_underscore = lambda obj: '_' in obj.name + names_with_underscores = list(filter(with_underscore, objects)) + + set_of_names_pre_underscores = set([n.name.split('_')[0] for n in names_with_underscores]) + if '' in set_of_names_pre_underscores: + set_of_names_pre_underscores.remove('') + + n = random.choice(greek_alphabet) + + # not picked yet. + if not n in set_of_names_pre_underscores: + return n + + # at this point the name was already picked, we don't want to overwrite + # existing obj/meshes and instead append digits onto the greek letter + # if Alpha is present already a new one will be Alpha2, Alpha3 etc.. + # (not Alpha002, or Alpha.002) + similar_names = [name for name in set_of_names_pre_underscores if n in name] + plus_one = natural_plus_one(similar_names) + return n + str(plus_one) + + +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 explicitely. + 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', '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 = 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_v2() + + 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 to_group(self, objs): + groups = bpy.data.groups + named = self.basedata_name + + # alias group, or generate new group and alias that + group = groups.get(named) + if not group: + group = groups.new(named) + + for obj in objs: + if obj.name not in group.objects: + group.objects.link(obj) + + def ensure_parent(self): + if self.parent_to_empty: + self.parent_name = 'Empty_' + self.basedata_name + scene = bpy.context.scene + if not self.parent_name in bpy.data.objects: + empty = bpy.data.objects.new(self.parent_name, None) + scene.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='', update=updateNode) + + # to be used as standard toggles for object attributes of same name + object_hide = BoolProperty(name='object hide', 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. + + def sv_init_helper_basedata_name(self): + """ + this is to be used in sv_init, at the top + """ + gai = bpy.context.scene.SvGreekAlphabet_index + self.basemesh_name = greek_alphabet[gai] + bpy.context.scene.SvGreekAlphabet_index += 1 + self.use_custom_color = True + + + def icons(self, TYPE): + NAMED_ICON = { + 'object_hide': 'RESTRICT_VIEW', + 'object_hide_render': 'RESTRICT_RENDER', + 'object_hide_select': 'RESTRICT_SELECT'}.get(TYPE) + if not NAMED_ICON: + return 'WARNING' + return NAMED_ICON + ['_ON', '_OFF'][getattr(self, TYPE)] + + def draw_live_and_outliner(self, context, layout): + view_icon = 'RESTRICT_VIEW_' + ('OFF' if self.activate else 'ON') + + col = layout.column(align=True) + row = col.row(align=True) + row.column().prop(self, "activate", text="LIVE", toggle=True, icon=view_icon) + + 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', bpy.data, 'materials', text='', icon='MATERIAL_DATA') + tracked_operator(self, row, fn_name='add_material', icon="ZOOMIN") + + 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="ZOOMIN") + + def set_corresponding_materials(self): + if bpy.data.materials.get(self.material): + for obj in self.get_children(): + obj.active_material = bpy.data.materials[self.material] + + 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 + + objects = bpy.data.objects + scene = bpy.context.scene + + # remove excess objects + for object_name in obj_names: + obj = objects[object_name] + obj.hide_select = False + scene.objects.unlink(obj) + objects.remove(obj, do_unlink=True) + + # delete associated meshes + 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 scene. + """ + 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.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 + scene = bpy.context.scene + + curve_name = self.basedata_name + '.' + str("%04d" % obj_index) + + # 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 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 copy(self, other): + self.basedata_name = get_random_init_v2() + + +def register(): + bpy.utils.register_class(SvObjectsHelperCallback) + + +def unregister(): + bpy.utils.unregister_class(SvObjectsHelperCallback)