2025-07-01
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import bpy
|
||||
from ..Program.Run_Operator import on_operator_ref_update
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ButtonNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ButtonNodeNew"
|
||||
bl_label = "Button"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 240
|
||||
|
||||
def on_create(self, context):
|
||||
self.version = 1
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label").default_value = "My Button"
|
||||
self.add_boolean_input("Emboss").default_value = True
|
||||
self.add_boolean_input("Depress")
|
||||
self.add_icon_input("Icon")
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
self.ref_ntree = self.node_tree
|
||||
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
on_operator_ref_update(self, node, data, self.ref_ntree, self.ref_SN_OperatorNode, 5)
|
||||
|
||||
|
||||
def reset_inputs(self):
|
||||
""" Remove all operator inputs """
|
||||
for i in range(len(self.inputs)-1, -1, -1):
|
||||
inp = self.inputs[i]
|
||||
if inp.can_be_disabled:
|
||||
self.inputs.remove(inp)
|
||||
|
||||
|
||||
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 = True
|
||||
inp.disabled = not prop.is_required
|
||||
|
||||
|
||||
def update_custom_operator(self, context):
|
||||
""" Updates the nodes settings when a new parent panel is selected """
|
||||
self.reset_inputs()
|
||||
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)
|
||||
|
||||
def update_source_type(self, context):
|
||||
self.hide_disabled_inputs = False
|
||||
if self.source_type == "BLENDER":
|
||||
self.pasted_operator = self.pasted_operator
|
||||
elif self.source_type == "CUSTOM":
|
||||
self.ref_SN_OperatorNode = self.ref_SN_OperatorNode
|
||||
self._evaluate(context)
|
||||
|
||||
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
|
||||
name="Panel Node Tree",
|
||||
description="The node tree to select the operator from",
|
||||
poll=SN_ScriptingBaseNode.ntree_poll,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
source_type: bpy.props.EnumProperty(name="Source Type",
|
||||
description="Use a custom operator or a blender internal",
|
||||
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
|
||||
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
|
||||
update=update_source_type)
|
||||
|
||||
ref_SN_OperatorNode: bpy.props.StringProperty(name="Custom Operator",
|
||||
description="The operator ran by this button",
|
||||
update=update_custom_operator)
|
||||
|
||||
|
||||
def update_pasted_operator(self, context):
|
||||
self.reset_inputs()
|
||||
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
self.pasted_name = op_rna.name
|
||||
self.create_inputs(op_rna)
|
||||
self._evaluate(context)
|
||||
|
||||
pasted_operator: bpy.props.StringProperty(default="bpy.ops.sn.dummy_button_operator()",
|
||||
update=update_pasted_operator)
|
||||
|
||||
pasted_name: bpy.props.StringProperty(default="Paste Operator")
|
||||
|
||||
|
||||
def update_hide_disabled_inputs(self, context):
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and inp.disabled:
|
||||
inp.set_hide(self.hide_disabled_inputs)
|
||||
|
||||
hide_disabled_inputs: bpy.props.BoolProperty(default=False,
|
||||
name="Hide Disabled Inputs",
|
||||
description="Hides the disabled inputs of this node",
|
||||
update=update_hide_disabled_inputs)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.source_type == "BLENDER":
|
||||
op_name = self.pasted_operator[8:].split("(")[0]
|
||||
code = f"op = {self.active_layout}.operator('{op_name}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and 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):
|
||||
code += "\n" + f"op.{prop.identifier} = {inp.python_value}"
|
||||
self.code = f"""
|
||||
{self.indent(code, 6)}
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
|
||||
else:
|
||||
if self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
|
||||
node = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
|
||||
code = f"op = {self.active_layout}.operator('sna.{node.operator_python_name}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and not inp.disabled:
|
||||
for prop in node.properties:
|
||||
if prop.name == inp.name:
|
||||
code += "\n" + f"op.{prop.python_name} = {inp.python_value}"
|
||||
else:
|
||||
code = f"op = {self.active_layout}.operator('sn.dummy_button_operator', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
self.code = f"""
|
||||
{self.indent(code, 6)}
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "source_type", text="", icon_only=True)
|
||||
|
||||
if self.source_type == "BLENDER":
|
||||
name = "Paste Operator"
|
||||
if self.pasted_operator:
|
||||
if self.pasted_name:
|
||||
name = self.pasted_name
|
||||
elif len(self.pasted_operator.split(".")) > 2:
|
||||
name = self.pasted_operator.split(".")[3].split("(")[0].replace("_", " ").title()
|
||||
else:
|
||||
name = self.pasted_operator
|
||||
op = row.operator("sn.paste_operator", text=name, icon="PASTEDOWN")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
elif self.source_type == "CUSTOM":
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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", bpy.data.node_groups[parent_tree.name].node_collection("SN_OperatorNode"), "refs", text="")
|
||||
|
||||
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
|
||||
|
||||
row.prop(self, "hide_disabled_inputs", text="", icon="HIDE_ON" if self.hide_disabled_inputs else "HIDE_OFF", emboss=False)
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
from ...utils import get_python_name
|
||||
|
||||
|
||||
|
||||
class SN_DisplayCollectionListNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayCollectionListNodeNew"
|
||||
bl_label = "Display Collection List"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input()
|
||||
self.add_property_input("Index Property")
|
||||
self.add_integer_input("Rows")
|
||||
self.add_dynamic_interface_output("Item Row")
|
||||
self.add_dynamic_interface_output(
|
||||
"Interface").passthrough_layout_type = True
|
||||
self.add_property_output("Item")
|
||||
self.add_integer_output("Item Index")
|
||||
|
||||
def update_function_node(self, context):
|
||||
self._evaluate(context)
|
||||
|
||||
ref_SN_FunctionNode: bpy.props.StringProperty(name="Function",
|
||||
description="Filter function which should have a single property input and return a boolean (True keeps the item, False removes it)",
|
||||
update=update_function_node)
|
||||
|
||||
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
|
||||
name="Function Node Tree",
|
||||
description="The node tree to select the function from",
|
||||
poll=SN_ScriptingBaseNode.ntree_poll,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Filter:")
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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_FunctionNode", bpy.data.node_groups[parent_tree.name].node_collection(
|
||||
"SN_FunctionNode"), "refs", text="")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Index Property"].is_linked and self.inputs["Collection Property"].is_linked:
|
||||
ui_list_idname = f"SNA_UL_{get_python_name(self.name, 'List')}_{self.static_uid}"
|
||||
|
||||
func = None
|
||||
if self.ref_ntree and self.ref_SN_FunctionNode in self.ref_ntree.nodes:
|
||||
func = self.ref_ntree.nodes[self.ref_SN_FunctionNode]
|
||||
|
||||
self.code_imperative = f"""
|
||||
def display_collection_id(uid, vars):
|
||||
id = f"coll_{{uid}}"
|
||||
for var in vars.keys():
|
||||
if var.startswith("i_"):
|
||||
id += f"_{{var}}_{{vars[var]}}"
|
||||
return id
|
||||
|
||||
class {ui_list_idname}(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item_{self.static_uid}, icon, active_data, active_propname, index_{self.static_uid}):
|
||||
row = layout
|
||||
{self.indent([out.python_value if out.name == 'Item Row' else '' for out in self.outputs], 11)}
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
flt_flags = []
|
||||
for item in getattr(data, propname):
|
||||
if not self.filter_name or self.filter_name.lower() in item.name.lower():
|
||||
if {f'{func.func_name}(item)' if func else 'True'}:
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
return flt_flags, []
|
||||
"""
|
||||
self.code_register = f"""
|
||||
bpy.utils.register_class({ui_list_idname})
|
||||
"""
|
||||
self.code_unregister = f"""
|
||||
bpy.utils.unregister_class({ui_list_idname})
|
||||
"""
|
||||
self.code = f"""
|
||||
coll_id = display_collection_id('{self.static_uid}', locals())
|
||||
{self.active_layout}.template_list('{ui_list_idname}', coll_id, {self.inputs['Collection Property'].python_source}, '{self.inputs['Collection Property'].python_attr}', {self.inputs['Index Property'].python_source}, '{self.inputs['Index Property'].python_attr}', rows={self.inputs['Rows'].python_value})
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 4)}
|
||||
"""
|
||||
self.outputs["Item"].python_value = f"item_{self.static_uid}"
|
||||
if "Item Index" in self.outputs:
|
||||
self.outputs["Item Index"].python_value = f"index_{self.static_uid}"
|
||||
else:
|
||||
self.code = f"{self.active_layout}.label(text='No Property connected!', icon='ERROR')"
|
||||
self.outputs["Item"].reset_value()
|
||||
if "Item Index" in self.outputs:
|
||||
self.outputs["Item Index"].reset_value()
|
||||
@@ -0,0 +1,35 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplayEnumItemNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayEnumItemNode"
|
||||
bl_label = "Display Enum Item"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input()
|
||||
self.add_string_input("Item")
|
||||
self.add_string_input("Label")
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Property"].is_linked and (self.inputs["Item"].is_linked or self.inputs["Item"].default_value):
|
||||
self.code = f"""
|
||||
{self.active_layout}.prop_enum({self.inputs['Property'].python_source}, '{self.inputs['Property'].python_attr.replace("'",'"')}', text={self.inputs['Label'].python_value}, value={self.inputs['Item'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
elif not self.inputs["Property"].is_linked:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Enum Item specified!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
@@ -0,0 +1,25 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplayIconNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayIconNodeNew"
|
||||
bl_label = "Display Icon"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_icon_input("Icon")
|
||||
self.add_float_input("Scale").default_value = 1
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
{self.active_layout}.template_icon(icon_value={self.inputs['Icon'].python_value}, scale={self.inputs['Scale'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.label(text="Use this node with care! This might slow down your UI", icon="INFO")
|
||||
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
|
||||
class SN_DisplayPreviewNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayPreviewNodeNew"
|
||||
bl_label = "Display Preview"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input()
|
||||
self.add_boolean_input("Show Buttons")
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Property"].is_linked:
|
||||
self.code = f"""
|
||||
{self.active_layout}.template_preview({self.inputs['Property'].python_value}, show_buttons={self.inputs['Show Buttons'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 6)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
box = layout.box()
|
||||
box.label(text="Careful! This element is prone to crashes!", icon="INFO")
|
||||
box.label(text="Only materials, textures, lights, worlds and line styles can be displayed")
|
||||
@@ -0,0 +1,60 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplayPropertyNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayPropertyNodeNew"
|
||||
bl_label = "Display Property"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input("Icon")
|
||||
self.add_boolean_input("Emboss").default_value = True
|
||||
inp = self.add_boolean_input("Expand")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Slider")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Toggle")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Invert Checkbox")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Full Shortcut")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_integer_input("Index")
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Property"].is_linked:
|
||||
attributes = ""
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and not inp.disabled:
|
||||
attributes += f", {inp.name.lower().replace(' ', '_')}={inp.python_value}"
|
||||
attribute = f"attr_{self.static_uid} = '[\"' + str({self.inputs['Property'].python_attr[1:-1]} + '\"]') " if self.inputs["Property"].python_is_attribute else ""
|
||||
self.code = f"""
|
||||
{attribute}
|
||||
{self.active_layout}.prop({self.inputs['Property'].python_source}, {f"attr_{self.static_uid}" if self.inputs["Property"].python_is_attribute else "'" + self.inputs['Property'].python_attr.replace("'", '"') + "'"}, text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}{attributes})
|
||||
{self.indent(self.outputs[0].python_value, 7)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 7)}
|
||||
"""
|
||||
@@ -0,0 +1,31 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplaySearchNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplaySearchNodeNew"
|
||||
bl_label = "Display Search"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input("Collection")
|
||||
self.add_property_input("String/Pointer")
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input("Blender Icon").subtype = "BLENDER_ONLY"
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
if len(self.inputs["String/Pointer"].links) and len(self.inputs["Collection"].links):
|
||||
self.code = f"""
|
||||
{self.active_layout}.prop_search({self.inputs['String/Pointer'].python_source}, '{self.inputs['String/Pointer'].python_attr.replace("'",'"')}', {self.inputs['Collection'].python_source}, '{self.inputs['Collection'].python_attr.replace("'",'"')}', text={self.inputs['Label'].python_value}, icon={self.inputs['Blender Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_DisplaySerpensShortcutNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplaySerpensShortcutNodeNew"
|
||||
bl_label = "Display Serpens Shortcut"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
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_OnKeypressNode: bpy.props.StringProperty(name="Shortcut",
|
||||
description="The shortcut to display",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.ref_ntree and self.ref_SN_OnKeypressNode in self.ref_ntree.nodes:
|
||||
self.code_imperative = """
|
||||
def find_user_keyconfig(key):
|
||||
km, kmi = addon_keymaps[key]
|
||||
for item in bpy.context.window_manager.keyconfigs.user.keymaps[km.name].keymap_items:
|
||||
found_item = False
|
||||
if kmi.idname == item.idname:
|
||||
found_item = True
|
||||
for name in dir(kmi.properties):
|
||||
if not name in ["bl_rna", "rna_type"] and not name[0] == "_":
|
||||
if name in kmi.properties and name in item.properties and not kmi.properties[name] == item.properties[name]:
|
||||
found_item = False
|
||||
if found_item:
|
||||
return item
|
||||
print(f"Couldn't find keymap item for {key}, using addon keymap instead. This won't be saved across sessions!")
|
||||
return kmi
|
||||
"""
|
||||
node = self.ref_ntree.nodes[self.ref_SN_OnKeypressNode]
|
||||
src = f"find_user_keyconfig('{node.static_uid}')"
|
||||
self.code = f"""
|
||||
{self.active_layout}.prop({src}, 'type', text={self.inputs['Label'].python_value}, full_event=True)
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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_OnKeypressNode", parent_tree.node_collection(
|
||||
"SN_OnKeypressNode"), "refs", text="")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_OnKeypressNode 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_OnKeypressNode
|
||||
|
||||
layout.label(
|
||||
text="Copy blender shortcuts from the blend data browser in display property", icon="INFO")
|
||||
@@ -0,0 +1,45 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
from ...utils import normalize_code
|
||||
|
||||
|
||||
|
||||
class SN_EnumMapInterfaceNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_EnumMapInterfaceNode"
|
||||
bl_label = "Enum Map (Interface)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
passthrough_layout_type = True
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Enum Value")
|
||||
self.add_interface_output("Interface")
|
||||
self.add_interface_output("Other Option")
|
||||
out = self.add_dynamic_interface_output("Enum Option")
|
||||
out.is_variable = True
|
||||
|
||||
def on_socket_name_change(self, socket):
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
def evaluate(self, context):
|
||||
options = ""
|
||||
for out in self.outputs:
|
||||
if out.is_variable and not out.dynamic:
|
||||
option_code = self.indent(out.python_value, 7)
|
||||
option = f"""
|
||||
{"el" if options else ""}if {self.inputs["Enum Value"].python_value} == "{out.name}":
|
||||
{option_code}
|
||||
"""
|
||||
if option_code.strip():
|
||||
options += normalize_code(option) + "\n"
|
||||
|
||||
other_opt_code = self.indent(self.outputs['Other Option'].python_value, 6)
|
||||
self.code = f"""
|
||||
{self.indent(options, 5)}
|
||||
{"else:" if options.strip() else "if True:"}
|
||||
{other_opt_code if other_opt_code.strip() else "pass"}
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
@@ -0,0 +1,30 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IconGalleryNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IconGalleryNodeNew"
|
||||
bl_label = "Icon Gallery"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input("Enum")
|
||||
self.add_boolean_input("Show Labels")
|
||||
self.add_float_input("Scale").default_value = 5
|
||||
self.add_float_input("Scale Popup").default_value = 5
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Enum"].is_linked:
|
||||
self.code = f"""
|
||||
{self.active_layout}.template_icon_view({self.inputs['Enum'].python_source}, '{self.inputs['Enum'].python_attr}', show_labels={self.inputs['Show Labels'].python_value}, scale={self.inputs['Scale'].python_value}, scale_popup={self.inputs['Scale Popup'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LabelNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LabelNodeNew"
|
||||
bl_label = "Label"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label").default_value = "My Label"
|
||||
self.add_icon_input("Icon")
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
pass
|
||||
@@ -0,0 +1,73 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AddToMenuNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AddToMenuNodeNew"
|
||||
bl_label = "Add To Menu"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_dynamic_interface_output("Menu")
|
||||
|
||||
|
||||
append: bpy.props.EnumProperty(default="APPEND", items=[
|
||||
("PREPEND", "Prepend", "Prepend this to the start of the menu"),
|
||||
("APPEND", "Append", "Append this to the end of the menu")],
|
||||
name="Position",
|
||||
description="Position of this interface to the selected menu",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
menu_parent: bpy.props.StringProperty(default="VIEW3D_MT_add",
|
||||
name="Parent",
|
||||
description="The menu id this interface should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
uid = self.uuid
|
||||
func_name = f"sna_add_to_{self.menu_parent.lower()}_{uid}"
|
||||
|
||||
self.code = f"""
|
||||
def {func_name}(self, context):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value if out.name == 'Menu' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
|
||||
if self.menu_parent == "WM_MT_button_context":
|
||||
self.code_imperative = """
|
||||
class WM_MT_button_context(bpy.types.Menu):
|
||||
bl_label = ""
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
{"if getattr(bpy.types, 'WM_MT_button_context', None) == None: bpy.utils.register_class(WM_MT_button_context)" if self.menu_parent == "WM_MT_button_context" else ""}
|
||||
bpy.types.{self.menu_parent}.{self.append.lower()}({func_name})
|
||||
"""
|
||||
self.code_unregister = f"""
|
||||
bpy.types.{self.menu_parent}.remove({func_name})
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.3
|
||||
op = row.operator("sn.activate_menu_picker", text=f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
layout.prop(self, "append", expand=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
@@ -0,0 +1,60 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AddToPanelNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AddToPanelNodeNew"
|
||||
bl_label = "Add To Panel"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_dynamic_interface_output("Panel")
|
||||
|
||||
|
||||
append: bpy.props.EnumProperty(default="APPEND", items=[
|
||||
("PREPEND", "Prepend", "Prepend this to the start of the panel"),
|
||||
("APPEND", "Append", "Append this to the end of the panel")],
|
||||
name="Position",
|
||||
description="Position of this interface to the selected panel",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
uid = self.uuid
|
||||
func_name = f"sna_add_to_{self.panel_parent.lower()}_{uid}"
|
||||
|
||||
self.code = f"""
|
||||
def {func_name}(self, context):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value if out.name == 'Panel' else '' for out in self.outputs], 7)}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.types.{self.panel_parent}.{self.append.lower()}({func_name})"
|
||||
self.code_unregister = f"bpy.types.{self.panel_parent}.remove({func_name})"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.3
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
layout.prop(self, "append", expand=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import bpy
|
||||
from ....utils import get_python_name
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RunInterfaceFunctionNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RunInterfaceFunctionNodeNew"
|
||||
bl_label = "Function Run (Interface)"
|
||||
bl_width_default = 240
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_dynamic_interface_output().passthrough_layout_type = True
|
||||
self.ref_ntree = self.node_tree
|
||||
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname == "SN_InterfaceFunctionNode" and data:
|
||||
index = 0
|
||||
for i, out in enumerate(node.outputs):
|
||||
if out.bl_idname != "SN_InterfaceSocket":
|
||||
index = i-1
|
||||
break
|
||||
# inputs has been added
|
||||
if "added" in data:
|
||||
socket_index = list(data["added"].node.outputs).index(data["added"])
|
||||
self.add_input_from_socket(data["added"])
|
||||
self.inputs.move(len(self.inputs)-1, socket_index-index)
|
||||
# input has been removed
|
||||
elif "removed" in data:
|
||||
self.inputs.remove(self.inputs[data["removed"]])
|
||||
# input has changed
|
||||
elif "changed" in data:
|
||||
self.convert_socket(self.inputs[data["changed"].index-index], data["changed"].bl_idname)
|
||||
# input has updated
|
||||
elif "updated" in data:
|
||||
if data["updated"].index-index < len(self.inputs):
|
||||
self.inputs[data["updated"].index-index].name = data["updated"].name
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
def update_function_reference(self, context):
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
# remember connections
|
||||
links = []
|
||||
for inp in self.inputs[1:]:
|
||||
links.append(None)
|
||||
if inp.is_linked:
|
||||
links[-1] = inp.from_socket()
|
||||
# remove current data inputs
|
||||
for i in range(len(self.inputs)-1, 0, -1):
|
||||
self.inputs.remove(self.inputs[i])
|
||||
# add new data inputs
|
||||
if self.ref_SN_InterfaceFunctionNode in parent_tree.nodes:
|
||||
for out in parent_tree.nodes[self.ref_SN_InterfaceFunctionNode].outputs[1:-1]:
|
||||
if out.bl_idname != "SN_InterfaceSocket":
|
||||
self.add_input_from_socket(out)
|
||||
# restore connections
|
||||
if len(links) == len(self.inputs)-1:
|
||||
for i, from_socket in enumerate(links):
|
||||
if from_socket:
|
||||
self.node_tree.links.new(from_socket, self.inputs[i+1])
|
||||
self._evaluate(context)
|
||||
|
||||
ref_SN_InterfaceFunctionNode: bpy.props.StringProperty(name="Function",
|
||||
description="The function to run",
|
||||
update=update_function_reference)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
if self.ref_SN_InterfaceFunctionNode in parent_tree.nodes:
|
||||
# get input values
|
||||
inp_values = []
|
||||
for inp in self.inputs[1:]:
|
||||
inp_values.append(inp.python_value)
|
||||
inp_values = ", ".join(inp_values)
|
||||
|
||||
self.code = f"""
|
||||
layout_function = {self.active_layout}
|
||||
{parent_tree.nodes[self.ref_SN_InterfaceFunctionNode].func_name}(layout_function, {inp_values})
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 6)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 4)}
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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_InterfaceFunctionNode", bpy.data.node_groups[parent_tree.name].node_collection("SN_InterfaceFunctionNode"), "refs", text="")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_InterfaceFunctionNode 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_InterfaceFunctionNode
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
from ....utils import get_python_name, unique_collection_name
|
||||
|
||||
|
||||
|
||||
class SN_InterfaceFunctionNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_InterfaceFunctionNode"
|
||||
bl_label = "Function (Interface)"
|
||||
def layout_type(self, _): return "layout_function"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_dynamic_interface_output()
|
||||
out = self.add_dynamic_data_output("Input")
|
||||
out.is_variable = True
|
||||
out.changeable = True
|
||||
|
||||
|
||||
def on_dynamic_socket_add(self, socket):
|
||||
if not socket.bl_idname == "SN_InterfaceSocket":
|
||||
sockets = []
|
||||
for out in self.outputs:
|
||||
if not out.bl_idname in ["SN_InterfaceSocket", "SN_DynamicInterfaceSocket"]:
|
||||
sockets.append(out.name)
|
||||
socket["name"] = get_python_name(socket.name, "Input", lower=False)
|
||||
socket["name"] = unique_collection_name(socket.name, "Input", sockets[:-1], "_", includes_name=True)
|
||||
self.trigger_ref_update({ "added": socket })
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
def on_dynamic_socket_remove(self, index, is_output):
|
||||
first_index = 0
|
||||
for out in self.outputs:
|
||||
if out.bl_idname in ["SN_InterfaceSocket", "SN_DynamicInterfaceSocket"]:
|
||||
first_index += 1
|
||||
|
||||
if index >= first_index:
|
||||
self.trigger_ref_update({ "removed": index - first_index + 1 })
|
||||
|
||||
def on_socket_type_change(self, socket):
|
||||
if not socket.bl_idname == "SN_InterfaceSocket":
|
||||
self.trigger_ref_update({ "changed": socket })
|
||||
|
||||
def on_socket_name_change(self, socket):
|
||||
if not socket.bl_idname == "SN_InterfaceSocket":
|
||||
sockets = []
|
||||
for out in self.outputs:
|
||||
if not out.bl_idname in ["SN_InterfaceSocket", "SN_DynamicInterfaceSocket"]:
|
||||
sockets.append(out.name)
|
||||
socket["name"] = get_python_name(socket.name, "Input", lower=False)
|
||||
socket["name"] = unique_collection_name(socket.name, "Input", sockets[:-1], "_", includes_name=True)
|
||||
self.trigger_ref_update({ "updated": socket })
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
@property
|
||||
def func_name(self):
|
||||
return f"sna_{get_python_name(self.name, 'func')}_{self.static_uid}"
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
out_values = []
|
||||
index = 0
|
||||
for i, out in enumerate(self.outputs):
|
||||
if out.bl_idname != "SN_InterfaceSocket":
|
||||
index = i
|
||||
break
|
||||
for i, out in enumerate(self.outputs[index:-1]):
|
||||
out_values.append(get_python_name(out.name, f"parameter_{i}"))
|
||||
out_names = ", ".join(out_values)
|
||||
|
||||
if index > 1:
|
||||
code = self.indent([out.python_value for out in self.outputs[:index-1]], 6)
|
||||
else:
|
||||
code = self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 0)
|
||||
self.code = f"""
|
||||
def {self.func_name}(layout_function, {out_names}):
|
||||
{code if code.strip() else "pass"}
|
||||
"""
|
||||
|
||||
for i, out in enumerate(self.outputs[index:-1]):
|
||||
out.python_value = out_values[i]
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "name")
|
||||
op = row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM")
|
||||
op.node = self.name
|
||||
op.add_node = "SN_RunInterfaceFunctionNodeNew"
|
||||
row.operator("sn.copy_python_name", text="", icon="COPYDOWN").name = self.func_name
|
||||
@@ -0,0 +1,68 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_MenuNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_MenuNode"
|
||||
bl_label = "Menu"
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_integer_input("Columns")["default_value"] = 1
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_dynamic_interface_output("Menu")
|
||||
|
||||
idname_override: bpy.props.StringProperty(default="",
|
||||
name="Idname Override",
|
||||
description="Use this if you want to define the idname of this panel yourself",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
title: bpy.props.StringProperty(default="",
|
||||
name="Label",
|
||||
description="The label shown at the top of this menu",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
@property
|
||||
def idname(self):
|
||||
if self.idname_override:
|
||||
return self.idname_override
|
||||
return f"SNA_MT_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.trigger_ref_update()
|
||||
self.code = f"""
|
||||
class {self.idname}(bpy.types.Menu):
|
||||
bl_idname = "{self.idname}"
|
||||
bl_label = "{self.title}"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not ({self.inputs["Hide"].python_value})
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout.column_flow(columns={self.inputs["Columns"].python_value})
|
||||
layout.operator_context = "INVOKE_DEFAULT"
|
||||
{self.indent([out.python_value if out.name == 'Menu' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
bpy.utils.register_class({self.idname})
|
||||
"""
|
||||
|
||||
self.code_unregister = f"""
|
||||
bpy.utils.unregister_class({self.idname})
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "name")
|
||||
row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM").node = self.name
|
||||
layout.prop(self, "title")
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "idname_override")
|
||||
@@ -0,0 +1,295 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
from ....utils import get_python_name
|
||||
|
||||
|
||||
|
||||
class SN_PanelNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PanelNode"
|
||||
bl_label = "Panel"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_dynamic_interface_output("Panel")
|
||||
self.add_dynamic_interface_output("Header")
|
||||
self.ref_ntree = self.node_tree
|
||||
|
||||
|
||||
def update_from_parent(self, parent):
|
||||
""" Update this subpanels nodes values from the parent node """
|
||||
self["space"] = parent.space
|
||||
self["region"] = parent.region
|
||||
self["context"] = parent.context
|
||||
|
||||
|
||||
def update_custom_parent(self, context):
|
||||
""" Updates the nodes settings when a new parent panel is selected """
|
||||
if self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
|
||||
parent = self.ref_ntree.nodes[self.ref_SN_PanelNode]
|
||||
self.update_from_parent(parent)
|
||||
self._evaluate(context)
|
||||
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
""" Called when a parent panel is updated, updates this nodes settings """
|
||||
if self.is_subpanel:
|
||||
if node.node_tree == self.ref_ntree and node.name == self.ref_SN_PanelNode:
|
||||
self.update_from_parent(node)
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
def update_is_subpanel(self, context):
|
||||
""" Updates the compile order when turned into a subpanel """
|
||||
if self.is_subpanel:
|
||||
self["parent_type"] = "BLENDER"
|
||||
self["ref_SN_PanelNode"] = ""
|
||||
self["space"] = "PROPERTIES"
|
||||
self["region"] = "WINDOW"
|
||||
self["context"] = "render"
|
||||
self.order = 1
|
||||
else:
|
||||
self["space"] = "VIEW_3D"
|
||||
self["region"] = "UI"
|
||||
self["context"] = ""
|
||||
self.order = 0
|
||||
self._evaluate(context)
|
||||
|
||||
|
||||
panel_label: bpy.props.StringProperty(default="New Panel",
|
||||
name="Label",
|
||||
description="The label of your panel",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
space: bpy.props.StringProperty(default="VIEW_3D",
|
||||
name="Space",
|
||||
description="The space your panel is in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
region: bpy.props.StringProperty(default="UI",
|
||||
name="Region",
|
||||
description="The region your panel is in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
context: bpy.props.StringProperty(default="",
|
||||
name="Context",
|
||||
description="The context your panel is in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
category: bpy.props.StringProperty(default="New Category",
|
||||
name="Category",
|
||||
description="The category your panel is in (Only relevant if in an N-Panel)",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_order: bpy.props.IntProperty(default=0, min=0,
|
||||
name="Order",
|
||||
description="The order of your panel compared to other custom panels",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
hide_header: bpy.props.BoolProperty(default=False,
|
||||
name="Hide Header",
|
||||
description="Hide the panels header",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
expand_header: bpy.props.BoolProperty(default=False,
|
||||
name="Expand Header",
|
||||
description="Expands the header to fill the full panel width",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
default_closed: bpy.props.BoolProperty(default=False,
|
||||
name="Default Closed",
|
||||
description="Closes the panel by default",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def update_shortcut_only(self, context):
|
||||
if self.shortcut_only:
|
||||
self.space = "VIEW_3D"
|
||||
self.region = "WINDOW"
|
||||
self.context = ""
|
||||
self.category = ""
|
||||
self._evaluate(context)
|
||||
|
||||
shortcut_only: bpy.props.BoolProperty(default=False,
|
||||
name="Shortcut Only",
|
||||
description="Only displays this panel when opened with a shortcut and not in the interface",
|
||||
update=update_shortcut_only)
|
||||
|
||||
is_subpanel: bpy.props.BoolProperty(default=False,
|
||||
name="Is Subpanel",
|
||||
description="If this panel should be a subpanel",
|
||||
update=update_is_subpanel)
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="RENDER_PT_context",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
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=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_PanelNode: bpy.props.StringProperty(name="Custom Parent",
|
||||
description="The panel used as a custom parent panel for this subpanel",
|
||||
update=update_custom_parent)
|
||||
|
||||
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)
|
||||
|
||||
idname_override: bpy.props.StringProperty(default="",
|
||||
name="Idname Override",
|
||||
description="Use this if you want to define the idname of this panel yourself",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_width: bpy.props.IntProperty(name="Panel Width", default=0,
|
||||
description="The panel width for popovers and popups",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
|
||||
last_idname: bpy.props.StringProperty(name="Last Idname",
|
||||
description="The last idname that this panel had when it was compiled",
|
||||
default="")
|
||||
|
||||
def update_idname(self):
|
||||
py_name = get_python_name(self.panel_label)
|
||||
alt_py_name = get_python_name(self.name)
|
||||
idname = f"SNA_PT_{py_name.upper() if py_name else alt_py_name}_{self.uuid}"
|
||||
if self.idname_override:
|
||||
idname = self.idname_override
|
||||
self.last_idname = idname
|
||||
self.trigger_ref_update()
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
self.update_idname()
|
||||
|
||||
options = []
|
||||
if self.hide_header: options.append("'HIDE_HEADER'")
|
||||
if self.expand_header: options.append("'HEADER_LAYOUT_EXPAND'")
|
||||
if self.default_closed: options.append("'DEFAULT_CLOSED'")
|
||||
|
||||
parent = ""
|
||||
if self.is_subpanel:
|
||||
if self.parent_type == "BLENDER":
|
||||
parent = self.panel_parent
|
||||
elif self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
|
||||
parent_node = self.ref_ntree.nodes[self.ref_SN_PanelNode]
|
||||
parent = parent_node.last_idname
|
||||
|
||||
self.code = f"""
|
||||
class {self.last_idname}(bpy.types.Panel):
|
||||
bl_label = '{self.panel_label}'
|
||||
bl_idname = '{self.last_idname}'
|
||||
bl_space_type = '{self.space}'
|
||||
bl_region_type = '{self.region}'
|
||||
bl_context = '{self.context}'
|
||||
{f"bl_category = '{self.category}'" if self.category and not parent else ""}
|
||||
bl_order = {self.panel_order}
|
||||
{f"bl_options = {{{', '.join(options)}}}" if options else ""}
|
||||
{f"bl_parent_id = '{parent}'" if self.is_subpanel and parent else ""}
|
||||
bl_ui_units_x={self.panel_width}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not ({self.inputs["Hide"].python_value})
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value if out.name == 'Header' else '' for out in self.outputs], 7)}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value if out.name == 'Panel' else '' for out in self.outputs], 7)}
|
||||
"""
|
||||
|
||||
if self.is_subpanel and parent and not context.scene.sn.is_exporting and self.parent_type == "CUSTOM":
|
||||
self.code_register = f"if '{parent}' in globals(): bpy.utils.register_class({self.last_idname})"
|
||||
self.code_unregister = f"if '{parent}' in globals(): bpy.utils.unregister_class({self.last_idname})"
|
||||
else:
|
||||
if bpy.context.scene.sn.is_exporting:
|
||||
self.code_register = f"bpy.utils.register_class({self.last_idname})"
|
||||
self.code_unregister = f"bpy.utils.unregister_class({self.last_idname})"
|
||||
else:
|
||||
self.code_register = f"""
|
||||
try: bpy.utils.register_class({self.last_idname})
|
||||
except: pass
|
||||
"""
|
||||
self.code_unregister = f"""
|
||||
try: bpy.utils.unregister_class({self.last_idname})
|
||||
except: pass
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
if not self.shortcut_only:
|
||||
if not self.is_subpanel:
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.4
|
||||
op = row.operator("sn.activate_panel_picker", text=f"{self.space.replace('_', ' ').title()} {self.region.replace('_', ' ').title()} {self.context.replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
if self.parent_type == "BLENDER":
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
else:
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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", bpy.data.node_groups[parent_tree.name].node_collection(self.bl_idname), "refs", text="")
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
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
|
||||
|
||||
if self.ref_SN_PanelNode == self.name and self.ref_ntree == self.node_tree:
|
||||
layout.label(text="Can't use self as panel parent!", icon="ERROR")
|
||||
|
||||
layout.prop(self, "is_subpanel")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "name")
|
||||
row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM").node = self.name
|
||||
|
||||
layout.prop(self, "panel_label")
|
||||
if not self.shortcut_only:
|
||||
if not self.is_subpanel:
|
||||
layout.prop(self, "category")
|
||||
|
||||
layout.prop(self, "panel_order")
|
||||
layout.prop(self, "hide_header")
|
||||
layout.prop(self, "expand_header")
|
||||
layout.prop(self, "default_closed")
|
||||
layout.prop(self, "shortcut_only")
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
col = layout.column()
|
||||
col.enabled = not self.is_subpanel
|
||||
col.prop(self, "space")
|
||||
col.prop(self, "region")
|
||||
col.prop(self, "context")
|
||||
row = layout.row()
|
||||
row.enabled = self.is_subpanel
|
||||
row.prop(self, "panel_parent")
|
||||
layout.prop(self, "idname_override")
|
||||
layout.prop(self, "panel_width")
|
||||
@@ -0,0 +1,66 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_PieMenuNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PieMenuNode"
|
||||
bl_label = "Pie Menu"
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_dynamic_interface_output("Menu")
|
||||
|
||||
idname_override: bpy.props.StringProperty(default="",
|
||||
name="Idname Override",
|
||||
description="Use this if you want to define the idname of this panel yourself",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
title: bpy.props.StringProperty(default="",
|
||||
name="Label",
|
||||
description="The label shown at the top of this menu",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
@property
|
||||
def idname(self):
|
||||
if self.idname_override:
|
||||
return self.idname_override
|
||||
return f"SNA_MT_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.trigger_ref_update()
|
||||
self.code = f"""
|
||||
class {self.idname}(bpy.types.Menu):
|
||||
bl_idname = "{self.idname}"
|
||||
bl_label = "{self.title}"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not ({self.inputs["Hide"].python_value})
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout.menu_pie()
|
||||
{self.indent([out.python_value if out.name == 'Menu' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
bpy.utils.register_class({self.idname})
|
||||
"""
|
||||
|
||||
self.code_unregister = f"""
|
||||
bpy.utils.unregister_class({self.idname})
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "name")
|
||||
row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM").node = self.name
|
||||
layout.prop(self, "title")
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "idname_override")
|
||||
@@ -0,0 +1,103 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
from ...templates.PropertyNode import PropertyNode
|
||||
|
||||
|
||||
class SN_PreferencesNode(SN_ScriptingBaseNode, bpy.types.Node, PropertyNode):
|
||||
|
||||
bl_idname = "SN_PreferencesNode"
|
||||
bl_label = "Preferences"
|
||||
bl_width_default = 200
|
||||
|
||||
def layout_type(self, _):
|
||||
return "layout"
|
||||
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide").default_value = False
|
||||
self.add_dynamic_interface_output("Preferences")
|
||||
|
||||
def evaluate(self, context):
|
||||
props_imperative_list = self.props_imperative(context).split("\n")
|
||||
props_code_list = self.props_code(context).split("\n")
|
||||
props_register_list = self.props_register(context).split("\n")
|
||||
props_unregister_list = self.props_unregister(context).split("\n")
|
||||
|
||||
idname = f"SNA_TempAddonPreferences_{self.static_uid}"
|
||||
prop_name = f"sna_addon_prefs_temp"
|
||||
|
||||
self.code = f"""
|
||||
{self.indent(props_imperative_list, 5)}
|
||||
|
||||
class {idname}(bpy.types.PropertyGroup):
|
||||
{self.indent(props_code_list, 6) if self.indent(props_code_list, 6).strip() else "pass"}
|
||||
|
||||
def sna_prefs(layout):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
self = bpy.context.scene.{prop_name}
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 7)}
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
{self.indent(props_register_list, 7)}
|
||||
bpy.utils.register_class({idname})
|
||||
bpy.types.Scene.{prop_name} = bpy.props.PointerProperty(type={idname})
|
||||
bpy.context.scene.sn.preferences.append(sna_prefs)
|
||||
for a in bpy.context.screen.areas: a.tag_redraw()
|
||||
"""
|
||||
|
||||
self.code_unregister = f"""
|
||||
bpy.context.scene.sn.preferences.clear()
|
||||
for a in bpy.context.screen.areas: a.tag_redraw()
|
||||
del bpy.types.Scene.{prop_name}
|
||||
bpy.utils.unregister_class({idname})
|
||||
{self.indent(props_unregister_list, 7)}
|
||||
"""
|
||||
|
||||
def evaluate_export(self, context):
|
||||
props_imperative_list = self.props_imperative(context).split("\n")
|
||||
props_code_list = self.props_code(context).split("\n")
|
||||
props_register_list = self.props_register(context).split("\n")
|
||||
props_unregister_list = self.props_unregister(context).split("\n")
|
||||
|
||||
idname = f"SNA_AddonPreferences_{self.static_uid}"
|
||||
|
||||
self.code = f"""
|
||||
{self.indent(props_imperative_list, 5)}
|
||||
|
||||
class {idname}(bpy.types.AddonPreferences):
|
||||
|
||||
bl_idname = {"__package__" if bpy.context.scene.sn.is_exporting else "'"+bpy.context.scene.sn.module_name+"'"}
|
||||
|
||||
{self.indent(props_code_list, 6)}
|
||||
|
||||
def draw(self, context):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value if out.name == 'Preferences' else '' for out in self.outputs], 8)}
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
{self.indent(props_register_list, 7)}
|
||||
bpy.utils.register_class({idname})
|
||||
"""
|
||||
|
||||
self.code_unregister = f"""
|
||||
bpy.utils.unregister_class({idname})
|
||||
{self.indent(props_unregister_list, 7)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.operator("sn.open_preferences", icon="PREFERENCES").navigation = "CUSTOM"
|
||||
amount = 0
|
||||
for ntree in bpy.data.node_groups:
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
amount += len(ntree.node_collection(self.bl_idname).refs)
|
||||
if amount > 1:
|
||||
layout.label(
|
||||
text="Multiple preferences nodes! Only one will be used", icon="ERROR"
|
||||
)
|
||||
|
||||
self.draw_list(layout)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AddToMenuNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AddToMenuNode"
|
||||
bl_label = "Add To Menu (Legacy)"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_interface_output("Menu").prev_dynamic = True
|
||||
self.add_dynamic_interface_output("Menu")
|
||||
|
||||
|
||||
append: bpy.props.EnumProperty(default="APPEND", items=[
|
||||
("PREPEND", "Prepend", "Prepend this to the start of the menu"),
|
||||
("APPEND", "Append", "Append this to the end of the menu")],
|
||||
name="Position",
|
||||
description="Position of this interface to the selected menu",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
menu_parent: bpy.props.StringProperty(default="VIEW3D_MT_add",
|
||||
name="Parent",
|
||||
description="The menu id this interface should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
uid = self.uuid
|
||||
func_name = f"sna_add_to_{self.menu_parent.lower()}_{uid}"
|
||||
|
||||
self.code = f"""
|
||||
def {func_name}(self, context):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 5)}
|
||||
"""
|
||||
|
||||
if self.menu_parent == "WM_MT_button_context":
|
||||
self.code_imperative = """
|
||||
class WM_MT_button_context(bpy.types.Menu):
|
||||
bl_label = "Unused"
|
||||
def draw(self, context):
|
||||
pass
|
||||
"""
|
||||
|
||||
self.code_register = f"""
|
||||
{"if not hasattr(bpy.types, 'WM_MT_button_context'): bpy.utils.register_class(WM_MT_button_context)" if self.menu_parent == "WM_MT_button_context" else ""}
|
||||
bpy.types.{self.menu_parent}.{self.append.lower()}({func_name})
|
||||
"""
|
||||
self.code_unregister = f"""
|
||||
bpy.types.{self.menu_parent}.remove({func_name})
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.3
|
||||
op = row.operator("sn.activate_menu_picker", text=f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
layout.prop(self, "append", expand=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AddToPanelNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AddToPanelNode"
|
||||
bl_label = "Add To Panel (Legacy)"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
is_trigger = True
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Hide")
|
||||
self.add_interface_output("Panel").prev_dynamic = True
|
||||
self.add_dynamic_interface_output("Panel")
|
||||
|
||||
|
||||
append: bpy.props.EnumProperty(default="APPEND", items=[
|
||||
("PREPEND", "Prepend", "Prepend this to the start of the panel"),
|
||||
("APPEND", "Append", "Append this to the end of the panel")],
|
||||
name="Position",
|
||||
description="Position of this interface to the selected panel",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
uid = self.uuid
|
||||
func_name = f"sna_add_to_{self.panel_parent.lower()}_{uid}"
|
||||
|
||||
self.code = f"""
|
||||
def {func_name}(self, context):
|
||||
if not ({self.inputs["Hide"].python_value}):
|
||||
layout = self.layout
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 7)}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.types.{self.panel_parent}.{self.append.lower()}({func_name})"
|
||||
self.code_unregister = f"bpy.types.{self.panel_parent}.remove({func_name})"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.3
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
layout.prop(self, "append", expand=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
import bpy
|
||||
from ...Program.Run_Operator import on_operator_ref_update
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ButtonNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ButtonNode"
|
||||
bl_label = "Button (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.version = 1
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label").default_value = "My Button"
|
||||
self.add_boolean_input("Emboss").default_value = True
|
||||
self.add_boolean_input("Depress")
|
||||
self.add_icon_input("Icon")
|
||||
self.ref_ntree = self.node_tree
|
||||
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
on_operator_ref_update(self, node, data, self.ref_ntree, self.ref_SN_OperatorNode, 5)
|
||||
|
||||
|
||||
def reset_inputs(self):
|
||||
""" Remove all operator inputs """
|
||||
for i in range(len(self.inputs)-1, -1, -1):
|
||||
inp = self.inputs[i]
|
||||
if inp.can_be_disabled:
|
||||
self.inputs.remove(inp)
|
||||
|
||||
|
||||
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 = True
|
||||
inp.disabled = not prop.is_required
|
||||
|
||||
|
||||
def update_custom_operator(self, context):
|
||||
""" Updates the nodes settings when a new parent panel is selected """
|
||||
self.reset_inputs()
|
||||
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
|
||||
else:
|
||||
self._add_input(self.socket_names[prop.property_type], prop.name).can_be_disabled = True
|
||||
|
||||
self._evaluate(context)
|
||||
|
||||
def update_source_type(self, context):
|
||||
self.hide_disabled_inputs = False
|
||||
if self.source_type == "BLENDER":
|
||||
self.pasted_operator = self.pasted_operator
|
||||
elif self.source_type == "CUSTOM":
|
||||
self.ref_SN_OperatorNode = self.ref_SN_OperatorNode
|
||||
self._evaluate(context)
|
||||
|
||||
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
|
||||
name="Panel Node Tree",
|
||||
description="The node tree to select the operator from",
|
||||
poll=SN_ScriptingBaseNode.ntree_poll,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
source_type: bpy.props.EnumProperty(name="Source Type",
|
||||
description="Use a custom operator or a blender internal",
|
||||
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
|
||||
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
|
||||
update=update_source_type)
|
||||
|
||||
ref_SN_OperatorNode: bpy.props.StringProperty(name="Custom Operator",
|
||||
description="The operator ran by this button",
|
||||
update=update_custom_operator)
|
||||
|
||||
|
||||
def update_pasted_operator(self, context):
|
||||
self.reset_inputs()
|
||||
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
self.pasted_name = op_rna.name
|
||||
self.create_inputs(op_rna)
|
||||
self._evaluate(context)
|
||||
|
||||
pasted_operator: bpy.props.StringProperty(default="bpy.ops.sn.dummy_button_operator()",
|
||||
update=update_pasted_operator)
|
||||
|
||||
pasted_name: bpy.props.StringProperty(default="Paste Operator")
|
||||
|
||||
|
||||
def update_hide_disabled_inputs(self, context):
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and inp.disabled:
|
||||
inp.set_hide(self.hide_disabled_inputs)
|
||||
|
||||
hide_disabled_inputs: bpy.props.BoolProperty(default=False,
|
||||
name="Hide Disabled Inputs",
|
||||
description="Hides the disabled inputs of this node",
|
||||
update=update_hide_disabled_inputs)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.source_type == "BLENDER":
|
||||
op_name = self.pasted_operator[8:].split("(")[0]
|
||||
self.code = f"op = {self.active_layout}.operator('{op_name}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and 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}"
|
||||
|
||||
else:
|
||||
if self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
|
||||
node = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
|
||||
self.code = f"op = {self.active_layout}.operator('sna.{node.operator_python_name}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and not inp.disabled:
|
||||
for prop in node.properties:
|
||||
if prop.name == inp.name:
|
||||
self.code += "\n" + f"op.{prop.python_name} = {inp.python_value}"
|
||||
else:
|
||||
self.code = f"op = {self.active_layout}.operator('sn.dummy_button_operator', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}, depress={self.inputs['Depress'].python_value})"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "source_type", text="", icon_only=True)
|
||||
|
||||
if self.source_type == "BLENDER":
|
||||
name = "Paste Operator"
|
||||
if self.pasted_operator:
|
||||
if self.pasted_name:
|
||||
name = self.pasted_name
|
||||
elif len(self.pasted_operator.split(".")) > 2:
|
||||
name = self.pasted_operator.split(".")[3].split("(")[0].replace("_", " ").title()
|
||||
else:
|
||||
name = self.pasted_operator
|
||||
op = row.operator("sn.paste_operator", text=name, icon="PASTEDOWN")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
elif self.source_type == "CUSTOM":
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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", bpy.data.node_groups[parent_tree.name].node_collection("SN_OperatorNode"), "refs", text="")
|
||||
|
||||
row.prop(self, "hide_disabled_inputs", text="", icon="HIDE_ON" if self.hide_disabled_inputs else "HIDE_OFF", emboss=False)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CopyMenuNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CopyMenuNode"
|
||||
bl_label = "Copy Menu (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
|
||||
menu_parent: bpy.props.StringProperty(name="Menu",
|
||||
default="VIEW3D_MT_add",
|
||||
description="The menu that should be displayed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if hasattr(bpy.types,"{self.menu_parent}"):
|
||||
if not hasattr(bpy.types.{self.menu_parent}, "poll") or bpy.types.{self.menu_parent}.poll(context):
|
||||
bpy.types.{self.menu_parent}.draw(self, context)
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this menu here!", icon="ERROR")
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this menu!", icon="ERROR")
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
name = f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}"
|
||||
op = row.operator("sn.activate_menu_picker", icon="EYEDROPPER", text=name)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CopyPanelNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CopyPanelNode"
|
||||
bl_label = "Copy Panel (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if hasattr(bpy.types,"{self.panel_parent}"):
|
||||
if not hasattr(bpy.types.{self.panel_parent}, "poll") or bpy.types.{self.panel_parent}.poll(context):
|
||||
bpy.types.{self.panel_parent}.draw(self, context)
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this panel here!", icon="ERROR")
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this panel!", icon="ERROR")
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
from ....utils import get_python_name
|
||||
|
||||
|
||||
|
||||
class SN_DisplayCollectionListNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayCollectionListNode"
|
||||
bl_label = "Display Collection List (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
def layout_type(self, _): return "layout"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input()
|
||||
self.add_property_input("Index Property")
|
||||
self.add_integer_input("Rows")
|
||||
self.add_interface_output("Item Row").prev_dynamic = True
|
||||
self.add_dynamic_interface_output("Item Row")
|
||||
self.add_property_output("Item")
|
||||
self.add_integer_output("Item Index")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Index Property"].is_linked and self.inputs["Collection Property"].is_linked:
|
||||
ui_list_idname = f"SNA_UL_{get_python_name(self.name, 'List')}_{self.static_uid}"
|
||||
self.code_imperative = f"""
|
||||
class {ui_list_idname}(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item_{self.static_uid}, icon, active_data, active_propname, index_{self.static_uid}):
|
||||
row = layout
|
||||
{self.indent([out.python_value for out in self.outputs[:-2]], 11)}
|
||||
"""
|
||||
self.code_register = f"""
|
||||
bpy.utils.register_class({ui_list_idname})
|
||||
"""
|
||||
self.code_unregister = f"""
|
||||
bpy.utils.unregister_class({ui_list_idname})
|
||||
"""
|
||||
self.code = f"{self.active_layout}.template_list('{ui_list_idname}', '{self.static_uid}', {self.inputs['Collection Property'].python_source}, '{self.inputs['Collection Property'].python_attr}', {self.inputs['Index Property'].python_source}, '{self.inputs['Index Property'].python_attr}', rows={self.inputs['Rows'].python_value})"
|
||||
self.outputs["Item"].python_value = f"item_{self.static_uid}"
|
||||
if "Item Index" in self.outputs:
|
||||
self.outputs["Item Index"].python_value = f"index_{self.static_uid}"
|
||||
else:
|
||||
self.code = f"{self.active_layout}.label(text='No Property connected!', icon='ERROR')"
|
||||
self.outputs["Item"].reset_value()
|
||||
if "Item Index" in self.outputs:
|
||||
self.outputs["Item Index"].reset_value()
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplayIconNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayIconNode"
|
||||
bl_label = "Display Icon (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_icon_input("Icon")
|
||||
self.add_float_input("Scale").default_value = 1
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"{self.active_layout}.template_icon(icon_value={self.inputs['Icon'].python_value}, scale={self.inputs['Scale'].python_value})"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.label(text="Use this node with care! This might slow down your UI", icon="INFO")
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
|
||||
class SN_DisplayPreviewNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayPreviewNode"
|
||||
bl_label = "Display Preview (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input()
|
||||
self.add_boolean_input("Show Buttons")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Property"].is_linked:
|
||||
self.code = f"""
|
||||
{self.active_layout}.template_preview({self.inputs['Property'].python_value}, show_buttons={self.inputs['Show Buttons'].python_value})
|
||||
"""
|
||||
else:
|
||||
self.code = f"{self.active_layout}.label(text='No Property connected!', icon='ERROR')"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
box = layout.box()
|
||||
box.label(text="Careful! This element is prone to crashes!", icon="INFO")
|
||||
box.label(text="Only materials, textures, lights, worlds and line styles can be displayed")
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplayPropertyNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplayPropertyNode"
|
||||
bl_label = "Display Property (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input("Icon")
|
||||
self.add_boolean_input("Emboss").default_value = True
|
||||
inp = self.add_boolean_input("Expand")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Slider")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Toggle")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Invert Checkbox")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_boolean_input("Full Shortcut")
|
||||
inp.default_value = False
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp = self.add_integer_input("Index")
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Property"].is_linked:
|
||||
attributes = ""
|
||||
for inp in self.inputs:
|
||||
if inp.can_be_disabled and not inp.disabled:
|
||||
attributes += f", {inp.name.lower().replace(' ', '_')}={inp.python_value}"
|
||||
self.code = f"""
|
||||
{self.active_layout}.prop({self.inputs['Property'].python_source}, '{self.inputs['Property'].python_attr.replace("'",'"')}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value}, emboss={self.inputs['Emboss'].python_value}{attributes})
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
"""
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplaySearchNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplaySearchNode"
|
||||
bl_label = "Display Search (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input("Collection")
|
||||
self.add_property_input("String/Pointer")
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input("Blender Icon").subtype = "BLENDER_ONLY"
|
||||
|
||||
def evaluate(self, context):
|
||||
if len(self.inputs["String/Pointer"].links) and len(self.inputs["Collection"].links):
|
||||
self.code = f"""{self.active_layout}.prop_search({self.inputs['String/Pointer'].python_source}, '{self.inputs['String/Pointer'].python_attr.replace("'",'"')}', {self.inputs['Collection'].python_source}, '{self.inputs['Collection'].python_attr.replace("'",'"')}', text={self.inputs['Label'].python_value}, icon={self.inputs['Blender Icon'].python_value})"""
|
||||
else:
|
||||
self.code = f"{self.active_layout}.label(text='No Property connected!', icon='ERROR')"
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DisplaySerpensShortcutNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DisplaySerpensShortcutNode"
|
||||
bl_label = "Display Serpens Shortcut (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
|
||||
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_OnKeypressNode: bpy.props.StringProperty(name="Shortcut",
|
||||
description="The shortcut to display",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.ref_ntree and self.ref_SN_OnKeypressNode in self.ref_ntree.nodes:
|
||||
self.code_imperative = """
|
||||
def find_user_keyconfig(key):
|
||||
km, kmi = addon_keymaps[key]
|
||||
for item in bpy.context.window_manager.keyconfigs.user.keymaps[km.name].keymap_items:
|
||||
found_item = False
|
||||
if kmi.idname == item.idname:
|
||||
found_item = True
|
||||
for name in dir(kmi.properties):
|
||||
if not name in ["bl_rna", "rna_type"] and not name[0] == "_":
|
||||
if not kmi.properties[name] == item.properties[name]:
|
||||
found_item = False
|
||||
if found_item:
|
||||
return item
|
||||
print(f"Couldn't find keymap item for {key}, using addon keymap instead. This won't be saved across sessions!")
|
||||
return kmi
|
||||
"""
|
||||
node = self.ref_ntree.nodes[self.ref_SN_OnKeypressNode]
|
||||
src = f"find_user_keyconfig('{node.static_uid}')"
|
||||
self.code = f"{self.active_layout}.prop({src}, 'type', text={self.inputs['Label'].python_value}, full_event=True)"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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_OnKeypressNode", parent_tree.node_collection("SN_OnKeypressNode"), "refs", text="")
|
||||
|
||||
layout.label(text="Copy blender shortcuts from the blend data browser in display property", icon="INFO")
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IconGalleryNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IconGalleryNode"
|
||||
bl_label = "Icon Gallery (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_property_input("Enum")
|
||||
self.add_boolean_input("Show Labels")
|
||||
self.add_float_input("Scale").default_value = 5
|
||||
self.add_float_input("Scale Popup").default_value = 5
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["Enum"].is_linked:
|
||||
self.code = f"""
|
||||
{self.active_layout}.template_icon_view({self.inputs['Enum'].python_source}, '{self.inputs['Enum'].python_attr}', show_labels={self.inputs['Show Labels'].python_value}, scale={self.inputs['Scale'].python_value}, scale_popup={self.inputs['Scale Popup'].python_value})
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Property connected!', icon='ERROR')
|
||||
"""
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IfElseInterfaceNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IfElseInterfaceNode"
|
||||
bl_label = "If/Else (Interface) (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
passthrough_layout_type = True
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_boolean_input("Condition").default_value = True
|
||||
self.add_interface_output("True")
|
||||
self.add_interface_output("False")
|
||||
self.add_interface_output("Continue")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if {self.inputs['Condition'].python_value}:
|
||||
{self.indent(self.outputs['True'].python_value, 6) if self.outputs['True'].python_value.strip() else 'pass'}
|
||||
{"else:" if self.outputs['False'].python_value.strip() else ""}
|
||||
{self.indent(self.outputs['False'].python_value, 6) if self.outputs['False'].python_value.strip() else ''}
|
||||
{self.indent(self.outputs['Continue'].python_value, 5)}
|
||||
"""
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import bpy
|
||||
from ....utils import get_python_name
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RunInterfaceFunctionNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RunInterfaceFunctionNode"
|
||||
bl_label = "Function Run (Interface) (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname == "SN_InterfaceFunctionNode" and data:
|
||||
index = 0
|
||||
for i, out in enumerate(node.outputs):
|
||||
if out.bl_idname != "SN_InterfaceSocket":
|
||||
index = i-1
|
||||
break
|
||||
# inputs has been added
|
||||
if "added" in data:
|
||||
socket_index = list(data["added"].node.outputs).index(data["added"])
|
||||
self.add_input_from_socket(data["added"])
|
||||
self.inputs.move(len(self.inputs)-1, socket_index-index)
|
||||
# input has been removed
|
||||
elif "removed" in data:
|
||||
self.inputs.remove(self.inputs[data["removed"]])
|
||||
# input has changed
|
||||
elif "changed" in data:
|
||||
self.convert_socket(self.inputs[data["changed"].index-index], data["changed"].bl_idname)
|
||||
# input has updated
|
||||
elif "updated" in data:
|
||||
if data["updated"].index-index < len(self.inputs):
|
||||
self.inputs[data["updated"].index-index].name = data["updated"].name
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
def update_function_reference(self, context):
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
# remember connections
|
||||
links = []
|
||||
for inp in self.inputs[1:]:
|
||||
links.append(None)
|
||||
if inp.is_linked:
|
||||
links[-1] = inp.from_socket()
|
||||
# remove current data inputs
|
||||
for i in range(len(self.inputs)-1, 0, -1):
|
||||
self.inputs.remove(self.inputs[i])
|
||||
# add new data inputs
|
||||
if self.ref_SN_InterfaceFunctionNode in parent_tree.nodes:
|
||||
for out in parent_tree.nodes[self.ref_SN_InterfaceFunctionNode].outputs[1:-1]:
|
||||
if out.bl_idname != "SN_InterfaceSocket":
|
||||
self.add_input_from_socket(out)
|
||||
# restore connections
|
||||
if len(links) == len(self.inputs)-1:
|
||||
for i, from_socket in enumerate(links):
|
||||
if from_socket:
|
||||
self.node_tree.links.new(from_socket, self.inputs[i+1])
|
||||
self._evaluate(context)
|
||||
|
||||
ref_SN_InterfaceFunctionNode: bpy.props.StringProperty(name="Function",
|
||||
description="The function to run",
|
||||
update=update_function_reference)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
if self.ref_SN_InterfaceFunctionNode in parent_tree.nodes:
|
||||
# get input values
|
||||
inp_values = []
|
||||
for inp in self.inputs[1:]:
|
||||
inp_values.append(inp.python_value)
|
||||
inp_values = ", ".join(inp_values)
|
||||
|
||||
self.code = f"""
|
||||
layout_function = {self.active_layout}
|
||||
{parent_tree.nodes[self.ref_SN_InterfaceFunctionNode].func_name}(layout_function, {inp_values})
|
||||
"""
|
||||
else:
|
||||
self.code = f""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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_InterfaceFunctionNode", bpy.data.node_groups[parent_tree.name].node_collection("SN_InterfaceFunctionNode"), "refs", text="")
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LabelNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LabelNode"
|
||||
bl_label = "Label (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label").default_value = "My Label"
|
||||
self.add_icon_input("Icon")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"{self.active_layout}.label(text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
pass
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutBoxNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutBoxNode"
|
||||
bl_label = "Box (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
@property
|
||||
def layout_type(self):
|
||||
return f"box_{self.static_uid}"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_interface_output().prev_dynamic = True
|
||||
self.add_dynamic_interface_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
box_{self.static_uid} = {self.active_layout}.box()
|
||||
box_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
box_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"box_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
box_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
box_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
box_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
box_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
box_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 5)}
|
||||
"""
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutColumnNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutColumnNode"
|
||||
bl_label = "Column (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, _):
|
||||
return f"col_{self.static_uid}"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_interface_output().prev_dynamic = True
|
||||
self.add_dynamic_interface_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
col_{self.static_uid} = {self.active_layout}.column(heading={self.inputs["Label"].python_value}, align={self.inputs["Align"].python_value})
|
||||
col_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
col_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"col_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
col_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
col_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
col_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
col_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
col_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 5)}
|
||||
"""
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutRowNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutRowNode"
|
||||
bl_label = "Row (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, _):
|
||||
return f"row_{self.static_uid}"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_interface_output().prev_dynamic = True
|
||||
self.add_dynamic_interface_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
row_{self.static_uid} = {self.active_layout}.row(heading={self.inputs["Label"].python_value}, align={self.inputs["Align"].python_value})
|
||||
row_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
row_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"row_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
row_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
row_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
row_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
row_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
row_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
{self.indent([out.python_value for out in self.outputs[:-1]], 5)}
|
||||
"""
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutSplitNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutSplitNode"
|
||||
bl_label = "Split (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, _):
|
||||
return f"split_{self.static_uid}"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
inp = self.add_float_input("Factor")
|
||||
inp.subtype = "FACTOR"
|
||||
inp.default_value = 0.5
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_interface_output()
|
||||
self.add_interface_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
split_{self.static_uid} = {self.active_layout}.split(factor={self.inputs["Factor"].python_value}, align={self.inputs["Align"].python_value})
|
||||
split_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
split_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"split_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
split_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
split_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
split_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
split_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
split_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
{self.indent(self.outputs[1].python_value, 5)}
|
||||
"""
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ForInterfaceNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ForInterfaceNode"
|
||||
bl_label = "Loop For (Interface) (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
passthrough_layout_type = True
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input("Collection")
|
||||
self.add_interface_output("Repeat")
|
||||
self.add_interface_output("Continue")
|
||||
self.add_property_output("Item").changeable = True
|
||||
self.add_integer_output("Index")
|
||||
|
||||
|
||||
def update_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[1], self.socket_names[self.for_type])
|
||||
inp.name = self.for_type
|
||||
self.convert_socket(self.outputs["Item"], self.socket_names["Data"] if self.for_type == "List" else self.socket_names["Property"])
|
||||
self._evaluate(context)
|
||||
|
||||
for_type: bpy.props.EnumProperty(name="Type",
|
||||
description="Collection Type",
|
||||
items=[("List", "List", "List"),
|
||||
("Collection", "Collection", "Collection")],
|
||||
default="Collection",
|
||||
update=update_type)
|
||||
|
||||
reverse: bpy.props.BoolProperty(name="Reverse",
|
||||
description="Reverse the order the loop runs through the items",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs[1].is_linked:
|
||||
self.outputs["Index"].python_value = f"i_{self.static_uid}"
|
||||
self.outputs["Item"].python_value = f"{self.inputs[1].python_value}[i_{self.static_uid}]"
|
||||
self.code = f"""
|
||||
for i_{self.static_uid} in range({f"len({self.inputs[1].python_value})" if not self.reverse else f"len({self.inputs[1].python_value})-1,-1,-1"}):
|
||||
{self.indent(self.outputs['Repeat'].python_value, 7) if self.outputs['Repeat'].python_value.strip() else 'pass'}
|
||||
{self.indent(self.outputs['Continue'].python_value, 6)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Collection connected!', icon='ERROR')
|
||||
{self.indent(self.outputs['Continue'].python_value, 6)}
|
||||
"""
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Item"].reset_value()
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "for_type")
|
||||
layout.prop(self, "reverse")
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RepeatInterfaceNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RepeatInterfaceNode"
|
||||
bl_label = "Loop Repeat (Interface) (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
passthrough_layout_type = True
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_integer_input("Repetitions").default_value = 2
|
||||
self.add_interface_output("Repeat")
|
||||
self.add_interface_output("Continue")
|
||||
self.add_integer_output("Step")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Step"].python_value = f"i_{self.static_uid}"
|
||||
self.code = f"""
|
||||
for i_{self.static_uid} in range({self.inputs['Repetitions'].python_value}):
|
||||
{self.indent(self.outputs['Repeat'].python_value, 6) if self.outputs['Repeat'].python_value.strip() else 'pass'}
|
||||
{self.indent(self.outputs['Continue'].python_value, 5)}
|
||||
"""
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_PopoverNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PopoverNode"
|
||||
bl_label = "Popover (Legacy)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input()
|
||||
|
||||
|
||||
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=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
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 display with this popover",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname == "SN_PanelNode":
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.parent_type == "CUSTOM":
|
||||
if self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
|
||||
node = self.ref_ntree.nodes[self.ref_SN_PanelNode]
|
||||
self.code = f"""
|
||||
{self.active_layout}.popover('{node.last_idname}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
"""
|
||||
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.popover('{self.panel_parent}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
if self.parent_type == "CUSTOM":
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
subrow.prop_search(self, "ref_SN_PanelNode", parent_tree.node_collection("SN_PanelNode"), "refs", text="", icon="VIEWZOOM")
|
||||
else:
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SeparatorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SeparatorNode"
|
||||
bl_label = "Separator (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_float_input("Factor").default_value = 1.0
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
{self.active_layout}.separator(factor={self.inputs['Factor'].python_value})
|
||||
"""
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SubmenuNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SubmenuNode"
|
||||
bl_label = "Submenu (Legacy)"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input()
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname in ["SN_PanelNode", "SN_MenuNode", "SN_PieMenuNode"]:
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
parent_type: bpy.props.EnumProperty(name="Parent Type",
|
||||
description="Use a custom panel as a parent",
|
||||
default="CUSTOM",
|
||||
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
|
||||
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_MenuNode: bpy.props.StringProperty(name="Custom Parent",
|
||||
description="The panel used as a custom parent panel for this subpanel",
|
||||
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)
|
||||
|
||||
menu_parent: bpy.props.StringProperty(name="Menu",
|
||||
default="VIEW3D_MT_add",
|
||||
description="The menu that should be displayed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.parent_type == "CUSTOM":
|
||||
if self.ref_ntree and self.ref_SN_MenuNode in self.ref_ntree.nodes:
|
||||
self.code = f"{self.active_layout}.menu('{self.ref_ntree.nodes[self.ref_SN_MenuNode].idname}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})"
|
||||
else:
|
||||
self.code = f"{self.active_layout}.menu('{self.menu_parent}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
|
||||
if self.parent_type == "CUSTOM":
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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="")
|
||||
else:
|
||||
name = f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}"
|
||||
op = row.operator("sn.activate_menu_picker", icon="EYEDROPPER", text=name)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
@@ -0,0 +1,25 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_ProgressNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
bl_idname = "SN_ProgressNode"
|
||||
bl_label = "Progress Indicator"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label").default_value = "My Label"
|
||||
self.add_float_input("Factor").default_value = 0.0
|
||||
self.add_boolean_input("Ring").default_value = False
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
{self.active_layout}.progress(text={self.inputs['Label'].python_value}, factor={self.inputs['Factor'].python_value}, type='RING' if {self.inputs['Ring'].python_value} else 'BAR')
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
pass
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SeparatorNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SeparatorNodeNew"
|
||||
bl_label = "Separator"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_float_input("Factor").default_value = 1.0
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
{self.active_layout}.separator(factor={self.inputs['Factor'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CopyMenuNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CopyMenuNodeNew"
|
||||
bl_label = "Copy Menu"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
menu_parent: bpy.props.StringProperty(name="Menu",
|
||||
default="VIEW3D_MT_add",
|
||||
description="The menu that should be displayed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if hasattr(bpy.types,"{self.menu_parent}"):
|
||||
if not hasattr(bpy.types.{self.menu_parent}, "poll") or bpy.types.{self.menu_parent}.poll(context):
|
||||
bpy.types.{self.menu_parent}.draw(self, context)
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this menu here!", icon="ERROR")
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this menu!", icon="ERROR")
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
name = f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}"
|
||||
op = row.operator("sn.activate_menu_picker", icon="EYEDROPPER", text=name)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CopyPanelNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CopyPanelNodeNew"
|
||||
bl_label = "Copy Panel"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if hasattr(bpy.types,"{self.panel_parent}"):
|
||||
if not hasattr(bpy.types.{self.panel_parent}, "poll") or bpy.types.{self.panel_parent}.poll(context):
|
||||
bpy.types.{self.panel_parent}.draw(self, context)
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this panel here!", icon="ERROR")
|
||||
else:
|
||||
{self.active_layout}.label(text="Can't display this panel!", icon="ERROR")
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
@@ -0,0 +1,27 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IfElseInterfaceNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IfElseInterfaceNodeNew"
|
||||
bl_label = "If/Else (Interface)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_boolean_input("Condition").default_value = True
|
||||
self.add_interface_output("True").passthrough_layout_type = True
|
||||
self.add_interface_output("False").passthrough_layout_type = True
|
||||
self.add_interface_output("Interface").passthrough_layout_type = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
if {self.inputs['Condition'].python_value}:
|
||||
{self.indent(self.outputs['True'].python_value, 6) if self.outputs['True'].python_value.strip() else 'pass'}
|
||||
{"else:" if self.outputs['False'].python_value.strip() else ""}
|
||||
{self.indent(self.outputs['False'].python_value, 6) if self.outputs['False'].python_value.strip() else ''}
|
||||
{self.indent(self.outputs['Interface'].python_value, 5)}
|
||||
"""
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutBoxNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutBoxNodeNew"
|
||||
bl_label = "Box"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, socket):
|
||||
if socket.name == "Box":
|
||||
return f"box_{self.static_uid}"
|
||||
return self.active_layout
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_boolean_input("Use Invoke")["default_value"] = True
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_dynamic_interface_output("Box")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
box_{self.static_uid} = {self.active_layout}.box()
|
||||
box_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
box_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"box_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
box_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
box_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
box_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
box_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
box_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
if not {self.inputs['Use Invoke'].python_value if 'Use Invoke' in self.inputs else 'False'}: box_{self.static_uid}.operator_context = "EXEC_DEFAULT"
|
||||
{self.indent([out.python_value if out.name == 'Box' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_LayoutColumnNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
bl_idname = "SN_LayoutColumnNodeNew"
|
||||
bl_label = "Column"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, socket):
|
||||
if socket.name == "Column":
|
||||
return f"col_{self.static_uid}"
|
||||
return self.active_layout
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_boolean_input("Use Invoke")["default_value"] = True
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(
|
||||
["Expand", "Left", "Center", "Right"]
|
||||
)
|
||||
self.add_dynamic_interface_output("Column")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
col_{self.static_uid} = {self.active_layout}.column(heading={self.inputs["Label"].python_value}, align={self.inputs["Align"].python_value})
|
||||
col_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
col_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"col_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
col_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
col_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
col_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
col_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
col_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
col_{self.static_uid}.operator_context = "INVOKE_DEFAULT" if {self.inputs['Use Invoke'].python_value if 'Use Invoke' in self.inputs else 'True'} else "EXEC_DEFAULT"
|
||||
{self.indent([out.python_value if out.name == 'Column' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutGridNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutGridNode"
|
||||
bl_label = "Grid"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, socket):
|
||||
if socket.name == "Grid":
|
||||
return f"grid_{self.static_uid}"
|
||||
return self.active_layout
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_integer_input("Columns").default_value = 6
|
||||
self.add_boolean_input("Fill Row By Row").default_value = False
|
||||
self.add_boolean_input("Even Columns").default_value = False
|
||||
self.add_boolean_input("Even Rows").default_value = False
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_boolean_input("Use Invoke")["default_value"] = True
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_dynamic_interface_output("Grid")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
grid_{self.static_uid} = {self.active_layout}.grid_flow(columns={self.inputs["Columns"].python_value}, row_major={self.inputs["Fill Row By Row"].python_value}, even_columns={self.inputs["Even Columns"].python_value}, even_rows={self.inputs["Even Rows"].python_value}, align={self.inputs["Align"].python_value})
|
||||
grid_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"grid_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
grid_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
grid_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
grid_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
grid_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
grid_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
if not {self.inputs['Use Invoke'].python_value if 'Use Invoke' in self.inputs else 'False'}: grid_{self.static_uid}.operator_context = "EXEC_DEFAULT"
|
||||
{self.indent([out.python_value if out.name == 'Grid' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_LayoutRowNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
bl_idname = "SN_LayoutRowNodeNew"
|
||||
bl_label = "Row"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, socket):
|
||||
if socket.name == "Row":
|
||||
return f"row_{self.static_uid}"
|
||||
return self.active_layout
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_boolean_input("Use Invoke")["default_value"] = True
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(
|
||||
["Expand", "Left", "Center", "Right"]
|
||||
)
|
||||
self.add_dynamic_interface_output("Row")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
row_{self.static_uid} = {self.active_layout}.row(heading={self.inputs["Label"].python_value}, align={self.inputs["Align"].python_value})
|
||||
row_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
row_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"row_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
row_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
row_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
row_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
row_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
row_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
row_{self.static_uid}.operator_context = "INVOKE_DEFAULT" if {self.inputs['Use Invoke'].python_value if 'Use Invoke' in self.inputs else 'True'} else "EXEC_DEFAULT"
|
||||
{self.indent([out.python_value if out.name == 'Row' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_LayoutSplitNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LayoutSplitNodeNew"
|
||||
bl_label = "Split"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def layout_type(self, socket):
|
||||
if socket.name == "Split":
|
||||
return f"split_{self.static_uid}"
|
||||
return self.active_layout
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
inp = self.add_float_input("Factor")
|
||||
inp.subtype = "FACTOR"
|
||||
inp.default_value = 0.5
|
||||
self.add_boolean_input("Align")
|
||||
self.add_boolean_input("Alert")
|
||||
self.add_boolean_input("Enabled")["default_value"] = True
|
||||
self.add_boolean_input("Active")["default_value"] = True
|
||||
self.add_boolean_input("Split Layout")
|
||||
self.add_boolean_input("Decorate Layout")
|
||||
self.add_boolean_input("Use Invoke")["default_value"] = True
|
||||
self.add_float_input("Scale X")["default_value"] = 1
|
||||
self.add_float_input("Scale Y")["default_value"] = 1
|
||||
self.add_enum_input("Alignment")["items"] = str(["Expand", "Left", "Center", "Right"])
|
||||
self.add_interface_output("Split")
|
||||
self.add_interface_output("Split")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
split_{self.static_uid} = {self.active_layout}.split(factor={self.inputs["Factor"].python_value}, align={self.inputs["Align"].python_value})
|
||||
split_{self.static_uid}.alert = {self.inputs["Alert"].python_value}
|
||||
split_{self.static_uid}.enabled = {self.inputs["Enabled"].python_value}
|
||||
{f"split_{self.static_uid}.active = {self.inputs['Active'].python_value}" if "Active" in self.inputs else ""}
|
||||
split_{self.static_uid}.use_property_split = {self.inputs["Split Layout"].python_value}
|
||||
split_{self.static_uid}.use_property_decorate = {self.inputs["Decorate Layout"].python_value}
|
||||
split_{self.static_uid}.scale_x = {self.inputs["Scale X"].python_value}
|
||||
split_{self.static_uid}.scale_y = {self.inputs["Scale Y"].python_value}
|
||||
split_{self.static_uid}.alignment = {self.inputs["Alignment"].python_value}.upper()
|
||||
if not {self.inputs['Use Invoke'].python_value if 'Use Invoke' in self.inputs else 'False'}: split_{self.static_uid}.operator_context = "EXEC_DEFAULT"
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
{self.indent(self.outputs[1].python_value, 5)}
|
||||
"""
|
||||
@@ -0,0 +1,59 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ForInterfaceNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ForInterfaceNodeNew"
|
||||
bl_label = "Loop For (Interface)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_collection_property_input("Collection")
|
||||
self.add_interface_output("Repeat").passthrough_layout_type = True
|
||||
self.add_dynamic_interface_output().passthrough_layout_type = True
|
||||
self.add_property_output("Item").changeable = True
|
||||
self.add_integer_output("Index")
|
||||
|
||||
|
||||
def update_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[1], self.socket_names[self.for_type])
|
||||
inp.name = self.for_type
|
||||
self.convert_socket(self.outputs["Item"], self.socket_names["Data"] if self.for_type == "List" else self.socket_names["Property"])
|
||||
self._evaluate(context)
|
||||
|
||||
for_type: bpy.props.EnumProperty(name="Type",
|
||||
description="Collection Type",
|
||||
items=[("List", "List", "List"),
|
||||
("Collection", "Collection", "Collection")],
|
||||
default="Collection",
|
||||
update=update_type)
|
||||
|
||||
reverse: bpy.props.BoolProperty(name="Reverse",
|
||||
description="Reverse the order the loop runs through the items",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs[1].is_linked:
|
||||
self.outputs["Index"].python_value = f"i_{self.static_uid}"
|
||||
self.outputs["Item"].python_value = f"{self.inputs[1].python_value}[i_{self.static_uid}]"
|
||||
self.code = f"""
|
||||
for i_{self.static_uid} in range({f"len({self.inputs[1].python_value})" if not self.reverse else f"len({self.inputs[1].python_value})-1,-1,-1"}):
|
||||
{self.indent(self.outputs['Repeat'].python_value, 7) if self.outputs['Repeat'].python_value.strip() else 'pass'}
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 6)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.label(text='No Collection connected!', icon='ERROR')
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 6)}
|
||||
"""
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Item"].reset_value()
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "for_type")
|
||||
layout.prop(self, "reverse")
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RepeatInterfaceNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RepeatInterfaceNodeNew"
|
||||
bl_label = "Loop Repeat (Interface)"
|
||||
bl_width_default = 200
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_integer_input("Repetitions").default_value = 2
|
||||
self.add_interface_output("Repeat").passthrough_layout_type = True
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
self.add_integer_output("Step")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Step"].python_value = f"i_{self.static_uid}"
|
||||
self.code = f"""
|
||||
for i_{self.static_uid} in range({self.inputs['Repetitions'].python_value}):
|
||||
{self.indent(self.outputs['Repeat'].python_value, 6) if self.outputs['Repeat'].python_value.strip() else 'pass'}
|
||||
{self.indent([out.python_value if out.name == 'Interface' else '' for out in self.outputs], 5)}
|
||||
"""
|
||||
@@ -0,0 +1,91 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_PopoverNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PopoverNodeNew"
|
||||
bl_label = "Popover"
|
||||
bl_width_default = 240
|
||||
node_color = "INTERFACE"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input()
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
self.ref_ntree = self.node_tree
|
||||
self.order = 1
|
||||
|
||||
|
||||
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=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
panel_parent: bpy.props.StringProperty(default="EEVEE_MATERIAL_PT_surface",
|
||||
name="Parent",
|
||||
description="The panel id this subpanel should be shown in",
|
||||
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 display with this popover",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname == "SN_PanelNode":
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.parent_type == "CUSTOM":
|
||||
if self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
|
||||
node = self.ref_ntree.nodes[self.ref_SN_PanelNode]
|
||||
self.code = f"""
|
||||
{self.active_layout}.popover('{node.last_idname}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.popover('{self.panel_parent}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
if self.parent_type == "CUSTOM":
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
subrow.prop_search(self, "ref_SN_PanelNode", parent_tree.node_collection("SN_PanelNode"), "refs", text="", icon="VIEWZOOM")
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
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
|
||||
else:
|
||||
op = row.operator("sn.activate_subpanel_picker", text=f"{self.panel_parent.replace('_PT_', ' ').replace('_', ' ').title()}", icon="EYEDROPPER")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.allow_subpanels = True
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "panel_parent")
|
||||
@@ -0,0 +1,86 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SubmenuNodeNew(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SubmenuNodeNew"
|
||||
bl_label = "Submenu"
|
||||
node_color = "INTERFACE"
|
||||
bl_width_default = 240
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_interface_input()
|
||||
self.add_string_input("Label")
|
||||
self.add_icon_input()
|
||||
self.add_interface_output().passthrough_layout_type = True
|
||||
self.ref_ntree = self.node_tree
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname in ["SN_PanelNode", "SN_MenuNode", "SN_PieMenuNode"]:
|
||||
self._evaluate(bpy.context)
|
||||
|
||||
parent_type: bpy.props.EnumProperty(name="Parent Type",
|
||||
description="Use a custom panel as a parent",
|
||||
default="CUSTOM",
|
||||
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
|
||||
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_MenuNode: bpy.props.StringProperty(name="Custom Parent",
|
||||
description="The menu used for this submenu",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
|
||||
name="Menu Node Tree",
|
||||
description="The node tree to select the menu from",
|
||||
poll=SN_ScriptingBaseNode.ntree_poll,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
menu_parent: bpy.props.StringProperty(name="Menu",
|
||||
default="VIEW3D_MT_add",
|
||||
description="The menu that should be displayed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.parent_type == "CUSTOM":
|
||||
if self.ref_ntree and self.ref_SN_MenuNode in self.ref_ntree.nodes:
|
||||
self.code = f"""
|
||||
{self.active_layout}.menu('{self.ref_ntree.nodes[self.ref_SN_MenuNode].idname}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
else:
|
||||
self.code = f"""
|
||||
{self.active_layout}.menu('{self.menu_parent}', text={self.inputs['Label'].python_value}, icon_value={self.inputs['Icon'].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 4)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
|
||||
if self.parent_type == "CUSTOM":
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
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="")
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
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
|
||||
else:
|
||||
name = f"{self.menu_parent.replace('_MT_', ' ').replace('_', ' ').title()}"
|
||||
op = row.operator("sn.activate_menu_picker", icon="EYEDROPPER", text=name)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
row.prop(self, "parent_type", text="", icon_only=True)
|
||||
|
||||
|
||||
def draw_node_panel(self, context, layout):
|
||||
layout.prop(self, "menu_parent")
|
||||
@@ -0,0 +1,31 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_OT_DummyButtonOperator(bpy.types.Operator):
|
||||
bl_idname = "sn.dummy_button_operator"
|
||||
bl_label = "Dummy Button"
|
||||
bl_description = "This button has no operator associated"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.report({"INFO"}, message="No operator associated with this button!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_PasteOperator(bpy.types.Operator):
|
||||
bl_idname = "sn.paste_operator"
|
||||
bl_label = "Paste Operator"
|
||||
bl_description = "Paste a copied operator into this button"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if "bpy.ops." in context.window_manager.clipboard:
|
||||
bpy.data.node_groups[self.node_tree].nodes[self.node].pasted_operator = context.window_manager.clipboard
|
||||
else:
|
||||
self.report({"ERROR"}, message="Not a valid blender operator. Use the Rightclick Menu -> Get Serpens Operator button")
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,85 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
registered_menus = []
|
||||
|
||||
|
||||
|
||||
def register_dummy_interface(ntree, node, menu):
|
||||
func_name = f"sna_dummy_{menu.bl_rna.identifier}"
|
||||
label = getattr(menu, "bl_label", menu.bl_rna.identifier.replace('_PT_', ' ').replace('_', ' ').title())
|
||||
menu = f"""
|
||||
def {func_name}(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.alert = True
|
||||
op = row.operator("sn.pick_menu_location", text="Select '{label}'", icon="EYEDROPPER")
|
||||
op.node_tree = "{ntree}"
|
||||
op.node = "{node}"
|
||||
op.menu = "{menu.bl_rna.identifier}"
|
||||
|
||||
bpy.types.{menu.bl_rna.identifier}.append({func_name})
|
||||
registered_menus.append([bpy.types.{menu.bl_rna.identifier}, {func_name}])
|
||||
"""
|
||||
try: exec(menu, globals())
|
||||
except: pass
|
||||
|
||||
|
||||
|
||||
class SN_OT_ActivateMenuPicker(bpy.types.Operator):
|
||||
bl_idname = "sn.activate_menu_picker"
|
||||
bl_label = "Select Menu Location"
|
||||
bl_description = "Select the location of this menu in the Interface"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not context.scene.sn.picker_active
|
||||
|
||||
def execute(self, context):
|
||||
# register menus
|
||||
for menu in bpy.types.Menu.__subclasses__() + bpy.types.Header.__subclasses__():
|
||||
register_dummy_interface(self.node_tree, self.node,menu)
|
||||
|
||||
# redraw screen
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
|
||||
# set picker active
|
||||
context.scene.sn.picker_active = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_PickMenuLocation(bpy.types.Operator):
|
||||
bl_idname = "sn.pick_menu_location"
|
||||
bl_label = "Pick Location"
|
||||
bl_description = "Pick this location to put the menu into"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
menu: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
# find node and set properties
|
||||
if self.node_tree in bpy.data.node_groups:
|
||||
ntree = bpy.data.node_groups[self.node_tree]
|
||||
if self.node in ntree.nodes:
|
||||
node = ntree.nodes[self.node]
|
||||
node.menu_parent = self.menu
|
||||
|
||||
# unregister menus
|
||||
for menu in registered_menus:
|
||||
try: menu[0].remove(menu[1])
|
||||
except: pass
|
||||
|
||||
# reset saves and picker
|
||||
registered_menus.clear()
|
||||
context.scene.sn.picker_active = False
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,123 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
registered_panels = []
|
||||
|
||||
|
||||
|
||||
def register_dummy_panel(ntree, node, space, region):
|
||||
idname = f"SNA_PT_Dummy{space}{region}"
|
||||
|
||||
panel = f"""
|
||||
class {idname}(bpy.types.Panel):
|
||||
bl_label = "test"
|
||||
bl_space_type = '{space}'
|
||||
bl_region_type = '{region}'
|
||||
bl_category = 'SERPENS'
|
||||
bl_options = {{"HIDE_HEADER"}}
|
||||
bl_order = 0
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.scale_y = 1.5
|
||||
row.alert = True
|
||||
op = row.operator("sn.pick_panel_location", text="Select {space.replace("_", " ").title()} {region.replace("_", " ").title()}")
|
||||
op.node_tree = "{ntree}"
|
||||
op.node = "{node}"
|
||||
op.space = "{space}"
|
||||
op.region = "{region}"
|
||||
op.context = ""
|
||||
if hasattr(context.space_data, "context"):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.5
|
||||
row.alert = True
|
||||
op = row.operator("sn.pick_panel_location", text="Select {space.replace("_", " ").title()} {region.replace("_", " ").title()} "+context.space_data.context.replace("_", " ").title())
|
||||
op.node_tree = "{ntree}"
|
||||
op.node = "{node}"
|
||||
op.space = "{space}"
|
||||
op.region = "{region}"
|
||||
op.context = context.space_data.context.lower()
|
||||
|
||||
bpy.utils.register_class({idname})
|
||||
registered_panels.append({idname})
|
||||
"""
|
||||
#register panel
|
||||
try: exec(panel, globals())
|
||||
except: pass
|
||||
|
||||
|
||||
|
||||
class SN_OT_ActivatePanelPicker(bpy.types.Operator):
|
||||
bl_idname = "sn.activate_panel_picker"
|
||||
bl_label = "Select Panel Location"
|
||||
bl_description = "Select the location of this panel in the Interface"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not context.scene.sn.picker_active
|
||||
|
||||
def execute(self, context):
|
||||
# possible panel locations
|
||||
space_types = ["EMPTY", "VIEW_3D", "IMAGE_EDITOR", "NODE_EDITOR",
|
||||
"SEQUENCE_EDITOR", "CLIP_EDITOR", "DOPESHEET_EDITOR",
|
||||
"GRAPH_EDITOR", "NLA_EDITOR", "TEXT_EDITOR", "CONSOLE",
|
||||
"INFO", "TOPBAR", "STATUSBAR", "OUTLINER", "PROPERTIES",
|
||||
"FILE_BROWSER", "SPREADSHEET", "PREFERENCES"]
|
||||
region_types = ["WINDOW", "HEADER", "CHANNELS", "TEMPORARY", "UI", "TOOLS",
|
||||
"TOOL_PROPS", "PREVIEW", "HUD", "NAVIGATION_BAR", "EXECUTE",
|
||||
"FOOTER", "TOOL_HEADER", "XR"]
|
||||
|
||||
# register panels
|
||||
registered_panels.clear()
|
||||
for space in space_types:
|
||||
for region in region_types:
|
||||
register_dummy_panel(self.node_tree, self.node, space, region)
|
||||
|
||||
# redraw screen
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
|
||||
# set picker active
|
||||
context.scene.sn.picker_active = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_PickPanelLocation(bpy.types.Operator):
|
||||
bl_idname = "sn.pick_panel_location"
|
||||
bl_label = "Pick Location"
|
||||
bl_description = "Pick this location to put the panel into"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
space: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
region: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
context: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
# find node and set properties
|
||||
if self.node_tree in bpy.data.node_groups:
|
||||
ntree = bpy.data.node_groups[self.node_tree]
|
||||
if self.node in ntree.nodes:
|
||||
node = ntree.nodes[self.node]
|
||||
node.space = self.space
|
||||
node.region = self.region
|
||||
node.context = self.context
|
||||
|
||||
# unregister panels
|
||||
for panel in registered_panels:
|
||||
try: bpy.utils.unregister_class(panel)
|
||||
except: pass
|
||||
|
||||
# reset saves and picker
|
||||
registered_panels.clear()
|
||||
context.scene.sn.picker_active = False
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,103 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
registered_subpanels = []
|
||||
|
||||
|
||||
|
||||
def register_dummy_subpanel(ntree, node, panel):
|
||||
func_name = f"sna_dummy_{panel.bl_rna.identifier}"
|
||||
label = getattr(panel, "bl_label", panel.bl_rna.identifier.replace('_PT_', ' ').replace('_', ' ').title())
|
||||
panel = f"""
|
||||
def {func_name}(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.alert = True
|
||||
# if "SNA_PT_" in "{panel.bl_rna.identifier}":
|
||||
# row.label(text="Select this custom panel on the subpanel node", icon="ERROR")
|
||||
# else:
|
||||
row.scale_y = 1.5
|
||||
op = row.operator("sn.pick_subpanel_location", text="Select {label}")
|
||||
op.node_tree = "{ntree}"
|
||||
op.node = "{node}"
|
||||
op.panel = "{panel.bl_rna.identifier}"
|
||||
|
||||
bpy.types.{panel.bl_rna.identifier}.append({func_name})
|
||||
registered_subpanels.append([bpy.types.{panel.bl_rna.identifier}, {func_name}])
|
||||
"""
|
||||
#
|
||||
#register panel
|
||||
try: exec(panel, globals())
|
||||
except: pass
|
||||
|
||||
|
||||
|
||||
class SN_OT_ActivateSubpanelPicker(bpy.types.Operator):
|
||||
bl_idname = "sn.activate_subpanel_picker"
|
||||
bl_label = "Select Subpanel Location"
|
||||
bl_description = "Select the location of this subpanel in the Interface"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
allow_subpanels: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not context.scene.sn.picker_active
|
||||
|
||||
def execute(self, context):
|
||||
# register panels
|
||||
for panel in bpy.types.Panel.__subclasses__():
|
||||
if not getattr(panel, "bl_parent_id", False) or self.allow_subpanels:
|
||||
register_dummy_subpanel(self.node_tree, self.node, panel)
|
||||
|
||||
# redraw screen
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
|
||||
# set picker active
|
||||
context.scene.sn.picker_active = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_PickSubpanelLocation(bpy.types.Operator):
|
||||
bl_idname = "sn.pick_subpanel_location"
|
||||
bl_label = "Pick Location"
|
||||
bl_description = "Pick this location to put the subpanel into"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
panel: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
# find node and set properties
|
||||
if self.node_tree in bpy.data.node_groups:
|
||||
ntree = bpy.data.node_groups[self.node_tree]
|
||||
if self.node in ntree.nodes:
|
||||
node = ntree.nodes[self.node]
|
||||
node.panel_parent = self.panel
|
||||
|
||||
panel = getattr(bpy.types, self.panel)
|
||||
if hasattr(panel, "bl_space_type") and hasattr(node, "space"):
|
||||
node.space = panel.bl_space_type
|
||||
if hasattr(panel, "bl_region_type") and hasattr(node, "region"):
|
||||
node.region = panel.bl_region_type
|
||||
if hasattr(panel, "bl_context") and hasattr(node, "context"):
|
||||
node.context = panel.bl_context
|
||||
else:
|
||||
node.context = ""
|
||||
|
||||
# unregister panels
|
||||
for panel in registered_subpanels:
|
||||
try: panel[0].remove(panel[1])
|
||||
except: pass
|
||||
|
||||
# reset saves and picker
|
||||
registered_subpanels.clear()
|
||||
context.scene.sn.picker_active = False
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,25 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_OT_OpenPreferences(bpy.types.Operator):
|
||||
bl_idname = "sn.open_preferences"
|
||||
bl_label = "Open Preferences"
|
||||
bl_description = "Open Preferences"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
navigation: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
has_prefs = False
|
||||
for area in context.screen.areas:
|
||||
if area.type == "PREFERENCES":
|
||||
has_prefs = True
|
||||
break
|
||||
if not has_prefs:
|
||||
bpy.ops.screen.userpref_show("INVOKE_DEFAULT")
|
||||
context.preferences.active_section = "ADDONS"
|
||||
context.window_manager.addon_search = "Serpens"
|
||||
context.preferences.addons[__name__.partition('.')[ 0]].preferences.navigation = self.navigation
|
||||
bpy.ops.preferences.addon_expand(module="blender_visual_scripting_addon")
|
||||
return {"FINISHED"}
|
||||
Reference in New Issue
Block a user