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,19 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_GetAttributeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_GetAttributeNode"
bl_label = "Get Attribute"
bl_width_default = 200
def on_create(self, context):
self.add_data_input()
self.add_string_input("Attribute")
self.add_data_input("Fallback")
self.add_data_output("Data").changeable = True
def evaluate(self, context):
self.outputs[0].python_value = f"getattr({self.inputs[0].python_value}, {self.inputs['Attribute'].python_value}, {self.inputs['Fallback'].python_value})"
@@ -0,0 +1,18 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_HasAttributeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_HasAttributeNode"
bl_label = "Has Attribute"
bl_width_default = 200
def on_create(self, context):
self.add_data_input()
self.add_string_input("Attribute")
self.add_boolean_output("Has Attribute")
def evaluate(self, context):
self.outputs[0].python_value = f"hasattr({self.inputs[0].python_value}, {self.inputs['Attribute'].python_value})"
@@ -0,0 +1,23 @@
import bpy
from ...base_node import SN_ScriptingBaseNode
class SN_SetAttributeNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_SetAttributeNode"
bl_label = "Set Attribute"
bl_width_default = 200
def on_create(self, context):
self.add_execute_input()
self.add_data_input()
self.add_string_input("Attribute")
self.add_data_input("Value")
self.add_execute_output()
def evaluate(self, context):
self.code = f"""
setattr({self.inputs[1].python_value}, {self.inputs['Attribute'].python_value}, {self.inputs['Value'].python_value})
{self.indent(self.outputs[0].python_value, 5)}
"""
@@ -0,0 +1,21 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_GetDataScriptlineNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_GetDataScriptlineNode"
bl_label = "Get Data Scriptline"
bl_width_default = 200
def on_create(self, context):
self.add_string_input("Line")
self.add_data_output("Data").changeable = True
def evaluate(self, context):
self.outputs[0].python_value = f"eval({self.inputs[0].python_value})"
def draw_node(self, context, layout):
if self.outputs[0].bl_label == "Property" or self.outputs[0].bl_label == "Collection Property":
layout.label(text="Use Get Property Scriptline for transformable properties!", icon="INFO")
@@ -0,0 +1,30 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_GetPropertyScriptlineNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_GetPropertyScriptlineNode"
bl_label = "Get Property Scriptline"
bl_width_default = 200
def on_create(self, context):
self.add_property_input()
self.add_property_output()
property_name: bpy.props.StringProperty(name="Property",
description="The name of the property you want to get",
update=SN_ScriptingBaseNode._evaluate)
def evaluate(self, context):
if self.property_name:
if self.property_name[0] == "[":
self.outputs[0].python_value = f"{self.inputs[0].python_value}{self.property_name}"
else:
self.outputs[0].python_value = f"{self.inputs[0].python_value}.{self.property_name}"
else:
self.outputs[0].reset_value()
def draw_node(self, context, layout):
layout.prop(self, "property_name")
@@ -0,0 +1,114 @@
import bpy
import os
from ..base_node import SN_ScriptingBaseNode
from ...utils import unique_collection_name, get_python_name
class SN_InterfaceScriptNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_InterfaceScriptNode"
bl_label = "Interface Script"
node_color = "INTERFACE"
bl_width_default = 200
def on_create(self, context):
self.add_interface_input()
self.add_dynamic_data_input("Variable").is_variable = True
def on_socket_name_change(self, socket):
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [inp.name for inp in self.inputs[1:-1]], "_", includes_name=True)
self._evaluate(bpy.context)
def on_dynamic_socket_add(self, socket):
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [inp.name for inp in self.inputs[1:-1]], "_", includes_name=True)
self._evaluate(bpy.context)
source: bpy.props.EnumProperty(name="Source",
description="The source of where to get the code from",
items=[("BLENDER", "Blender", "Blender"),
("EXTERNAL", "External", "External")],
update=SN_ScriptingBaseNode._evaluate)
script: bpy.props.PointerProperty(name="File",
type=bpy.types.Text,
update=SN_ScriptingBaseNode._evaluate)
def update_path(self, context):
if not self.external_script == bpy.path.abspath(self.external_script):
self.external_script = bpy.path.abspath(self.external_script)
self._evaluate(context)
external_script: bpy.props.StringProperty(name="File",
subtype="FILE_PATH",
description="The external file that holds your script",
update=update_path)
def evaluate(self, context):
script_locals = "script_locals = {"
for socket in self.inputs[1:-1]:
script_locals += f"'{socket.name}': {socket.name}, "
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
script_locals += f"'{ntree.python_name}': {ntree.python_name}, "
script_locals += "}"
if self.source == "BLENDER":
if self.script:
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[1:-1]], 7)}
{script_locals}
script_locals.update(locals())
try:
exec("\\n".join([line.body for line in bpy.data.texts["{self.script.name}"].lines]), globals(), script_locals)
except:
layout.label(text="Error when running script!", icon="ERROR")
"""
elif self.source == "EXTERNAL":
self.code_import = "import os"
path = self.external_script.replace('\\', '/')
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[1:-1]], 6)}
{script_locals}
script_locals.update(locals())
if os.path.exists(r"{path}"):
with open(r"{path}", "r") as script_file:
try:
exec(script_file.read(), globals(), script_locals)
except:
layout.label(text="Error when running script!", icon="ERROR")
else:
layout.label(text="Couldn't find script path!", icon="ERROR")
"""
def evaluate_export(self, context):
if self.source == "BLENDER":
if self.script:
text = "\n".join([line.body for line in bpy.data.texts[self.script.name].lines])
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[1:-1]], 7)}
{self.indent(text, 7)}
"""
elif self.source == "EXTERNAL" and self.external_script:
text = ""
if os.path.exists(self.external_script):
with open(self.external_script, "r") as script_file:
text = script_file.read()
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[1:-1]], 6)}
{self.indent(text, 6)}
"""
def draw_node(self, context, layout):
layout.prop(self, "source", expand=True)
if self.source == "BLENDER":
layout.template_ID(self, "script", open="text.open", new="text.new")
elif self.source == "EXTERNAL":
layout.prop(self, "external_script", text="Path")
@@ -0,0 +1,22 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_InterfaceScriptlineNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_InterfaceScriptlineNode"
bl_label = "Interface Scriptline"
node_color = "INTERFACE"
bl_width_default = 200
def on_create(self, context):
self.add_interface_input()
self.add_string_input("Line")
self.add_dynamic_string_input("Line")
def evaluate(self, context):
lines = [f"exec({inp.python_value})" for inp in self.inputs[1:-1]]
self.code = f"""
{self.indent(lines, 5)}
"""
@@ -0,0 +1,173 @@
import os
import bpy
from ..base_node import SN_ScriptingBaseNode
from ...utils import normalize_code, unique_collection_name, get_python_name
class SN_OT_ReloadScript(bpy.types.Operator):
bl_idname = "sn.reload_script"
bl_label = "Reload Script"
bl_description = "Reloads the script from the choosen source"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
node = bpy.data.node_groups[self.node_tree].nodes[self.node]
node._evaluate(context)
return {"FINISHED"}
class SN_RunScriptNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RunScriptNode"
bl_label = "Run Script"
node_color = "PROGRAM"
bl_width_default = 200
def on_socket_name_change(self, socket):
if socket in self.inputs[2:-1]:
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [inp.name for inp in self.inputs[2:-1]], "_", includes_name=True)
elif socket in self.outputs[1:-1]:
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [out.name for out in self.outputs[1:-1]], "_", includes_name=True)
socket.python_value = socket.name
self._evaluate(bpy.context)
def on_dynamic_socket_add(self, socket):
if socket in self.inputs[2:-1]:
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [inp.name for inp in self.inputs[2:-1]], "_", includes_name=True)
elif socket in self.outputs[1:-1]:
socket["name"] = get_python_name(socket.name, "Variable", lower=False)
socket["name"] = unique_collection_name(socket.name, "Variable", [out.name for out in self.outputs[1:-1]], "_", includes_name=True)
socket.python_value = socket.name
def on_create(self, context):
self.add_execute_input()
self.add_execute_output()
inp = self.add_string_input("Script Path")
inp.subtype = "FILE_PATH"
inp.set_hide(True)
self.add_dynamic_data_input("Variable").is_variable = True
out = self.add_dynamic_data_output("Variable")
out.is_variable = True
out.changeable = True
def update_source(self, context):
self._evaluate(context)
source: bpy.props.EnumProperty(name="Source",
description="The source of where to get the code from",
items=[("BLENDER", "Blender", "Blender"),
("EXTERNAL", "External", "External")],
update=update_source)
script: bpy.props.PointerProperty(name="File", type=bpy.types.Text, update=SN_ScriptingBaseNode._evaluate)
def find_block_bounds(self, lines, name):
start = -1
end = -1
for i, line in enumerate(lines):
if start < 0 and name in line:
start = i
elif start >= 0 and len(line) - len(line.lstrip()) == 0 and line.strip():
end = i-1
break
if end == -1: end = len(lines)-1
return start, end
def get_script_code(self, script):
register = ""
unregister = ""
imports = ""
script = script.split("\n")
for line in script:
if line.startswith("import ") or (line.startswith("from ") and "import " in line):
imports += line+"\n"
script.remove(line)
script = "\n".join(script)
if not "def register()" in script and not "def unregister()" in script:
return (normalize_code(script), register, unregister, imports)
lines = script.split("\n")
if "def register()" in script:
start, end = self.find_block_bounds(lines, "def register():")
if start >= 0 and end >= 0:
lines_register = lines[start:end+1][1:]
register = normalize_code("\n".join(lines_register))
after = lines[end+1:]
lines = lines[:start] + after
if "def unregister()" in script:
start, end = self.find_block_bounds(lines, "def unregister():")
if start >= 0 and end >= 0:
lines_unregister = lines[start:end+1][1:]
unregister = normalize_code("\n".join(lines_unregister))
after = lines[end+1:]
lines = lines[:start] + after
if "if __name__ == '__main__'" in script.replace('"', "'"):
start, end = self.find_block_bounds(lines, "if __name__ == ")
if start >= 0 and end >= 0:
after = lines[end+1:]
lines = lines[:start] + after
script = "\n".join(lines)
return (normalize_code(script), register, unregister, imports)
def evaluate(self, context):
for socket in self.outputs[1:-1]:
socket.python_value = socket.name
if self.source == "BLENDER":
if self.script:
script = ("", "", "", "")
text = "\n".join([line.body for line in bpy.data.texts[self.script.name].lines])
script = self.get_script_code(text)
self.code_register = f"""{script[1]}"""
self.code_unregister = f"""{script[2]}"""
self.code_import = f"""{script[3]}"""
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[2:-1]], 7)}
{self.indent([f"{out.name} = None" for out in self.outputs[1:-1]], 7)}
{self.indent(script[0], 7)}
{self.indent(self.outputs[0].python_value, 7)}
"""
elif self.source == "EXTERNAL":
script = ("", "", "", "")
if os.path.exists(eval(self.inputs['Script Path'].python_value)):
with open(eval(self.inputs['Script Path'].python_value), "r") as script_file:
text = script_file.read()
script = self.get_script_code(text)
self.code_register = script[1]
self.code_unregister = script[2]
self.code_import = script[3]
self.code = f"""
{self.indent([f"{inp.name} = {inp.python_value}" for inp in self.inputs[2:-1]], 6)}
{self.indent([f"{out.name} = None" for out in self.outputs[1:-1]], 6)}
{self.indent(script[0], 6)}
{self.indent(self.outputs[0].python_value, 6)}
"""
def draw_node(self, context, layout):
row = layout.row(align=True)
row.prop(self, "source", expand=True)
op = row.operator("sn.reload_script", text="", icon="FILE_REFRESH")
op.node = self.name
op.node_tree = self.node_tree.name
if self.source == "BLENDER":
layout.template_ID(self, "script", open="text.open", new="text.new")
else:
layout.prop(self.inputs["Script Path"], "value_file_path", text="Path")
@@ -0,0 +1,24 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_ScriptlineNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ScriptlineNode"
bl_label = "Scriptline"
node_color = "PROGRAM"
bl_width_default = 200
def on_create(self, context):
self.add_execute_input()
self.add_execute_output()
self.add_string_input("Line")
self.add_dynamic_string_input("Line")
def evaluate(self, context):
lines = [f"exec({inp.python_value})" for inp in self.inputs[1:-1]]
self.code = f"""
{self.indent(lines, 5)}
{self.indent(self.outputs[0].python_value, 5)}
"""
@@ -0,0 +1,19 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_ScriptNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ScriptNode"
bl_label = "Script"
node_color = "PROGRAM"
is_trigger = True
bl_width_default = 200
def on_create(self, context):
self.add_execute_output()
def evaluate(self, context):
self.code = self.indent(self.outputs[0].python_value, 0)