2025-07-01
This commit is contained in:
@@ -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]
|
||||
Reference in New Issue
Block a user