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,42 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_AfterRenderNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_AfterRenderNode"
bl_label = "On Render Finish"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("render_cancel", "Cancel", "On canceling a render job"), ("render_post", "After", "On render (after)"), ("render_complete", "Complete", "On completion of render job")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.handler = self.action
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,34 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_BeforeExitNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BeforeExitNode"
bl_label = "On Blender Close"
is_trigger = True
bl_width_default = 200
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = "EXIT"
def evaluate(self, context):
self.code_import = f"import atexit"
self.code = f"""
def before_exit_handler_{self.static_uid}():
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"""atexit.register(before_exit_handler_{self.static_uid})"""
self.code_unregister = f"""atexit.unregister(before_exit_handler_{self.static_uid})"""
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_BeforeRenderNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_BeforeRenderNode"
bl_label = "On Render Start"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("render_init", "Init", "On initialization of a render job"), ("render_pre", "Before", "On render (before)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_DepsgraphUpdateNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_DepsgraphUpdateNode"
bl_label = "On Depsgraph Update"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("depsgraph_update_pre", "Before", "On depsgraph update (before)"), ("depsgraph_update_post", "After", "On depsgraph update (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_ChangeFrameNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_ChangeFrameNode"
bl_label = "On Frame Change"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("frame_change_pre", "Before", "Called after frame change for playback and rendering, before any data is evaluated for the new frame"), ("frame_change_post", "After", "Called after frame change for playback and rendering, after the data has been evaluated for the new frame")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,386 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
from ..Program.Run_Operator import on_operator_ref_update
space_names = {
"EMPTY": "Window",
"VIEW_3D": "3D View",
"IMAGE_EDITOR": "Image",
"NODE_EDITOR": "Node Editor",
"SEQUENCE_EDITOR": "Sequencer",
"CLIP_EDITOR": "Clip",
"DOPESHEET_EDITOR": "Dopesheet",
"GRAPH_EDITOR": "Graph Editor",
"NLA_EDITOR": "NLA Editor",
"TEXT_EDITOR": "Text",
"CONSOLE": "Console",
"INFO": "Info",
"OUTLINER": "Outliner",
"PROPERTIES": "Property Editor",
"FILE_BROWSER": "File Browser"
}
class SN_OnKeypressNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_OnKeypressNode"
bl_label = "On Keypress"
is_trigger = True
bl_width_default = 200
key: bpy.props.StringProperty(name="Key",default="Y", update=SN_ScriptingBaseNode._evaluate)
recording: bpy.props.BoolProperty(name="Recording",default=False)
ctrl: bpy.props.BoolProperty(name="Ctrl",
description="If the ctrl key has to be pressed",
default=False,
update=SN_ScriptingBaseNode._evaluate)
alt: bpy.props.BoolProperty(name="Alt",
description="If the alt key has to be pressed",
default=False,
update=SN_ScriptingBaseNode._evaluate)
shift: bpy.props.BoolProperty(name="Shift",
description="If the shift key has to be pressed",
default=False,
update=SN_ScriptingBaseNode._evaluate)
value: bpy.props.EnumProperty(name="Value",
description="The way the key has to be pressed",
items=[("PRESS","On Press","The action is run when the key is pressed once"),
("RELEASE","On Release","The action is run when the key is released"),
("CLICK","On Click","The action is run when the mouse button is clicked once"),
("DOUBLE_CLICK","On Double Click","The action is run when the mouse button is clicked twice"),
("ANY","Any","The action will run with any of the above ways"),
("CLICK_DRAG","On Click Drag","The action is run when you click and drag")],
update=SN_ScriptingBaseNode._evaluate)
direction: bpy.props.EnumProperty(name="Direction",
description="Direction to drag the mouse in to run the action",
items=[("ANY","Any Direction","The action will run when you click and drag in any direction"),
("NORTH","North","The action will run when you click and drag in this direction"),
("NORTH_EAST","North East","The action will run when you click and drag in this direction"),
("EAST","East","The action will run when you click and drag in this direction"),
("SOUTH_EAST","South East","The action will run when you click and drag in this direction"),
("SOUTH","South","The action will run when you click and drag in this direction"),
("SOUT_WEST","South West","The action will run when you click and drag in this direction"),
("WEST","West","The action will run when you click and drag in this direction"),
("NORTH_WEST","North West","The action will run when you click and drag in this direction")],
update=SN_ScriptingBaseNode._evaluate)
repeat: bpy.props.BoolProperty(name="Repeat Key",
description="Repeat the action when the key is held down",
default=False,
update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.ref_ntree = self.node_tree
self.order = 3
def reset_inputs(self, context=None):
self.inputs.clear()
self.pasted_operator = ""
self.ref_SN_OperatorNode = ""
self._evaluate(context)
def get_spaces(self, context):
items = [("EMPTY","Any Space","Run this shortcut in any space")]
for item in list(space_names.keys())[1:]:
items.append((item,item.replace("_"," ").title(),item.replace("_"," ").title()))
return items
space: bpy.props.EnumProperty(items=get_spaces,
name="Space",
description="The space which this action should be able to run in",
update=SN_ScriptingBaseNode._evaluate)
action: bpy.props.EnumProperty(items=[("OPERATOR","Operator","Operator"),
("PANEL","Panel","Panel"),
("MENU","Menu","Menu"),
("PIE_MENU","Pie Menu","Pie Menu")],
name="Shortcut Type",
description="The type of action to run from this shortcut",
update=reset_inputs)
parent_type: bpy.props.EnumProperty(name="Parent Type",
description="Use a custom panel as a parent",
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
update=reset_inputs)
def update_picked(self,context):
if self.picked:
elem = eval("bpy.types." + self.picked)
if elem.bl_rna.base.identifier == "Menu":
if "pie" in self.picked.lower():
self.action = "PIE_MENU"
self.pie = self.picked
else:
self.action = "MENU"
self.menu = self.picked
elif elem.bl_rna.base.identifier == "Panel":
self.action = "PANEL"
self.panel = self.picked
self.picked = ""
picked: bpy.props.StringProperty(update=update_picked)
panel: bpy.props.StringProperty(name="Panel",
description="The panel to open when the key is pressed",
update=SN_ScriptingBaseNode._evaluate)
menu: bpy.props.StringProperty(name="Menu",
description="The menu to open when the key is pressed",
update=SN_ScriptingBaseNode._evaluate)
pie: bpy.props.StringProperty(name="Pie Menu",
description="The pie menu to open when the key is pressed",
update=SN_ScriptingBaseNode._evaluate)
def create_inputs(self, op_rna):
""" Create inputs for operator """
for prop in op_rna.properties:
if not prop.identifier in ["rna_type", "settings"]:
inp = self.add_input_from_property(prop)
if inp:
inp.can_be_disabled = not prop.is_required
inp.disabled = not prop.is_required
def update_pasted_operator(self, context):
self.inputs.clear()
if self.pasted_operator:
op = eval(self.pasted_operator.split("(")[0])
op_rna = op.get_rna_type()
self.create_inputs(op_rna)
self._evaluate(context)
pasted_operator: bpy.props.StringProperty(name="Operator",
description="The operator to run when the key is pressed",
update=update_pasted_operator)
keep_open: bpy.props.BoolProperty(name="Keep Open",
description="Keep the panel open after a property is changed",
default=True,
update=SN_ScriptingBaseNode._evaluate)
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
name="Panel Node Tree",
description="The node tree to select the panel from",
poll=SN_ScriptingBaseNode.ntree_poll,
update=SN_ScriptingBaseNode._evaluate)
ref_SN_PanelNode: bpy.props.StringProperty(name="Panel",
description="The panel to open with this shortcut",
update=SN_ScriptingBaseNode._evaluate)
ref_SN_MenuNode: bpy.props.StringProperty(name="Menu",
description="The menu to open with this shortcut",
update=SN_ScriptingBaseNode._evaluate)
ref_SN_PieMenuNode: bpy.props.StringProperty(name="Menu",
description="The menu to open with this shortcut",
update=SN_ScriptingBaseNode._evaluate)
def on_ref_update(self, node, data=None):
if node.bl_idname in ["SN_PanelNode", "SN_MenuNode", "SN_PieMenuNode"]:
self._evaluate(bpy.context)
elif node.bl_idname in ["SN_OperatorNode", "SN_ModalOperatorNode"]:
on_operator_ref_update(self, node, data, self.ref_ntree, self.ref_SN_OperatorNode, 0)
def update_custom_operator(self, context):
""" Updates the nodes settings when a new parent panel is selected """
self.inputs.clear()
if self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
parent = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
for prop in parent.properties:
if prop.property_type in ["Integer", "Float", "Boolean"] and prop.settings.is_vector:
socket = self._add_input(self.socket_names[prop.property_type + " Vector"], prop.name)
socket.size = prop.settings.size
socket.can_be_disabled = True
elif prop.property_type == "Enum":
if prop.stngs_enum.enum_flag:
socket = self._add_input(self.socket_names["Enum Set"], prop.name)
else:
socket = self._add_input(self.socket_names[prop.property_type], prop.name)
socket.items = str(list(map(lambda item: item.name, prop.stngs_enum.items)))
else:
self._add_input(self.socket_names[prop.property_type], prop.name).can_be_disabled = True
self._evaluate(context)
ref_SN_OperatorNode: bpy.props.StringProperty(name="Operator",
description="The operator to run with this shortcut",
update=update_custom_operator)
def evaluate(self, context):
if self.key:
input_code = ""
operator = ""
if self.action == "PANEL":
if self.parent_type == "BLENDER" and self.panel:
operator = "wm.call_panel"
input_code = f"kmi.properties.name = '{self.panel}'\n"
input_code += f"kmi.properties.keep_open = {self.keep_open}\n"
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
operator = "wm.call_panel"
node = self.ref_ntree.nodes[self.ref_SN_PanelNode]
input_code = f"kmi.properties.name = '{node.last_idname}'\n"
input_code += f"kmi.properties.keep_open = {self.keep_open}\n"
elif self.action == "MENU":
if self.parent_type == "BLENDER" and self.menu:
operator = "wm.call_menu"
input_code = f"kmi.properties.name = '{self.menu}'\n"
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_MenuNode in self.ref_ntree.nodes:
operator = "wm.call_menu"
node = self.ref_ntree.nodes[self.ref_SN_MenuNode]
input_code = f"kmi.properties.name = '{node.idname}'\n"
elif self.action == "PIE_MENU":
if self.parent_type == "BLENDER" and self.pie:
operator = "wm.call_menu_pie"
input_code = f"kmi.properties.name = '{self.pie}'\n"
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_PieMenuNode in self.ref_ntree.nodes:
operator = "wm.call_menu_pie"
node = self.ref_ntree.nodes[self.ref_SN_PieMenuNode]
input_code = f"kmi.properties.name = '{node.idname}'\n"
elif self.action == "OPERATOR":
if self.parent_type == "BLENDER" and self.pasted_operator:
if not self.pasted_operator: return
operator = self.pasted_operator.split("(")[0].replace("bpy.ops.", "")
op = eval(self.pasted_operator.split("(")[0])
op_rna = op.get_rna_type()
for inp in self.inputs:
if not inp.disabled:
for prop in op_rna.properties:
if (self.version == 0 and (prop.name and prop.name == inp.name) or (not prop.name and prop.identifier.replace("_", " ").title() == inp.name)) \
or (self.version == 1 and (inp.name.replace(" ", "_").lower() == prop.identifier)):
self.code += "\n" + f"op.{prop.identifier} = {inp.python_value}"
input_code += f"kmi.properties.{prop.identifier} = {inp.python_value}\n"
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
node = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
operator = f"sna.{node.operator_python_name}"
for inp in self.inputs:
if not inp.disabled:
for prop in node.properties:
if prop.name == inp.name:
input_code += f"kmi.properties.{prop.python_name} = {inp.python_value}\n"
if operator:
self.code_register = f"""
kc = bpy.context.window_manager.keyconfigs.addon
km = kc.keymaps.new(name='{space_names[self.space]}', space_type='{self.space}')
kmi = km.keymap_items.new('{operator}', '{self.key}', '{self.value}',
ctrl={self.ctrl}, alt={self.alt}, shift={self.shift}, repeat={self.repeat})
{f'kmi.direction = "{self.direction}"' if self.value == "CLICK_DRAG" else ''}
{self.indent(input_code, 5)}
addon_keymaps['{self.static_uid}'] = (km, kmi)
"""
def draw_node(self, context, layout):
row = layout.row(align=True)
row.prop(self, "space", text="")
row.prop(self, "value", text="")
if self.value == "CLICK_DRAG":
layout.prop(self, "direction", text="")
layout.operator("sn.record_key", text=self.key, depress=self.recording).node = self.name
row = layout.row(align=True)
row.prop(self,"ctrl", toggle=True)
row.prop(self,"shift", toggle=True)
row.prop(self,"alt", toggle=True)
layout.prop(self, "repeat")
layout.separator()
row = layout.row(align=True)
row.prop(self, "name")
row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM").node = self.name
layout.separator()
row = layout.row(align=True)
row.prop(self,"action", text="")
row.prop(self,"parent_type", text="", icon_only=True)
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
if self.action == "PANEL":
if self.parent_type == "BLENDER":
name = self.panel.replace("_", " ").title() if self.panel else "Select Panel"
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
op.node = self.name
op.selection = "PANELS"
else:
row = layout.row(align=True)
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
subrow = row.row(align=True)
subrow.enabled = self.ref_ntree != None
subrow.prop_search(self, "ref_SN_PanelNode", parent_tree.node_collection("SN_PanelNode"), "refs", text="", icon="VIEWZOOM")
subrow = row.row()
subrow.enabled = self.ref_ntree != None and self.ref_SN_PanelNode in self.ref_ntree.nodes
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
op.node = self.ref_SN_PanelNode
layout.prop(self, "keep_open")
elif self.action == "MENU":
if self.parent_type == "BLENDER":
name = self.menu.replace("_", " ").title() if self.menu else "Select Menu"
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
op.node = self.name
op.selection = "MENUS"
else:
row = layout.row(align=True)
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
subrow = row.row(align=True)
subrow.enabled = self.ref_ntree != None
subrow.prop_search(self, "ref_SN_MenuNode", parent_tree.node_collection("SN_MenuNode"), "refs", text="", icon="VIEWZOOM")
subrow = row.row()
subrow.enabled = self.ref_ntree != None and self.ref_SN_MenuNode in self.ref_ntree.nodes
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
op.node = self.ref_SN_MenuNode
elif self.action == "PIE_MENU":
if self.parent_type == "BLENDER":
name = self.pie.replace("_"," ").title() if self.pie else "Select Pie Menu"
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
op.node = self.name
op.selection = "MENUS"
else:
row = layout.row(align=True)
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
subrow = row.row(align=True)
subrow.enabled = self.ref_ntree != None
subrow.prop_search(self, "ref_SN_PieMenuNode", parent_tree.node_collection("SN_PieMenuNode"), "refs", text="", icon="VIEWZOOM")
subrow = row.row()
subrow.enabled = self.ref_ntree != None and self.ref_SN_PieMenuNode in self.ref_ntree.nodes
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
op.node = self.ref_SN_PieMenuNode
elif self.action == "OPERATOR":
if self.parent_type == "BLENDER":
name = self.pasted_operator.split("(")[0].split(".")[-1].replace("_"," ").title() if self.pasted_operator else "Paste Operator"
op = layout.operator("sn.paste_operator", text=name, icon="PASTEDOWN")
op.node_tree = self.node_tree.name
op.node = self.name
else:
row = layout.row(align=True)
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
subrow = row.row(align=True)
subrow.enabled = self.ref_ntree != None
subrow.prop_search(self, "ref_SN_OperatorNode", parent_tree.node_collection("SN_OperatorNode"), "refs", text="", icon="VIEWZOOM")
subrow = row.row()
subrow.enabled = self.ref_ntree != None and self.ref_SN_OperatorNode in self.ref_ntree.nodes
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
op.node = self.ref_SN_OperatorNode
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_OnLoadNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_OnLoadNode"
bl_label = "On Load"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("load_pre", "Before", "On loading a new blend file (before)"), ("load_post", "After", "On loading a new blend file (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_OnSaveNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_OnSaveNode"
bl_label = "On Save"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("save_pre", "Before", "On blend file save (before)"), ("save_post", "After", "On blend file save (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_RedoEventNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_RedoEventNode"
bl_label = "On Redo"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("redo_pre", "Before", "On loading a redo step (before)"), ("redo_post", "After", "On loading a redo step (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
from ..base_node import SN_ScriptingBaseNode
class SN_UndoEventNode(SN_ScriptingBaseNode, bpy.types.Node):
bl_idname = "SN_UndoEventNode"
bl_label = "On Undo"
is_trigger = True
bl_width_default = 200
action: bpy.props.EnumProperty(items=[("undo_pre", "Before", "On loading an undo step (before)"), ("undo_post", "After", "On loading an undo step (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
def on_create(self, context):
self.add_execute_output()
def draw_node(self,context,layout):
row = layout.row()
row.scale_y = 1.2
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
op.uid = self.static_uid
op.handler = self.action
layout.prop(self, "action", expand=True)
@property
def handler_name(self):
return f"{self.action}_handler_{self.static_uid}"
def evaluate(self, context):
self.code_import = f"from bpy.app.handlers import persistent"
self.code = f"""
@persistent
def {self.handler_name}(dummy):
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
"""
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
@@ -0,0 +1,41 @@
import bpy
import atexit
class SN_OT_RunEventButton(bpy.types.Operator):
bl_idname = "sn.run_event"
bl_label = "Run Event"
bl_description = "Run the event code without triggering event"
bl_options = {"REGISTER", "INTERNAL"}
uid: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
handler: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
def get_atexit_functions(self):
funs = []
class Capture:
def __eq__(self, other):
funs.append(other)
return False
c = Capture()
atexit.unregister(c)
return funs
def execute(self, context):
if self.handler == "EXIT":
for func in self.get_atexit_functions():
if self.uid in func.__name__:
func()
return {"FINISHED"}
else:
handlers = getattr(bpy.app.handlers, self.handler)
for handler in handlers:
if self.uid in handler.__name__:
handler(None)
return {"FINISHED"}
self.report({'WARNING'}, "Failed when trying to run event. Try recompiling!")
return {"CANCELLED"}
@@ -0,0 +1,123 @@
import bpy
class SN_OT_RecordKey(bpy.types.Operator):
bl_idname = "sn.record_key"
bl_label = "Record Key"
bl_description = "Records the next pressed key"
bl_options = {"REGISTER","UNDO","INTERNAL"}
node: bpy.props.StringProperty()
def invoke(self, context, event):
context.space_data.node_tree.nodes[self.node].recording = True
context.window_manager.modal_handler_add(self)
return {"RUNNING_MODAL"}
def assign_key(self,context,key_type):
if hasattr(context,"space_data") and context.space_data:
if hasattr(context.space_data,"node_tree"):
node = context.space_data.node_tree.nodes[self.node]
node.key = key_type
node.recording = False
context.area.tag_redraw()
def modal(self, context, event):
invalid = ["MOUSEMOVE","INBETWEEN_MOUSEMOVE","TRACKPADPAN","TRACKPADZOOM","MOUSEROTATE","MOUSESMARTZOOM",
"TEXTINPUT","WINDOW_DEACTIVATE","ACTIONZONE_AREA","ACTIONZONE_REGION","ACTIONZONE_FULLSCREEN"]
if not event.type in invalid and not "TIMER" in event.type:
self.assign_key(context,event.type)
return {"FINISHED"}
return {"RUNNING_MODAL"}
def sn_append_interface(self, context):
row = self.layout.row()
row.alert = True
op = row.operator("sn.select_interface_add",text="Select",icon="CHECKMARK")
op.idname = self.bl_idname
remove_interfaces = []
picker_node = None
class SN_OT_SelectInterfaceAdd(bpy.types.Operator):
bl_idname = "sn.select_interface_add"
bl_label = "Select"
bl_description = "Select this element in the interface"
bl_options = {"REGISTER","UNDO","INTERNAL"}
idname: bpy.props.StringProperty(options={"HIDDEN"})
def remove_registered_interfaces(self):
global remove_interfaces
for interface in remove_interfaces:
try:
interface.remove(sn_append_interface)
except:
pass
remove_interfaces.clear()
def execute(self, context):
global picker_node
if picker_node:
picker_node.picked = self.idname
picker_node = None
self.remove_registered_interfaces()
for area in context.screen.areas:
area.tag_redraw()
return {"FINISHED"}
class SN_OT_StartInterfacePicker(bpy.types.Operator):
bl_idname = "sn.pick_interface"
bl_label = "Select"
bl_description = "Select an element in the interface"
bl_options = {"REGISTER","UNDO","INTERNAL"}
node: bpy.props.StringProperty()
selection: bpy.props.EnumProperty(items=[("ALL","ALL","ALL"),
("PANELS","PANELS","PANELS"),
("MENUS","MENUS","MENUS")],
options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
global remove_interfaces
return len(remove_interfaces) == 0
def get_interfaces(self):
interfaces = []
for name in dir(bpy.types):
try:
interface = eval("bpy.types."+name)
if self.selection != "PANELS":
if hasattr(interface.bl_rna.base,"identifier") and interface.bl_rna.base.identifier == "Menu":
interfaces.append(name)
if self.selection != "MENUS":
if hasattr(interface.bl_rna.base,"identifier") and interface.bl_rna.base.identifier == "Panel":
interfaces.append(name)
except:
pass
return interfaces
def execute(self, context):
global picker_node
global remove_interfaces
picker_node = context.space_data.node_tree.nodes[self.node]
for interface in self.get_interfaces():
try:
eval("bpy.types."+interface+".append(sn_append_interface)")
remove_interfaces.append(eval("bpy.types."+interface))
except:
pass
for area in context.screen.areas:
area.tag_redraw()
return {"FINISHED"}