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,18 @@
import bpy
def ntree_variable_register_code(ntree):
if len(ntree.variables) == 0: return ""
code = f"{ntree.python_name} = {{"
for var in ntree.variables:
code += f"'{var.python_name}': {var.var_default}, "
code += "}\n"
return code
def variable_register_code():
code = ""
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
code += ntree_variable_register_code(ntree)
return code
@@ -0,0 +1,177 @@
import bpy
from ...nodes.compiler import compile_addon
class SN_OT_AddVariable(bpy.types.Operator):
bl_idname = "sn.add_variable"
bl_label = "Add Variable"
bl_description = "Adds a variable to the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
ntree = bpy.data.node_groups[self.node_tree]
new_var = ntree.variables.add()
new_var.name = "New Variable"
ntree.variables.move(len(ntree.variables)-1, ntree.variable_index+1)
ntree.variable_index += 1
ntree.variable_index = min(ntree.variable_index, len(ntree.variables)-1)
return {"FINISHED"}
class SN_OT_RemoveVariable(bpy.types.Operator):
bl_idname = "sn.remove_variable"
bl_label = "Remove Variable"
bl_description = "Removes this variable from the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
ntree = bpy.data.node_groups[self.node_tree]
ntree.variables.remove(ntree.variable_index)
ntree.variable_index -= 1
compile_addon()
return {"FINISHED"}
class SN_OT_MoveVariable(bpy.types.Operator):
bl_idname = "sn.move_variable"
bl_label = "Move Variable"
bl_description = "Moves this variable"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
move_up: bpy.props.BoolProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
ntree = bpy.data.node_groups[self.node_tree]
if self.move_up:
ntree.variables.move(ntree.variable_index, ntree.variable_index - 1)
ntree.variable_index -= 1
else:
ntree.variables.move(ntree.variable_index, ntree.variable_index + 1)
ntree.variable_index += 1
return {"FINISHED"}
class SN_OT_AddVariableNodePopup(bpy.types.Operator):
bl_idname = "sn.add_variable_node_popup"
bl_label = "Add Variable Node Popup"
bl_description = "Opens a popup to let you choose a variable node"
bl_options = {"REGISTER", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
return {"FINISHED"}
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.scale_y = 1.5
op = col.operator("sn.add_variable_node", text="Get Variable", icon="ADD")
op.type = "SN_GetVariableNode"
op.node_tree = self.node_tree
op = col.operator("sn.add_variable_node", text="Set Variable", icon="ADD")
op.type = "SN_SetVariableNode"
op.node_tree = self.node_tree
op = col.operator("sn.add_variable_node", text="Reset Variable", icon="ADD")
op.type = "SN_ResetVariableNode"
op.node_tree = self.node_tree
def invoke(self, context, event):
return context.window_manager.invoke_popup(self)
class SN_OT_AddVariableNode(bpy.types.Operator):
bl_idname = "sn.add_variable_node"
bl_label = "Add Variable Node"
bl_description = "Adds this node to the editor"
bl_options = {"REGISTER", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
type: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
bpy.ops.node.add_node("INVOKE_DEFAULT", type=self.type, use_transform=True)
node = context.space_data.node_tree.nodes.active
ntree = bpy.data.node_groups[self.node_tree]
if ntree.variable_index < len(ntree.variables):
var = ntree.variables[ntree.variable_index]
node.ref_ntree = ntree
node.var_name = var.name
return {"FINISHED"}
class SN_OT_FindVariable(bpy.types.Operator):
bl_idname = "sn.find_variable"
bl_label = "Find Variable"
bl_description = "Finds this variable in the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def execute(self, context):
return {"FINISHED"}
def draw(self, context):
layout = self.layout
ntree = bpy.data.node_groups[self.node_tree]
# init variable nodes
empty_nodes = []
variable_nodes = []
variable = None
if ntree.variable_index < len(ntree.variables):
variable = ntree.variables[ntree.variable_index]
# find variable nodes
for ngroup in bpy.data.node_groups:
if ngroup.bl_idname == "ScriptingNodesTree":
for node in ngroup.nodes:
if hasattr(node, "var_name") and hasattr(node, "ref_ntree"):
if variable and node.var_name == variable.name and node.ref_ntree == ntree:
variable_nodes.append(node)
elif not node.var_name or not node.ref_ntree:
empty_nodes.append(node)
# draw nodes for selected variable
if ntree.variable_index < len(ntree.variables):
col = layout.column()
row = col.row()
row.enabled = False
row.label(text=f"Variable: {variable.name}")
for node in variable_nodes:
op = col.operator("sn.find_node", text=node.name, icon="RESTRICT_SELECT_OFF")
op.node_tree = node.node_tree.name
op.node = node.name
if not variable_nodes:
col.label(text="No nodes found for this variable", icon="INFO")
# draw nodes with empty variable
col = layout.column()
row = col.row()
row.label(text="Empty Variable Nodes")
row.enabled = False
for node in empty_nodes:
op = col.operator("sn.find_node", text=node.name, icon="RESTRICT_SELECT_OFF")
op.node_tree = node.node_tree.name
op.node = node.name
if not empty_nodes:
col.label(text="No empty variable nodes found", icon="INFO")
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=250)
@@ -0,0 +1,126 @@
import bpy
from ...utils import get_python_name, unique_collection_name
from ..properties.settings.settings import property_icons
from ...nodes.compiler import compile_addon
class SN_VariableProperties(bpy.types.PropertyGroup):
@property
def node_tree(self):
return self.id_data
# cache python names so they only have to be generated once
cached_python_names = {}
cached_python_name: bpy.props.StringProperty()
cached_human_name: bpy.props.StringProperty()
@property
def python_name(self):
if self.name == self.cached_human_name and self.cached_python_name: return self.cached_python_name
if self.name in self.cached_python_names: return self.cached_python_names[self.name]
names = []
for var in self.node_tree.variables:
if var == self:
break
names.append(var.python_name)
name = unique_collection_name(f"sna_{get_python_name(self.name, 'sna_new_variable')}", "sna_new_variable", names, "_")
try:
self.cached_python_name = name
self.cached_human_name = self.name
except AttributeError: pass
self.cached_python_names[self.name] = name
return name
@property
def data_path(self):
return f"{self.node_tree.python_name}['{self.python_name}']"
@property
def icon(self):
return property_icons[self.variable_type]
def compile(self, context=None):
""" Registers the variable and unregisters previous version """
# print(f"Serpens Log: Variable {self.name} received an update")
compile_addon()
def get_name(self):
return self.get("name", "Variable Default")
def get_to_update_nodes(self):
to_update_nodes = []
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
for node in ntree.nodes:
if getattr(node, "var_name", None) == self.name:
to_update_nodes.append(node)
return to_update_nodes
def set_name(self, value):
names = list(map(lambda item: item.name, list(filter(lambda item: item!=self, self.node_tree.variables))))
value = unique_collection_name(value, "New Variable", names, " ")
to_update = self.get_to_update_nodes()
# set value
self["name"] = value
self.compile()
# update node references
for node in to_update:
node.var_name = value
name: bpy.props.StringProperty(name="Variable Name",
description="Name of this variable",
default="Variable Default",
get=get_name,
set=set_name,
update=compile)
def update_variable_type(self, context):
for node in self.get_to_update_nodes():
if hasattr(node, "on_var_changed"):
node.on_var_changed()
self.compile()
variable_type: bpy.props.EnumProperty(name="Type",
description="The type of data this variable stores",
update=update_variable_type,
items=[("Data", "Data", "Stores any type of data", property_icons["Data"], 0),
("String", "String", "Stores a string of characters", property_icons["String"], 1),
("Boolean", "Boolean", "Stores True or False", property_icons["Boolean"], 2),
("Float", "Float", "Stores a decimal number", property_icons["Float"], 3),
("Integer", "Integer", "Stores an integer number", property_icons["Integer"], 4),
("List", "List", "Stores a list of data", property_icons["List"], 5),
("Pointer", "Pointer", "Stores a reference to certain types of blend data, collection or group properties", property_icons["Pointer"], 6),
("Collection", "Collection", "Stores a list of certain blend data or property groups to be displayed in lists", property_icons["Collection"], 7)])
string_default: bpy.props.StringProperty(name="Default", description="Default value for the variable", update=compile)
boolean_default: bpy.props.BoolProperty(name="Default", description="Default value for the variable", update=compile)
float_default: bpy.props.FloatProperty(name="Default", description="Default value for the variable", update=compile)
integer_default: bpy.props.IntProperty(name="Default", description="Default value for the variable", update=compile)
@property
def var_default(self):
return {
"Data": None,
"String": f"'{self.string_default}'",
"Boolean": self.boolean_default,
"Float": self.float_default,
"Integer": self.integer_default,
"List": [],
"Pointer": None,
"Collection": None,
}[self.variable_type]