2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,46 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_AddonInfoNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AddonInfoNode"
bl_label = "Addon Info"
node_color = "DEFAULT"
def on_create(self, context):
self.add_string_output("Name")
self.add_string_output("Description")
self.add_string_output("Author")
self.add_string_output("Location")
self.add_string_output("Warning")
self.add_string_output("Doc URL")
self.add_string_output("Tracker URL")
self.add_string_output("Category")
self.add_integer_vector_output("Version")
self.add_integer_vector_output("Blender Version")
def evaluate(self, context):
self.outputs[0].python_value = f"bpy.context.scene.sn.addon_name"
self.outputs[1].python_value = f"bpy.context.scene.sn.description"
self.outputs[2].python_value = f"bpy.context.scene.sn.author"
self.outputs[3].python_value = f"bpy.context.scene.sn.location"
self.outputs[4].python_value = f"bpy.context.scene.sn.warning"
self.outputs[5].python_value = f"bpy.context.scene.sn.doc_url"
self.outputs[6].python_value = f"bpy.context.scene.sn.tracker_url"
self.outputs[7].python_value = f"bpy.context.scene.sn.category"
self.outputs[8].python_value = f"tuple(bpy.context.scene.sn.version)"
self.outputs[9].python_value = f"tuple(bpy.context.scene.sn.blender)"
def evaluate_export(self, context):
self.outputs[0].python_value = f"'{bpy.context.scene.sn.addon_name}'"
self.outputs[1].python_value = f"'{bpy.context.scene.sn.description}'"
self.outputs[2].python_value = f"'{bpy.context.scene.sn.author}'"
self.outputs[3].python_value = f"'{bpy.context.scene.sn.location}'"
self.outputs[4].python_value = f"'{bpy.context.scene.sn.warning}'"
self.outputs[5].python_value = f"'{bpy.context.scene.sn.doc_url}'"
self.outputs[6].python_value = f"'{bpy.context.scene.sn.tracker_url}'"
self.outputs[7].python_value = f"'{bpy.context.scene.sn.category}'"
self.outputs[8].python_value = str(tuple(bpy.context.scene.sn.version))
self.outputs[9].python_value = str(tuple(bpy.context.scene.sn.blender))
@@ -0,0 +1,30 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_2DViewZoomNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_2DViewZoomNode"
bl_label = "2D View Zoom"
def on_create(self, context):
self.add_property_input("Area")
self.add_float_output("Zoom Level")
def evaluate(self, context):
self.code_imperative = """
def get_zoom_level(area):
ui_scale = bpy.context.preferences.system.ui_scale
for region in area.regions:
if region.type == "WINDOW":
test_length = 1000
x0, y0 = region.view2d.view_to_region(0, 0, clip=False)
x1, y1 = region.view2d.view_to_region(test_length, test_length, clip=False)
xl = x1 - x0
yl = y1 - y0
return (math.sqrt(xl**2 + yl**2) / test_length) * ui_scale
return 1 * ui_scale
"""
self.code_import = "import math"
self.outputs[0].python_value = f"get_zoom_level({self.inputs[0].python_value})"
@@ -0,0 +1,73 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_AreaByTypeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AreaByTypeNode"
bl_label = "Area By Type"
node_color = "PROPERTY"
def on_create(self, context):
self.add_property_input("Screen")
self.add_property_output("First Area")
self.add_property_output("Last Area")
self.add_property_output("Biggest Area")
self.add_list_output("All Areas")
self.add_boolean_output("Area Exists")
self.add_integer_output("Area Amount")
def area_items(self,context):
types = ["VIEW_3D", "IMAGE_EDITOR", "NODE_EDITOR", "SEQUENCE_EDITOR",
"CLIP_EDITOR", "DOPESHEET_EDITOR", "GRAPH_EDITOR", "NLA_EDITOR",
"TEXT_EDITOR", "CONSOLE", "INFO", "TOPBAR", "STATUSBAR", "OUTLINER",
"PROPERTIES", "FILE_BROWSER", "PREFERENCES"]
items = []
for a_type in types:
items.append((a_type,a_type.replace("_"," ").title(),a_type))
return items
area_type: bpy.props.EnumProperty(name="Area Type",
description="The type of area to find",
items=area_items,
update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
self.code_imperative = f"""
def find_areas_of_type(screen, area_type):
areas = []
for area in screen.areas:
if area.type == area_type:
areas.append(area)
return areas
def find_area_by_type(screen, area_type, index):
areas = find_areas_of_type(screen, area_type)
if areas:
return areas[index]
return None
def find_biggest_area_by_type(screen, area_type):
areas = find_areas_of_type(screen, area_type)
if not areas: return []
max_area = (areas[0], areas[0].width * areas[0].height)
for area in areas:
if area.width * area.height > max_area[1]:
max_area = (area, area.width * area.height)
return max_area[0]
"""
screen = "bpy.context.screen" if not "Screen" in self.inputs else self.inputs["Screen"].python_value
self.outputs["First Area"].python_value = f"find_area_by_type({screen}, '{self.area_type}', 0)"
self.outputs["Last Area"].python_value = f"find_area_by_type({screen}, '{self.area_type}', -1)"
self.outputs["Biggest Area"].python_value = f"find_biggest_area_by_type({screen}, '{self.area_type}')"
self.outputs["All Areas"].python_value = f"find_areas_of_type({screen}, '{self.area_type}')"
self.outputs["Area Exists"].python_value = f"bool(find_areas_of_type({screen}, '{self.area_type}'))"
self.outputs["Area Amount"].python_value = f"len(find_areas_of_type({screen}, '{self.area_type}'))"
def draw_node(self, context, layout):
layout.prop(self, "area_type", text="")
@@ -0,0 +1,41 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_AreaLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AreaLocationsNode"
bl_label = "Area Locations"
node_color = "DEFAULT"
def on_create(self, context):
self.add_property_input("Area")
self.add_integer_vector_output("Top Left").size = 2
self.add_integer_vector_output("Top Center").size = 2
self.add_integer_vector_output("Top Right").size = 2
self.add_integer_vector_output("Bottom Left").size = 2
self.add_integer_vector_output("Bottom Center").size = 2
self.add_integer_vector_output("Bottom Right").size = 2
self.add_integer_vector_output("Left Center").size = 2
self.add_integer_vector_output("Right Center").size = 2
self.add_integer_vector_output("Center").size = 2
def evaluate(self, context):
self.code_imperative = """
def region_by_type(area, region_type):
for region in area.regions:
if region.type == region_type:
return region
return area.regions[0]
"""
self.outputs["Top Left"].python_value = f"(0, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
self.outputs["Top Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
self.outputs["Top Right"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
self.outputs["Bottom Left"].python_value = f"(0, 0)"
self.outputs["Bottom Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, 0)"
self.outputs["Bottom Right"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, 0)"
self.outputs["Left Center"].python_value = f"(0, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
self.outputs["Right Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
self.outputs["Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
@@ -0,0 +1,38 @@
import bpy
import os
from ..base_node import SN_ScriptingBaseNode
class SN_AssetNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AssetNode"
bl_label = "Asset"
node_color = "STRING"
def on_create(self, context):
self.add_string_output("Path").subtype = "FILE_PATH"
asset: bpy.props.StringProperty(name="Asset",
description="Asset to get the path from",
update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
if self.asset and self.asset in context.scene.sn.assets:
if context.scene.sn.assets[self.asset].path:
self.outputs["Path"].python_value = f"r\'{context.scene.sn.assets[self.asset].path}\'"
return
self.outputs["Path"].python_value = "\'\'"
def evaluate_export(self, context):
if self.asset and self.asset in context.scene.sn.assets:
if context.scene.sn.assets[self.asset].path:
self.code_import = "import os"
name = os.path.basename(context.scene.sn.assets[self.asset].path)
if not name: name = os.path.basename(os.path.dirname(context.scene.sn.assets[self.asset].path))
self.outputs["Path"].python_value = f"os.path.join(os.path.dirname(__file__), 'assets', '{name}')"
return
self.outputs["Path"].python_value = "\'\'"
def draw_node(self, context, layout):
layout.prop_search(self, "asset", context.scene.sn, "assets")
@@ -0,0 +1,274 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
from .blend_data_base import BlendDataBaseNode
class SN_MaterialsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_MaterialsBlendDataNode"
bl_label = "Materials"
data_type = "Material"
data_type_plural = "Materials"
active_path = "bpy.context.object.active_material"
data_path = "bpy.data.materials"
class SN_MetaballsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_MetaballsBlendDataNode"
bl_label = "Metaballs"
data_type = "Metaball"
data_type_plural = "Metaballs"
data_path = "bpy.data.metaballs"
class SN_CurvesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_CurvesBlendDataNode"
bl_label = "Curves"
data_type = "Curve"
data_type_plural = "Curves"
data_path = "bpy.data.curves"
class SN_ActionsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ActionsBlendDataNode"
bl_label = "Actions"
data_type = "Action"
data_type_plural = "Actions"
data_path = "bpy.data.actions"
class SN_ArmaturesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ArmaturesBlendDataNode"
bl_label = "Armatures"
data_type = "Armature"
data_type_plural = "Armatures"
data_path = "bpy.data.armatures"
class SN_ImagesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ImagesBlendDataNode"
bl_label = "Images"
data_type = "Image"
data_type_plural = "Images"
data_path = "bpy.data.images"
class SN_LightsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_LightsBlendDataNode"
bl_label = "Lights"
data_type = "Light"
data_type_plural = "Lights"
data_path = "bpy.data.lights"
class SN_NodeGroupsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_NodeGroupsBlendDataNode"
bl_label = "Node Groups"
data_type = "Node Group"
data_type_plural = "Node Groups"
data_path = "bpy.data.node_groups"
class SN_TextsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_TextsBlendDataNode"
bl_label = "Texts"
data_type = "Text"
data_type_plural = "Texts"
data_path = "bpy.data.texts"
class SN_TexturesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_TexturesBlendDataNode"
bl_label = "Textures"
data_type = "Texture"
data_type_plural = "Textures"
data_path = "bpy.data.textures"
class SN_WorkspacesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_WorkspacesBlendDataNode"
bl_label = "Workspaces"
data_type = "Workspace"
data_type_plural = "Workspaces"
data_path = "bpy.data.workspaces"
active_path = "bpy.context.workspace"
class SN_WorldsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_WorldsBlendDataNode"
bl_label = "Worlds"
data_type = "World"
data_type_plural = "Worlds"
data_path = "bpy.data.worlds"
active_path = "bpy.context.scene.world"
class SN_BrushesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_BrushesBlendDataNode"
bl_label = "Brushes"
data_type = "Brush"
data_type_plural = "Brushes"
data_path = "bpy.data.brushes"
class SN_CamerasBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_CamerasBlendDataNode"
bl_label = "Cameras"
data_type = "Camera"
data_type_plural = "Cameras"
data_path = "bpy.data.cameras"
active_path = "bpy.context.scene.camera.data"
class SN_FontsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_FontsBlendDataNode"
bl_label = "Fonts"
data_type = "Font"
data_type_plural = "Fonts"
data_path = "bpy.data.fonts"
class SN_GreasePencilsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_GreasePencilsBlendDataNode"
bl_label = "Grease Pencils"
data_type = "Grease Pencil"
data_type_plural = "Grease Pencils"
data_path = "bpy.data.grease_pencils"
class SN_LatticesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_LatticesBlendDataNode"
bl_label = "Lattices"
data_type = "Lattice"
data_type_plural = "Lattices"
data_path = "bpy.data.lattices"
class SN_ScenesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ScenesBlendDataNode"
bl_label = "Scenes"
data_type = "Scene"
data_type_plural = "Scenes"
data_path = "bpy.data.scenes"
active_path = "bpy.context.scene"
class SN_ScreensBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ScreensBlendDataNode"
bl_label = "Screens"
data_type = "Screen"
data_type_plural = "Screens"
data_path = "bpy.data.screens"
active_path = "bpy.context.screen"
class SN_ShapeKeysBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_ShapeKeysBlendDataNode"
bl_label = "Shape Keys"
data_type = "Shape Key"
data_type_plural = "Shape Keys"
data_path = "bpy.data.shape_keys"
class SN_VolumesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_VolumesBlendDataNode"
bl_label = "Volumes"
data_type = "Volume"
data_type_plural = "Volumes"
data_path = "bpy.data.volumes"
class SN_WindowManagersBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
bl_idname = "SN_WindowManagersBlendDataNode"
bl_label = "Window Managers"
data_type = "Window Manager"
data_type_plural = "Window Managers"
data_path = "bpy.data.window_managers"
active_path = "bpy.context.window_manager"
@@ -0,0 +1,22 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BlenderDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BlenderDataNode"
bl_label = "Blender Data"
node_color = "PROPERTY"
def on_create(self, context):
self.add_property_output("Current Context")
self.add_property_output("Blend Data")
self.add_property_output("App")
self.add_property_output("Path")
def evaluate(self, context):
self.outputs["Current Context"].python_value = "bpy.context"
self.outputs["Blend Data"].python_value = "bpy.data"
self.outputs["App"].python_value = "bpy.app"
self.outputs["Path"].python_value = "bpy.path"
@@ -0,0 +1,39 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_CollectionsBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CollectionsBlendDataNode"
bl_label = "Collections"
node_color = "PROPERTY"
def on_create(self, context):
self.add_collection_property_output("All Collections")
self.add_collection_property_output("Scene Collections")
self.add_property_output("Scene Collection")
self.add_property_output("Active Collection")
self.add_property_output("Indexed")
self.add_integer_input("Index")
def update_index_type(self, context):
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
inp.name = "Index" if self.index_type == "Integer" else "Name"
self._evaluate(context)
index_type: bpy.props.EnumProperty(name="Index Type",
description="The type of index to use",
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
("String", "Name", "Refers to the name property of the element.")],
update=update_index_type)
def evaluate(self, context):
self.outputs["All Collections"].python_value = f"bpy.data.collections"
self.outputs["Scene Collections"].python_value = f"bpy.context.scene.collection.children"
self.outputs["Scene Collection"].python_value = f"bpy.context.scene.collection"
self.outputs["Active Collection"].python_value = f"bpy.context.collection"
self.outputs["Indexed"].python_value = f"bpy.data.collections[{self.inputs[0].python_value}]"
def draw_node(self, context, layout):
layout.prop(self, "index_type", expand=True)
@@ -0,0 +1,37 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_MeshBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_MeshBlendDataNode"
bl_label = "Meshes"
node_color = "PROPERTY"
def on_create(self, context):
self.add_collection_property_output("All Meshes")
self.add_list_output("Active Scene Meshes")
self.add_property_output("Active Object Mesh")
self.add_property_output("Indexed")
self.add_integer_input("Index")
def update_index_type(self, context):
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
inp.name = "Index" if self.index_type == "Integer" else "Name"
self._evaluate(context)
index_type: bpy.props.EnumProperty(name="Index Type",
description="The type of index to use",
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
("String", "Name", "Refers to the name property of the element.")],
update=update_index_type)
def evaluate(self, context):
self.outputs["All Meshes"].python_value = f"bpy.data.meshes"
self.outputs["Active Scene Meshes"].python_value = f"list(filter(lambda obj: obj, [obj.data if obj.type == 'MESH' else None for obj in bpy.context.scene.objects]))"
self.outputs["Active Object Mesh"].python_value = f"(bpy.context.active_object.data if bpy.context.active_object.type == 'MESH' else None)"
self.outputs["Indexed"].python_value = f"bpy.data.meshes[{self.inputs[0].python_value}]"
def draw_node(self, context, layout):
layout.prop(self, "index_type", expand=True)
@@ -0,0 +1,39 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ObjectBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ObjectBlendDataNode"
bl_label = "Objects"
node_color = "PROPERTY"
def on_create(self, context):
self.add_collection_property_output("All Objects")
self.add_collection_property_output("Active Scene Objects")
self.add_collection_property_output("Selected Objects")
self.add_property_output("Active Object")
self.add_property_output("Indexed")
self.add_integer_input("Index")
def update_index_type(self, context):
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
inp.name = "Index" if self.index_type == "Integer" else "Name"
self._evaluate(context)
index_type: bpy.props.EnumProperty(name="Index Type",
description="The type of index to use",
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
("String", "Name", "Refers to the name property of the element.")],
update=update_index_type)
def evaluate(self, context):
self.outputs["All Objects"].python_value = f"bpy.data.objects"
self.outputs["Active Scene Objects"].python_value = f"bpy.context.scene.objects"
self.outputs["Selected Objects"].python_value = f"bpy.context.view_layer.objects.selected"
self.outputs["Active Object"].python_value = f"bpy.context.view_layer.objects.active"
self.outputs["Indexed"].python_value = f"bpy.data.objects[{self.inputs[0].python_value}]"
def draw_node(self, context, layout):
layout.prop(self, "index_type", expand=True)
@@ -0,0 +1,38 @@
import bpy
class BlendDataBaseNode():
data_type = ""
data_type_plural = ""
active_path = ""
data_path = ""
node_color = "PROPERTY"
def on_create(self, context):
self.add_collection_property_output(f"All {self.data_type_plural}")
if self.active_path: self.add_property_output(f"Active {self.data_type}")
self.add_property_output("Indexed")
self.add_integer_input("Index")
def update_index_type(self, context):
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
inp.name = "Index" if self.index_type == "Integer" else "Name"
self._evaluate(context)
index_type: bpy.props.EnumProperty(name="Index Type",
description="The type of index to use",
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
("String", "Name", "Refers to the name property of the element.")],
update=update_index_type)
def evaluate(self, context):
self.outputs[f"All {self.data_type_plural}"].python_value = self.data_path
if self.active_path: self.outputs[f"Active {self.data_type}"].python_value = self.active_path
self.outputs["Indexed"].python_value = f"{self.data_path}[{self.inputs[0].python_value}]"
def draw_node(self, context, layout):
layout.prop(self, "index_type", expand=True)
@@ -0,0 +1,21 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BooleanVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BooleanVectorNode"
bl_label = "Boolean Vector"
node_color = "VECTOR"
def on_create(self, context):
self.add_boolean_vector_input("Boolean").set_hide(True)
self.add_boolean_vector_output("Boolean")
def evaluate(self, context):
self.outputs["Boolean"].python_value = self.inputs["Boolean"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Boolean"], "size")
self.inputs["Boolean"].draw_socket(context, layout, self, "Value")
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BooleanNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BooleanNode"
bl_label = "Boolean"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_boolean_input("Boolean").set_hide(True)
self.add_boolean_output("Boolean")
def evaluate(self, context):
self.outputs["Boolean"].python_value = self.inputs["Boolean"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Boolean"], "default_value", text="Value")
@@ -0,0 +1,35 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ColorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ColorNode"
bl_label = "Color"
node_color = "VECTOR"
def update_size(self, context):
if self.use_four:
self.inputs[0].subtype = "COLOR_ALPHA"
self.outputs[0].subtype = "COLOR_ALPHA"
else:
self.inputs[0].subtype = "COLOR"
self.outputs[0].subtype = "COLOR"
use_four: bpy.props.BoolProperty(default=False,
name="Use Alpha",
update=update_size)
def on_create(self, context):
socket = self.add_float_vector_input("Color")
socket.set_hide(True)
socket.subtype = "COLOR"
self.add_float_vector_output("Color").subtype = "COLOR"
def evaluate(self, context):
self.outputs["Color"].python_value = self.inputs["Color"].python_value
def draw_node(self, context, layout):
layout.prop(self, "use_four")
self.inputs["Color"].draw_socket(context, layout, self, "")
@@ -0,0 +1,21 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_FloatVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_FloatVectorNode"
bl_label = "Float Vector"
node_color = "VECTOR"
def on_create(self, context):
self.add_float_vector_input("Float").set_hide(True)
self.add_float_vector_output("Float")
def evaluate(self, context):
self.outputs["Float"].python_value = self.inputs["Float"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Float"], "size")
self.inputs["Float"].draw_socket(context, layout, self, "Value")
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_FloatNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_FloatNode"
bl_label = "Float"
node_color = "FLOAT"
def on_create(self, context):
self.add_float_input("Float").set_hide(True)
self.add_float_output("Float")
def evaluate(self, context):
self.outputs["Float"].python_value = self.inputs["Float"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Float"], "default_value", text="")
@@ -0,0 +1,21 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_IntegerVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IntegerVectorNode"
bl_label = "Integer Vector"
node_color = "VECTOR"
def on_create(self, context):
self.add_integer_vector_input("Integer").set_hide(True)
self.add_integer_vector_output("Integer")
def evaluate(self, context):
self.outputs["Integer"].python_value = self.inputs["Integer"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Integer"], "size")
self.inputs["Integer"].draw_socket(context, layout, self, "Value")
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_IntegerNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IntegerNode"
bl_label = "Integer"
node_color = "INTEGER"
def on_create(self, context):
self.add_integer_input("Integer").set_hide(True)
self.add_integer_output("Integer")
def evaluate(self, context):
self.outputs["Integer"].python_value = self.inputs["Integer"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["Integer"], "default_value", text="")
@@ -0,0 +1,19 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ListNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ListNode"
bl_label = "List"
node_color = "LIST"
def on_create(self, context):
self.add_dynamic_data_input("Data")
self.add_list_output("List")
def evaluate(self, context):
items = [inp.python_value if inp.is_linked else None for inp in self.inputs[:-1]]
items = filter(lambda item: item != None, items)
self.outputs["List"].python_value = f"[{', '.join(items)}]"
@@ -0,0 +1,12 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_NoneNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_NoneNode"
bl_label = "None"
def on_create(self, context):
self.add_data_output("None")
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_StringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_StringNode"
bl_label = "String"
node_color = "STRING"
def on_create(self, context):
self.add_string_input("String").set_hide(True)
self.add_string_output("String")
def evaluate(self, context):
self.outputs["String"].python_value = self.inputs["String"].python_value
def draw_node(self, context, layout):
layout.prop(self.inputs["String"], "default_value", text="")
@@ -0,0 +1,26 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BMeshDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BMeshDataNode"
bl_label = "BMesh Object Data"
node_color = "PROPERTY"
def on_create(self, context):
self.add_property_input("BMesh")
self.add_collection_property_output("Vertices")
self.add_collection_property_output("Faces")
self.add_collection_property_output("Edges")
def evaluate(self, context):
if self.inputs["BMesh"].is_linked:
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh'].python_value}.verts"
self.outputs["Faces"].python_value = f"{self.inputs['BMesh'].python_value}.faces"
self.outputs["Edges"].python_value = f"{self.inputs['BMesh'].python_value}.edges"
else:
self.outputs["Vertices"].reset_value()
self.outputs["Faces"].reset_value()
self.outputs["Edges"].reset_value()
@@ -0,0 +1,58 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BMeshEdgeDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BMeshEdgeDataNode"
bl_label = "BMesh Edge Data"
node_color = "PROPERTY"
def update_with_transforms(self, context):
self.inputs["Object"].set_hide(not self.with_transforms)
self._evaluate(context)
def on_create(self, context):
self.add_property_input("BMesh Edge")
self.add_property_input("Object").set_hide(True)
self.add_collection_property_output("Vertices")
self.add_collection_property_output("Faces")
self.add_boolean_output("Selected")
self.add_boolean_output("Hidden")
self.add_boolean_output("Smooth")
self.add_integer_output("Index")
self.add_boolean_output("Is Manifold")
self.add_boolean_output("Is Boundary")
self.add_boolean_output("Is Contiguous")
self.add_boolean_output("Is Convex")
self.add_boolean_output("Is Wire")
self.add_boolean_output("Is Seam")
def evaluate(self, context):
if self.inputs["BMesh Edge"].is_linked:
self.outputs["Faces"].python_value = f"{self.inputs['BMesh Edge'].python_value}.link_faces"
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh Edge'].python_value}.verts"
self.outputs["Is Boundary"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_boundary"
self.outputs["Is Contiguous"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_contiguous"
self.outputs["Is Convex"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_convex"
self.outputs["Is Wire"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_wire"
self.outputs["Is Seam"].python_value = f"{self.inputs['BMesh Edge'].python_value}.seam"
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Edge'].python_value}.select"
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Edge'].python_value}.hide"
self.outputs["Smooth"].python_value = f"{self.inputs['BMesh Edge'].python_value}.smooth"
self.outputs["Index"].python_value = f"{self.inputs['BMesh Edge'].python_value}.index"
self.outputs["Is Manifold"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_manifold"
else:
self.outputs["Index"].reset_value()
self.outputs["Selected"].reset_value()
self.outputs["Hidden"].reset_value()
self.outputs["Smooth"].reset_value()
self.outputs["Is Boundary"].reset_value()
self.outputs["Is Contiguous"].reset_value()
self.outputs["Is Convex"].reset_value()
self.outputs["Is Wire"].reset_value()
self.outputs["Is Seam"].reset_value()
self.outputs["Vertices"].reset_value()
self.outputs["Faces"].reset_value()
self.outputs["Is Manifold"].reset_value()
@@ -0,0 +1,48 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BMeshFaceDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BMeshFaceDataNode"
bl_label = "BMesh Face Data"
node_color = "PROPERTY"
def on_create(self, context):
self.add_property_input("BMesh Face")
self.add_property_input("Object").set_hide(True)
self.add_collection_property_output("Edges")
self.add_collection_property_output("Vertices")
self.add_float_vector_output("Center")
self.add_float_vector_output("Center Weighted")
self.add_float_vector_output("Normal")
self.add_boolean_output("Selected")
self.add_boolean_output("Hidden")
self.add_boolean_output("Smooth")
self.add_integer_output("Index")
self.add_integer_output("Material Index")
def evaluate(self, context):
if self.inputs["BMesh Face"].is_linked:
self.outputs["Edges"].python_value = f"{self.inputs['BMesh Face'].python_value}.edges"
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh Face'].python_value}.verts"
self.outputs["Center"].python_value = f"{self.inputs['BMesh Face'].python_value}.calc_center_median()"
self.outputs["Center Weighted"].python_value = f"{self.inputs['BMesh Face'].python_value}.calc_center_median_weighted()"
self.outputs["Normal"].python_value = f"{self.inputs['BMesh Face'].python_value}.normal"
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Face'].python_value}.select"
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Face'].python_value}.hide"
self.outputs["Smooth"].python_value = f"{self.inputs['BMesh Face'].python_value}.smooth"
self.outputs["Index"].python_value = f"{self.inputs['BMesh Face'].python_value}.index"
self.outputs["Material Index"].python_value = f"{self.inputs['BMesh Face'].python_value}.material_index"
else:
self.outputs["Normal"].reset_value()
self.outputs["Index"].reset_value()
self.outputs["Selected"].reset_value()
self.outputs["Hidden"].reset_value()
self.outputs["Center"].reset_value()
self.outputs["Center Weighted"].reset_value()
self.outputs["Smooth"].reset_value()
self.outputs["Edges"].reset_value()
self.outputs["Vertices"].reset_value()
self.outputs["Material Index"].reset_value()
@@ -0,0 +1,47 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BMeshVertexDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BMeshVertexDataNode"
bl_label = "BMesh Vertex Data"
node_color = "PROPERTY"
def update_with_transforms(self, context):
self.inputs["Object"].set_hide(not self.with_transforms)
self._evaluate(context)
def on_create(self, context):
self.add_property_input("BMesh Vertex")
self.add_property_input("Object").set_hide(True)
self.add_float_vector_output("Location")
self.add_float_vector_output("Normal")
self.add_boolean_output("Selected")
self.add_boolean_output("Hidden")
self.add_integer_output("Index")
self.add_boolean_output("Is Boundary")
self.add_boolean_output("Is Manifold")
self.add_boolean_output("Is Wire")
def evaluate(self, context):
if self.inputs["BMesh Vertex"].is_linked:
self.outputs["Location"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.co"
self.outputs["Normal"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.normal"
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.select"
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.hide"
self.outputs["Index"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.index"
self.outputs["Is Boundary"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_boundary"
self.outputs["Is Manifold"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_manifold"
self.outputs["Is Wire"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_wire"
else:
self.outputs["Location"].reset_value()
self.outputs["Normal"].reset_value()
self.outputs["Index"].reset_value()
self.outputs["Selected"].reset_value()
self.outputs["Hidden"].reset_value()
self.outputs["Index"].reset_value()
self.outputs["Is Boundary"].reset_value()
self.outputs["Is Manifold"].reset_value()
self.outputs["Is Wire"].reset_value()
@@ -0,0 +1,45 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_CreateLineLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CreateLineLocationsNode"
bl_label = "Create Line"
node_color = "PROGRAM"
def update_use3d(self, context):
for input in self.inputs:
if input.bl_label == "Float Vector" and input.subtype == "NONE":
input.size = 3 if self.use_3d else 2
self._evaluate(context)
use_3d: bpy.props.BoolProperty(name="Use 3D",
description="Whether to use 3D or 2D coordinates",
default=False,
update=update_use3d)
def on_create(self, context):
inp = self.add_float_vector_input("Point 1")
inp.size = 2
inp.default_value[0] = 0
inp.default_value[1] = 0
inp.default_value[2] = 0
inp = self.add_float_vector_input("Point 2")
inp.size = 2
inp.default_value[0] = 0
inp.default_value[1] = 1
inp.default_value[2] = 1
self.add_list_output("Line")
def draw_node(self, context, layout):
layout.prop(self, "use_3d", text="Use 3D")
def evaluate(self, context):
p1 = self.inputs["Point 1"].python_value
p2 = self.inputs["Point 2"].python_value
self.outputs[0].python_value = f"[{p1}, {p2}]"
@@ -0,0 +1,59 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_CreateQuadLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CreateQuadLocationsNode"
bl_label = "Create Quad"
node_color = "PROGRAM"
def update_use3d(self, context):
for input in self.inputs:
if input.bl_label == "Float Vector" and input.subtype == "NONE":
input.size = 3 if self.use_3d else 2
self._evaluate(context)
use_3d: bpy.props.BoolProperty(name="Use 3D",
description="Whether to use 3D or 2D coordinates",
default=False,
update=update_use3d)
def on_create(self, context):
inp = self.add_float_vector_input("Bottom Left")
inp.size = 2
inp.default_value[0] = 0
inp.default_value[1] = 0
inp.default_value[2] = 0
inp = self.add_float_vector_input("Bottom Right")
inp.size = 2
inp.default_value[0] = 1
inp.default_value[1] = 0
inp.default_value[2] = 0
inp = self.add_float_vector_input("Top Right")
inp.size = 2
inp.default_value[0] = 1
inp.default_value[1] = 1
inp.default_value[2] = 1
inp = self.add_float_vector_input("Top Left")
inp.size = 2
inp.default_value[0] = 0
inp.default_value[1] = 1
inp.default_value[2] = 1
self.add_list_output("Quad")
def draw_node(self, context, layout):
layout.prop(self, "use_3d", text="Use 3D")
def evaluate(self, context):
bl = self.inputs["Bottom Left"].python_value
br = self.inputs["Bottom Right"].python_value
tl = self.inputs["Top Left"].python_value
tr = self.inputs["Top Right"].python_value
self.outputs[0].python_value = f"[{bl}, {br}, {tl}, {tr}]"
@@ -0,0 +1,52 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_CreateTriangleLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CreateTriangleLocationsNode"
bl_label = "Create Triangle"
node_color = "PROGRAM"
def update_use3d(self, context):
for input in self.inputs:
if input.bl_label == "Float Vector" and input.subtype == "NONE":
input.size = 3 if self.use_3d else 2
self._evaluate(context)
use_3d: bpy.props.BoolProperty(name="Use 3D",
description="Whether to use 3D or 2D coordinates",
default=False,
update=update_use3d)
def on_create(self, context):
inp = self.add_float_vector_input("Corner 1")
inp.size = 2
inp.default_value[0] = 0
inp.default_value[1] = 0
inp.default_value[2] = 0
inp = self.add_float_vector_input("Corner 2")
inp.size = 2
inp.default_value[0] = 1
inp.default_value[1] = 0
inp.default_value[2] = 0
inp = self.add_float_vector_input("Corner 3")
inp.size = 2
inp.default_value[0] = 1
inp.default_value[1] = 1
inp.default_value[2] = 1
self.add_list_output("Triangle")
def draw_node(self, context, layout):
layout.prop(self, "use_3d", text="Use 3D")
def evaluate(self, context):
c1 = self.inputs["Corner 1"].python_value
c2 = self.inputs["Corner 2"].python_value
c3 = self.inputs["Corner 3"].python_value
self.outputs[0].python_value = f"[{c1}, {c2}, {c3}]"
@@ -0,0 +1,46 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_FacesToVertexLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_FacesToVertexLocationsNode"
bl_label = "BMesh Faces To Vertex Locations"
node_color = "PROPERTY"
def on_create(self, context):
self.add_collection_property_input("Faces")
self.add_list_output("All Locations")
self.add_list_output("Quad Locations Only")
self.add_list_output("Triangle Locations Only")
self.add_list_output("Ngon Locations Only")
def evaluate(self, context):
self.code_imperative = f"""
def get_bmesh_vertex_locations(faces):
locations = []
for face in faces:
face_locations = []
for vert in face.verts:
face_locations.append(vert.co)
locations.append(face_locations)
return locations
def get_bmesh_vertex_locations_quads(faces):
locations = get_bmesh_vertex_locations(faces)
return [loc for loc in locations if len(loc) == 4]
def get_bmesh_vertex_locations_triangles(faces):
locations = get_bmesh_vertex_locations(faces)
return [loc for loc in locations if len(loc) == 3]
def get_bmesh_vertex_locations_ngons(faces):
locations = get_bmesh_vertex_locations(faces)
return [loc for loc in locations if len(loc) > 4]
"""
self.outputs["All Locations"].python_value = f"get_bmesh_vertex_locations({self.inputs['Faces'].python_value})"
self.outputs["Quad Locations Only"].python_value = f"get_bmesh_vertex_locations_quads({self.inputs['Faces'].python_value})"
self.outputs["Triangle Locations Only"].python_value = f"get_bmesh_vertex_locations_triangles({self.inputs['Faces'].python_value})"
self.outputs["Ngon Locations Only"].python_value = f"get_bmesh_vertex_locations_ngons({self.inputs['Faces'].python_value})"
@@ -0,0 +1,43 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ObjectToBMeshNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ObjectToBMeshNode"
bl_label = "Object To BMesh"
node_color = "PROGRAM"
def on_create(self, context):
self.add_execute_input()
self.add_property_input("Object")
self.add_boolean_input("Use Edit Mode").default_value = True
self.add_boolean_input("Use Evaluated Mesh")
self.add_boolean_input("Use Object Transforms").default_value = True
self.add_execute_output()
self.add_property_output("BMesh")
def evaluate(self, context):
self.code_import = f"import bmesh"
self.code = f"""
bm_{self.static_uid} = bmesh.new()
if {self.inputs["Object"].python_value}:
if {self.inputs["Object"].python_value}.mode == 'EDIT' and {self.inputs["Use Edit Mode"].python_value}:
bm_{self.static_uid} = bmesh.from_edit_mesh({self.inputs["Object"].python_value}.data)
else:
if {self.inputs["Use Evaluated Mesh"].python_value}:
dg = bpy.context.evaluated_depsgraph_get()
bm_{self.static_uid}.from_mesh({self.inputs["Object"].python_value}.evaluated_get(dg).to_mesh())
else:
bm_{self.static_uid}.from_mesh({self.inputs["Object"].python_value}.data)
if {self.inputs["Use Object Transforms"].python_value}:
bm_{self.static_uid}.transform({self.inputs["Object"].python_value}.matrix_world)
bm_{self.static_uid}.verts.ensure_lookup_table()
bm_{self.static_uid}.faces.ensure_lookup_table()
bm_{self.static_uid}.edges.ensure_lookup_table()
{self.indent(self.outputs[0].python_value, 3)}
"""
self.outputs["BMesh"].python_value = f"bm_{self.static_uid}"
@@ -0,0 +1,34 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_NgonToTriangleLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_NgonToTriangleLocationsNode"
bl_label = "Ngon To Triangle Locations"
node_color = "PROPERTY"
def on_create(self, context):
self.add_property_input("Ngon")
self.add_list_output("Triangle Locations")
def evaluate(self, context):
self.code_imperative = """
def ngon_to_triangle_locations(ngon):
bm = bmesh.new()
for v in ngon.verts:
bm.verts.new(v.co)
bm.faces.new(bm.verts)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.verts.ensure_lookup_table()
new_faces = []
for f in bm.faces:
vert_locations = []
for v in f.verts:
vert_locations.append(tuple(v.co))
new_faces.append(vert_locations)
return new_faces
"""
self.outputs["Triangle Locations"].python_value = f"ngon_to_triangle_locations({self.inputs['Ngon'].python_value})"
@@ -0,0 +1,28 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_TriangulateBmeshNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_TriangulateBmeshNode"
bl_label = "Triangulate BMesh"
node_color = "PROGRAM"
def on_create(self, context):
self.add_execute_input()
self.add_property_input("BMesh")
self.add_execute_output()
self.add_property_output("BMesh")
def evaluate(self, context):
self.code = f"""
bm_{self.static_uid} = {self.inputs["BMesh"].python_value}.copy()
bmesh.ops.triangulate(bm_{self.static_uid}, faces=bm_{self.static_uid}.faces)
bm_{self.static_uid}.verts.ensure_lookup_table()
bm_{self.static_uid}.faces.ensure_lookup_table()
bm_{self.static_uid}.edges.ensure_lookup_table()
{self.indent(self.outputs[0].python_value, 3)}
"""
self.outputs["BMesh"].python_value = f"bm_{self.static_uid}"
@@ -0,0 +1,20 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_GetEditSelectNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_GetEditSelectNode"
bl_label = "Get Edit Select Mode"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_boolean_output("Vertex")
self.add_boolean_output("Edge")
self.add_boolean_output("Face")
def evaluate(self, context):
self.outputs[0].python_value = f"bpy.context.tool_settings.mesh_select_mode[0]"
self.outputs[1].python_value = f"bpy.context.tool_settings.mesh_select_mode[1]"
self.outputs[2].python_value = f"bpy.context.tool_settings.mesh_select_mode[2]"
@@ -0,0 +1,111 @@
import bpy
import os
from ..base_node import SN_ScriptingBaseNode
class SN_IconNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IconNode"
bl_label = "Icon"
node_color = "ICON"
bl_width_default = 200
def on_create(self, context):
inp = self.add_string_input("Image Path")
inp.subtype = "FILE_PATH"
inp.set_hide(True)
self.add_icon_output("Icon")
def update_icon_source(self, context):
if self.icon_source == "PATH":
self.inputs[0].set_hide(False)
else:
self.inputs[0].set_hide(True)
self._evaluate(context)
icon_source: bpy.props.EnumProperty(name="Icon Source",
description="The source of the icons",
items=[("BLENDER","Blender","Blender",0),
("CUSTOM","Image","Image",1),
("PATH","Path","Path",2)],
update=update_icon_source)
icon: bpy.props.IntProperty(name="Value", description="Value of this socket", update=SN_ScriptingBaseNode._evaluate)
def update_icon_file(self, context):
if self.icon_file:
self.icon_file.use_fake_user = True
self.icon_file.preview_ensure()
self._evaluate(context)
icon_file: bpy.props.PointerProperty(type=bpy.types.Image,
name="Image File",
description="The image you want to use as an icon",
update=update_icon_file)
def evaluate(self, context):
if self.icon_source == "BLENDER":
self.outputs["Icon"].python_value = f"{self.icon}"
elif self.icon_source == "CUSTOM":
if self.icon_file:
self.outputs["Icon"].python_value = f"bpy.data.images['{self.icon_file.name}'].preview.icon_id"
else:
self.outputs["Icon"].python_value = "0"
elif self.icon_source == "PATH":
self.code_import = "import os"
self.code_imperative = f"""
def load_preview_icon(path):
global _icons
if not path in _icons:
if os.path.exists(path):
_icons.load(path, path, "IMAGE")
else:
return 0
return _icons[path].icon_id
"""
self.outputs["Icon"].python_value = f"load_preview_icon({self.inputs[0].python_value})"
def evaluate_export(self, context):
if self.icon_source == "BLENDER":
self.outputs["Icon"].python_value = f"{self.icon}"
elif self.icon_source == "CUSTOM":
if self.icon_file:
self.outputs["Icon"].python_value = f"_icons['{self.icon_file.name}'].icon_id"
self.code_import = "import os"
self.code_register = f"""
if not '{self.icon_file.name}' in _icons: _icons.load('{self.icon_file.name}', os.path.join(os.path.dirname(__file__), 'icons', '{self.icon_file.name}'), "IMAGE")
"""
else:
self.outputs["Icon"].python_value = "0"
elif self.icon_source == "PATH":
self.code_import = "import os"
self.code_imperative = f"""
def load_preview_icon(path):
global _icons
if not path in _icons:
if os.path.exists(path):
_icons.load(path, path, "IMAGE")
else:
return 0
return _icons[path].icon_id
"""
self.outputs["Icon"].python_value = f"load_preview_icon({self.inputs[0].python_value})"
def draw_node(self, context, layout):
row = layout.row()
row.scale_y = 1.2
row.prop(self,"icon_source",expand=True)
if self.icon_source == "BLENDER":
op = layout.operator("sn.select_icon", text="Choose Icon", icon_value=self.icon)
op.icon_data_path = f"bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}']"
elif self.icon_source == "CUSTOM":
layout.template_ID(self, "icon_file", new="image.new", open="image.open", live_icon=True)
if self.icon_file and not self.icon_file.filepath:
layout.label(text="Image not saved!", icon="ERROR")
@@ -0,0 +1,19 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_IsExportNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IsExportNode"
bl_label = "Is Export"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_boolean_output("Is Export")
def evaluate(self, context):
self.outputs[0].python_value = f"False"
def evaluate_export(self, context):
self.outputs[0].python_value = f"True"
@@ -0,0 +1,34 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_InModeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_InModeNode"
bl_label = "In Mode"
node_color = "BOOLEAN"
def get_modes(self,context):
items = []
modes = ["EDIT_MESH", "EDIT_CURVE", "EDIT_SURFACE", "EDIT_TEXT", "EDIT_ARMATURE",
"EDIT_METABALL", "EDIT_LATTICE", "POSE", "SCULPT", "PAINT_WEIGHT", "PAINT_VERTEX",
"PAINT_TEXTURE", "PARTICLE", "OBJECT", "PAINT_GPENCIL", "EDIT_GPENCIL",
"SCULPT_GPENCIL" "WEIGHT_GPENCIL", "VERTEX_GPENCIL", "SCULPT_CURVES"]
for mode in modes:
items.append((mode,mode.replace("_"," ").title(),mode))
return items
modes: bpy.props.EnumProperty(items=get_modes,
update=SN_ScriptingBaseNode._evaluate,
name="Mode",
description="The mode which the active mode is compared to")
def draw_node(self, context, layout):
layout.prop(self, "modes", text="")
def on_create(self, context):
self.add_boolean_output("In Mode")
def evaluate(self, context):
self.outputs[0].python_value = f"'{self.modes}'==bpy.context.mode"
@@ -0,0 +1,46 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
from ..Input.Node_Idname import NodeType
class SN_NodeIsIdname(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_NodeIsIdname"
bl_label = "Node Is Idname"
node_color = "PROPERTY"
nodes: bpy.props.CollectionProperty(type=NodeType)
node: bpy.props.StringProperty(name="Node",
description="The node to get the type for",
update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_property_input("Node")
self.add_boolean_output("Is ID Name")
# load internal nodes
for name in dir(bpy.types):
cls = getattr(bpy.types, name)
if hasattr(cls, "bl_rna") and cls.bl_rna.base and "Node" in cls.bl_rna.base.bl_rna.name:
item = self.nodes.add()
item.name = f"{cls.bl_rna.name} ({cls.bl_rna.base.bl_rna.name})"
item.identifier = cls.bl_rna.identifier
# load python nodes
for cls in bpy.types.Node.__subclasses__():
item = self.nodes.add()
item.name = f"{cls.bl_rna.name} (Python)"
item.identifier = cls.bl_rna.identifier
def evaluate(self, context):
if self.node:
self.outputs[0].python_value = f"{self.inputs[0].python_value}.bl_rna.identifier == '{self.nodes[self.node].identifier}'"
else:
self.outputs[0].python_value = "False"
def draw_node(self, context, layout):
layout.prop_search(self, "node", self, "nodes", text="")
@@ -0,0 +1,26 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_IsObjectType(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IsObjectType"
bl_label = "Is Object Type"
node_color = "PROPERTY"
types: bpy.props.EnumProperty(items=[('MESH', 'Mesh', ''), ('CURVE', 'Curve', ''), ('SURFACE', 'Surface', ''), ('META', 'Metaball', ''), ('FONT', 'Text', ''), ('HAIR', 'Hair', ''), ('POINTCLOUD', 'Point Cloud', ''), ('VOLUME', 'Volume', ''), ('GPENCIL', 'Grease Pencil', ''), ('ARMATURE', 'Armature', ''), ('LATTICE', 'Lattice', ''), ('EMPTY', 'Empty', ''), ('LIGHT', 'Light', ''), ('LIGHT_PROBE', 'Light Probe', ''), ('CAMERA', 'Camera', ''), ('SPEAKER', 'Speaker', '')],
update=SN_ScriptingBaseNode._evaluate,
name="Type",
description="The type which the object is compared to")
def draw_node(self, context, layout):
layout.prop(self, "types", text="")
def on_create(self, context):
self.add_property_input("Object")
self.add_boolean_output("Is Type")
def evaluate(self, context):
self.outputs[0].python_value = f"{self.inputs[0].python_value}.type == '{self.types}'"
@@ -0,0 +1,33 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_NamedIconNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_NamedIconNode"
bl_label = "Named Icon"
node_color = "ICON"
def on_create(self, context):
self.add_string_output("Icon")
def update_selected(self, context):
for icon in bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items:
if icon.value == self.icon:
self.icon_name = icon.name
return
self.icon_name = "NONE"
self["icon"] = 0
icon: bpy.props.IntProperty(name="Value", description="Value of this socket", update=update_selected)
icon_name: bpy.props.StringProperty(default="NONE" ,update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
self.outputs[0].python_value = f"'{self.icon_name}'"
def draw_node(self, context, layout):
op = layout.operator("sn.select_icon", text="Choose Icon", icon_value=self.icon)
op.icon_data_path = f"bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}']"
@@ -0,0 +1,53 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class NodeType(bpy.types.PropertyGroup):
name: bpy.props.StringProperty()
identifier: bpy.props.StringProperty()
class SN_NodeIdnameNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_NodeIdnameNode"
bl_label = "Node Idname"
node_color = "STRING"
def on_create(self, context):
self.add_string_output("Idname")
# load internal nodes
for name in dir(bpy.types):
cls = getattr(bpy.types, name)
if hasattr(cls, "bl_rna") and cls.bl_rna.base and "Node" in cls.bl_rna.base.bl_rna.name:
item = self.nodes.add()
item.name = f"{cls.bl_rna.name} ({cls.bl_rna.base.bl_rna.name})"
item.identifier = cls.bl_rna.identifier
# load python nodes
for cls in bpy.types.Node.__subclasses__():
item = self.nodes.add()
item.name = f"{cls.bl_rna.name} (Python)"
item.identifier = cls.bl_rna.identifier
nodes: bpy.props.CollectionProperty(type=NodeType)
node: bpy.props.StringProperty(name="Node",
description="The node to get the type for",
update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
if self.node:
self.outputs["Idname"].python_value = f"'{self.nodes[self.node].identifier}'"
else:
self.outputs["Idname"].python_value = "''"
def draw_node(self, context, layout):
layout.prop_search(self, "node", self, "nodes", text="")
@@ -0,0 +1,33 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_RandomColorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RandomColorNode"
bl_label = "Random Color"
node_color = "VECTOR"
def on_create(self, context):
self.add_boolean_input("Use Alpha")
inp = self.add_float_input("Fixed Alpha")
inp.can_be_disabled = True
inp.disabled = True
inp.default_value = 1.0
inp = self.add_integer_input("Seed")
inp.can_be_disabled = True
inp.disabled = True
self.add_float_vector_output("Random Color").subtype = "COLOR"
def evaluate(self, context):
self.code_import = "import random"
self.code_imperative = """
def random_color(use_alpha, fixed_alpha, seed):
random.seed(seed)
if use_alpha:
return (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1), fixed_alpha if fixed_alpha != None else random.uniform(0, 1))
else:
return (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))
"""
self.outputs[0].python_value = f"random_color({self.inputs['Use Alpha'].python_value}, {'None' if self.inputs['Fixed Alpha'].disabled else self.inputs['Fixed Alpha'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
@@ -0,0 +1,47 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_RandomNumberNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RandomNumberNode"
bl_label = "Random Number"
node_color = "FLOAT"
def on_create(self, context):
self.add_float_input("Minimum")
self.add_float_input("Maximum")
inp = self.add_integer_input("Seed")
inp.can_be_disabled = True
inp.disabled = True
self.add_integer_output("Random Number")
def update_num_type(self, context):
self.convert_socket(self.outputs[0], self.socket_names[self.number_type])
self._evaluate(context)
number_type: bpy.props.EnumProperty(name="Type",
description="Type of number",
items=[("Integer", "Integer", "Integer"),
("Float", "Float", "Float")],
update=update_num_type)
def evaluate(self, context):
self.code_import = "import random"
self.code_imperative = """
def random_integer(min, max, seed):
random.seed(seed)
return random.randint(int(min), int(max))
def random_float(min, max, seed):
random.seed(seed)
return random.uniform(min, max)
"""
if self.number_type == "Integer":
self.outputs[0].python_value = f"random_integer({self.inputs['Minimum'].python_value}, {self.inputs['Maximum'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
else:
self.outputs[0].python_value = f"random_float({self.inputs['Minimum'].python_value}, {self.inputs['Maximum'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
def draw_node(self, context, layout):
layout.prop(self, "number_type")
@@ -0,0 +1,46 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_SceneContextNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SceneContextNode"
bl_label = "Scene Context"
node_color = "DEFAULT"
def on_create(self, context):
self.add_property_output("Active Scene")
self.add_property_output("Active Area")
self.add_property_output("Active View Layer")
self.add_property_output("Active Camera")
self.add_property_output("Active Bone")
self.add_property_output("Active Pose Bone")
self.add_property_output("Preferences")
self.add_property_output("Window Manager")
self.add_property_output("Window")
self.add_property_output("Screen")
self.add_string_output("Engine")
self.add_string_output("Mode")
self.add_string_output("Blend Filepath")
self.add_boolean_output("File Is Saved")
self.add_boolean_output("File Has Changes")
self.add_integer_output("Current Frame")
def evaluate(self, context):
self.outputs["Active Scene"].python_value = f"bpy.context.scene"
self.outputs["Active Area"].python_value = f"bpy.context.area"
self.outputs["Active View Layer"].python_value = f"bpy.context.view_layer"
self.outputs["Active Camera"].python_value = f"bpy.context.scene.camera"
self.outputs["Active Bone"].python_value = f"bpy.context.active_bone"
self.outputs["Active Pose Bone"].python_value = f"bpy.context.active_pose_bone"
self.outputs["Preferences"].python_value = f"bpy.context.preferences"
self.outputs["Window Manager"].python_value = f"bpy.context.window_manager"
self.outputs["Window"].python_value = f"bpy.context.window"
self.outputs["Screen"].python_value = f"bpy.context.screen"
self.outputs["Engine"].python_value = f"bpy.context.engine"
self.outputs["Mode"].python_value = f"bpy.context.mode"
self.outputs["Blend Filepath"].python_value = f"bpy.data.filepath"
self.outputs["File Is Saved"].python_value = f"bpy.data.is_saved"
self.outputs["File Has Changes"].python_value = f"bpy.data.is_dirty"
self.outputs["Current Frame"].python_value = f"bpy.context.scene.frame_current"
@@ -0,0 +1,33 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_TimeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_TimeNode"
bl_label = "Time and Date"
node_color = "INTEGER"
def on_create(self, context):
self.add_string_output("Time")
self.add_integer_output("Hours")
self.add_integer_output("Minutes")
self.add_integer_output("Seconds")
self.add_integer_output("Milliseconds")
self.add_string_output("Date")
self.add_integer_output("Year")
self.add_integer_output("Month")
self.add_integer_output("Day")
def evaluate(self, context):
self.code_import = f"from datetime import datetime"
self.outputs[0].python_value = "str(datetime.now().time()).split(\".\")[0]"
self.outputs[1].python_value = "datetime.now().time().hour"
self.outputs[2].python_value = "datetime.now().time().minute"
self.outputs[3].python_value = "datetime.now().time().second"
self.outputs[4].python_value = "datetime.now().time().microsecond//1000"
self.outputs[5].python_value = "str(datetime.now().date())"
self.outputs[6].python_value = "datetime.now().date().year"
self.outputs[7].python_value = "datetime.now().date().month"
self.outputs[8].python_value = "datetime.now().date().day"