2025-07-01
This commit is contained in:
+19
@@ -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})"
|
||||
+18
@@ -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})"
|
||||
+23
@@ -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)
|
||||
Reference in New Issue
Block a user