class SvPulgaFitForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Grow / Shrink Force Tooltip: Shrink Radius (reducing mass) if collide with others / Grow if does not """ bl_idname = 'SvPulgaFitForceNode' bl_label = 'Pulga Fit Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_FIT_FORCE' force: FloatProperty( name='Magnitude', description='Shrink if collide with others / Grow if does not ', default=0.1, update=updateNode) min_rad: FloatProperty( name='Min. Radius', description='Do not shrink under this value', default=0.1, precision=3, update=updateNode) max_rad: FloatProperty( name='Max. Radius', description='Do not grow over this value', default=1.0, precision=3, update=updateNode) mode: EnumProperty(name="Mode", items=enum_item_4(['Absolute', 'Relative', 'Percent']), update=updateNode) algorithm: EnumProperty( name='Algorithm', description='Algorithm used for calculation', items=enum_item_4(['Brute Force', 'Kd-tree']), default='Kd-tree', update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Magnitude").prop_name = 'force' self.inputs.new('SvStringsSocket', "Min Radius").prop_name = 'min_rad' self.inputs.new('SvStringsSocket', "Max Radius").prop_name = 'max_rad' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): layout.prop(self, 'mode') if scipy is not None and Cython is not None: layout.prop(self, 'algorithm') def process(self): if not any(s.is_linked for s in self.outputs): return forces_in = self.inputs["Magnitude"].sv_get(deepcopy=False) min_rad_in = self.inputs["Min Radius"].sv_get(deepcopy=False) max_rad_in = self.inputs["Max Radius"].sv_get(deepcopy=False) forces_out = [] use_kdtree = self.algorithm == "Kd-tree" and scipy is not None and Cython is not None for force in zip(forces_in, min_rad_in, max_rad_in): forces_out.append(SvFitForce(*force, self.mode, use_kdtree=use_kdtree)) self.outputs[0].sv_set([forces_out])
class SvPulgaAlignForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Velocity Alignment Tooltip: Take part of the velocity of the near particles """ bl_idname = 'SvPulgaAlignForceNode' bl_label = 'Pulga Align Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_ALIGN_FORCE' strength: FloatProperty( name='Strength', description='Drag Force Constant', default=0.1, precision=3, update=updateNode) decay: FloatProperty( name='Decay', description='0 = no decay, 1 = linear, 2 = quadratic...', default=1.0, precision=3, update=updateNode) max_distance: FloatProperty( name='Max. Distance', description='Maximum distance', default=10.0, precision=3, update=updateNode) mode: EnumProperty( name='Mode', description='Algorithm used for calculation', items=enum_item_4(['Brute Force', 'Kd-tree']), default='Kd-tree', update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Strength").prop_name = 'strength' self.inputs.new('SvStringsSocket', "Decay").prop_name = 'decay' self.inputs.new('SvStringsSocket', "Max. Distance").prop_name = 'max_distance' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): if scipy is not None and Cython is not None: layout.prop(self, 'mode') def process(self): if not any(s.is_linked for s in self.outputs): return strength = self.inputs["Strength"].sv_get(deepcopy=False) decay = self.inputs["Decay"].sv_get(deepcopy=False) max_distance = self.inputs["Max. Distance"].sv_get(deepcopy=False) use_kdtree = self.mode in "Kd-tree" and scipy is not None and Cython is not None forces_out = [] for force in zip_long_repeat(strength, decay, max_distance): forces_out.append(SvAlignForce(*force, use_kdtree=use_kdtree)) self.outputs[0].sv_set([forces_out])
class SvMeshBeautify(bpy.types.Node, SverchCustomTreeNode): """ Triggers: beauty existing fill Tooltip: rearrange faces with bmesh operator useful for typography converted to geometry. """ bl_idname = 'SvMeshBeautify' bl_label = 'Mesh Beautify' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_MESH_BEAUTIFY' beautify_mode: bpy.props.EnumProperty(name='Beautify', items=enum_item_4(['AREA', 'ANGLE']), default="AREA", update=updateNode) def draw_buttons(self, context, layout): layout.prop(self, 'beautify_mode', expand=True) def sv_init(self, context): self.inputs.new('SvVerticesSocket', 'Verts') self.inputs.new('SvStringsSocket', 'Faces') self.outputs.new('SvVerticesSocket', 'Verts') self.outputs.new('SvStringsSocket', 'Faces') def process(self): if not any(s.is_linked for s in self.outputs): return in_verts = self.inputs['Verts'].sv_get() in_faces = self.inputs['Faces'].sv_get() out_verts, out_faces = [], [] if in_verts and in_faces: fill = bmesh.ops.beautify_fill for verts, faces in zip(in_verts, in_faces): bm = bmesh_from_pydata(verts, [], faces) bm.verts.ensure_lookup_table() fill(bm, faces=bm.faces[:], edges=bm.edges[:], use_restrict_tag=False, method=self.beautify_mode) nv, ne, nf = pydata_from_bmesh(bm) out_verts.append(nv) out_faces.append(nf) self.outputs['Verts'].sv_set(out_verts) self.outputs['Faces'].sv_set(out_faces)
class SvPulgaPinForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Constrain Vertices Tooltip: Pin Particles (Vertices) movement along defined axis """ bl_idname = 'SvPulgaPinForceNode' bl_label = 'Pulga Pin Force' bl_icon = 'PINNED' fixed_len: FloatProperty(name='Length', description='Force', default=0.0, update=updateNode) pin_type: EnumProperty(name='Axis', description='Constrained', items=enum_item_4( ['XYZ', 'XY', 'XZ', 'YZ', 'X', 'Y', 'Z']), default='XYZ', update=updateNode) force: FloatVectorProperty(name='Force', description='Force', size=3, default=(0.0, 0, 0), update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Pins") self.inputs.new('SvStringsSocket', "Pin Type").prop_name = 'pin_type' self.inputs.new('SvVerticesSocket', "Pins Goal") self.outputs.new('SvPulgaForceSocket', "Force") def process(self): if not any(s.is_linked for s in self.outputs): return # indices, pin_type, pins_goal_pos,use_pins_goal pins_in = self.inputs["Pins"].sv_get(deepcopy=False) pin_type = self.inputs["Pin Type"].sv_get(deepcopy=False) pins_goal_pos = self.inputs["Pins Goal"].sv_get(deepcopy=False, default=[[]]) forces_out = [] use_pin_goal = self.inputs["Pins Goal"].is_linked for force_params in zip_long_repeat(pins_in, pin_type, pins_goal_pos): forces_out.append(SvPinForce(*force_params, use_pin_goal)) self.outputs[0].sv_set([forces_out])
class SvPulgaCollisionForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Collide verts Tooltip: Collision forces between vertices """ bl_idname = 'SvPulgaCollisionForceNode' bl_label = 'Pulga Collision Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_COLLISION_FORCE' strength: FloatProperty(name='Strength', description='Collision forces between vertices', default=0.01, precision=4, step=1e-2, update=updateNode) mode: EnumProperty(name='Mode', description='Algorithm used for calculation', items=enum_item_4(['Brute Force', 'Kd-tree']), default='Kd-tree', update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Strength").prop_name = 'strength' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): if scipy is not None and Cython is not None: layout.prop(self, 'mode') def process(self): if not any(s.is_linked for s in self.outputs): return forces_in = self.inputs["Strength"].sv_get(deepcopy=False) forces_out = [] use_kdtree = self.mode in "Kd-tree" and scipy is not None and Cython is not None for force in forces_in: forces_out.append(SvCollisionForce(force, use_kdtree=use_kdtree)) self.outputs[0].sv_set([forces_out])
class SvFlatGeometryNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: 3D to 2D Tooltip: Projection of 3d vertices into defined plane """ bl_idname = 'SvFlatGeometryNode' bl_label = 'Flat Geometry' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_FLAT_GEOMETRY' projection_mode: EnumProperty( name='Mode', description='Projection mode', items=enum_item_4(["Orthogrphic", 'Perspective']), update=updateNode) def sv_init(self, context): self.inputs.new('SvVerticesSocket', 'Vertices') self.inputs.new('SvMatrixSocket', 'Plane Matrix') self.outputs.new('SvVerticesSocket', 'Vertices') self.outputs.new('SvStringsSocket', 'Z coord') def draw_buttons(self, context, layout): layout.prop(self, 'projection_mode') def process(self): if not any(s.is_linked for s in self.outputs): return if self.inputs['Vertices'].is_linked: verts_in = self.inputs['Vertices'].sv_get(deepcopy=False) plane_in = self.inputs['Plane Matrix'].sv_get(deepcopy=False) if self.projection_mode == 'Orthogrphic': verts_out, z_coord_out = ortho_projection(verts_in, plane_in) else: verts_out, z_coord_out = perspective_projection(verts_in, plane_in, 2) self.outputs['Vertices'].sv_set(verts_out) self.outputs['Z coord'].sv_set(z_coord_out)
class SvSmoothLines(bpy.types.Node, SverchCustomTreeNode): """ Triggers: smooth lines fil Tooltip: accepts seq of verts and weights, returns smoothed lines This node should accept verts and weights and return the smoothed line representation. """ bl_idname = 'SvSmoothLines' bl_label = 'Smooth Lines' bl_icon = 'NORMALIZE_FCURVES' smooth_selected_mode: EnumProperty( items=enum_item_4(["absolute", "relative", "arc"]), default="absolute", description="gives various representations of the smooth corner", update=updateNode) type_selected_mode: EnumProperty(items=enum_item_4(["cyclic", "open"]), default="open", update=updateNode) n_verts: IntProperty(default=5, name="n_verts", min=2, update=updateNode) weights: FloatProperty(default=0.0, name="weights", min=0.0, update=updateNode) def sv_init(self, context): self.inputs.new("VerticesSocket", "vectors") self.inputs.new("StringsSocket", "weights").prop_name = "weights" self.inputs.new("StringsSocket", "attributes") self.outputs.new("VerticesSocket", "verts") self.outputs.new("StringsSocket", "edges") def draw_buttons(self, context, layout): # with attrs socket connected all params must be passed via this socket, to override node variables attr_socket = self.inputs.get('attributes') if attr_socket and attr_socket.is_linked: return col = layout.column() row1 = col.row(align=True) row1.prop(self, "smooth_selected_mode", text="mode", expand=True) row2 = col.row(align=True) row2.prop(self, "type_selected_mode", text="type", expand=True) col.prop(self, "n_verts", text='num verts') def process(self): necessary_sockets = [ self.inputs["vectors"], ] if not all(s.is_linked for s in necessary_sockets): return if self.inputs["attributes"].is_linked: # gather own data, rather than socket data # NOT IMPLEMENTED YET ... edges_socket = self.outputs['edges'] verts_socket = self.outputs['verts'] V_list = self.inputs['vectors'].sv_get() W_list = self.inputs['weights'].sv_get() verts_out = [] edges_out = [] if W_list and V_list: # ensure all vectors from V_list are matched by a weight. W_list = extend_if_needed(V_list, W_list, default=0.5) params = self.get_params() for vlist, wlist in zip(V_list, W_list): new_verts = func_xpline_2d(vlist, wlist, params) verts_out.append(new_verts) if edges_socket.is_linked: edges_out.append( edge_sequence_from_verts(len(new_verts), params)) verts_socket.sv_set(verts_out) edges_socket.sv_set(edges_out) def get_params(self): params = lambda: None params.num_points = self.n_verts params.loop = False if not self.type_selected_mode == 'cyclic' else True params.remove_doubles = False params.weight = self.weights params.mode = self.smooth_selected_mode return params
class SverchokPreferences(AddonPreferences): bl_idname = __package__ def update_debug_mode(self, context): data_structure.DEBUG_MODE = self.show_debug def update_heat_map(self, context): data_structure.heat_map_state(self.heat_map) def set_frame_change(self, context): handlers.set_frame_change(self.frame_change_mode) def update_theme(self, context): color_def.rebuild_color_cache() if self.auto_apply_theme: color_def.apply_theme() tab_modes = data_structure.enum_item_4(["General", "Node Defaults", "Extra Nodes", "Theme"]) selected_tab: bpy.props.EnumProperty( items=tab_modes, description="pick viewing mode", default="General" ) # debugish... show_debug: BoolProperty( name="Print update timings", description="Print update timings in console", default=False, subtype='NONE', update=update_debug_mode) no_data_color: FloatVectorProperty( name="No data", description='When a node can not get data', size=3, min=0.0, max=1.0, default=(1, 0.3, 0), subtype='COLOR', update=update_system.update_error_colors) exception_color: FloatVectorProperty( name="Error", description='When node has an exception', size=3, min=0.0, max=1.0, default=(0.8, 0.0, 0), subtype='COLOR', update=update_system.update_error_colors) # heat map settings heat_map: BoolProperty( name="Heat map", description="Color nodes according to time", default=False, subtype='NONE', update=update_heat_map) heat_map_hot: FloatVectorProperty( name="Heat map hot", description='', size=3, min=0.0, max=1.0, default=(.8, 0, 0), subtype='COLOR') heat_map_cold: FloatVectorProperty( name="Heat map cold", description='', size=3, min=0.0, max=1.0, default=(1, 1, 1), subtype='COLOR') # Profiling settings profiling_sections = [ ("NONE", "Disable", "Disable profiling", 0), ("MANUAL", "Marked methods only", "Profile only methods that are marked with @profile decorator", 1), ("UPDATE", "Node tree update", "Profile whole node tree update process", 2) ] profile_mode: EnumProperty(name = "Profiling mode", items = profiling_sections, default = "NONE", description = "Performance profiling mode") developer_mode: BoolProperty(name = "Developer mode", description = "Show some additional panels or features useful for Sverchok developers only", default = False) # theme settings sv_theme: EnumProperty( items=color_def.themes, name="Theme preset", description="Select a theme preset", update=color_def.color_callback, default="default_theme") auto_apply_theme: BoolProperty( name="Apply theme", description="Apply theme automaticlly", default=False) apply_theme_on_open: BoolProperty( name="Apply theme", description="Apply theme automaticlly", default=False) color_viz: FloatVectorProperty( name="Visualization", description='', size=3, min=0.0, max=1.0, default=(1, 0.589, 0.214), subtype='COLOR', update=update_theme) color_tex: FloatVectorProperty( name="Text", description='', size=3, min=0.0, max=1.0, default=(0.5, 0.5, 1), subtype='COLOR', update=update_theme) color_sce: FloatVectorProperty( name="Scene", description='', size=3, min=0.0, max=1.0, default=(0, 0.5, 0.2), subtype='COLOR', update=update_theme) color_lay: FloatVectorProperty( name="Layout", description='', size=3, min=0.0, max=1.0, default=(0.674, 0.242, 0.363), subtype='COLOR', update=update_theme) color_gen: FloatVectorProperty( name="Generator", description='', size=3, min=0.0, max=1.0, default=(0, 0.5, 0.5), subtype='COLOR', update=update_theme) # frame change frame_change_modes = [ ("PRE", "Pre", "Update Sverchok before frame change", 0), ("POST", "Post", "Update Sverchok after frame change", 1), ("NONE", "None", "Sverchok doesn't update on frame change", 2) ] frame_change_mode: EnumProperty( items=frame_change_modes, name="Frame change", description="Select frame change handler", default="POST", update=set_frame_change) # ctrl+space settings show_icons: BoolProperty( name="Show icons in ctrl+space menu", default=False, description="Use icons in ctrl+space menu") over_sized_buttons: BoolProperty( default=False, name="Big buttons", description="Very big buttons") node_panel_modes = [ ("X", "Do not show", "Do not show node buttons", 0), ("T", "T panel", "Show node buttons under the T panel", 1), ("N", "N panel", "Show node under the N panel", 2) ] node_panels: EnumProperty( items = node_panel_modes, name = "Display node buttons", description = "Where to show node insertion buttons. Restart Blender to apply changes.", default = "X") node_panels_icons_only : BoolProperty( name = "Display icons only", description = "Show node icon only when icon has an icon, otherwise show it's name", default = True ) node_panels_columns : IntProperty( name = "Columns", description = "Number of icon panels per row; Set to 0 for automatic selection", default = 4, min = 0, max = 12 ) enable_live_objin: BoolProperty( description="Objects in edit mode will be updated in object-in Node") ## BLF/BGL/GPU scale and location props render_scale: FloatProperty( default=1.0, min=0.01, step=0.01, description='default render scale') render_location_xy_multiplier: FloatProperty( default=1.0, min=0.01, step=0.01, description='default render location scale') stethoscope_view_scale: FloatProperty( default=1.0, min=0.01, step=0.01, description='default stethoscope scale') index_viewer_scale: FloatProperty( default=1.0, min=0.01, step=0.01, description='default index viewer scale') auto_update_angle_values: BoolProperty( default=True, description="Auto update angle values when angle units are changed to preserve the angle") def set_nodeview_render_params(self, context): # i think these are both the same.. self.render_scale = get_dpi_factor() self.render_location_xy_multiplier = get_dpi_factor() print(f'set render_scale to: {self.render_scale}') print(f'set render_location_xy_multiplier to: {self.render_location_xy_multiplier}') ## datafiles = os.path.join(bpy.utils.user_resource('DATAFILES', path='sverchok', create=True)) defaults_location: StringProperty(default=datafiles, description='usually ..data_files\\sverchok\\defaults\\nodes.json') external_editor: StringProperty(description='which external app to invoke to view sources') real_sverchok_path: StringProperty(description='use with symlinked to get correct src->dst') github_token : StringProperty(name = "GitHub API Token", description = "GitHub API access token. Should have 'gist' OAuth scope.", subtype="PASSWORD") # Logging settings def update_log_level(self, context): logging.info("Setting log level to %s", self.log_level) logging.setLevel(self.log_level) log_levels = [ ("DEBUG", "Debug", "Debug output", 0), ("INFO", "Information", "Informational output", 1), ("WARNING", "Warnings", "Show only warnings and errors", 2), ("ERROR", "Errors", "Show errors only", 3) ] log_level: EnumProperty(name = "Logging level", description = "Minimum events severity level to output. All more severe messages will be logged as well.", items = log_levels, update = update_log_level, default = "INFO") log_update_events: BoolProperty( name="Log update events", description="Print name of methods which are triggered upon changes in BLender", default=False) log_to_buffer: BoolProperty(name = "Log to text buffer", description = "Enable log output to internal Blender's text buffer", default = True) log_to_buffer_clean: BoolProperty(name = "Clear buffer at startup", description = "Clear text buffer at each Blender startup", default = False) log_to_file: BoolProperty(name = "Log to file", description = "Enable log output to external file", default = False) log_to_console: BoolProperty(name = "Log to console", description = "Enable log output to console / terminal / standard output.", default = True) log_buffer_name: StringProperty(name = "Buffer name", default = "sverchok.log") log_file_name: StringProperty(name = "File path", default = os.path.join(datafiles, "sverchok.log")) # updating sverchok dload_archive_name: StringProperty(name="archive name", default="master") # default = "master" dload_archive_path: StringProperty(name="archive path", default="https://github.com/nortikin/sverchok/archive/") FreeCAD_folder: StringProperty(name="FreeCAD python 3.7 folder") def general_tab(self, layout): col = layout.row().column() col_split = col.split(factor=0.5) col1 = col_split.column() col1.label(text="UI:") col1.prop(self, "show_icons") toolbar_box = col1.box() toolbar_box.label(text="Node toolbars") toolbar_box.prop(self, "node_panels") if self.node_panels != "X": toolbar_box.prop(self, "node_panels_icons_only") if self.node_panels_icons_only: toolbar_box.prop(self, "node_panels_columns") col1.prop(self, "over_sized_buttons") col1.prop(self, "enable_live_objin", text='Enable Live Object-In') col1.prop(self, "external_editor", text="Ext Editor") col1.prop(self, "real_sverchok_path", text="Src Directory") box = col1.box() box.label(text="Export to Gist") box.prop(self, "github_token") box.label(text="To export node trees to gists, you have to create a GitHub API access token.") box.label(text="For more information, visit " + TOKEN_HELP_URL) box.operator("node.sv_github_api_token_help", text="Visit documentation page") col2 = col_split.split().column() col2.label(text="Frame change handler:") col2.row().prop(self, "frame_change_mode", expand=True) col2.separator() col2box = col2.box() col2box.label(text="Debug:") col2box.prop(self, "profile_mode") col2box.prop(self, "show_debug") col2box.prop(self, "heat_map") col2box.prop(self, "developer_mode") log_box = col2.box() log_box.label(text="Logging:") log_box.prop(self, "log_level") if self.log_level == "DEBUG": log_box.prop(self, "log_update_events") buff_row = log_box.row() buff_row.prop(self, "log_to_buffer") if self.log_to_buffer: buff_row.prop(self, "log_buffer_name") log_box.prop(self, "log_to_buffer_clean") file_row = log_box.row() file_row.prop(self, "log_to_file") if self.log_to_file: file_row.prop(self, "log_file_name") log_box.prop(self, "log_to_console") def node_defaults_tab(self, layout): row = layout.row() col = row.column(align=True) row_sub1 = col.row().split(factor=0.5) box_sub1 = row_sub1.box() box_sub1_col = box_sub1.column(align=True) box_sub1_col.label(text='Render Scale & Location') # box_sub1_col.prop(self, 'render_location_xy_multiplier', text='xy multiplier') # box_sub1_col.prop(self, 'render_scale', text='scale') box_sub1_col.label(text=f'xy multiplier: {self.render_location_xy_multiplier}') box_sub1_col.label(text=f'render_scale : {self.render_scale}') box_sub1_col.label(text='Stethoscope') box_sub1_col.prop(self, 'stethoscope_view_scale', text='scale') box_sub1_col.label(text='Index Viewer') box_sub1_col.prop(self, 'index_viewer_scale', text='scale') box_sub2 = box_sub1.box() box_sub2_col = box_sub2.column(align=True) box_sub2_col.label(text='Angle Units') box_sub2_col.prop(self, 'auto_update_angle_values', text="Auto Update Angle Values") col3 = row_sub1.split().column() col3.label(text='Location of custom defaults') col3.prop(self, 'defaults_location', text='') def theme_tab(self, layout): row = layout.row() col = row.column(align=True) split = col.row().split(factor=0.66) split2 = col.row().split(factor=0.66) left_split = split.row() right_split = split.row() split_viz_colors = left_split.column().split(factor=0.5, align=True) if True: col1 = split_viz_colors.column() for name in ['color_viz', 'color_tex', 'color_sce']: r = col1.row() r.prop(self, name) col2 = split_viz_colors.column() for name in ['color_lay', 'color_gen']: r = col2.row() r.prop(self, name) split_extra_colors = split2.column().split() col_x1 = split_extra_colors.column() col_x1.label(text="Error colors: ( error / no data )") row_x1 = col_x1.row() row_x1.prop(self, "exception_color", text='') row_x1.prop(self, "no_data_color", text='') col_x2 = split_extra_colors.split().column() col_x2.label(text="Heat map colors: ( hot / cold )") row_x2 = col_x2.row() row_x2.active = self.heat_map row_x2.prop(self, "heat_map_hot", text='') row_x2.prop(self, "heat_map_cold", text='') col3 = right_split.column() col3.label(text='Theme:') col3.prop(self, 'sv_theme', text='') col3.separator() col3.prop(self, 'auto_apply_theme', text="Auto apply theme changes") col3.prop(self, 'apply_theme_on_open', text="Apply theme when opening file") col3.operator('node.sverchok_apply_theme', text="Apply theme to layouts") def extra_nodes_tab(self, layout): def draw_freecad_ops(): dependency = sv_dependencies['freecad'] col = box.column(align=True) col.label(text=dependency.message, icon=get_icon(dependency.module)) row = col.row(align=True) row.operator('wm.url_open', text="Visit package website").url = dependency.url if dependency.module is None: tx = "Set path" else: tx = "Reset path" row.prop(self, 'FreeCAD_folder') row.operator('node.sv_set_freecad_path', text=tx).FreeCAD_folder = self.FreeCAD_folder return row box = layout.box() box.label(text="Dependencies:") row = draw_message(box, "pip") if pip is not None: row.operator('node.sv_ex_pip_install', text="Upgrade PIP").package = "pip setuptools wheel" else: if ensurepip is not None: row.operator('node.sv_ex_ensurepip', text="Install PIP") else: row.operator('wm.url_open', text="Installation instructions").url = "https://pip.pypa.io/en/stable/installing/" draw_message(box, "scipy") draw_message(box, "geomdl") draw_message(box, "skimage") draw_message(box, "mcubes") draw_message(box, "circlify") draw_message(box, "lbt-ladybug") draw_freecad_ops() if any(package.module is None for package in sv_dependencies.values()): box.operator('wm.url_open', text="Read installation instructions for missing dependencies").url = "https://github.com/portnov/sverchok-extra" def draw(self, context): layout = self.layout layout.row().prop(self, 'selected_tab', expand=True) if self.selected_tab == "General": self.general_tab(layout) if self.selected_tab == "Node_Defaults": self.node_defaults_tab(layout) if self.selected_tab == "Extra_Nodes": self.extra_nodes_tab(layout) if self.selected_tab == "Theme": self.theme_tab(layout) # FOOTER row = layout.row() col = row.column(align=True) col.label(text="Links:") row1 = col.row(align=True) row1.scale_y = 2.0 row1.operator('wm.url_open', text='Sverchok home page').url = 'http://nikitron.cc.ua/blend_scripts.html' row1.operator('wm.url_open', text='Documentation').url = 'http://nikitron.cc.ua/sverch/html/main.html' if context.scene.sv_new_version: row1.operator('node.sverchok_update_addon', text='Upgrade Sverchok addon') else: row1.operator('node.sverchok_check_for_upgrades_wsha', text='Check for new version')
class SvPulgaAngleForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Angles at edges Tooltip: Force the keeps angles between edges """ bl_idname = 'SvPulgaAngleForceNode' bl_label = 'Pulga Angle Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_ANGLES_FORCE' fixed_angle: FloatProperty( name='Rest Angle', description= 'Specify spring rest angle, 0 to calculate it from initial position', default=0.0, update=updateNode) stiffness: FloatProperty(name='Stiffness', description='Springs stiffness constant', default=0.1, precision=4, update=updateNode) def update_sockets(self, context): self.inputs[0].label = self.mode mode: EnumProperty(name='Mode', items=enum_item_4(['Edges', 'Polygons']), default='Edges', update=update_sockets) mass_dependent: BoolProperty(name='mass_dependent', update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Edge_Pol") self.inputs[0].label = 'Edges' self.inputs.new('SvStringsSocket', "Stiffness").prop_name = 'stiffness' self.inputs.new('SvStringsSocket', "Angle").prop_name = 'fixed_angle' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): layout.prop(self, 'mode') def process(self): if not any(s.is_linked for s in self.outputs): return springs_in = self.inputs["Edge_Pol"].sv_get(deepcopy=False) stiffness_in = self.inputs["Stiffness"].sv_get(deepcopy=False) lengths_in = self.inputs["Angle"].sv_get(deepcopy=False) forces_out = [] use_fix_len = self.inputs["Angle"].is_linked for force_params in zip_long_repeat(springs_in, stiffness_in, lengths_in): if self.mode == 'Edges': forces_out.append(SvEdgesAngleForce(*force_params, use_fix_len)) else: forces_out.append( SvPolygonsAngleForce(*force_params, use_fix_len)) self.outputs[0].sv_set([forces_out])
class SvSvgDimensionNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Text SVG Tooltip: Creates SVG Dimensions """ bl_idname = 'SvSvgDimensionNode' bl_label = 'Dimension SVG' bl_icon = 'MESH_CIRCLE' sv_icon = 'SV_DIMENSION_SVG' font_size: FloatProperty(name='Font Size', description='Font Size', default=10, update=updateNode) font_family: EnumProperty(name='Font', description='Font Size', items=enum_item_4([ "serif", 'sans-serif', 'monospace', 'cursive', 'fantasy', 'user' ]), default='monospace', update=updateNode) user_font: StringProperty(name='Font Name', description='Define font name', default='', update=updateNode) dimension_type: EnumProperty( name='Type', description='Dimension type. Horizontal = 0, Vertical = 1, Aligned = 2', items=enum_item_4(["Horizontal", 'Vertical', 'Aligned']), default='Aligned', update=updateNode) dimension_offset: FloatProperty(name='Dim. Offset', description='Dimension offset', default=0, update=updateNode) line_extension: FloatProperty(name='Lines Extension', description='Text Rotation', default=0, update=updateNode) decimal_precision: IntProperty(name='Decimals', description='Text Rotation', default=2, update=updateNode) units: StringProperty(name='Units', description='units', default='', update=updateNode) units_real: EnumProperty(name='Units_real', description='Dimentions feets or meters', items=enum_item_4(['Metric', 'Imperialistic']), default='Metric', update=updateNode) text: StringProperty(name='Text', description='Text', default='', update=updateNode) text_offset: FloatProperty(name='Text Offset', description='Text offset', default=0, update=updateNode) def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Location A") self.inputs.new('SvVerticesSocket', "Location B") self.inputs.new('SvStringsSocket', "Dim. Type").prop_name = 'dimension_type' self.inputs.new('SvStringsSocket', "Dim. Offset").prop_name = 'dimension_offset' self.inputs.new('SvStringsSocket', "Font Size").prop_name = 'font_size' self.inputs.new('SvStringsSocket', "Text Offset").prop_name = 'text_offset' self.inputs.new('SvSvgSocket', "Text Fill / Stroke") self.inputs.new('SvSvgSocket', "Lines Fill / Stroke") self.outputs.new('SvSvgSocket', "SVG Objects") def draw_buttons(self, context, layout): layout.prop(self, "line_extension", expand=False) layout.prop(self, "decimal_precision", expand=False) layout.prop(self, "units", expand=False) layout.prop(self, "units_real", expand=True) layout.prop(self, "font_family", expand=False) if self.font_family == 'user': layout.prop(self, "user_font") def process(self): if not self.outputs[0].is_linked: return params_in = [s.sv_get(deepcopy=False) for s in self.inputs[:6]] texts_out = [] params_in.append(self.inputs['Text Fill / Stroke'].sv_get( deepcopy=False, default=[[None]])) params_in.append(self.inputs['Lines Fill / Stroke'].sv_get( deepcopy=False, default=[[None]])) font_family = self.user_font if self.font_family == 'user' else self.font_family for params in zip(*mlr(params_in)): svg_texts = [] for local_params in zip(*mlr(params)): svg_texts.append(SvgDimension(*local_params, font_family, self)) texts_out.append(SvgGroup(svg_texts)) self.outputs[0].sv_set(texts_out)
class SvPulgaBoundingBoxForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Spacial Ambit Tooltip: Define simulation Limits by Volume or Surface """ bl_idname = 'SvPulgaBoundingBoxForceNode' bl_label = 'Pulga Boundaries Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_BOUNDARIES_FORCE' def update_sockets_and_node(self, context): self.update_sockets() updateNode(self, context) def update_sockets(self): self.inputs['Bounding Box'].hide_safe = self.mode != 'Box' self.inputs[ 'Center'].hide_safe = not 'Sphere' in self.mode and not 'Plane' in self.mode self.inputs['Radius'].hide_safe = not 'Sphere' in self.mode self.inputs['Normal'].hide_safe = not 'Plane' in self.mode self.inputs['Vertices'].hide_safe = not 'Mesh' in self.mode self.inputs['Polygons'].hide_safe = not 'Mesh' in self.mode self.inputs['Solid'].hide_safe = not 'Solid_(' in self.mode self.inputs['Solid Face'].hide_safe = self.mode != 'Solid_Face' mode_items = [ 'Box', 'Sphere', 'Sphere Surface', 'Plane', 'Mesh (Surface)', 'Mesh (Volume)' ] if FreeCAD is not None: mode_items.append('Solid (Surface)') mode_items.append('Solid (Volume)') mode_items.append('Solid Face') mode: EnumProperty(name='Mode', description='Boundaries definition mode', items=enum_item_4(mode_items), default='Box', update=update_sockets_and_node) center: FloatVectorProperty(name='Center', description='Bounding Sphere center', default=(0, 0, 0), size=3, update=updateNode) radius: FloatProperty(name='Radius', description='Bounding Sphere radius', default=0.0, update=updateNode) normal: FloatVectorProperty(name='Normal', description='Bounding Sphere center', default=(0, 0, 0), size=3, update=updateNode) def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Bounding Box") self.inputs.new('SvVerticesSocket', "Center").prop_name = 'center' self.inputs.new('SvStringsSocket', "Radius").prop_name = 'radius' self.inputs.new('SvVerticesSocket', "Normal").prop_name = 'normal' self.inputs.new('SvVerticesSocket', "Vertices") self.inputs.new('SvStringsSocket', "Polygons") self.inputs.new('SvSolidSocket', "Solid") self.inputs.new('SvSurfaceSocket', "Solid Face") self.update_sockets() self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): layout.prop(self, 'mode') def process(self): if not any(s.is_linked for s in self.outputs): return if self.mode == 'Box': b_box = self.inputs["Bounding Box"].sv_get(deepcopy=False) forces_out = [] for force in b_box: forces_out.append(SvBoundingBoxForce(force)) elif self.mode == 'Sphere': center = self.inputs["Center"].sv_get(deepcopy=False) radius = self.inputs["Radius"].sv_get(deepcopy=False) forces_out = [] for force in zip_long_repeat(center, radius): forces_out.append(SvBoundingSphereForce(*force)) elif self.mode == 'Sphere_Surface': center = self.inputs["Center"].sv_get(deepcopy=False) radius = self.inputs["Radius"].sv_get(deepcopy=False) forces_out = [] for force in zip_long_repeat(center, radius): forces_out.append(SvBoundingSphereSurfaceForce(*force)) elif self.mode == 'Plane': center = self.inputs["Center"].sv_get(deepcopy=False) radius = self.inputs["Normal"].sv_get(deepcopy=False) forces_out = [] for force in zip_long_repeat(center, radius): forces_out.append(SvBoundingPlaneSurfaceForce(*force)) elif 'Mesh' in self.mode: verts = self.inputs["Vertices"].sv_get(deepcopy=False) polygons = self.inputs["Polygons"].sv_get(deepcopy=False) volume = self.mode == "Mesh_(Volume)" forces_out = [] for force in zip_long_repeat(verts, polygons): forces_out.append(SvBoundingMeshForce(*force, volume)) elif 'Solid' in self.mode: input_name = 'Solid' if self.mode in [ 'Solid_(Surface)', 'Solid_(Volume)' ] else 'Solid Face' solid = self.inputs[input_name].sv_get(deepcopy=False) volume = self.mode == "Solid_(Volume)" forces_out = [] for force in solid: forces_out.append(SvBoundingSolidForce(force, volume=volume)) self.outputs[0].sv_set([forces_out])
class SvSvgTextNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Text SVG Tooltip: Creates SVG text. """ bl_idname = 'SvSvgTextNode' bl_label = 'Text SVG' bl_icon = 'MESH_CIRCLE' sv_icon = 'SV_TEXT_SVG' font_size: FloatProperty(name='Text Size', description='Font Size', default=10, update=updateNode) font_family: EnumProperty(name='Font', description='Font Size', items=enum_item_4([ 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'user' ]), default='monospace', update=updateNode) user_font: StringProperty(name='Name', description='Define font name', default='', update=updateNode) font_alignment: EnumProperty(name='Font Name', description='Define font name', items=enum_item_4(['start', 'middle', 'end']), update=updateNode) weight: EnumProperty(name='Font Name', description='Define font name', items=enum_item_4(['normal', 'bold']), update=updateNode) angle: FloatProperty(name='Angle', description='Text Rotation', default=0, update=updateNode) text: StringProperty(name='Text', description='Text', default='', update=updateNode) def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Location") self.inputs.new('SvStringsSocket', "Text").prop_name = 'text' self.inputs.new('SvStringsSocket', "Font Size").prop_name = 'font_size' self.inputs.new('SvStringsSocket', "Angle").prop_name = 'angle' self.inputs.new('SvSvgSocket', "Fill / Stroke") self.outputs.new('SvSvgSocket', "SVG Objects") def draw_buttons(self, context, layout): layout.prop(self, "font_family", expand=False) if self.font_family == 'user': layout.prop(self, "user_font") layout.prop(self, "font_alignment", expand=True) layout.prop(self, "weight", expand=True) def process(self): if not self.outputs[0].is_linked: return params_in = [s.sv_get(deepcopy=False) for s in self.inputs[:4]] texts_out = [] params_in.append(self.inputs['Fill / Stroke'].sv_get(deepcopy=False, default=[[None]])) font_family = self.user_font if self.font_family == 'user' else self.font_family print("process") for params in zip(*mlr(params_in)): svg_texts = [] for loc, text, size, angle, atts in zip(*mlr(params)): svg_texts.append( SvgText(loc, text, size, angle, self.weight, atts, font_family, self.font_alignment)) texts_out.append(SvgGroup(svg_texts)) self.outputs[0].sv_set(texts_out)
class SvKDTreeEdgesNodeMK2(bpy.types.Node, SverchCustomTreeNode): ''' Triggers: Create Edges by distance Tooltip: Join verts pairs by defining distance range and number of connections ''' bl_idname = 'SvKDTreeEdgesNodeMK2' bl_label = 'KDT Closest Edges MK2' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_KDT_EDGES' mindist: FloatProperty( name='mindist', description='Minimum dist', min=0.0, default=0.1, update=updateNode) maxdist: FloatProperty( name='maxdist', description='Maximum dist', min=0.0, default=2.0, update=updateNode) maxNum: IntProperty( name='maxNum', description='max edge count', default=4, min=1, update=updateNode) skip: IntProperty( name='skip', description='skip first n', default=0, min=0, update=updateNode) def update_sockets(self, context): self.inputs['maxNum'].hide_safe = self.mode == 'Fast' self.inputs['skip'].hide_safe = self.mode in ['Fast', 'No_Skip'] updateNode(self, context) mode: EnumProperty( name='Mode', description='Implementation used', items=enum_item_4(['Fast', 'Max Queried', 'No Skip', 'Complete']), default='Fast', update=update_sockets) list_match: EnumProperty( name="List Match", description="Behavior on different list lengths", items=list_match_modes, default="REPEAT", update=updateNode) def sv_init(self, context): self.inputs.new('SvVerticesSocket', 'Verts') self.inputs.new('SvStringsSocket', 'mindist').prop_name = 'mindist' self.inputs.new('SvStringsSocket', 'maxdist').prop_name = 'maxdist' self.inputs.new('SvStringsSocket', 'maxNum').prop_name = 'maxNum' self.inputs.new('SvStringsSocket', 'skip').prop_name = 'skip' self.inputs['maxNum'].hide_safe = True self.inputs['skip'].hide_safe = True self.outputs.new('SvStringsSocket', 'Edges') def draw_buttons(self, context, layout): if fast_mode(): layout.prop(self, 'mode') def draw_buttons_ext(self, context, layout): if fast_mode(): layout.prop(self, 'mode') layout.prop(self, 'list_match') def process(self): inputs = self.inputs outputs = self.outputs if not inputs['Verts'].is_linked or not outputs['Edges'].is_linked: return params = [inputs['Verts'].sv_get(deepcopy=False)] match = list_match_func[self.list_match] if fast_mode() and self.mode != 'Complete': if self.mode == 'Fast': params.extend([sk.sv_get(deepcopy=False)[0] for sk in self.inputs[1:3]]) result = [scipy_kdt_closest_edges_fast(vs, min_d, max_d) for vs, min_d, max_d in zip(*match(params))] elif self.mode == 'Max_Queried': params.extend([sk.sv_get(deepcopy=False)[0] for sk in self.inputs[1:]]) result = [scipy_kdt_closest_max_queried(vs, min_d, max_d, max_num, skip) for vs, min_d, max_d, max_num, skip in zip(*match(params))] elif self.mode == 'No_Skip': params.extend([sk.sv_get(deepcopy=False)[0] for sk in self.inputs[1:]]) result = [scipy_kdt_closest_edges_no_skip(vs, min_d, max_d, max_num, skip) for vs, min_d, max_d, max_num, skip in zip(*match(params))] else: params.extend([sk.sv_get(deepcopy=False)[0] for sk in self.inputs[1:]]) result = [kdt_closest_edges(p[0], p[1:]) for p in zip(*match(params))] outputs['Edges'].sv_set(result)
class SvSvgFillStrokeNodeMk2(bpy.types.Node, SverchCustomTreeNode): """ Triggers: color, line width Tooltip: Define Fill /Stroke Style for svg objects """ bl_idname = 'SvSvgFillStrokeNodeMk2' bl_label = 'Fill / Stroke SVG' bl_icon = 'MESH_CIRCLE' sv_icon = 'SV_FILL_STROKE_SVG' def update_actual_sockets(self): self.inputs['Fill Color'].hide_safe = self.fill_mode != 'FLAT' self.inputs['Fill Pattern'].hide_safe = self.fill_mode != 'PATTERN' self.inputs['Stroke Color'].hide_safe = self.stroke_mode != 'FLAT' self.inputs['Stroke Pattern'].hide_safe = self.stroke_mode != 'PATTERN' self.inputs['Stroke Width'].hide_safe = self.stroke_mode == 'NONE' self.inputs[ 'Dash Pattern'].hide_safe = self.stroke_type == 'Solid' or self.stroke_mode == 'NONE' def update_sockets(self, context): self.update_actual_sockets() updateNode(self, context) blend_mode: EnumProperty(name='Blend', items=enum_item_4([ 'Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'Color-dodge', 'Color-burn', 'Hard-light', 'Soft-light', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity' ]), default="Normal", update=update_sockets) fill_modes = [('NONE', 'None', '', 0), ('FLAT', 'Flat', '', 1), ('PATTERN', 'Pattern', '', 2)] fill_mode: EnumProperty(name='Fill', items=fill_modes, default="FLAT", update=update_sockets) stroke_mode: EnumProperty(name='Stroke', items=fill_modes, update=update_sockets) stroke_width: FloatProperty(name='Stroke width', description='Stroke width', default=1.0, update=updateNode) stroke_color: FloatVectorProperty(name="Stroke Color", description="Color", size=4, min=0.0, max=1.0, default=(0, 0, 0, 1), subtype='COLOR', update=update_sockets) stroke_linecap: EnumProperty(name='Cap', description='Line Cap', items=enum_item_4(['Butt', 'Round', 'Square']), update=updateNode) stroke_linejoin: EnumProperty(name='Join', description='Line Join', items=enum_item_4( ['Bevel', 'Miter', 'Round']), update=updateNode) paint_order: EnumProperty(name='Order', description="Paint Order", items=enum_item_4(['Fill Stroke', 'Stroke Fill']), update=updateNode) stroke_type: EnumProperty(name='Type', items=enum_item_4(['Solid', 'Dashed']), update=update_sockets) fill_color: FloatVectorProperty(name="Fill Color", description="Color", size=4, min=0.0, max=1.0, default=(0, 0, 0, 1), subtype='COLOR', update=updateNode) def sv_init(self, context): self.inputs.new('SvColorSocket', "Fill Color").prop_name = 'fill_color' self.inputs.new('SvSvgSocket', "Fill Pattern") self.inputs.new('SvColorSocket', "Stroke Color").prop_name = 'stroke_color' self.inputs.new('SvSvgSocket', "Stroke Pattern") self.inputs.new('SvStringsSocket', "Stroke Width").prop_name = 'stroke_width' self.inputs.new('SvStringsSocket', "Dash Pattern") self.update_actual_sockets() self.outputs.new('SvSvgSocket', "Fill / Stroke") def draw_buttons(self, context, layout): layout.prop(self, "blend_mode", expand=False) layout.prop(self, "fill_mode", expand=False) col = layout.column(align=True) col.prop(self, "stroke_mode", expand=False) if self.stroke_mode != 'NONE': col.prop(self, "stroke_linecap", expand=False) col.prop(self, "stroke_linejoin", expand=False) col.prop(self, "stroke_type", expand=False) if self.fill_mode != 'NONE': layout.prop(self, "paint_order", expand=False) def get_data(self): if self.fill_mode == 'FLAT': fill = self.inputs['Fill Color'].sv_get(deepcopy=False) elif self.fill_mode == 'PATTERN': fill = self.inputs['Fill Pattern'].sv_get(deepcopy=False, default=[[None]]) else: fill = [[None]] if self.stroke_mode == 'FLAT': stroke = self.inputs['Stroke Color'].sv_get(deepcopy=False) elif self.stroke_mode == 'PATTERN': stroke = self.inputs['Stroke Pattern'].sv_get(deepcopy=False, default=[[None]]) else: stroke = [[None]] stroke_width = self.inputs['Stroke Width'].sv_get(deepcopy=False) dash_pattern = self.inputs['Dash Pattern'].sv_get(deepcopy=False, default=[[None]]) return mlr([fill, stroke, stroke_width, dash_pattern]) def process(self): if not self.outputs[0].is_linked: return params_in = [ s.sv_get(deepcopy=False, default=[[None]]) for s in self.inputs ] params_in = self.get_data() attributes_out = [] for params in zip(*params_in): attributes = [] dash_pattern = params[-1] for fill, stroke, stroke_width in zip(*mlr(params[:-1])): attributes.append( SvgAttributes(fill, stroke, stroke_width, dash_pattern, self)) attributes_out.append(attributes) self.outputs[0].sv_set(attributes_out)
class SvLoopOutNode(SverchCustomTreeNode, bpy.types.Node): """ Triggers: For Loop End, Tooltip: End node to define a nodes for-loop. """ bl_idname = 'SvLoopOutNode' bl_label = 'Loop Out' bl_icon = 'CON_FOLLOWPATH' mode: EnumProperty( name='Mode', description='Maximum allowed iterations', items=enum_item_4(['Range', 'For Each']), default='Range', ) def sv_init(self, context): self.inputs.new('SvLoopControlSocket', 'Loop In') self.inputs.new('SvStringsSocket', 'Break') self.inputs.new('SvStringsSocket', 'Data 0') self.outputs.new('SvStringsSocket', 'Data 0') def draw_buttons_ext(self, context, layout): if self.mode == 'For_Each': socket_labels_box = layout.box() socket_labels_box.label(text="Socket Labels") for socket in self.inputs[2:]: socket_labels_box.prop(socket, "label", text=socket.name) socket_labels_box.operator("node.update_loop_out_socket_labels", icon='CON_FOLLOWPATH', text="Update Socket Labels") def update_sockets_range_mode(self, loop_in_node): while len(loop_in_node.inputs) > len(self.inputs): name = 'Data ' + str(len(self.inputs) - 2) self.inputs.new('SvStringsSocket', name) self.outputs.new('SvStringsSocket', name) while len(loop_in_node.inputs) < len(self.inputs): self.inputs.remove(self.inputs[-1]) self.outputs.remove(self.outputs[-1]) # in case the loop_in has not been updated yet if loop_in_node.inputs[-1].links: name = 'Data ' + str(len(self.inputs) - 2) self.inputs.new('SvStringsSocket', name) self.outputs.new('SvStringsSocket', name) # dynamic sockets for idx, socket in enumerate(loop_in_node.inputs): if idx == 0: continue if socket.links: if type(socket.links[0].from_socket) != type( self.outputs[socket.name]): self.inputs.remove(self.inputs[socket.name]) self.inputs.new(socket.links[0].from_socket.bl_idname, socket.name) self.inputs.move(len(self.inputs) - 1, idx + 1) self.outputs.remove(self.outputs[socket.name]) self.outputs.new(socket.links[0].from_socket.bl_idname, socket.name) self.outputs.move(len(self.outputs) - 1, idx - 1) for inp, self_inp, self_outp in zip(loop_in_node.inputs[1:], self.inputs[2:], self.outputs): self_outp.label = inp.label self_inp.label = inp.label def update_sockets_for_each_mode(self): # socket handling if len(self.outputs) + 3 > len(self.inputs): self.outputs.remove(self.outputs[-1]) if self.inputs[-1].links: name_input = 'Data ' + str(len(self.inputs) - 2) name_output = 'Data ' + str(len(self.inputs) - 3) other_socket = self.inputs[-1].other new_label = other_socket.label if other_socket.label else other_socket.name self.inputs[-1].label = new_label self.inputs.new('SvStringsSocket', name_input) self.outputs.new('SvStringsSocket', name_output) else: while len(self.inputs) > 3 and not self.inputs[-2].links: self.inputs.remove(self.inputs[-1]) self.outputs.remove(self.outputs[-1]) # match input socket n with output socket n for idx, socket in enumerate(self.inputs[2:]): if socket.links: if type(socket.links[0].from_socket) != type( self.outputs[socket.name]): self.outputs.remove(self.outputs[socket.name]) self.outputs.new(socket.links[0].from_socket.bl_idname, socket.name) self.outputs.move(len(self.outputs) - 1, idx) for inp, outp in zip(self.inputs[2:-1], self.outputs): outp.label = inp.label def change_mode(self, loop_in_node): if loop_in_node.mode == 'For_Each': name = 'Data ' + str(len(self.inputs) - 2) self.inputs.new('SvStringsSocket', name) self.mode = loop_in_node.mode def sv_update(self): if not self.inputs[0].is_linked: return loop_in_node = self.inputs[0].links[0].from_socket.node if not loop_in_node.bl_idname == 'SvLoopInNode': return if loop_in_node.mode != self.mode: self.change_mode(loop_in_node) if loop_in_node.mode == 'Range': self.update_sockets_range_mode(loop_in_node) else: self.update_sockets_for_each_mode() def bad_inner_loops(self, intersection): inner_loops_out, inner_loops_in = [], [] ng = self.id_data for node in intersection: if ng.nodes[node].bl_idname == 'SvLoopOutNode': inner_loops_out.append(node) if ng.nodes[node].bl_idname == 'SvLoopInNode': inner_loops_in.append(node) for node in inner_loops_out: if not ng.nodes[node].ready(): return True inner_loop_in_node = ng.nodes[node].inputs[0].links[ 0].from_socket.node if not inner_loop_in_node.name in inner_loops_in: print("Inner Loop not well connected") return True inner_loops_in.remove(inner_loop_in_node.name) if inner_loops_in: return True return False def get_affected_nodes(self, loop_in_node): tree = self.id_data nodes_to_loop_out = make_tree_from_nodes([self.name], tree, down=False) nodes_from_loop_in = make_tree_from_nodes([loop_in_node.name], tree, down=True) nodes_from_loop_out = make_tree_from_nodes([self.name], tree, down=True) set_nodes_from_loop_in = frozenset(nodes_from_loop_in) set_nodes_from_loop_out = frozenset(nodes_from_loop_out) intersection = [ x for x in nodes_to_loop_out if x in set_nodes_from_loop_in and tree.nodes[x].bl_idname != 'NodeReroute' ] related_nodes = [ x for x in nodes_from_loop_in if x not in set_nodes_from_loop_out and x not in intersection ] return intersection, related_nodes def ready(self): if not self.inputs[0].is_linked: print("Inner Loop not connected") return False if not any([socket.is_linked for socket in self.outputs]): return False loop_in_node = self.inputs[0].links[0].from_socket.node if not loop_in_node.bl_idname == 'SvLoopInNode': print("Inner Loop not well connected") return False return True def process(self): if not self.ready(): return loop_in_node = self.inputs[0].links[0].from_socket.node self.inputs[1].label = socket_labels[loop_in_node.mode] if loop_in_node.mode == 'Range': self.range_mode(loop_in_node) else: self.for_each_mode(loop_in_node) def break_loop(self): stop_ = self.inputs['Break'].sv_get(deepcopy=False, default=[[False]]) return stop_[0][0] def append_data(self, out_data): if not self.break_loop(): for inp, out in zip(self.inputs[2:len(self.outputs) + 2], out_data): out.append(inp.sv_get(deepcopy=False, default=[[]])[0]) def for_each_mode(self, loop_in_node): list_match = list_match_func[loop_in_node.list_match] params = list_match([ inp.sv_get(deepcopy=False, default=[]) for inp in loop_in_node.inputs[1:-1] ]) if len(params[0]) == 1: if not self.break_loop(): for inp, outp in zip(self.inputs[2:], self.outputs): outp.sv_set(inp.sv_get(deepcopy=False, default=[])) else: for outp in self.outputs: outp.sv_set([]) else: intersection, related_nodes = self.get_affected_nodes(loop_in_node) if self.bad_inner_loops(intersection): raise Exception("Loops inside not well connected") tree_nodes = self.id_data.nodes do_print = loop_in_node.print_to_console idx = 0 out_data = [[] for inp in self.inputs[2:]] do_update(intersection[:-1], tree_nodes) self.append_data(out_data) for item_params in zip(*params): if idx == 0: idx += 1 continue for j, data in enumerate(item_params): loop_in_node.outputs[j + 3].sv_set([data]) loop_in_node.outputs['Loop Number'].sv_set([[idx]]) idx += 1 if do_print: print(f"Looping Object Number {idx}") process_looped_nodes(intersection[1:-1], tree_nodes, 'Element', idx) self.append_data(out_data) for inp, outp in zip(out_data, self.outputs): outp.sv_set(inp) do_update(related_nodes, self.id_data.nodes) def range_mode(self, loop_in_node): iterations = min(int(loop_in_node.inputs['Iterations'].sv_get()[0][0]), loop_in_node.max_iterations) if iterations == 0: for inp, outp in zip(loop_in_node.inputs[1:-1], self.outputs): outp.sv_set(inp.sv_get(deepcopy=False, default=[])) elif iterations == 1: for inp, outp in zip(self.inputs[2:], self.outputs): outp.sv_set(inp.sv_get(deepcopy=False, default=[])) else: intersection, related_nodes = self.get_affected_nodes(loop_in_node) if self.bad_inner_loops(intersection): raise Exception("Loops inside not well connected") do_print = loop_in_node.print_to_console tree_nodes = self.id_data.nodes do_update(intersection[:-1], tree_nodes) for i in range(iterations - 1): if self.break_loop(): break for j, socket in enumerate(self.inputs[2:]): data = socket.sv_get(deepcopy=False, default=[]) loop_in_node.outputs[j + 3].sv_set(data) loop_in_node.outputs['Loop Number'].sv_set([[i + 1]]) if do_print: print(f"Looping iteration Number {i+1}") process_looped_nodes(intersection[1:-1], tree_nodes, 'Iteration', i + 1) for inp, outp in zip(self.inputs[2:], self.outputs): outp.sv_set(inp.sv_get(deepcopy=False, default=[])) do_update(related_nodes, self.id_data.nodes)
class SvSolidifyNodeMk2(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Extrude/Thicken Mesh Tooltip: Extrude along normal the mesh surface. """ bl_idname = 'SvSolidifyNodeMk2' bl_label = 'Solidify' bl_icon = 'MOD_SOLIDIFY' thickness: FloatProperty( name='Thickness', description='Shell thickness', default=0.1, update=updateNode) offset: FloatProperty( name='Offset', description='Offset Thickness from center', default=1, soft_min=-1, soft_max=1, update=updateNode) even: BoolProperty( name='Even Thickness', description='Mantain Thinkness by adjusting sharp corners', default=True, update=updateNode) def update_sockets(self, context): self.inputs['Offset'].hide_safe = self.implementation == 'Blender' updateNode(self, context) implementation: EnumProperty( name='Implementation', items=enum_item_4(['Sverchok', 'Blender']), description='Mantain Thinkness by adjusting sharp corners', default='Sverchok', update=update_sockets) def sv_init(self, context): self.inputs.new('SvVerticesSocket', 'Vertices') self.inputs.new('SvStringsSocket', 'Edges') self.inputs.new('SvStringsSocket', 'Polygons') self.inputs.new('SvStringsSocket', 'Thickness').prop_name = 'thickness' self.inputs.new('SvStringsSocket', 'Offset').prop_name = 'offset' self.outputs.new('SvVerticesSocket', 'Vertices') self.outputs.new('SvStringsSocket', 'Edges') self.outputs.new('SvStringsSocket', 'Polygons') self.outputs.new('SvStringsSocket', 'New Pols') self.outputs.new('SvStringsSocket', 'Rim Pols') self.outputs.new('SvStringsSocket', 'Pols Group') self.outputs.new('SvStringsSocket', 'New Verts Mask') def draw_buttons(self, context, layout): if self.implementation == 'Sverchok': layout.prop(self, 'even') def draw_buttons_ext(self, context, layout): layout.prop(self, 'implementation') self.draw_buttons(context, layout) def process(self): if not any((s.is_linked for s in self.outputs)): return if not (self.inputs['Vertices'].is_linked and self.inputs['Polygons'].is_linked): return verts = self.inputs['Vertices'].sv_get(deepcopy=False) edges = self.inputs['Edges'].sv_get(deepcopy=False, default=[[]]) polys = self.inputs['Polygons'].sv_get(deepcopy=False) thickness = self.inputs['Thickness'].sv_get(deepcopy=False) offset = self.inputs['Offset'].sv_get(deepcopy=False) if self.implementation == 'Blender': func = solidify_blender else: func = solidify res = [] for v, e, p, t, o in zip_long_repeat(verts, edges, polys, thickness, offset): res.append(func(v, e, p, t, o, self.even)) verts_out, edges_out, polys_out, new_pols, rim_pols, pols_groups, new_verts_mask = zip(*res) self.outputs['Vertices'].sv_set(verts_out) self.outputs['Edges'].sv_set(edges_out) self.outputs['Polygons'].sv_set(polys_out) self.outputs['New Pols'].sv_set(new_pols) self.outputs['Rim Pols'].sv_set(rim_pols) self.outputs['Pols Group'].sv_set(pols_groups) self.outputs['New Verts Mask'].sv_set(new_verts_mask)
class SvPulgaAttractionForceNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Attraction among vertices Tooltip: Attraction between vertices """ bl_idname = 'SvPulgaAttractionForceNode' bl_label = 'Pulga Attraction Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_ATTRACTION_FORCE' strength: FloatProperty(name='Strength', description='Attraction between vertices', default=0.01, precision=4, step=1e-2, update=updateNode) decay: FloatProperty( name='Decay', description='0 = no decay, 1 = linear, 2 = quadratic...', default=1.0, precision=3, update=updateNode) max_distance: FloatProperty(name='Max Distance', description='Maximum distance', default=10.0, precision=3, update=updateNode) stop_on_collide: BoolProperty( name='Stop when colliding', description='Stop attraction if they are colliding', update=updateNode) mode: EnumProperty(name='Mode', description='Algorithm used for calculation', items=enum_item_4(['Brute Force', 'Kd-tree']), default='Kd-tree', update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', "Strength").prop_name = 'strength' self.inputs.new('SvStringsSocket', "Decay").prop_name = 'decay' self.inputs.new('SvStringsSocket', "Max Distance").prop_name = 'max_distance' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): if scipy is not None and Cython is not None: layout.prop(self, 'mode') layout.prop(self, 'stop_on_collide') def process(self): if not any(s.is_linked for s in self.outputs): return strength = self.inputs["Strength"].sv_get(deepcopy=False) decay = self.inputs["Decay"].sv_get(deepcopy=False) max_distance = self.inputs["Max Distance"].sv_get(deepcopy=False) use_kdtree = self.mode in "Kd-tree" and scipy is not None and Cython is not None forces_out = [] for force_params in zip_long_repeat(strength, decay, max_distance): forces_out.append( SvAttractionForce(*force_params, stop_on_collide=self.stop_on_collide, use_kdtree=use_kdtree)) self.outputs[0].sv_set([forces_out])
class SvFormulaNodeMk5(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Formula Tooltip: Calculate by custom formula. """ bl_idname = 'SvFormulaNodeMk5' bl_label = 'Formula' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_FORMULA' def on_update(self, context): self.adjust_sockets() updateNode(self, context) def on_update_dims(self, context): if self.output_dimensions < 4: self.formula4 = "" if self.output_dimensions < 3: self.formula3 = "" if self.output_dimensions < 2: self.formula2 = "" self.adjust_sockets() updateNode(self, context) output_dimensions: IntProperty(name="Dimensions", default=1, min=1, max=4, update=on_update_dims) formula1: StringProperty(default="x+y", update=on_update) formula2: StringProperty(update=on_update) formula3: StringProperty(update=on_update) formula4: StringProperty(update=on_update) separate: BoolProperty(name="Separate", default=False, update=updateNode) wrapping: bpy.props.EnumProperty( items=[(k, k, '', i) for i, k in enumerate(["-1", "0", "+1"])], description= "+1: adds a set of square brackets around the output\n 0: Keeps result unchanged\n-1: Removes a set of outer square brackets", default="0", update=updateNode) use_ast: BoolProperty(name="AST", description="uses the ast.literal_eval module", update=updateNode) as_list: BoolProperty(name="List output", description="Forces a regular list output", update=updateNode) ui_message: StringProperty(name="ui message") list_match: EnumProperty(name="List Match", description="Behavior on different list lengths", items=numpy_list_match_modes, default="REPEAT", update=updateNode) def update_output_socket(self, context): if self.outputs[0].bl_idname != socket_dict[self.output_type]: self.outputs.remove(self.outputs[0]) self.outputs.new(socket_dict[self.output_type], 'Result') updateNode(self, context) output_type: EnumProperty(name="Output", description="Behavior on different list lengths", items=enum_item_4(socket_dict.keys()), default="Number_/_Generic", update=update_output_socket) def formulas(self): return [self.formula1, self.formula2, self.formula3, self.formula4] def formula(self, k): return self.formulas()[k] def draw_buttons(self, context, layout): if self.ui_message: r = layout.row() r.alert = True r.label(text=self.ui_message, icon='INFO') layout.prop(self, "formula1", text="") if self.output_dimensions > 1: layout.prop(self, "formula2", text="") if self.output_dimensions > 2: layout.prop(self, "formula3", text="") if self.output_dimensions > 3: layout.prop(self, "formula4", text="") row = layout.row() if self.inputs: row.prop(self, "separate", text="Split", toggle=True) else: row.prop(self, "use_ast", text="", icon="SCRIPTPLUGINS") row.prop(self, "wrapping", expand=True) def draw_buttons_ext(self, context, layout): layout.prop(self, "output_dimensions") layout.prop(self, "list_match") layout.prop(self, "as_list") layout.prop(self, "output_type") self.draw_buttons(context, layout) def sv_init(self, context): self.width = 230 self.inputs.new('SvFormulaSocket', "x") self.outputs.new('SvStringsSocket', "Result") def get_variables(self): variables = set() for formula in self.formulas(): vs = get_variables(formula) variables.update(vs) return list(sorted(variables)) def adjust_sockets(self): variables = self.get_variables() # if current node sockets match the variables sequence, do nothing skip # this is the logic path that will be encountered most often. if len(self.inputs) == len(variables): if variables == [socket.name for socket in self.inputs]: # self.info("no UI change: socket inputs same") return # else to avoid making things complicated we rebuild the UI inputs, even when it is technically sub optimal self.hot_reload_sockets() def clear_and_repopulate_sockets_from_variables(self): self.inputs.clear() variables = self.get_variables() for v in variables: self.inputs.new('SvFormulaSocket', v) def hot_reload_sockets(self): """ function hoisted from functorb, with deletions and edits - store current input socket links by name/origin - wipe all inputs - recreate new sockets from variables - relink former links by name on this socket, but by index from their origin. """ self.debug('handling input wipe and relink') nodes = self.id_data.nodes node_tree = self.id_data # if any current connections... gather them reconnections = [] for i in (i for i in self.inputs if i.is_linked): for L in i.links: link = lambda: None link.from_node = L.from_socket.node.name link.from_socket = L.from_socket.index # index used here because these can come from reroute link.to_socket = L.to_socket.name # this node will always have unique socket names reconnections.append(link) depths = {} transform = {} for node_input in self.inputs: depths[node_input.name] = node_input.depth transform[node_input.name] = node_input.transform self.clear_and_repopulate_sockets_from_variables() for node_input in self.inputs: if node_input.name in depths: node_input.depth = depths[node_input.name] node_input.transform = transform[node_input.name] # restore connections where applicable (by socket name), if no links.. this is a no op. for link in reconnections: try: from_part = nodes[link.from_node].outputs[link.from_socket] to_part = self.inputs[link.to_socket] node_tree.links.new(from_part, to_part) except Exception as err: str_from = f'nodes[{link.from_node}].outputs[{link.from_socket}]' str_to = f'nodes[{self}].inputs[{link.to_socket}]' self.exception(f'failed: {str_from} -> {str_to}') self.exception(err) def sv_update(self): ''' update analyzes the state of the node and returns if the criteria to start processing are not met. ''' if not any(len(formula) for formula in self.formulas()): return self.adjust_sockets() def get_input(self): variables = self.get_variables() inputs = {} for var in variables: if var in self.inputs and self.inputs[var].is_linked: inputs[var] = self.inputs[var].sv_get() return inputs def all_inputs_connected(self): if self.inputs: return all([socket.is_linked for socket in self.inputs]) return True def migrate_from(self, old_node): self.output_dimensions = old_node.dimensions def process(self): if not self.outputs[0].is_linked: return # if the user specifies a variable, they must also link a value into that socket, this will prevent Exception self.ui_message = "" if not self.all_inputs_connected(): self.ui_message = "node not fully connected" return var_names = self.get_variables() inputs = self.get_input() results = [] if var_names: input_values = [inputs.get(name) for name in var_names] matching_f = list_match_func[self.list_match] parameters = matching_f(input_values) desired_levels = [s.depth for s in self.inputs] ops = [ self.formulas(), self.separate, var_names, [s.transform for s in self.inputs], self.as_list ] results = recurse_f_level_control(parameters, ops, formula_func, matching_f, desired_levels) else: def joined_formulas(f1, f2, f3, f4): built_string = "" if f1: built_string += f1 if f2: built_string += f",{f2}" if f3: built_string += f",{f3}" if f4: built_string += f",{f4}" return list(ast.literal_eval(built_string)) if self.use_ast: results = joined_formulas(*self.formulas()) else: vector = [] for formula in self.formulas(): if formula: value = safe_eval(formula, dict()) vector.append(value) results.extend(vector) if self.wrapping == "+1": results = [results] elif self.wrapping == "-1": results = results[0] if len(results) else results self.outputs['Result'].sv_set(results)
class SvLoopInNode(SverchCustomTreeNode, bpy.types.Node): """ Triggers: For Loop Start, Tooltip: Start node to define a nodes for-loop. """ bl_idname = 'SvLoopInNode' bl_label = 'Loop In' bl_icon = 'FILE_REFRESH' def update_iterations(self, context): if self.iterations > self.max_iterations: self.iterations = self.max_iterations else: updateNode(self, context) iterations: IntProperty( name='Iterations', description='Times to repeat the loop (define maximum value on N-Panel properties)', default=1, min=0, update=update_iterations) def update_max_iterations(self, context): if self.iterations > self.max_iterations: self.iterations = self.max_iterations max_iterations: IntProperty( name='Max Iterations', description='Maximum allowed iterations', default=5, min=2, update=update_max_iterations) linked_to_loop_out: BoolProperty( name='linked_to_loop_out', description='Maximum allowed iterations', default=False) print_to_console: BoolProperty( name='Print progress in console', description='Maximum allowed iterations', default=False) def update_mode(self, context): self.inputs['Iterations'].hide_safe = self.mode == "For_Each" if self.mode == "For_Each": self.outputs[1].label = 'Item Number' self.outputs[2].label = 'Total Items' else: self.outputs[1].label = 'Loop Number' self.outputs[2].label = 'Total Loops' updateNode(self, context) mode: EnumProperty( name='Mode', description='Maximum allowed iterations', items=enum_item_4(['Range', 'For Each']), default='Range', update=update_mode) list_match: EnumProperty( name="List Match", description="Behavior on different list lengths", items=numpy_list_match_modes, default="REPEAT", update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Iterations').prop_name = "iterations" self.inputs.new('SvStringsSocket', 'Data 0') self.outputs.new('SvLoopControlSocket', 'Loop Out') self.outputs["Loop Out"].link_limit = 1 self.outputs.new('SvStringsSocket', 'Loop Number') self.outputs.new('SvStringsSocket', 'Total Loops') def draw_buttons(self, ctx, layout): if not self.linked_to_loop_out: layout.operator("node.create_loop_out", icon='CON_FOLLOWPATH', text="Create Loop Out") layout.prop(self, 'mode', expand=True) def draw_buttons_ext(self, ctx, layout): layout.prop(self, 'mode', expand=True) if self.mode == "Range": layout.prop(self, "max_iterations") else: layout.prop(self, "list_match") layout.prop(self, 'print_to_console') socket_labels = layout.box() socket_labels.label(text="Socket Labels") for socket in self.inputs[1:]: socket_labels.prop(socket, "label", text=socket.name) socket_labels.operator("node.update_loop_in_socket_labels", icon='CON_FOLLOWPATH', text="Update Socket Labels") def rclick_menu(self, context, layout): layout.prop_menu_enum(self, 'mode') if self.mode == "Range": layout.prop(self, "max_iterations") else: layout.prop_menu_enum(self, 'list_match') def sv_update(self): # socket handling if self.inputs[-1].links: name_input = 'Data '+str(len(self.inputs)-1) name_output = 'Data '+str(len(self.inputs)-2) other_socket = self.inputs[-1].other new_label = other_socket.label if other_socket.label else other_socket.name self.inputs[-1].label = new_label self.inputs.new('SvStringsSocket', name_input) self.outputs.new('SvStringsSocket', name_output) else: while len(self.inputs) > 2 and not self.inputs[-2].links: self.inputs.remove(self.inputs[-1]) self.outputs.remove(self.outputs[-1]) # match input socket n with output socket n for idx, socket in enumerate(self.inputs[1:]): if socket.links: if type(socket.links[0].from_socket) != type(self.outputs[socket.name]): self.outputs.remove(self.outputs[socket.name]) self.outputs.new(socket.links[0].from_socket.bl_idname, socket.name) self.outputs.move(len(self.outputs)-1, idx+3) for inp, outp in zip(self.inputs[1:], self.outputs[3:]): outp.label = inp.label if self.outputs: if self.outputs[0].is_linked and self.outputs[0].links[0].to_socket.node.bl_idname == 'SvLoopOutNode': self.linked_to_loop_out = True else: self.linked_to_loop_out = False def process(self): self.outputs[0].sv_set([["Link to Loop Out node"]]) self.outputs[1].sv_set([[0]]) if self.mode == 'Range': iterations = int(self.inputs['Iterations'].sv_get()[0][0]) self.outputs['Total Loops'].sv_set([[min(iterations, self.max_iterations)]]) for inp, outp in zip(self.inputs[1:-1], self.outputs[3:]): outp.sv_set(inp.sv_get(deepcopy=False, default=[])) else: lens = [] for inp, outp in zip(self.inputs[1:-1], self.outputs[3:]): data = inp.sv_get(deepcopy=False, default=[]) lens.append(len(data)) outp.sv_set([data[0]]) self.outputs[2].sv_set([[max(lens)]])
class SvEasingNode(bpy.types.Node, SverchCustomTreeNode): '''Curved interpolation''' bl_idname = 'SvEasingNode' bl_label = 'Easing 0..1' sv_icon = 'SV_EASING' n_id: StringProperty(default='') activate: BoolProperty(name='Show', description='Activate drawing', default=True, update=updateNode) selected_mode: EnumProperty(items=easing_list, description="Set easing Function to:", default="0", update=updateNode) in_float: FloatProperty(min=0.0, max=1.0, default=0.0, name='Float Input', description='input to the easy function', update=updateNode) selected_theme_mode: EnumProperty(items=enum_item_4( ["default", "scope", "sniper"]), default="default", update=updateNode) def custom_draw_socket(self, socket, context, l): info = socket.get_socket_info() r = l.row(align=True) split = r.split(factor=0.85) r1 = split.row(align=True) r1.prop(self, "selected_mode", text="") r1.prop(self, 'activate', icon='NORMALIZE_FCURVES', text="") if info: r2 = split.row() r2.label(text=info) def draw_buttons_ext(self, context, l): l.prop(self, "selected_theme_mode") def sv_init(self, context): self.inputs.new('SvStringsSocket', "Float").prop_name = 'in_float' self.outputs.new('SvStringsSocket', "Float").custom_draw = 'custom_draw_socket' self.get_and_set_gl_scale_info() def get_drawing_attributes(self): """ adjust render location based on preference multiplier setting """ x, y = [ int(j) for j in (Vector(self.absolute_location) + Vector((self.width + 20, 0)))[:] ] try: with sv_preferences() as prefs: multiplier = prefs.render_location_xy_multiplier scale = prefs.render_scale except: # print('did not find preferences - you need to save user preferences') multiplier = 1.0 scale = 1.0 x, y = [x * multiplier, y * multiplier] return x, y, scale, multiplier def generate_shader(self, geom): shader = gpu.shader.from_builtin('2D_SMOOTH_COLOR') batch = batch_for_shader(shader, 'LINES', { "pos": geom.vertices, "color": geom.vertex_colors }, indices=geom.indices) return batch, shader def generate_graph_geom(self, config): geom = lambda: None x, y = config.loc size = 140 * config.scale back_color, grid_color, line_color = config.palette easing_func = config.easing_func # background geom w = size h = size geom.background_coords = [(x, y), (x + w, y), (w + x, y - h), (x, y - h)] geom.background_indices = [(0, 1, 2), (0, 2, 3)] # grid geom and associated vertex colors num_divs = 8 offset = size / num_divs vertices = [] vertex_colors = [] indices = [] for i in range(num_divs + 1): xpos1 = x + (i * offset) ypos1 = y ypos2 = y - size vertices.extend([[xpos1, ypos1], [xpos1, ypos2]]) ypos = y - (i * offset) vertices.extend([[x, ypos], [x + size, ypos]]) vertex_colors.extend([ grid_color, ] * 4) for i in range(0, (num_divs + 1) * 4, 2): indices.append([i, i + 1]) # graph-line geom and associated vertex colors idx_offset = len(vertices) graphline = [] num_points = 100 seg_diff = 1 / num_points for i in range(num_points + 1): _px = x + ((i * seg_diff) * size) _py = y - (1 - easing_func(i * seg_diff) * size) - size graphline.append([_px, _py]) vertex_colors.append(line_color) vertices.extend(graphline) for i in range(num_points): indices.append([idx_offset + i, idx_offset + i + 1]) geom.vertices = vertices geom.vertex_colors = vertex_colors geom.indices = indices return geom def process(self): p = self.inputs['Float'].sv_get() n_id = node_id(self) # end early nvBGL.callback_disable(n_id) float_out = self.outputs['Float'] easing_func = easing_dict.get(int(self.selected_mode)) if float_out.is_linked: out = [] for obj in p: r = [] for i in obj: r.append(easing_func(i)) out.append(r) float_out.sv_set(out) else: float_out.sv_set([[None]]) if self.activate: config = lambda: None x, y, scale, multiplier = self.get_drawing_attributes() config.loc = (x, y) config.palette = palette_dict.get(self.selected_theme_mode)[:] config.scale = scale config.easing_func = easing_func geom = self.generate_graph_geom(config) config.batch, config.shader = self.generate_shader(geom) draw_data = { 'mode': 'custom_function', 'tree_name': self.id_data.name[:], 'loc': (x, y), 'custom_function': simple28_grid_xy, 'args': (geom, config) } nvBGL.callback_enable(n_id, draw_data) def sv_free(self): nvBGL.callback_disable(node_id(self)) def sv_copy(self, node): # reset n_id on copy self.n_id = '' def sv_update(self): # handle disconnecting sockets, also disconnect drawing to view? if not ("Float" in self.inputs): return try: if not self.inputs[0].other: nvBGL.callback_disable(node_id(self)) except: print('Easing node update holdout (not a problem)')
class SvEasingNode(bpy.types.Node, SverchCustomTreeNode): '''Curved interpolation''' bl_idname = 'SvEasingNode' bl_label = 'Easing 0..1' sv_icon = 'SV_EASING' n_id: StringProperty(default='') activate: BoolProperty(name='Show', description='Activate drawing', default=True, update=updateNode) selected_mode: EnumProperty(items=easing_list, description="Set easing Function to:", default="0", update=updateNode) in_float: FloatProperty(min=0.0, max=1.0, default=0.0, name='Float Input', description='input to the easy function', update=updateNode) selected_theme_mode: EnumProperty(items=enum_item_4( ["default", "scope", "sniper"]), default="sniper", update=updateNode) location_theta: FloatProperty(name="location theta") def custom_draw_socket(self, socket, context, l): r = l.row(align=True) split = r.split(factor=0.85) r1 = split.row(align=True) r1.prop(self, "selected_mode", text="") r1.prop(self, 'activate', icon='NORMALIZE_FCURVES', text="") r2 = split.row() r2.label(text=f"{socket.objects_number or ''}") def draw_buttons_ext(self, context, l): l.prop(self, "selected_theme_mode") def sv_init(self, context): self.inputs.new('SvStringsSocket', "Float").prop_name = 'in_float' self.outputs.new('SvStringsSocket', "Float").custom_draw = 'custom_draw_socket' self.id_data.update_gl_scale_info() def get_offset(self): return [ int(j) for j in (Vector(self.absolute_location) + Vector((self.width + 20, 0)))[:] ] def get_drawing_attributes(self): """ adjust render location based on preference multiplier setting """ from sverchok.settings import get_param self.location_theta = get_param('render_location_xy_multiplier', 1.0) def generate_graph_geom(self, config): geom = lambda: None x, y = config.loc size = 140 back_color, grid_color, line_color = config.palette easing_func = config.easing_func # background geom w = size h = size geom.background_coords = [(x, y), (x + w, y), (w + x, y - h), (x, y - h)] geom.background_indices = [(0, 1, 2), (0, 2, 3)] # grid geom and associated vertex colors num_divs = 8 offset = size / num_divs vertices = [] vertex_colors = [] indices = [] for i in range(num_divs + 1): xpos1 = x + (i * offset) ypos1 = y ypos2 = y - size vertices.extend([[xpos1, ypos1], [xpos1, ypos2]]) ypos = y - (i * offset) vertices.extend([[x, ypos], [x + size, ypos]]) vertex_colors.extend([ grid_color, ] * 4) for i in range(0, (num_divs + 1) * 4, 2): indices.append([i, i + 1]) # graph-line geom and associated vertex colors idx_offset = len(vertices) graphline = [] num_points = 100 seg_diff = 1 / num_points for i in range(num_points + 1): _px = x + ((i * seg_diff) * size) _py = y - (1 - easing_func(i * seg_diff) * size) - size graphline.append([_px, _py]) vertex_colors.append(line_color) vertices.extend(graphline) for i in range(num_points): indices.append([idx_offset + i, idx_offset + i + 1]) geom.vertices = vertices geom.vertex_colors = vertex_colors geom.indices = indices return geom def process(self): p = self.inputs['Float'].sv_get() n_id = node_id(self) # end early nvBGL.callback_disable(n_id) float_out = self.outputs['Float'] easing_func = easing_dict.get(int(self.selected_mode)) if float_out.is_linked: out = [] for obj in p: r = [] for i in obj: r.append(easing_func(i)) out.append(r) float_out.sv_set(out) else: float_out.sv_set([[None]]) if self.activate and self.inputs[0].is_linked: self.get_drawing_attributes() config = lambda: None config.loc = (0, 0) config.palette = palette_dict.get(self.selected_theme_mode)[:] config.easing_func = easing_func geom = self.generate_graph_geom(config) # config.batch, config.shader = self.generate_shader(geom) draw_data = { 'mode': 'custom_function', 'tree_name': self.id_data.name[:], 'node_name': self.name[:], 'loc': get_drawing_location, 'custom_function': simple28_grid_xy, 'args': (geom, config) } nvBGL.callback_enable(n_id, draw_data) def sv_free(self): nvBGL.callback_disable(node_id(self)) def sv_copy(self, node): self.n_id = ''
class SvNewSocketOpExp(Operator, MonadOpCommon): """Generate new socket""" bl_idname = "node.sverchok_new_socket_exp" bl_label = "New Socket" # private kind: StringProperty(name="io kind") # client socket_type: EnumProperty(items=socket_types, default="SvStringsSocket") new_prop_name: StringProperty(name="prop name") new_prop_type: EnumProperty(name="prop type", items=enum_item_4(["Int", "Float"]), default='Int') new_prop_description: StringProperty(name="description", default="lazy?") # no subtype. it is not worth it. enable_min: BoolProperty(default=False, name="enable min") enable_max: BoolProperty(default=False, name="enable max") enable_soft_min: BoolProperty(default=False, name="enable soft min") enable_soft_max: BoolProperty(default=False, name="enable soft max") # int specific new_prop_int_default: IntProperty(default=0, name="default") new_prop_int_min: IntProperty(default=-2**31, name="min") new_prop_int_max: IntProperty(default=2**31 - 1, name="max") new_prop_int_soft_min: IntProperty(default=-2**31, name="soft min") new_prop_int_soft_max: IntProperty(default=2**31 - 1, name="soft max") new_prop_int_step: IntProperty(default=1, name="step") # float specific new_prop_float_default: FloatProperty(default=0, name="default") new_prop_float_min: FloatProperty(default=-2**31, name="min") new_prop_float_max: FloatProperty(default=2**31 - 1, name="max") new_prop_float_soft_min: FloatProperty(default=-2**31, name="soft min") new_prop_float_soft_max: FloatProperty(default=2**31 - 1, name="soft max") new_prop_float_step: FloatProperty(default=1.0, name="step") @classmethod def poll(cls, context): try: if context.space_data.edit_tree.bl_idname == 'SverchGroupTreeType': return not context.space_data.edit_tree.library except: return False def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def get_display_props(self): prop_prefix = f"new_prop_{self.new_prop_type.lower()}_" props = "default", "min", "max", "soft_min", "soft_max", "step" return [(prop_prefix + prop) for prop in props] def draw(self, context): layout = self.layout col1 = layout.column() socket_row = col1.row() socket_row.prop(self, 'socket_type', text='Socket Type', expand=True) col1.prop(self, 'new_prop_name', text="Name") if self.kind == "outputs": # there are no other properties to configure for the <output node> return col1.prop(self, 'new_prop_type') if self.socket_type == "SvStringsSocket": default, min, max, soft_min, soft_max, step = self.get_display_props( ) col1.prop(self, default, text="default") # enable min / max row1 = col1.row(align=True) row1_col1_r = row1.column().row(align=True) row1_col1_r.active = self.enable_min row1_col1_r.prop(self, "enable_min", text="", icon="CHECKMARK") row1_col1_r.prop(self, min) row1_col2_r = row1.column().row(align=True) row1_col2_r.active = self.enable_max row1_col2_r.prop(self, "enable_max", text="", icon="CHECKMARK") row1_col2_r.prop(self, max) # enable soft min / max row2 = col1.row(align=True) row2_col1_r = row2.column().row(align=True) row2_col1_r.active = self.enable_soft_min row2_col1_r.prop(self, "enable_soft_min", text="", icon="CHECKMARK") row2_col1_r.prop(self, soft_min) row2_col2_r = row2.column().row(align=True) row2_col2_r.active = self.enable_soft_max row2_col2_r.prop(self, "enable_soft_max", text="", icon="CHECKMARK") row2_col2_r.prop(self, soft_max) col1.prop(self, "new_prop_description") def get_prop_dict(self): prop_dict = {} prop_dict['name'] = self.new_prop_name # we do not set the slider on the <output node> sockets if self.kind == 'outputs': return {} sig = self.new_prop_type.lower() if sig in {"int", "float"}: # prop_dict['update'] = updateNode prop_dict['default'] = getattr(self, f"new_prop_{sig}_default") properties = 'min max soft_min soft_max'.split() for prop in properties: if getattr(self, f"enable_{prop}"): prop_dict[prop] = getattr(self, f"new_prop_{sig}_{prop}") return prop_dict def execute(self, context): monad = context.space_data.edit_tree monad_node = monad.instances[0] with monad_node.sv_throttle_tree_update(): self.add_node(monad) return {'FINISHED'} def add_node(self, tree): class SvSingleSocketNode(): bl_idname = 'SvSingleSocketNode' bl_label = 'DO NOT USE' bl_icon = 'MOD_CURVE' # [x] define new node prop_dict = self.get_prop_dict() bases = (SvSingleSocketNode, Node, SverchCustomTreeNode) prop_func = IntProperty if self.new_prop_type == "int" else FloatProperty # [x] -- add prop if needed (only when inputs) cls_dict = {} cls_dict['__annotations__'] = {} cls_dict['__annotations__'][self.new_prop_name] = prop_func( **prop_dict) cls_name = SvSingleSocketNode.bl_idname cls_ref = type(cls_name, bases, cls_dict) # [x] register new node (but unregister first if needed..) old_cls_ref = get_node_class_reference(cls_name) if old_cls_ref: unregister_node_class(old_cls_ref) register_node_class(cls_ref) # [x] add node property_node = tree.nodes.new(cls_name) # [x] -- add socket to node (add both in the template) io_sockets = getattr(property_node, self.kind) if self.kind == 'outputs': io_sockets.new(self.socket_type, self.new_prop_name) else: io_sockets.new(self.socket_type, prop_dict['name']).prop_name = prop_dict['name'] # [x] link node io_node = tree.input_node if self.kind == 'inputs' else tree.output_node out2in = self.kind == 'inputs' AB_LINK = (io_node.outputs[-1], io_sockets[-1]) if out2in else (io_sockets[-1], io_node.inputs[-1]) tree.links.new(*AB_LINK) # [x] unlink, remove node # tree.links.remove(*AB_LINK) tree.nodes.remove(property_node) # [x] unregister node unregister_node_class(cls_ref)
class SvSvgFillStrokeNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: color, line width Tooltip: Define Fill /Stroke Style for svg objects """ bl_idname = 'SvSvgFillStrokeNode' bl_label = 'Fill / Stroke SVG' bl_icon = 'MESH_CIRCLE' sv_icon = 'SV_FILL_STROKE_SVG' replacement_nodes = [('SvSvgFillStrokeNodeMk2', None, None)] def update_actual_sockets(self): self.inputs['Fill Color'].hide_safe = self.fill_mode == 'NONE' self.inputs['Stroke Color'].hide_safe = self.stroke_mode == 'NONE' self.inputs['Stroke Width'].hide_safe = self.stroke_mode == 'NONE' self.inputs[ 'Dash Pattern'].hide_safe = self.stroke_type == 'Solid' or self.stroke_mode == 'NONE' def update_sockets(self, context): self.update_actual_sockets() updateNode(self, context) fill_modes = [('NONE', 'None', '', 0), ('FLAT', 'Flat', '', 1)] fill_mode: EnumProperty(name='Fill', items=fill_modes, default="FLAT", update=update_sockets) stroke_mode: EnumProperty(name='Stroke', items=fill_modes, update=update_sockets) stroke_width: FloatProperty(name='Stroke width', description='Stroke width', default=1.0, update=updateNode) stroke_color: FloatVectorProperty(name="Stroke Color", description="Color", size=4, min=0.0, max=1.0, default=(0, 0, 0, 1), subtype='COLOR', update=update_sockets) stroke_linecap: EnumProperty(name='Cap', description='Line Cap', items=enum_item_4(['Butt', 'Round', 'Square']), update=updateNode) stroke_linejoin: EnumProperty(name='Join', description='Line Join', items=enum_item_4( ['Bevel', 'Miter', 'Round']), update=updateNode) paint_order: EnumProperty(name='Order', description="Paint Order", items=enum_item_4(['Fill Stroke', 'Stroke Fill']), update=updateNode) stroke_type: EnumProperty(name='Type', items=enum_item_4(['Solid', 'Dashed']), update=update_sockets) fill_color: FloatVectorProperty(name="Fill Color", description="Color", size=4, min=0.0, max=1.0, default=(0, 0, 0, 1), subtype='COLOR', update=updateNode) # fill-rule stroke-dasharray stroke-linecap, stroke-linejoin def sv_init(self, context): self.inputs.new('SvColorSocket', "Fill Color").prop_name = 'fill_color' self.inputs.new('SvColorSocket', "Stroke Color").prop_name = 'stroke_color' self.inputs.new('SvStringsSocket', "Stroke Width").prop_name = 'stroke_width' self.inputs.new('SvStringsSocket', "Dash Pattern") self.update_actual_sockets() self.outputs.new('SvSvgSocket', "Fill / Stroke") def draw_buttons(self, context, layout): layout.prop(self, "fill_mode", expand=False) col = layout.column(align=True) col.prop(self, "stroke_mode", expand=False) if self.stroke_mode != 'NONE': col.prop(self, "stroke_linecap", expand=False) col.prop(self, "stroke_linejoin", expand=False) col.prop(self, "stroke_type", expand=False) if self.fill_mode != 'NONE': layout.prop(self, "paint_order", expand=False) def process(self): if not self.outputs[0].is_linked: return params_in = [ s.sv_get(deepcopy=False, default=[[None]]) for s in self.inputs ] attributes_out = [] for params in zip(*mlr(params_in)): attributes = [] dash_pattern = params[-1] for fill_color, stroke_color, stroke_width in zip( *mlr(params[:-1])): attributes.append( SvgAttributes(fill_color, stroke_width, stroke_color, dash_pattern, self)) attributes_out.append(attributes) self.outputs[0].sv_set(attributes_out)
class SvSvgMeshNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Polygons/Edges SVG Tooltip: Generate SVG Mesh Data """ bl_idname = 'SvSvgMeshNode' bl_label = 'Mesh SVG' bl_icon = 'MESH_CIRCLE' sv_icon = 'SV_MESH_SVG' elements_sort: BoolProperty( name='Elements Sort', description='Sort Polygons / Edges according to distance', update=updateNode) sort_mode: EnumProperty(name="Sort mode", description="How polygons/ edges are sorted", items=z_sort_mode_items, update=updateNode) invert_sort: BoolProperty(name='Invert Order', update=updateNode) def update_sockets(self, context): self.inputs[ 'Projection Plane'].hide_safe = self.projection_mode == "Orthogrphic" and self.projection_plane != 'User' updateNode(self, context) projection_mode: EnumProperty(name='Mode', description='Projection mode', items=enum_item_4( ["Orthogrphic", 'Perspective']), update=update_sockets) projection_plane: EnumProperty(name='Plane', description='Projection plane', items=enum_item_4( ['XY', 'XZ', 'YZ', 'User']), update=update_sockets) def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Vertices") self.inputs.new('SvStringsSocket', "Polygons / Edges") self.inputs.new('SvMatrixSocket', "Projection Plane") self.inputs["Projection Plane"].hide_safe = True self.inputs.new('SvMatrixSocket', "Offset") self.inputs.new('SvSvgSocket', "Fill / Stroke") self.outputs.new('SvSvgSocket', "SVG Objects") self.outputs.new('SvVerticesSocket', "Verts to project") def draw_buttons(self, context, layout): layout.prop(self, 'projection_mode') if self.projection_mode == 'Orthogrphic': layout.prop(self, 'projection_plane') layout.prop(self, 'elements_sort') if self.elements_sort: layout.prop(self, 'sort_mode') layout.prop(self, 'invert_sort') def process(self): if not self.outputs[0].is_linked: return verts_in = self.inputs['Vertices'].sv_get(deepcopy=True) pols_in = self.inputs['Polygons / Edges'].sv_get(deepcopy=True) planes_in = self.inputs['Projection Plane'].sv_get(deepcopy=True, default=[Matrix()]) offset_in = self.inputs['Offset'].sv_get(deepcopy=True, default=[Matrix()]) atts_in = self.inputs['Fill / Stroke'].sv_get(deepcopy=False, default=None) shapes = [] verts_to_project = [] if self.projection_mode == 'Orthogrphic': projection_func = ortho_projection_func_dict[self.projection_plane] else: projection_func = perspective_proyection for verts, pols, p_plane, offset, atts in zip( *mlr([verts_in, pols_in, planes_in, offset_in, atts_in])): verts_p = projection_func(verts, p_plane, offset) verts_to_project.append(verts_p) shapes.append(SvgMesh(verts_p, pols, atts, self)) self.outputs[0].sv_set(shapes) self.outputs[1].sv_set(verts_to_project)
class SvPulgaAttractorsForceNodeMk2(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Attraction Points Tooltip: Attractor/Repeller points with distance limit """ bl_idname = 'SvPulgaAttractorsForceNodeMk2' bl_label = 'Pulga Attractors Force' bl_icon = 'MOD_PHYSICS' sv_icon = 'SV_PULGA_ATTRACTORS_FORCE' strength: FloatProperty(name='Strength', description='Attractors Force magnitude', default=1.0, precision=3, update=updateNode) max_distance: FloatProperty( name='Max Distance', description='Attractors maximum influence distance', default=10.0, precision=3, update=updateNode) decay_power: FloatProperty( name='Decay', description= 'Decay with distance 0 = no decay, 1 = linear, 2 = cubic...', default=0.0, precision=3, update=updateNode) def update_sockets(self, context): self.inputs['Direction'].hide_safe = self.mode not in ['Line', 'Plane'] if self.mode == 'Line': self.inputs['Direction'].label = 'Direction' else: self.inputs['Direction'].label = 'Normal' updateNode(self, context) mode: EnumProperty(name='Mode', items=enum_item_4(['Point', 'Line', 'Plane']), default='Point', update=update_sockets) def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Location") self.inputs.new('SvVerticesSocket', "Direction") self.inputs["Direction"].hide_safe = True self.inputs.new('SvStringsSocket', "Strength").prop_name = 'strength' self.inputs.new('SvStringsSocket', "Max. Distance").prop_name = 'max_distance' self.inputs.new('SvStringsSocket', "Decay").prop_name = 'decay_power' self.outputs.new('SvPulgaForceSocket', "Force") def draw_buttons(self, context, layout): layout.prop(self, 'mode') def process(self): if not any(s.is_linked for s in self.outputs): return loc = self.inputs["Location"].sv_get(deepcopy=False) strength = self.inputs["Strength"].sv_get(deepcopy=False) max_distance = self.inputs["Max. Distance"].sv_get(deepcopy=False) decay_power = self.inputs["Decay"].sv_get(deepcopy=False) forces_out = [] forces_out = [] if self.mode == 'Point': for force in zip_long_repeat(loc, strength, max_distance, decay_power): forces_out.append(SvAttractorsForce(*force)) else: direction = self.inputs["Direction"].sv_get(deepcopy=False) force_class = SvAttractorsLineForce if self.mode == 'Line' else SvAttractorsPlaneForce for force in zip_long_repeat(loc, direction, strength, max_distance, decay_power): forces_out.append(force_class(*force)) self.outputs[0].sv_set([forces_out])