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,48 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_BooleanMathNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BooleanMathNode"
bl_label = "Boolean Math"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_boolean_input("Boolean")
self.add_boolean_input("Boolean")
self.add_dynamic_boolean_input("Boolean")
self.add_boolean_output("Boolean")
def update_operation(self, context):
for inp in self.inputs[1:]:
inp.enabled = self.operation != "NOT"
self._evaluate(context)
operation: bpy.props.EnumProperty(items=[("AND", "And", "Returns True if both inputs are True"),
("OR", "Or", "Returns True if one or both inputs are True"),
("NOT", "Not", "Returns True if the inputs is False and False if the input is True")],
name="Operation",
description="Operation to perform on the input booleans",
update=update_operation)
def draw_node(self, context, layout):
layout.prop(self, "operation", text='')
def evaluate(self, context):
# not operation
if self.operation == "NOT":
self.outputs["Boolean"].python_value = f" not {self.inputs[0].python_value}"
# and or or operation
else:
# get all input values
values = []
for inp in self.inputs[:-1]:
if inp.enabled:
values.append(inp.python_value)
# join input values on operation name
join_op = f" {'and' if self.operation == 'AND' else 'or'} ".join(values)
self.outputs["Boolean"].python_value = f"({join_op})"
@@ -0,0 +1,17 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_InvertBooleanNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_InvertBooleanNode"
bl_label = "Invert Boolean"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_boolean_input("Boolean")
self.add_boolean_output("Boolean")
def evaluate(self, context):
self.outputs[0].python_value = f"(not {self.inputs[0].python_value})"
@@ -0,0 +1,33 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_CompareNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CompareNode"
bl_label = "Compare"
node_color = "BOOLEAN"
def on_create(self, context):
self.add_data_input("Data")
self.add_data_input("Data")
self.add_boolean_output("Boolean")
operation: bpy.props.EnumProperty(items=[("==", "=", "Equal"),
("!=", "", "Not equal"),
("<", "<", "Smaller than"),
(">", ">", "Bigger than"),
("<=", "", "Smaller or equal to"),
(">=", "", "Bigger or equal to")],
name="Operation",
description="Operation to perform on the input data",
update=SN_ScriptingBaseNode._evaluate)
def draw_node(self, context, layout):
layout.prop(self, "operation", text='')
def evaluate(self, context):
self.outputs["Boolean"].python_value = f"({self.inputs[0].python_value} {self.operation} {self.inputs[1].python_value})"
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_3DLocationTo2DNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_3DLocationTo2DNode"
bl_label = "3D View Coordinates To 2D"
def on_create(self, context):
self.add_property_input("Area")
self.add_float_vector_input("Coordinates").size = 3
self.add_float_vector_output("2D Coordinates")
def evaluate(self, context):
self.code_import = "from bpy_extras.view3d_utils import location_3d_to_region_2d"
self.outputs[0].python_value = f"location_3d_to_region_2d({self.inputs[0].python_value}.regions[5], {self.inputs[0].python_value}.spaces[0].region_3d, {self.inputs[1].python_value})"
@@ -0,0 +1,60 @@
import bpy
import string
from ...base_node import SN_ScriptingBaseNode
class SN_CombineVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CombineVectorNode"
bl_label = "Combine Vector"
node_color = "VECTOR"
def update_vector_type(self,context):
self.convert_socket(self.outputs[0], self.socket_names[self.vector_type])
for input in self.inputs:
self.convert_socket(input, self.socket_names[self.vector_type[:-7]])
self._evaluate(context)
def update_size(self,context):
self.outputs[0].size = self.size
if len(self.inputs) > self.size:
for i in range(len(self.inputs)-self.size):
self.inputs.remove(self.inputs[-1])
elif self.size > len(self.inputs):
for i in range(self.size-len(self.inputs)):
alphabet = list(string.ascii_lowercase) + list(string.ascii_uppercase)
if self.vector_type == "Float Vector":
self.add_float_input(alphabet[len(self.inputs)])
elif self.vector_type == "Integer Vector":
self.add_integer_input(alphabet[len(self.inputs)])
elif self.vector_type == "Boolean Vector":
self.add_boolean_input(alphabet[len(self.inputs)])
self._evaluate(context)
vector_type: bpy.props.EnumProperty(name="Type",
description="The type of vector that should be used",
items=[("Float Vector","Float","Float Vector"),
("Integer Vector","Integer","Integer Vector"),
("Boolean Vector","Boolean","Boolean Vector")],
update=update_vector_type)
size: bpy.props.IntProperty(default=3, min=2, max=32,
name="Size",
description="Size of this the vector",
update=update_size)
def on_create(self, context):
self.add_float_vector_output("Vector")
self.add_float_input("a")
self.add_float_input("b")
self.add_float_input("c")
def evaluate(self, context):
values = [inp.python_value for inp in self.inputs]
self.outputs[0].python_value = f"({', '.join(values)})"
def draw_node(self, context, layout):
layout.prop(self, "vector_type")
layout.prop(self, "size")
@@ -0,0 +1,37 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_DataToIconNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_DataToIconNode"
bl_label = "Data To Icon"
def on_create(self, context):
self.add_string_input("Name")
self.add_icon_output("Icon")
data_type: bpy.props.EnumProperty(name="Type",
description="Type of data to preview",
items=[("bpy.data.materials", "Material", "Material"),
("bpy.data.objects", "Object", "Object"),
("bpy.data.images", "Image", "Image"),
("bpy.data.textures", "Texture", "Texture")],
update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
self.code_imperative = f"""
def get_id_preview_id(data):
if hasattr(data, "preview"):
if not data.preview:
data.preview_ensure()
if hasattr(data.preview, "icon_id"):
return data.preview.icon_id
return 0
"""
self.outputs["Icon"].python_value = f"get_id_preview_id({self.data_type}[{self.inputs['Name'].python_value}])"
def draw_node(self, context, layout):
layout.label(text="Use this node with care! This might slow down your UI", icon="INFO")
layout.prop(self, "data_type")
@@ -0,0 +1,33 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_DefineDataType(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_DefineDataType"
bl_label = "Define Data Type"
node_color = "DEFAULT"
def get_data_items(self,context):
items = []
for label in list(self.socket_names.keys())[3:]:
items.append((self.socket_names[label], label, self.socket_names[label]))
return items
def update_conversion(self, context):
self.convert_socket(self.outputs[0], self.convert_to)
convert_to: bpy.props.EnumProperty(items=get_data_items,
update=update_conversion,
name="Data",
description="The type of data that you want to set the data input to",)
def draw_node(self, context, layout):
layout.prop(self, "convert_to", text="")
def on_create(self, context):
self.add_data_input("Data")
self.add_string_output("Data")
def evaluate(self, context):
self.outputs[0].python_value = self.inputs[0].python_value
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_EnumSetToListNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_EnumSetToListNode"
bl_label = "Enum Set To List"
def on_create(self, context):
self.add_property_input("Enum Set Property")
self.add_list_output("List")
def evaluate(self, context):
if self.inputs[0].is_linked:
self.outputs[0].python_value = f"list({self.inputs[0].python_value})"
else:
self.outputs[0].reset_value()
@@ -0,0 +1,32 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_RadiansNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RadiansNode"
bl_label = "Convert Radians/Degrees"
node_color = "FLOAT"
def update_operation(self, context):
if self.operation == "degrees":
self.inputs[0].name = "Radians"
self.outputs[0].name = "Degrees"
else:
self.inputs[0].name = "Degrees"
self.outputs[0].name = "Radians"
self._evaluate(context)
operation: bpy.props.EnumProperty(items=[("degrees", "Radians to Degrees", "Convert Radians to Degrees"), ("radians", "Degrees to Radians", "Convert Degrees to radians")],name="Operation", update=update_operation)
def on_create(self, context):
self.add_float_input("Radians")
self.add_float_output("Degrees")
def draw_node(self, context, layout):
layout.prop(self, "operation", text="")
def evaluate(self, context):
self.code_import = "import math"
self.outputs[0].python_value = f"math.{self.operation}({self.inputs[0].python_value})"
@@ -0,0 +1,26 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_RegionToViewNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RegionToViewNode"
bl_label = "Region To View"
def on_create(self, context):
self.add_property_input("Area")
self.add_float_vector_input("Coordinates").size = 2
self.add_float_vector_output("View Coordinates")
def evaluate(self, context):
self.code_imperative = f"""
def coords_region_to_view(area, coords):
for region in area.regions:
if region.type == "WINDOW":
ui_scale = bpy.context.preferences.system.ui_scale
x, y = region.view2d.region_to_view(coords[0], coords[1])
return (x/ui_scale, y/ui_scale)
return coords
"""
self.outputs[0].python_value = f"coords_region_to_view({self.inputs[0].python_value}, tuple({self.inputs[1].python_value}))"
@@ -0,0 +1,64 @@
import bpy
import string
from ...base_node import SN_ScriptingBaseNode
class SN_SplitVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SplitVectorNode"
bl_label = "Split Vector"
node_color = "VECTOR"
def update_vector_type(self,context):
self.convert_socket(self.inputs[0], self.socket_names[self.vector_type])
for output in self.outputs:
self.convert_socket(output, self.socket_names[self.vector_type[:-7]])
self._evaluate(context)
def update_size(self,context):
self.inputs[0].size = self.size
if len(self.outputs) > self.size:
for i in range(len(self.outputs)-self.size):
self.outputs.remove(self.outputs[-1])
elif self.size > len(self.outputs):
for i in range(self.size-len(self.outputs)):
alphabet = list(string.ascii_lowercase) + list(string.ascii_uppercase)
if self.vector_type == "Float Vector":
self.add_float_output(alphabet[len(self.outputs)])
elif self.vector_type == "Integer Vector":
self.add_integer_output(alphabet[len(self.outputs)])
elif self.vector_type == "Boolean Vector":
self.add_boolean_output(alphabet[len(self.outputs)])
self._evaluate(context)
vector_type: bpy.props.EnumProperty(name="Type",
description="The type of vector that should be used",
items=[("Float Vector","Float","Float Vector"),
("Integer Vector","Integer","Integer Vector"),
("Boolean Vector","Boolean","Boolean Vector")],
update=update_vector_type)
size: bpy.props.IntProperty(default=3, min=2, max=32,
name="Size",
description="Size of this the vector",
update=update_size)
def on_create(self, context):
self.add_float_vector_input("Vector")
self.add_float_output("a")
self.add_float_output("b")
self.add_float_output("c")
def evaluate(self, context):
if self.vector_type == "Integer Vector":
for i in range(len(self.outputs)):
self.outputs[i].python_value = f"int({self.inputs[0].python_value}[{i}])"
else:
for i in range(len(self.outputs)):
self.outputs[i].python_value = f"{self.inputs[0].python_value}[{i}]"
def draw_node(self, context, layout):
layout.prop(self, "vector_type")
layout.prop(self, "size")
@@ -0,0 +1,25 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ViewToRegionNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ViewToRegionNode"
bl_label = "View To Region"
def on_create(self, context):
self.add_property_input("Area")
self.add_float_vector_input("Coordinates").size = 2
self.add_float_vector_output("Region Coordinates")
def evaluate(self, context):
self.code_imperative = f"""
def coords_view_to_region(area, coords):
for region in area.regions:
if region.type == "WINDOW":
ui_scale = bpy.context.preferences.system.ui_scale
return region.view2d.view_to_region(coords[0]*ui_scale, coords[1]*ui_scale, clip=False)
return coords
"""
self.outputs[0].python_value = f"coords_view_to_region({self.inputs[0].python_value}, tuple({self.inputs[1].python_value}))"
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_JoinPathNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_JoinPathNode"
bl_label = "Join Path"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("Basepath").subtype = "DIR_PATH"
self.add_dynamic_string_input("Path Part")
self.add_string_output("Path").subtype = "FILE_PATH"
def evaluate(self, context):
self.code_import = "import os"
self.outputs[0].python_value = f"os.path.join({self.inputs[0].python_value},{','.join([inp.python_value for inp in self.inputs[1:-1]])})"
@@ -0,0 +1,81 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ListBlendContentNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ListBlendContentNode"
bl_label = "List Blend File Content"
def on_create(self, context):
self.add_string_input("Path").subtype = "FILE_PATH"
self.add_list_output("Names")
def get_append_types(self, context):
items = [
"actions",
"armatures",
"brushes",
"cache_files",
"cameras",
"collections",
"curves",
"images",
"fonts",
"grease_pencils",
"lattices",
"libraries",
"lightprobes",
"lights",
"linestyles",
"masks",
"materials",
"meshes",
"metaballs",
"movieclips",
"node_groups",
"objects",
"paint_curves",
"palettes",
"particles",
"pointclouds",
"scenes",
"screens",
"shape_keys",
"sounds",
"speakers",
"texts",
"textures",
"volumes",
"workspaces",
"worlds",
]
tuple_items = []
for item in items:
tuple_items.append((item, item.replace("_", " ").title(), item))
return tuple_items
append_type: bpy.props.EnumProperty(
items=get_append_types,
name="Type",
description="Type of the Append object",
update=SN_ScriptingBaseNode._evaluate,
)
def draw_node(self, context, layout):
layout.prop(self, "append_type")
def evaluate(self, context):
self.code_imperative = """
def get_blend_contents(path, data_type):
if os.path.exists(path):
with bpy.data.libraries.load(path) as (data_from, data_to):
return getattr(data_from, data_type)
return []
"""
self.code_import = "import os"
self.outputs[
0
].python_value = (
f"get_blend_contents({self.inputs[0].python_value}, '{self.append_type}')"
)
@@ -0,0 +1,38 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ListDirectoryFilesNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ListDirectoryFilesNode"
bl_label = "List Directory Files"
node_color = "STRING"
def on_create(self, context):
self.add_string_input("Path").subtype = "FILE_PATH"
self.add_list_output("Files + Directories")
self.add_list_output("Files")
self.add_list_output("Directories")
with_root: bpy.props.BoolProperty(name="With Path",
description="Returns a list of full paths instead of just the file and directory names",
default=True, update=SN_ScriptingBaseNode._evaluate)
def draw_node(self, context, layout):
layout.prop(self, "with_root")
def evaluate(self, context):
self.code_import = "import os"
if not self.with_root:
self.outputs[0].python_value = f"os.listdir({self.inputs['Path'].python_value})"
if len(self.outputs) > 1:
self.outputs[1].python_value = f"[f for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isfile(os.path.join({self.inputs['Path'].python_value}, f))]"
self.outputs[2].python_value = f"[f for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isdir(os.path.join({self.inputs['Path'].python_value}, f))]"
else:
self.outputs[0].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value})]"
if len(self.outputs) > 1:
self.outputs[1].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isfile(os.path.join({self.inputs['Path'].python_value}, f))]"
self.outputs[2].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isdir(os.path.join({self.inputs['Path'].python_value}, f))]"
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_AbsolutePathNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AbsolutePathNode"
bl_label = "Make Path Absolute"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("Relative").subtype = "FILE_PATH"
self.add_string_output("Absolute").subtype = "FILE_PATH"
def evaluate(self, context):
self.outputs["Absolute"].python_value = f"bpy.path.abspath({self.inputs[0].python_value})"
@@ -0,0 +1,26 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_PathInfoNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_PathInfoNode"
bl_label = "Path Info"
node_color = "STRING"
def on_create(self, context):
self.add_string_input("Path").subtype = "FILE_PATH"
self.add_boolean_output("Path Exists")
self.add_boolean_output("Is Directory")
self.add_string_output("Parent Name")
self.add_string_output("Base Name")
self.add_string_output("Extension")
def evaluate(self, context):
self.code_import = "import os"
self.outputs['Path Exists'].python_value = f"os.path.exists({self.inputs['Path'].python_value})"
self.outputs['Is Directory'].python_value = f"os.path.isdir({self.inputs['Path'].python_value})"
self.outputs['Parent Name'].python_value = f"os.path.dirname({self.inputs['Path'].python_value})"
self.outputs['Base Name'].python_value = f"os.path.basename({self.inputs['Path'].python_value})"
self.outputs['Extension'].python_value = f"os.path.splitext({self.inputs['Path'].python_value})[1]"
@@ -0,0 +1,29 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ClampNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ClampNode"
bl_label = "Clamp"
node_color = "FLOAT"
def on_create(self, context):
self.add_float_input("Value")
self.add_float_input("Min").can_be_disabled = True
self.add_float_input("Max").can_be_disabled = True
self.add_float_output("Float Result")
self.add_integer_output("Integer Result")
def evaluate(self, context):
smallest = self.inputs["Min"].python_value
largest = self.inputs["Max"].python_value
if self.inputs["Min"].disabled:
smallest = self.inputs["Value"].python_value
if self.inputs["Max"].disabled:
largest = self.inputs["Value"].python_value
self.outputs[0].python_value = f"float(max({smallest}, min({self.inputs[0].python_value}, {largest})))"
self.outputs[1].python_value = f"int(max({smallest}, min({self.inputs[0].python_value}, {largest})))"
@@ -0,0 +1,74 @@
import re
import bpy
import string
from ...base_node import SN_ScriptingBaseNode
class SN_MathNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_MathNode"
bl_label = "Math"
node_color = "FLOAT"
def on_dynamic_socket_add(self, socket):
alphabet = list(string.ascii_lowercase)
if len(self.inputs) > 26:
self.inputs.remove(socket)
for x, socket in enumerate(self.inputs):
socket.name = alphabet[x]
def on_dynamic_socket_remove(self, index, is_output):
alphabet = list(string.ascii_lowercase)
if self.inputs[-2].name != "z" and self.inputs[-1].hide:
self.inputs[-1].set_hide(False)
if self.inputs[-2].name != "z":
self.inputs[-1].name = alphabet[alphabet.index(self.inputs[-2].name)+1]
operation: bpy.props.EnumProperty(items=[(" + ", "Add", "Add two numbers"),
(" - ", "Subtract", "Subtract two numbers"),
(" * ", "Multiply", "Multiply two numbers"),
(" / ", "Divide", "Divide two numbers"),
("EXPRESSION","Expression","Enter your own expression")],
name="Operation",
description="Operation to perform on the input data",
update=SN_ScriptingBaseNode._evaluate)
expression: bpy.props.StringProperty(default="a + b", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_float_input("a")
self.add_float_input("b")
self.add_dynamic_float_input("c")
self.add_float_output("Float Result")
self.add_integer_output("Integer Result")
def draw_node(self, context, layout):
layout.prop(self, "operation", text="")
if self.operation == "EXPRESSION":
layout.prop(self,"expression",text="")
def multiple_replace(self, string, rep_dict):
for key, value in rep_dict.items():
string = re.sub(rf'\b{key}\b', value, string)
return string
def evaluate(self, context):
if not self.operation == "EXPRESSION":
values = [inp.python_value for inp in self.inputs[:-1]]
self.outputs[0].python_value = f"float({self.operation.join(values)})"
self.outputs[1].python_value = f"int({self.operation.join(values)})"
else:
self.code_import = "import math"
expression = self.expression
to_replace = {}
for inp in self.inputs:
if not inp.dynamic:
to_replace[inp.name] = inp.python_value
expression = self.multiple_replace(expression, to_replace)
self.outputs[0].python_value = f"eval(\"{expression}\")"
self.outputs[1].python_value = f"int(eval(\"{expression}\"))"
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_RoundNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RoundNode"
bl_label = "Round Number"
node_color = "FLOAT"
def on_create(self, context):
self.add_float_input("Value")
self.add_integer_input("Decimals")
self.add_float_output("Rounded Number")
def evaluate(self, context):
self.outputs[0].python_value = f"round({self.inputs['Value'].python_value}, abs({self.inputs['Decimals'].python_value}))"
@@ -0,0 +1,81 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_VectorMathNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_VectorMathNode"
bl_label = "Vector Math"
node_color = "VECTOR"
def update_operation(self,context):
if self.operation == "CROSS_PRODUCT":
self.size = 3
if self.operation == "LENGTH":
if len(self.inputs) == 2:
self.inputs.remove(self.inputs[1])
self.convert_socket(self.outputs[0], self.socket_names["Float"])
else:
if len(self.inputs) == 1:
self.add_float_vector_input("B").size = self.size
self.convert_socket(self.outputs[0], self.socket_names["Float Vector"])
if self.operation in ["DIVIDE"]:
self.convert_socket(self.inputs[1], self.socket_names["Float"])
elif len(self.inputs) > 1:
self.convert_socket(self.inputs[1], self.socket_names["Float Vector"])
self._evaluate(context)
def update_size(self,context):
if self.operation == "CROSS_PRODUCT":
self["size"] = 3
self.inputs[0].size = self.size
self.inputs[1].size = self.size
self._evaluate(context)
size: bpy.props.IntProperty(default=3, min=2, max=32,
name="Size",
description="Size of this the vector",
update=update_size)
operation: bpy.props.EnumProperty(items=[("ADD", "Add", "Add two vectors"),
("SUBTRACT", "Subtract", "Subtract two vectors"),
("MULTIPLY", "Multiply", "Multiply two vectors"),
("DIVIDE", "Divide", "Divide vector by float"),
("CROSS_PRODUCT", "Cross Product", "Cross Product of two vectors"),
("DOT_PRODUCT", "Dot Product", "Dot Product of two vectors"),
("LENGTH", "Length", "Length of a vector")],
name="Operation", description="The operation you want to commence",
update=update_operation)
def on_create(self, context):
self.add_float_vector_input("A")
self.add_float_vector_input("B")
self.add_float_vector_output("Vector")
def evaluate(self, context):
self.code_import = "import mathutils"
if self.operation == "ADD":
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) + mathutils.Vector({self.inputs[1].python_value}))"
elif self.operation == "SUBTRACT":
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) - mathutils.Vector({self.inputs[1].python_value}))"
elif self.operation == "MULTIPLY":
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) * mathutils.Vector({self.inputs[1].python_value}))"
elif self.operation == "DIVIDE":
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) / {self.inputs[1].python_value})"
elif self.operation == "CROSS_PRODUCT":
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}).cross(mathutils.Vector({self.inputs[1].python_value})))"
elif self.operation == "DOT_PRODUCT":
self.outputs[0].python_value = f"mathutils.Vector({self.inputs[0].python_value}).dot(mathutils.Vector({self.inputs[1].python_value}))"
elif self.operation == "LENGTH":
self.outputs[0].python_value = f"mathutils.Vector({self.inputs[0].python_value}).length"
def draw_node(self, context, layout):
layout.prop(self, "operation", text="")
if self.operation != "CROSS_PRODUCT":
layout.prop(self,"size")
@@ -0,0 +1,66 @@
import bpy
from random import randint
from ...base_node import SN_ScriptingBaseNode
class SN_OT_RandomIndex(bpy.types.Operator):
bl_idname = "sn.random_index"
bl_label = "New Random"
bl_description = "Shows you a new random item"
bl_options = {"REGISTER","UNDO","INTERNAL"}
max_value: bpy.props.IntProperty()
node: bpy.props.StringProperty()
def execute(self, context):
node = context.space_data.node_tree.nodes[self.node]
node.index = randint(0,self.max_value)
return {"FINISHED"}
class SN_LofiNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_LofiNode"
bl_label = "LoFi"
bl_width_default = 300
links = [
("Potsu Mix", "https://www.youtube.com/watch?v=FSnuF1FPSIU&list=PLp7HpIyLajPJM1np34q3Sad8DjDq2gkJd&ab_channel=potsu-Topic"),
("Relax/Study", "https://www.youtube.com/watch?v=5qap5aO4i9A&ab_channel=ChilledCow"),
("Sleep/Chill", "https://www.youtube.com/watch?v=DWcJFNfaw9c&ab_channel=ChilledCow"),
("Chillhop Radio", "https://www.youtube.com/watch?v=5yx6BWlEVcY&ab_channel=ChillhopMusic"),
("LoFi HipHop", "https://www.youtube.com/watch?v=0te6noMKffA&ab_channel=Netizens"),
("StarWars LoFi", "https://www.youtube.com/watch?v=78cyA-aGaKc&t=108s&ab_channel=Amemos-Ambience"),
("Samurai", "https://www.youtube.com/watch?v=jrTMMG0zJyI&t=9s&ab_channel=thebootlegboy"),
("Code-Fi", "https://www.youtube.com/watch?v=f02mOEt11OQ&ab_channel=TheAMPChannel"),
("LoFi Jazz", "https://www.youtube.com/watch?v=esX7SFtEjHg&ab_channel=CodePioneers"),
("Minecraft LoFi", "https://www.youtube.com/watch?v=hy0bAbznU6g&ab_channel=SolivagantSounds"),
("Late Night Nostalgia", "https://www.youtube.com/watch?v=dLmyp3xMsAo&ab_channel=DreamhopMusic"),
("Coffee Shop Radio", "https://www.youtube.com/watch?v=-5KAN9_CzSA&ab_channel=STEEZYASFUCK"),
("Old Songs LoFi", "https://www.youtube.com/watch?v=bPPiuludHKg&ab_channel=Lo-fiMusic"),
("Mind On Clouds", "https://www.youtube.com/watch?v=Hdncb04CdWw&ab_channel=TunableMusic"),
("Chill Beats", "https://www.youtube.com/watch?v=rA56B4JyTgI&ab_channel=WillSmith"),
("ibr Beats", "https://soundcloud.com/i-b-r/popular-tracks")
]
index: bpy.props.IntProperty(default=0)
def on_create(self, context):
self.index = randint(0,len(self.links)-1)
self.color = (0.184, 0.184, 0.184)
def on_copy(self,node):
self.index = randint(0,len(self.links)-1)
def draw_node(self, context, layout):
box = layout.box()
col = box.column()
col.label(text="This node is purely for your enjoyment!",icon="FUND")
col.label(text="Have a really nice day and enjoy the music!",icon="BLANK1")
row = layout.row(align=True)
row.scale_y = 1.5
row.operator("wm.url_open",text=self.links[self.index][0],depress=True,icon="FILE_SOUND").url = self.links[self.index][1]
op = row.operator("sn.random_index",text="",icon="FILE_REFRESH",depress=True)
op.node = self.name
op.max_value = len(self.links)-1
@@ -0,0 +1,19 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_SwitchDataNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SwitchDataNode"
bl_label = "Switch Data"
node_color = "DEFAULT"
def on_create(self, context):
self.add_boolean_input("Switch")
self.add_data_input("Data 1")
self.add_data_input("Data 2")
self.add_data_output("Data").changeable = True
def evaluate(self, context):
self.outputs[0].python_value = f"({self.inputs[2].python_value} if {self.inputs[0].python_value} else {self.inputs[1].python_value})"
@@ -0,0 +1,19 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_SwitchIconNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SwitchIconNode"
bl_label = "Switch Icons"
node_color = "ICON"
def on_create(self, context):
self.add_boolean_input("Switch")
self.add_icon_input("Icon 1")
self.add_icon_input("Icon 2")
self.add_icon_output("Icon")
def evaluate(self, context):
self.outputs[0].python_value = f"({self.inputs[2].python_value} if {self.inputs[0].python_value} else {self.inputs[1].python_value})"
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_CombineStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_CombineStringsNode"
bl_label = "Combine Strings"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String").prev_dynamic = True
self.add_dynamic_string_input("String")
self.add_string_output("Combined String")
def evaluate(self, context):
values = [" + " + inp.python_value for inp in self.inputs[1:-1]]
self.outputs["Combined String"].python_value = self.inputs[0].python_value + "".join(values)
@@ -0,0 +1,17 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_DecodeStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_DecodeStringNode"
bl_label = "Decode Byte String"
node_color = "STRING"
def on_create(self, context):
self.add_string_input("Bytes")
self.add_string_output("String")
def evaluate(self, context):
self.outputs[0].python_value = f"{self.inputs['Bytes'].python_value}.decode('utf-8') "
@@ -0,0 +1,17 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_EncodeStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_EncodeStringNode"
bl_label = "Encode Byte String"
node_color = "STRING"
def on_create(self, context):
self.add_string_input("String")
self.add_string_output("Bytes")
def evaluate(self, context):
self.outputs[0].python_value = f"{self.inputs['String'].python_value}.encode() "
@@ -0,0 +1,19 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_IsInStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_IsInStringNode"
bl_label = "Substring is in String"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_string_input("Substring")
self.add_boolean_output("Is in String")
def evaluate(self, context):
self.outputs[0].python_value = f"{self.inputs['Substring'].python_value} in {self.inputs['String'].python_value}"
@@ -0,0 +1,19 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_JoinStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_JoinStringsNode"
bl_label = "Join Strings"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_list_input("String List")
self.add_string_input("Join On")
self.add_string_output("String")
def evaluate(self, context):
self.outputs["String"].python_value = f"{self.inputs['Join On'].python_value}.join({self.inputs['String List'].python_value})"
@@ -0,0 +1,54 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class StringMap(bpy.types.PropertyGroup):
def update_item(self, context):
for node in self.id_data.node_collection("SN_MapStringsNode").nodes:
for item in node.map_collection:
if item == self:
node._evaluate(context)
return
name: bpy.props.StringProperty(name="From", description="The value from which to map", update=update_item)
to_string: bpy.props.StringProperty(name="To", description="The value to which to map", update=update_item)
class SN_MapStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_MapStringsNode"
bl_label = "Map Strings"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("Input")
self.add_string_output("Mapped")
map_collection: bpy.props.CollectionProperty(type=StringMap)
def evaluate(self, context):
lookup = "{"
for item in self.map_collection:
lookup += f"'{item.name}': '{item.to_string}', "
lookup += "}"
self.outputs[0].python_value = f"{lookup}[{self.inputs[0].python_value}]"
def draw_node(self, context, layout):
op = layout.operator("sn.add_string_map_item", icon="ADD")
op.node_tree = self.node_tree.name
op.node = self.name
col = layout.column(align=True)
for i, item in enumerate(self.map_collection):
box = col.box()
row = box.row()
row.prop(item, "name")
op = row.operator("sn.remove_string_map_item", text="", icon="PANEL_CLOSE", emboss=False)
op.node_tree = self.node_tree.name
op.node = self.name
op.index = i
box.prop(item, "to_string")
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_PadStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_PadStringNode"
bl_label = "Pad String"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_integer_input("Size")
self.add_string_input("Pad")
self.add_string_output("Padded String")
def evaluate(self, context):
self.outputs["Padded String"].python_value = f"{self.inputs['String'].python_value}.rjust({self.inputs['Size'].python_value}, {self.inputs['Pad'].python_value})"
@@ -0,0 +1,20 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_ReplaceStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ReplaceStringNode"
bl_label = "Replace in String"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_string_input("Old")
self.add_string_input("New")
self.add_string_output("String")
def evaluate(self, context):
self.outputs[0].python_value = f"{self.inputs[0].python_value}.replace({self.inputs[1].python_value}, {self.inputs[2].python_value})"
@@ -0,0 +1,21 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_SliceStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SliceStringNode"
bl_label = "Slice String"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_integer_input("Index")
self.add_string_output("Before")
self.add_string_output("After")
def evaluate(self, context):
self.outputs["Before"].python_value = f"{self.inputs['String'].python_value}[:{self.inputs['Index'].python_value}]"
self.outputs["After"].python_value = f"{self.inputs['String'].python_value}[{self.inputs['Index'].python_value}:]"
@@ -0,0 +1,19 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_SplitStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SplitStringNode"
bl_label = "Split String"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_string_input("Split On")
self.add_list_output("Split List")
def evaluate(self, context):
self.outputs["Split List"].python_value = f"{self.inputs['String'].python_value}.split({self.inputs['Split On'].python_value})"
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_StringLengthNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_StringLengthNode"
bl_label = "String Length"
node_color = "STRING"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("String")
self.add_integer_output("Length")
def evaluate(self, context):
self.outputs["Length"].python_value = f"len({self.inputs[0].python_value})"
@@ -0,0 +1,33 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_StripStringNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_StripStringNode"
bl_label = "Strip String"
node_color = "STRING"
bl_width_default = 200
position: bpy.props.EnumProperty(name="Position",
description="Sides of the string to strip",
items=[("BOTH", "Both", "Remove whitespace on both sides of the string"),
("LEFT", "Left", "Remove whitespace on the left side of the string"),
("RIGHT", "Right", "Remove whitespace on the right side of the string")],
update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_string_input("String")
self.add_string_output("Stripped String")
def evaluate(self, context):
if self.position == "BOTH":
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.strip()"
elif self.position == "LEFT":
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.lstrip()"
elif self.position == "RIGHT":
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.rstrip()"
def draw_node(self, context, layout):
layout.prop(self, "position", expand=True)
@@ -0,0 +1,36 @@
import bpy
class SN_OT_AddStringMapItem(bpy.types.Operator):
bl_idname = "sn.add_string_map_item"
bl_label = "Add Item"
bl_description = "Adds an item to this node"
bl_options = {"REGISTER", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
node: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
def execute(self, context):
node = bpy.data.node_groups[self.node_tree].nodes[self.node]
node.map_collection.add()
node._evaluate(context)
return {"FINISHED"}
class SN_OT_RemoveStringMapItem(bpy.types.Operator):
bl_idname = "sn.remove_string_map_item"
bl_label = "Remove Item"
bl_description = "Removes an item from this node"
bl_options = {"REGISTER", "INTERNAL"}
node: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
node_tree: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
index: bpy.props.IntProperty(options={"HIDDEN", "SKIP_SAVE"})
def execute(self, context):
node = bpy.data.node_groups[self.node_tree].nodes[self.node]
node.map_collection.remove(self.index)
node._evaluate(context)
return {"FINISHED"}