2025-07-01
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class PrintProperty(bpy.types.PropertyGroup):
|
||||
|
||||
text: bpy.props.StringProperty()
|
||||
|
||||
|
||||
|
||||
class SN_OT_ClearPrints(bpy.types.Operator):
|
||||
bl_idname = "sn.clear_prints"
|
||||
bl_label = "Clear Prints"
|
||||
bl_description = "Clear this print nodes messages"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
node: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.node_groups[self.node_tree].nodes[self.node].messages.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
|
||||
class SN_PrintNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PrintNode"
|
||||
bl_label = "Print"
|
||||
bl_width_default = 200
|
||||
node_color = "PROGRAM"
|
||||
|
||||
messages: bpy.props.CollectionProperty(type=PrintProperty)
|
||||
|
||||
print_on_node: bpy.props.BoolProperty(default=True,
|
||||
name="Print On Node",
|
||||
description="Show print results on this node",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
limit_prints: bpy.props.IntProperty(default=0, min=0,
|
||||
name="Amount of print messages that will be added to this node before a previous one is removed (0 is unlimited)",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_input()
|
||||
self.add_string_input()
|
||||
self.add_dynamic_string_input()
|
||||
self.add_execute_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.messages.clear()
|
||||
values = [inp.python_value for inp in self.inputs[1:-1]]
|
||||
self.code_imperative = f"""
|
||||
def sn_print(on_node, node, limit, *args):
|
||||
print(*args)
|
||||
if on_node:
|
||||
try:
|
||||
msg = node.messages.add()
|
||||
msg.text = str(args)[1:-1]
|
||||
if limit and len(node.messages) > limit:
|
||||
node.messages.remove(0)
|
||||
for area in bpy.context.screen.areas: area.tag_redraw()
|
||||
except:
|
||||
print("Can't add print outputs to the node when the print is run in an interface!")
|
||||
"""
|
||||
self.code = f"""
|
||||
sn_print({self.print_on_node}, bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}'], {self.limit_prints}, {", ".join(values)})
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
|
||||
def evaluate_export(self, context):
|
||||
self.messages.clear()
|
||||
values = [inp.python_value for inp in self.inputs[1:-1]]
|
||||
self.code = f"""
|
||||
print({", ".join(values)})
|
||||
{self.indent(self.outputs[0].python_value, 5)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "print_on_node", text="Print On Node")
|
||||
if self.print_on_node:
|
||||
layout.prop(self, "limit_prints", text="Limit")
|
||||
if self.print_on_node:
|
||||
if not self.messages:
|
||||
box = layout.box()
|
||||
box.label(text="Nothing printed!")
|
||||
else:
|
||||
row = layout.row()
|
||||
row.label(text="Messages:")
|
||||
op = row.operator("sn.clear_prints", text="", icon="TRASH", emboss=False)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
col = layout.column(align=True)
|
||||
col.scale_y = 0.9
|
||||
for msg in self.messages:
|
||||
box = col.box()
|
||||
box.label(text=msg.text)
|
||||
@@ -0,0 +1,23 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SleepNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SleepNode"
|
||||
bl_label = "Sleep"
|
||||
bl_width_default = 200
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_input()
|
||||
self.add_float_input("Seconds")
|
||||
self.add_execute_output()
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import time"
|
||||
self.code = f"""
|
||||
time.sleep({self.inputs[1].python_value})
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
@@ -0,0 +1,103 @@
|
||||
import bpy
|
||||
from random import uniform
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_TimestampNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_TimestampNode"
|
||||
bl_label = "Timestamp"
|
||||
bl_width_default = 200
|
||||
|
||||
def update_connected_portals(self, context=None):
|
||||
if self.timestamp_type == "START":
|
||||
for ntree in bpy.data.node_groups:
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
for node in ntree.node_collection(self.bl_idname).nodes:
|
||||
if node.timestamp_type == "END" and node.var_name == self.var_name:
|
||||
node.var_name = self.var_name
|
||||
|
||||
timestamp_type: bpy.props.EnumProperty(name="Type",
|
||||
description="Start the timestamp recording or print the total time since the start",
|
||||
items=[("START", "Start", "Start", "MOD_TIME", 0),
|
||||
("END", "End", "End", "TIME", 1)],
|
||||
update=update_connected_portals)
|
||||
|
||||
timestamp: bpy.props.FloatProperty(name="Timestamp")
|
||||
|
||||
def update_var_name(self, context):
|
||||
self.label = self.var_name
|
||||
self._evaluate(context)
|
||||
|
||||
def get_var_name(self):
|
||||
return self.get("var_name", "")
|
||||
|
||||
def set_var_name(self, value):
|
||||
if self.timestamp_type == "INPUT":
|
||||
for ntree in bpy.data.node_groups:
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
for node in ntree.node_collection(self.bl_idname).nodes:
|
||||
if node.timestamp_type == "OUTPUT" and node.var_name == self.var_name:
|
||||
node["var_name"] = value
|
||||
self["var_name"] = value
|
||||
|
||||
var_name: bpy.props.StringProperty(name="Name",
|
||||
description="The identifier that links this timestamp to other timestamps",
|
||||
get=get_var_name, set=set_var_name,
|
||||
update=update_var_name)
|
||||
|
||||
def update_custom_color(self, context):
|
||||
# update own color
|
||||
self.color = self.custom_color
|
||||
# update connected color
|
||||
if self.timestamp_type == "START":
|
||||
for ntree in bpy.data.node_groups:
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
for node in ntree.node_collection(self.bl_idname).nodes:
|
||||
if node.timestamp_type == "END" and node.var_name == self.var_name:
|
||||
node.custom_color = self.custom_color
|
||||
|
||||
custom_color: bpy.props.FloatVectorProperty(name="Color",
|
||||
size=3, min=0, max=1, subtype="COLOR",
|
||||
description="The color of this node",
|
||||
update=update_custom_color)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_input()
|
||||
self.add_execute_output()
|
||||
self.var_name = self.uuid
|
||||
self.custom_color = (uniform(0,1), uniform(0,1), uniform(0,1))
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import time"
|
||||
if self.timestamp_type == "START":
|
||||
self.code = f"""
|
||||
t_{self.var_name} = time.time()
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
elif self.timestamp_type == "END":
|
||||
self.code = f"""
|
||||
if "t_{self.var_name}" in locals(): print(f"Time elapsed for {self.var_name}: {{(time.time() - t_{self.var_name}) * 1000}}ms")
|
||||
try: bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}'].timestamp = time.time() - t_{self.var_name}
|
||||
except: print("Failed to set timestamp on node")
|
||||
for a in bpy.context.screen.areas: a.tag_redraw()
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
def evaluate_export(self, context):
|
||||
self.code = f"""
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "timestamp_type", expand=True)
|
||||
if self.timestamp_type == "START":
|
||||
row = layout.row(align=True)
|
||||
split = row.split(factor=0.6, align=True)
|
||||
split.prop(self, "var_name", text="")
|
||||
sub_split = split.split(factor=0.5, align=True)
|
||||
sub_split.prop(self, "custom_color", text="")
|
||||
sub_split.operator("sn.reset_portal", text="", icon="LOOP_BACK").node = self.name
|
||||
else:
|
||||
layout.label(text=f"Time elapsed: {self.timestamp*1000}ms")
|
||||
@@ -0,0 +1,55 @@
|
||||
import operator
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_OT_TriggerTriggerNode(bpy.types.Operator):
|
||||
bl_idname = "sn.trigger_trigger_node"
|
||||
bl_label = "Trigger Node"
|
||||
bl_description = "Trigger this node"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
uid: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if self.uid in context.scene.sn.function_store:
|
||||
context.scene.sn.function_store[self.uid]()
|
||||
else:
|
||||
self.report({"WARNING"}, message="Couldn't find trigger function!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
|
||||
class SN_TriggerNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_TriggerNode"
|
||||
bl_label = "Trigger"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.trigger_trigger_node", text="Run Trigger", icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"trigger_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
def {self.handler_name}():
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
self.code_register = f"bpy.context.scene.sn.function_store['{self.static_uid}'] = {self.handler_name}"
|
||||
|
||||
def evaluate_export(self, context):
|
||||
self.code = ""
|
||||
self.code_register = ""
|
||||
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AfterRenderNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AfterRenderNode"
|
||||
bl_label = "On Render Finish"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("render_cancel", "Cancel", "On canceling a render job"), ("render_post", "After", "On render (after)"), ("render_complete", "Complete", "On completion of render job")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.handler = self.action
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BeforeExitNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BeforeExitNode"
|
||||
bl_label = "On Blender Close"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = "EXIT"
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"import atexit"
|
||||
self.code = f"""
|
||||
def before_exit_handler_{self.static_uid}():
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"""atexit.register(before_exit_handler_{self.static_uid})"""
|
||||
self.code_unregister = f"""atexit.unregister(before_exit_handler_{self.static_uid})"""
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BeforeRenderNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BeforeRenderNode"
|
||||
bl_label = "On Render Start"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("render_init", "Init", "On initialization of a render job"), ("render_pre", "Before", "On render (before)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DepsgraphUpdateNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DepsgraphUpdateNode"
|
||||
bl_label = "On Depsgraph Update"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("depsgraph_update_pre", "Before", "On depsgraph update (before)"), ("depsgraph_update_post", "After", "On depsgraph update (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ChangeFrameNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ChangeFrameNode"
|
||||
bl_label = "On Frame Change"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("frame_change_pre", "Before", "Called after frame change for playback and rendering, before any data is evaluated for the new frame"), ("frame_change_post", "After", "Called after frame change for playback and rendering, after the data has been evaluated for the new frame")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,386 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
from ..Program.Run_Operator import on_operator_ref_update
|
||||
|
||||
|
||||
|
||||
space_names = {
|
||||
"EMPTY": "Window",
|
||||
"VIEW_3D": "3D View",
|
||||
"IMAGE_EDITOR": "Image",
|
||||
"NODE_EDITOR": "Node Editor",
|
||||
"SEQUENCE_EDITOR": "Sequencer",
|
||||
"CLIP_EDITOR": "Clip",
|
||||
"DOPESHEET_EDITOR": "Dopesheet",
|
||||
"GRAPH_EDITOR": "Graph Editor",
|
||||
"NLA_EDITOR": "NLA Editor",
|
||||
"TEXT_EDITOR": "Text",
|
||||
"CONSOLE": "Console",
|
||||
"INFO": "Info",
|
||||
"OUTLINER": "Outliner",
|
||||
"PROPERTIES": "Property Editor",
|
||||
"FILE_BROWSER": "File Browser"
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SN_OnKeypressNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_OnKeypressNode"
|
||||
bl_label = "On Keypress"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
key: bpy.props.StringProperty(name="Key",default="Y", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
recording: bpy.props.BoolProperty(name="Recording",default=False)
|
||||
|
||||
ctrl: bpy.props.BoolProperty(name="Ctrl",
|
||||
description="If the ctrl key has to be pressed",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
alt: bpy.props.BoolProperty(name="Alt",
|
||||
description="If the alt key has to be pressed",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
shift: bpy.props.BoolProperty(name="Shift",
|
||||
description="If the shift key has to be pressed",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
value: bpy.props.EnumProperty(name="Value",
|
||||
description="The way the key has to be pressed",
|
||||
items=[("PRESS","On Press","The action is run when the key is pressed once"),
|
||||
("RELEASE","On Release","The action is run when the key is released"),
|
||||
("CLICK","On Click","The action is run when the mouse button is clicked once"),
|
||||
("DOUBLE_CLICK","On Double Click","The action is run when the mouse button is clicked twice"),
|
||||
("ANY","Any","The action will run with any of the above ways"),
|
||||
("CLICK_DRAG","On Click Drag","The action is run when you click and drag")],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
direction: bpy.props.EnumProperty(name="Direction",
|
||||
description="Direction to drag the mouse in to run the action",
|
||||
items=[("ANY","Any Direction","The action will run when you click and drag in any direction"),
|
||||
("NORTH","North","The action will run when you click and drag in this direction"),
|
||||
("NORTH_EAST","North East","The action will run when you click and drag in this direction"),
|
||||
("EAST","East","The action will run when you click and drag in this direction"),
|
||||
("SOUTH_EAST","South East","The action will run when you click and drag in this direction"),
|
||||
("SOUTH","South","The action will run when you click and drag in this direction"),
|
||||
("SOUT_WEST","South West","The action will run when you click and drag in this direction"),
|
||||
("WEST","West","The action will run when you click and drag in this direction"),
|
||||
("NORTH_WEST","North West","The action will run when you click and drag in this direction")],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
repeat: bpy.props.BoolProperty(name="Repeat Key",
|
||||
description="Repeat the action when the key is held down",
|
||||
default=False,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.ref_ntree = self.node_tree
|
||||
self.order = 3
|
||||
|
||||
def reset_inputs(self, context=None):
|
||||
self.inputs.clear()
|
||||
self.pasted_operator = ""
|
||||
self.ref_SN_OperatorNode = ""
|
||||
self._evaluate(context)
|
||||
|
||||
def get_spaces(self, context):
|
||||
items = [("EMPTY","Any Space","Run this shortcut in any space")]
|
||||
for item in list(space_names.keys())[1:]:
|
||||
items.append((item,item.replace("_"," ").title(),item.replace("_"," ").title()))
|
||||
return items
|
||||
|
||||
space: bpy.props.EnumProperty(items=get_spaces,
|
||||
name="Space",
|
||||
description="The space which this action should be able to run in",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("OPERATOR","Operator","Operator"),
|
||||
("PANEL","Panel","Panel"),
|
||||
("MENU","Menu","Menu"),
|
||||
("PIE_MENU","Pie Menu","Pie Menu")],
|
||||
name="Shortcut Type",
|
||||
description="The type of action to run from this shortcut",
|
||||
update=reset_inputs)
|
||||
|
||||
parent_type: bpy.props.EnumProperty(name="Parent Type",
|
||||
description="Use a custom panel as a parent",
|
||||
items=[("BLENDER", "Blender", "Blender", "BLENDER", 0),
|
||||
("CUSTOM", "Custom", "Custom", "FILE_SCRIPT", 1)],
|
||||
update=reset_inputs)
|
||||
|
||||
def update_picked(self,context):
|
||||
if self.picked:
|
||||
elem = eval("bpy.types." + self.picked)
|
||||
if elem.bl_rna.base.identifier == "Menu":
|
||||
if "pie" in self.picked.lower():
|
||||
self.action = "PIE_MENU"
|
||||
self.pie = self.picked
|
||||
else:
|
||||
self.action = "MENU"
|
||||
self.menu = self.picked
|
||||
elif elem.bl_rna.base.identifier == "Panel":
|
||||
self.action = "PANEL"
|
||||
self.panel = self.picked
|
||||
self.picked = ""
|
||||
|
||||
picked: bpy.props.StringProperty(update=update_picked)
|
||||
|
||||
panel: bpy.props.StringProperty(name="Panel",
|
||||
description="The panel to open when the key is pressed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
menu: bpy.props.StringProperty(name="Menu",
|
||||
description="The menu to open when the key is pressed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
pie: bpy.props.StringProperty(name="Pie Menu",
|
||||
description="The pie menu to open when the key is pressed",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def create_inputs(self, op_rna):
|
||||
""" Create inputs for operator """
|
||||
for prop in op_rna.properties:
|
||||
if not prop.identifier in ["rna_type", "settings"]:
|
||||
inp = self.add_input_from_property(prop)
|
||||
if inp:
|
||||
inp.can_be_disabled = not prop.is_required
|
||||
inp.disabled = not prop.is_required
|
||||
|
||||
def update_pasted_operator(self, context):
|
||||
self.inputs.clear()
|
||||
|
||||
if self.pasted_operator:
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
self.create_inputs(op_rna)
|
||||
self._evaluate(context)
|
||||
|
||||
pasted_operator: bpy.props.StringProperty(name="Operator",
|
||||
description="The operator to run when the key is pressed",
|
||||
update=update_pasted_operator)
|
||||
|
||||
keep_open: bpy.props.BoolProperty(name="Keep Open",
|
||||
description="Keep the panel open after a property is changed",
|
||||
default=True,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_ntree: bpy.props.PointerProperty(type=bpy.types.NodeTree,
|
||||
name="Panel Node Tree",
|
||||
description="The node tree to select the panel from",
|
||||
poll=SN_ScriptingBaseNode.ntree_poll,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_PanelNode: bpy.props.StringProperty(name="Panel",
|
||||
description="The panel to open with this shortcut",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_MenuNode: bpy.props.StringProperty(name="Menu",
|
||||
description="The menu to open with this shortcut",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
ref_SN_PieMenuNode: bpy.props.StringProperty(name="Menu",
|
||||
description="The menu to open with this shortcut",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_ref_update(self, node, data=None):
|
||||
if node.bl_idname in ["SN_PanelNode", "SN_MenuNode", "SN_PieMenuNode"]:
|
||||
self._evaluate(bpy.context)
|
||||
elif node.bl_idname in ["SN_OperatorNode", "SN_ModalOperatorNode"]:
|
||||
on_operator_ref_update(self, node, data, self.ref_ntree, self.ref_SN_OperatorNode, 0)
|
||||
|
||||
def update_custom_operator(self, context):
|
||||
""" Updates the nodes settings when a new parent panel is selected """
|
||||
self.inputs.clear()
|
||||
if self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
|
||||
parent = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
|
||||
for prop in parent.properties:
|
||||
if prop.property_type in ["Integer", "Float", "Boolean"] and prop.settings.is_vector:
|
||||
socket = self._add_input(self.socket_names[prop.property_type + " Vector"], prop.name)
|
||||
socket.size = prop.settings.size
|
||||
socket.can_be_disabled = True
|
||||
elif prop.property_type == "Enum":
|
||||
if prop.stngs_enum.enum_flag:
|
||||
socket = self._add_input(self.socket_names["Enum Set"], prop.name)
|
||||
else:
|
||||
socket = self._add_input(self.socket_names[prop.property_type], prop.name)
|
||||
socket.items = str(list(map(lambda item: item.name, prop.stngs_enum.items)))
|
||||
else:
|
||||
self._add_input(self.socket_names[prop.property_type], prop.name).can_be_disabled = True
|
||||
|
||||
self._evaluate(context)
|
||||
|
||||
ref_SN_OperatorNode: bpy.props.StringProperty(name="Operator",
|
||||
description="The operator to run with this shortcut",
|
||||
update=update_custom_operator)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.key:
|
||||
input_code = ""
|
||||
operator = ""
|
||||
if self.action == "PANEL":
|
||||
if self.parent_type == "BLENDER" and self.panel:
|
||||
operator = "wm.call_panel"
|
||||
input_code = f"kmi.properties.name = '{self.panel}'\n"
|
||||
input_code += f"kmi.properties.keep_open = {self.keep_open}\n"
|
||||
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_PanelNode in self.ref_ntree.nodes:
|
||||
operator = "wm.call_panel"
|
||||
node = self.ref_ntree.nodes[self.ref_SN_PanelNode]
|
||||
input_code = f"kmi.properties.name = '{node.last_idname}'\n"
|
||||
input_code += f"kmi.properties.keep_open = {self.keep_open}\n"
|
||||
elif self.action == "MENU":
|
||||
if self.parent_type == "BLENDER" and self.menu:
|
||||
operator = "wm.call_menu"
|
||||
input_code = f"kmi.properties.name = '{self.menu}'\n"
|
||||
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_MenuNode in self.ref_ntree.nodes:
|
||||
operator = "wm.call_menu"
|
||||
node = self.ref_ntree.nodes[self.ref_SN_MenuNode]
|
||||
input_code = f"kmi.properties.name = '{node.idname}'\n"
|
||||
elif self.action == "PIE_MENU":
|
||||
if self.parent_type == "BLENDER" and self.pie:
|
||||
operator = "wm.call_menu_pie"
|
||||
input_code = f"kmi.properties.name = '{self.pie}'\n"
|
||||
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_PieMenuNode in self.ref_ntree.nodes:
|
||||
operator = "wm.call_menu_pie"
|
||||
node = self.ref_ntree.nodes[self.ref_SN_PieMenuNode]
|
||||
input_code = f"kmi.properties.name = '{node.idname}'\n"
|
||||
elif self.action == "OPERATOR":
|
||||
if self.parent_type == "BLENDER" and self.pasted_operator:
|
||||
if not self.pasted_operator: return
|
||||
operator = self.pasted_operator.split("(")[0].replace("bpy.ops.", "")
|
||||
op = eval(self.pasted_operator.split("(")[0])
|
||||
op_rna = op.get_rna_type()
|
||||
for inp in self.inputs:
|
||||
if not inp.disabled:
|
||||
for prop in op_rna.properties:
|
||||
if (self.version == 0 and (prop.name and prop.name == inp.name) or (not prop.name and prop.identifier.replace("_", " ").title() == inp.name)) \
|
||||
or (self.version == 1 and (inp.name.replace(" ", "_").lower() == prop.identifier)):
|
||||
self.code += "\n" + f"op.{prop.identifier} = {inp.python_value}"
|
||||
input_code += f"kmi.properties.{prop.identifier} = {inp.python_value}\n"
|
||||
elif self.parent_type == "CUSTOM" and self.ref_ntree and self.ref_SN_OperatorNode in self.ref_ntree.nodes:
|
||||
node = self.ref_ntree.nodes[self.ref_SN_OperatorNode]
|
||||
operator = f"sna.{node.operator_python_name}"
|
||||
for inp in self.inputs:
|
||||
if not inp.disabled:
|
||||
for prop in node.properties:
|
||||
if prop.name == inp.name:
|
||||
input_code += f"kmi.properties.{prop.python_name} = {inp.python_value}\n"
|
||||
if operator:
|
||||
self.code_register = f"""
|
||||
kc = bpy.context.window_manager.keyconfigs.addon
|
||||
km = kc.keymaps.new(name='{space_names[self.space]}', space_type='{self.space}')
|
||||
kmi = km.keymap_items.new('{operator}', '{self.key}', '{self.value}',
|
||||
ctrl={self.ctrl}, alt={self.alt}, shift={self.shift}, repeat={self.repeat})
|
||||
{f'kmi.direction = "{self.direction}"' if self.value == "CLICK_DRAG" else ''}
|
||||
{self.indent(input_code, 5)}
|
||||
addon_keymaps['{self.static_uid}'] = (km, kmi)
|
||||
"""
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "space", text="")
|
||||
row.prop(self, "value", text="")
|
||||
|
||||
if self.value == "CLICK_DRAG":
|
||||
layout.prop(self, "direction", text="")
|
||||
|
||||
layout.operator("sn.record_key", text=self.key, depress=self.recording).node = self.name
|
||||
row = layout.row(align=True)
|
||||
row.prop(self,"ctrl", toggle=True)
|
||||
row.prop(self,"shift", toggle=True)
|
||||
row.prop(self,"alt", toggle=True)
|
||||
layout.prop(self, "repeat")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "name")
|
||||
row.operator("sn.find_referencing_nodes", text="", icon="VIEWZOOM").node = self.name
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self,"action", text="")
|
||||
row.prop(self,"parent_type", text="", icon_only=True)
|
||||
|
||||
parent_tree = self.ref_ntree if self.ref_ntree else self.node_tree
|
||||
|
||||
if self.action == "PANEL":
|
||||
if self.parent_type == "BLENDER":
|
||||
name = self.panel.replace("_", " ").title() if self.panel else "Select Panel"
|
||||
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
|
||||
op.node = self.name
|
||||
op.selection = "PANELS"
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
subrow.prop_search(self, "ref_SN_PanelNode", parent_tree.node_collection("SN_PanelNode"), "refs", text="", icon="VIEWZOOM")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_PanelNode in self.ref_ntree.nodes
|
||||
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
|
||||
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
|
||||
op.node = self.ref_SN_PanelNode
|
||||
|
||||
layout.prop(self, "keep_open")
|
||||
|
||||
elif self.action == "MENU":
|
||||
if self.parent_type == "BLENDER":
|
||||
name = self.menu.replace("_", " ").title() if self.menu else "Select Menu"
|
||||
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
|
||||
op.node = self.name
|
||||
op.selection = "MENUS"
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
subrow.prop_search(self, "ref_SN_MenuNode", parent_tree.node_collection("SN_MenuNode"), "refs", text="", icon="VIEWZOOM")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_MenuNode in self.ref_ntree.nodes
|
||||
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
|
||||
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
|
||||
op.node = self.ref_SN_MenuNode
|
||||
|
||||
elif self.action == "PIE_MENU":
|
||||
if self.parent_type == "BLENDER":
|
||||
name = self.pie.replace("_"," ").title() if self.pie else "Select Pie Menu"
|
||||
op = layout.operator("sn.pick_interface", text=name, icon="EYEDROPPER")
|
||||
op.node = self.name
|
||||
op.selection = "MENUS"
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
subrow.prop_search(self, "ref_SN_PieMenuNode", parent_tree.node_collection("SN_PieMenuNode"), "refs", text="", icon="VIEWZOOM")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_PieMenuNode in self.ref_ntree.nodes
|
||||
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
|
||||
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
|
||||
op.node = self.ref_SN_PieMenuNode
|
||||
|
||||
elif self.action == "OPERATOR":
|
||||
if self.parent_type == "BLENDER":
|
||||
name = self.pasted_operator.split("(")[0].split(".")[-1].replace("_"," ").title() if self.pasted_operator else "Paste Operator"
|
||||
op = layout.operator("sn.paste_operator", text=name, icon="PASTEDOWN")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop_search(self, "ref_ntree", bpy.data, "node_groups", text="")
|
||||
subrow = row.row(align=True)
|
||||
subrow.enabled = self.ref_ntree != None
|
||||
subrow.prop_search(self, "ref_SN_OperatorNode", parent_tree.node_collection("SN_OperatorNode"), "refs", text="", icon="VIEWZOOM")
|
||||
|
||||
subrow = row.row()
|
||||
subrow.enabled = self.ref_ntree != None and self.ref_SN_OperatorNode in self.ref_ntree.nodes
|
||||
op = subrow.operator("sn.find_node", text="", icon="RESTRICT_SELECT_OFF", emboss=False)
|
||||
op.node_tree = self.ref_ntree.name if self.ref_ntree else ""
|
||||
op.node = self.ref_SN_OperatorNode
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_OnLoadNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_OnLoadNode"
|
||||
bl_label = "On Load"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("load_pre", "Before", "On loading a new blend file (before)"), ("load_post", "After", "On loading a new blend file (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_OnSaveNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_OnSaveNode"
|
||||
bl_label = "On Save"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("save_pre", "Before", "On blend file save (before)"), ("save_post", "After", "On blend file save (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RedoEventNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RedoEventNode"
|
||||
bl_label = "On Redo"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("redo_pre", "Before", "On loading a redo step (before)"), ("redo_post", "After", "On loading a redo step (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_UndoEventNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_UndoEventNode"
|
||||
bl_label = "On Undo"
|
||||
is_trigger = True
|
||||
bl_width_default = 200
|
||||
|
||||
action: bpy.props.EnumProperty(items=[("undo_pre", "Before", "On loading an undo step (before)"), ("undo_post", "After", "On loading an undo step (after)")],name="Time of Action", description="When you want your event handler to run", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_output()
|
||||
|
||||
|
||||
def draw_node(self,context,layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
op = row.operator("sn.run_event",text="Run Event",icon="PLAY")
|
||||
op.uid = self.static_uid
|
||||
op.handler = self.action
|
||||
layout.prop(self, "action", expand=True)
|
||||
|
||||
|
||||
@property
|
||||
def handler_name(self):
|
||||
return f"{self.action}_handler_{self.static_uid}"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from bpy.app.handlers import persistent"
|
||||
self.code = f"""
|
||||
@persistent
|
||||
def {self.handler_name}(dummy):
|
||||
{self.indent(self.outputs[0].python_value, 7) if self.outputs[0].python_value.strip() else 'pass'}
|
||||
"""
|
||||
|
||||
self.code_register = f"bpy.app.handlers.{self.action}.append({self.handler_name})"
|
||||
self.code_unregister = f"bpy.app.handlers.{self.action}.remove({self.handler_name})"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
import atexit
|
||||
|
||||
|
||||
class SN_OT_RunEventButton(bpy.types.Operator):
|
||||
bl_idname = "sn.run_event"
|
||||
bl_label = "Run Event"
|
||||
bl_description = "Run the event code without triggering event"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
uid: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
handler: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def get_atexit_functions(self):
|
||||
funs = []
|
||||
|
||||
class Capture:
|
||||
def __eq__(self, other):
|
||||
funs.append(other)
|
||||
return False
|
||||
|
||||
c = Capture()
|
||||
atexit.unregister(c)
|
||||
return funs
|
||||
|
||||
def execute(self, context):
|
||||
if self.handler == "EXIT":
|
||||
for func in self.get_atexit_functions():
|
||||
if self.uid in func.__name__:
|
||||
func()
|
||||
return {"FINISHED"}
|
||||
|
||||
else:
|
||||
handlers = getattr(bpy.app.handlers, self.handler)
|
||||
for handler in handlers:
|
||||
if self.uid in handler.__name__:
|
||||
handler(None)
|
||||
return {"FINISHED"}
|
||||
|
||||
self.report({'WARNING'}, "Failed when trying to run event. Try recompiling!")
|
||||
return {"CANCELLED"}
|
||||
@@ -0,0 +1,123 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_OT_RecordKey(bpy.types.Operator):
|
||||
bl_idname = "sn.record_key"
|
||||
bl_label = "Record Key"
|
||||
bl_description = "Records the next pressed key"
|
||||
bl_options = {"REGISTER","UNDO","INTERNAL"}
|
||||
|
||||
node: bpy.props.StringProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.space_data.node_tree.nodes[self.node].recording = True
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def assign_key(self,context,key_type):
|
||||
if hasattr(context,"space_data") and context.space_data:
|
||||
if hasattr(context.space_data,"node_tree"):
|
||||
node = context.space_data.node_tree.nodes[self.node]
|
||||
node.key = key_type
|
||||
node.recording = False
|
||||
context.area.tag_redraw()
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
invalid = ["MOUSEMOVE","INBETWEEN_MOUSEMOVE","TRACKPADPAN","TRACKPADZOOM","MOUSEROTATE","MOUSESMARTZOOM",
|
||||
"TEXTINPUT","WINDOW_DEACTIVATE","ACTIONZONE_AREA","ACTIONZONE_REGION","ACTIONZONE_FULLSCREEN"]
|
||||
|
||||
if not event.type in invalid and not "TIMER" in event.type:
|
||||
self.assign_key(context,event.type)
|
||||
return {"FINISHED"}
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
def sn_append_interface(self, context):
|
||||
row = self.layout.row()
|
||||
row.alert = True
|
||||
op = row.operator("sn.select_interface_add",text="Select",icon="CHECKMARK")
|
||||
op.idname = self.bl_idname
|
||||
|
||||
|
||||
|
||||
remove_interfaces = []
|
||||
picker_node = None
|
||||
|
||||
|
||||
|
||||
class SN_OT_SelectInterfaceAdd(bpy.types.Operator):
|
||||
bl_idname = "sn.select_interface_add"
|
||||
bl_label = "Select"
|
||||
bl_description = "Select this element in the interface"
|
||||
bl_options = {"REGISTER","UNDO","INTERNAL"}
|
||||
|
||||
idname: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
|
||||
def remove_registered_interfaces(self):
|
||||
global remove_interfaces
|
||||
for interface in remove_interfaces:
|
||||
try:
|
||||
interface.remove(sn_append_interface)
|
||||
except:
|
||||
pass
|
||||
remove_interfaces.clear()
|
||||
|
||||
def execute(self, context):
|
||||
global picker_node
|
||||
if picker_node:
|
||||
picker_node.picked = self.idname
|
||||
picker_node = None
|
||||
self.remove_registered_interfaces()
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_StartInterfacePicker(bpy.types.Operator):
|
||||
bl_idname = "sn.pick_interface"
|
||||
bl_label = "Select"
|
||||
bl_description = "Select an element in the interface"
|
||||
bl_options = {"REGISTER","UNDO","INTERNAL"}
|
||||
|
||||
node: bpy.props.StringProperty()
|
||||
selection: bpy.props.EnumProperty(items=[("ALL","ALL","ALL"),
|
||||
("PANELS","PANELS","PANELS"),
|
||||
("MENUS","MENUS","MENUS")],
|
||||
options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
global remove_interfaces
|
||||
return len(remove_interfaces) == 0
|
||||
|
||||
def get_interfaces(self):
|
||||
interfaces = []
|
||||
for name in dir(bpy.types):
|
||||
try:
|
||||
interface = eval("bpy.types."+name)
|
||||
if self.selection != "PANELS":
|
||||
if hasattr(interface.bl_rna.base,"identifier") and interface.bl_rna.base.identifier == "Menu":
|
||||
interfaces.append(name)
|
||||
if self.selection != "MENUS":
|
||||
if hasattr(interface.bl_rna.base,"identifier") and interface.bl_rna.base.identifier == "Panel":
|
||||
interfaces.append(name)
|
||||
except:
|
||||
pass
|
||||
return interfaces
|
||||
|
||||
def execute(self, context):
|
||||
global picker_node
|
||||
global remove_interfaces
|
||||
picker_node = context.space_data.node_tree.nodes[self.node]
|
||||
for interface in self.get_interfaces():
|
||||
try:
|
||||
eval("bpy.types."+interface+".append(sn_append_interface)")
|
||||
remove_interfaces.append(eval("bpy.types."+interface))
|
||||
except:
|
||||
pass
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AddonInfoNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AddonInfoNode"
|
||||
bl_label = "Addon Info"
|
||||
node_color = "DEFAULT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_output("Name")
|
||||
self.add_string_output("Description")
|
||||
self.add_string_output("Author")
|
||||
self.add_string_output("Location")
|
||||
self.add_string_output("Warning")
|
||||
self.add_string_output("Doc URL")
|
||||
self.add_string_output("Tracker URL")
|
||||
self.add_string_output("Category")
|
||||
self.add_integer_vector_output("Version")
|
||||
self.add_integer_vector_output("Blender Version")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"bpy.context.scene.sn.addon_name"
|
||||
self.outputs[1].python_value = f"bpy.context.scene.sn.description"
|
||||
self.outputs[2].python_value = f"bpy.context.scene.sn.author"
|
||||
self.outputs[3].python_value = f"bpy.context.scene.sn.location"
|
||||
self.outputs[4].python_value = f"bpy.context.scene.sn.warning"
|
||||
self.outputs[5].python_value = f"bpy.context.scene.sn.doc_url"
|
||||
self.outputs[6].python_value = f"bpy.context.scene.sn.tracker_url"
|
||||
self.outputs[7].python_value = f"bpy.context.scene.sn.category"
|
||||
self.outputs[8].python_value = f"tuple(bpy.context.scene.sn.version)"
|
||||
self.outputs[9].python_value = f"tuple(bpy.context.scene.sn.blender)"
|
||||
|
||||
def evaluate_export(self, context):
|
||||
self.outputs[0].python_value = f"'{bpy.context.scene.sn.addon_name}'"
|
||||
self.outputs[1].python_value = f"'{bpy.context.scene.sn.description}'"
|
||||
self.outputs[2].python_value = f"'{bpy.context.scene.sn.author}'"
|
||||
self.outputs[3].python_value = f"'{bpy.context.scene.sn.location}'"
|
||||
self.outputs[4].python_value = f"'{bpy.context.scene.sn.warning}'"
|
||||
self.outputs[5].python_value = f"'{bpy.context.scene.sn.doc_url}'"
|
||||
self.outputs[6].python_value = f"'{bpy.context.scene.sn.tracker_url}'"
|
||||
self.outputs[7].python_value = f"'{bpy.context.scene.sn.category}'"
|
||||
self.outputs[8].python_value = str(tuple(bpy.context.scene.sn.version))
|
||||
self.outputs[9].python_value = str(tuple(bpy.context.scene.sn.blender))
|
||||
@@ -0,0 +1,30 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_2DViewZoomNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_2DViewZoomNode"
|
||||
bl_label = "2D View Zoom"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Area")
|
||||
self.add_float_output("Zoom Level")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = """
|
||||
def get_zoom_level(area):
|
||||
ui_scale = bpy.context.preferences.system.ui_scale
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
test_length = 1000
|
||||
x0, y0 = region.view2d.view_to_region(0, 0, clip=False)
|
||||
x1, y1 = region.view2d.view_to_region(test_length, test_length, clip=False)
|
||||
xl = x1 - x0
|
||||
yl = y1 - y0
|
||||
return (math.sqrt(xl**2 + yl**2) / test_length) * ui_scale
|
||||
return 1 * ui_scale
|
||||
"""
|
||||
self.code_import = "import math"
|
||||
self.outputs[0].python_value = f"get_zoom_level({self.inputs[0].python_value})"
|
||||
@@ -0,0 +1,73 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AreaByTypeNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AreaByTypeNode"
|
||||
bl_label = "Area By Type"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Screen")
|
||||
self.add_property_output("First Area")
|
||||
self.add_property_output("Last Area")
|
||||
self.add_property_output("Biggest Area")
|
||||
self.add_list_output("All Areas")
|
||||
self.add_boolean_output("Area Exists")
|
||||
self.add_integer_output("Area Amount")
|
||||
|
||||
|
||||
def area_items(self,context):
|
||||
types = ["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", "PREFERENCES"]
|
||||
items = []
|
||||
for a_type in types:
|
||||
items.append((a_type,a_type.replace("_"," ").title(),a_type))
|
||||
return items
|
||||
|
||||
area_type: bpy.props.EnumProperty(name="Area Type",
|
||||
description="The type of area to find",
|
||||
items=area_items,
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = f"""
|
||||
def find_areas_of_type(screen, area_type):
|
||||
areas = []
|
||||
for area in screen.areas:
|
||||
if area.type == area_type:
|
||||
areas.append(area)
|
||||
return areas
|
||||
|
||||
def find_area_by_type(screen, area_type, index):
|
||||
areas = find_areas_of_type(screen, area_type)
|
||||
if areas:
|
||||
return areas[index]
|
||||
return None
|
||||
|
||||
def find_biggest_area_by_type(screen, area_type):
|
||||
areas = find_areas_of_type(screen, area_type)
|
||||
if not areas: return []
|
||||
max_area = (areas[0], areas[0].width * areas[0].height)
|
||||
for area in areas:
|
||||
if area.width * area.height > max_area[1]:
|
||||
max_area = (area, area.width * area.height)
|
||||
return max_area[0]
|
||||
"""
|
||||
|
||||
screen = "bpy.context.screen" if not "Screen" in self.inputs else self.inputs["Screen"].python_value
|
||||
self.outputs["First Area"].python_value = f"find_area_by_type({screen}, '{self.area_type}', 0)"
|
||||
self.outputs["Last Area"].python_value = f"find_area_by_type({screen}, '{self.area_type}', -1)"
|
||||
self.outputs["Biggest Area"].python_value = f"find_biggest_area_by_type({screen}, '{self.area_type}')"
|
||||
self.outputs["All Areas"].python_value = f"find_areas_of_type({screen}, '{self.area_type}')"
|
||||
self.outputs["Area Exists"].python_value = f"bool(find_areas_of_type({screen}, '{self.area_type}'))"
|
||||
self.outputs["Area Amount"].python_value = f"len(find_areas_of_type({screen}, '{self.area_type}'))"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "area_type", text="")
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AreaLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AreaLocationsNode"
|
||||
bl_label = "Area Locations"
|
||||
node_color = "DEFAULT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Area")
|
||||
self.add_integer_vector_output("Top Left").size = 2
|
||||
self.add_integer_vector_output("Top Center").size = 2
|
||||
self.add_integer_vector_output("Top Right").size = 2
|
||||
self.add_integer_vector_output("Bottom Left").size = 2
|
||||
self.add_integer_vector_output("Bottom Center").size = 2
|
||||
self.add_integer_vector_output("Bottom Right").size = 2
|
||||
self.add_integer_vector_output("Left Center").size = 2
|
||||
self.add_integer_vector_output("Right Center").size = 2
|
||||
self.add_integer_vector_output("Center").size = 2
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = """
|
||||
def region_by_type(area, region_type):
|
||||
for region in area.regions:
|
||||
if region.type == region_type:
|
||||
return region
|
||||
return area.regions[0]
|
||||
"""
|
||||
|
||||
self.outputs["Top Left"].python_value = f"(0, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
|
||||
self.outputs["Top Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
|
||||
self.outputs["Top Right"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height)"
|
||||
self.outputs["Bottom Left"].python_value = f"(0, 0)"
|
||||
self.outputs["Bottom Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, 0)"
|
||||
self.outputs["Bottom Right"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, 0)"
|
||||
self.outputs["Left Center"].python_value = f"(0, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
|
||||
self.outputs["Right Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
|
||||
self.outputs["Center"].python_value = f"(region_by_type({self.inputs['Area'].python_value}, 'WINDOW').width/2, region_by_type({self.inputs['Area'].python_value}, 'WINDOW').height/2)"
|
||||
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
import os
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AssetNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AssetNode"
|
||||
bl_label = "Asset"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_output("Path").subtype = "FILE_PATH"
|
||||
|
||||
asset: bpy.props.StringProperty(name="Asset",
|
||||
description="Asset to get the path from",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.asset and self.asset in context.scene.sn.assets:
|
||||
if context.scene.sn.assets[self.asset].path:
|
||||
self.outputs["Path"].python_value = f"r\'{context.scene.sn.assets[self.asset].path}\'"
|
||||
return
|
||||
self.outputs["Path"].python_value = "\'\'"
|
||||
|
||||
def evaluate_export(self, context):
|
||||
if self.asset and self.asset in context.scene.sn.assets:
|
||||
if context.scene.sn.assets[self.asset].path:
|
||||
self.code_import = "import os"
|
||||
name = os.path.basename(context.scene.sn.assets[self.asset].path)
|
||||
if not name: name = os.path.basename(os.path.dirname(context.scene.sn.assets[self.asset].path))
|
||||
self.outputs["Path"].python_value = f"os.path.join(os.path.dirname(__file__), 'assets', '{name}')"
|
||||
return
|
||||
self.outputs["Path"].python_value = "\'\'"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop_search(self, "asset", context.scene.sn, "assets")
|
||||
@@ -0,0 +1,274 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
from .blend_data_base import BlendDataBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_MaterialsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_MaterialsBlendDataNode"
|
||||
bl_label = "Materials"
|
||||
|
||||
data_type = "Material"
|
||||
data_type_plural = "Materials"
|
||||
|
||||
active_path = "bpy.context.object.active_material"
|
||||
data_path = "bpy.data.materials"
|
||||
|
||||
|
||||
|
||||
class SN_MetaballsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_MetaballsBlendDataNode"
|
||||
bl_label = "Metaballs"
|
||||
|
||||
data_type = "Metaball"
|
||||
data_type_plural = "Metaballs"
|
||||
|
||||
data_path = "bpy.data.metaballs"
|
||||
|
||||
|
||||
|
||||
class SN_CurvesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_CurvesBlendDataNode"
|
||||
bl_label = "Curves"
|
||||
|
||||
data_type = "Curve"
|
||||
data_type_plural = "Curves"
|
||||
|
||||
data_path = "bpy.data.curves"
|
||||
|
||||
|
||||
|
||||
class SN_ActionsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ActionsBlendDataNode"
|
||||
bl_label = "Actions"
|
||||
|
||||
data_type = "Action"
|
||||
data_type_plural = "Actions"
|
||||
|
||||
data_path = "bpy.data.actions"
|
||||
|
||||
|
||||
|
||||
class SN_ArmaturesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ArmaturesBlendDataNode"
|
||||
bl_label = "Armatures"
|
||||
|
||||
data_type = "Armature"
|
||||
data_type_plural = "Armatures"
|
||||
|
||||
data_path = "bpy.data.armatures"
|
||||
|
||||
|
||||
|
||||
class SN_ImagesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ImagesBlendDataNode"
|
||||
bl_label = "Images"
|
||||
|
||||
data_type = "Image"
|
||||
data_type_plural = "Images"
|
||||
|
||||
data_path = "bpy.data.images"
|
||||
|
||||
|
||||
|
||||
class SN_LightsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_LightsBlendDataNode"
|
||||
bl_label = "Lights"
|
||||
|
||||
data_type = "Light"
|
||||
data_type_plural = "Lights"
|
||||
|
||||
data_path = "bpy.data.lights"
|
||||
|
||||
|
||||
|
||||
class SN_NodeGroupsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_NodeGroupsBlendDataNode"
|
||||
bl_label = "Node Groups"
|
||||
|
||||
data_type = "Node Group"
|
||||
data_type_plural = "Node Groups"
|
||||
|
||||
data_path = "bpy.data.node_groups"
|
||||
|
||||
|
||||
|
||||
class SN_TextsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_TextsBlendDataNode"
|
||||
bl_label = "Texts"
|
||||
|
||||
data_type = "Text"
|
||||
data_type_plural = "Texts"
|
||||
|
||||
data_path = "bpy.data.texts"
|
||||
|
||||
|
||||
|
||||
class SN_TexturesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_TexturesBlendDataNode"
|
||||
bl_label = "Textures"
|
||||
|
||||
data_type = "Texture"
|
||||
data_type_plural = "Textures"
|
||||
|
||||
data_path = "bpy.data.textures"
|
||||
|
||||
|
||||
|
||||
class SN_WorkspacesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_WorkspacesBlendDataNode"
|
||||
bl_label = "Workspaces"
|
||||
|
||||
data_type = "Workspace"
|
||||
data_type_plural = "Workspaces"
|
||||
|
||||
data_path = "bpy.data.workspaces"
|
||||
active_path = "bpy.context.workspace"
|
||||
|
||||
|
||||
|
||||
class SN_WorldsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_WorldsBlendDataNode"
|
||||
bl_label = "Worlds"
|
||||
|
||||
data_type = "World"
|
||||
data_type_plural = "Worlds"
|
||||
|
||||
data_path = "bpy.data.worlds"
|
||||
active_path = "bpy.context.scene.world"
|
||||
|
||||
|
||||
|
||||
class SN_BrushesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_BrushesBlendDataNode"
|
||||
bl_label = "Brushes"
|
||||
|
||||
data_type = "Brush"
|
||||
data_type_plural = "Brushes"
|
||||
|
||||
data_path = "bpy.data.brushes"
|
||||
|
||||
|
||||
|
||||
class SN_CamerasBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_CamerasBlendDataNode"
|
||||
bl_label = "Cameras"
|
||||
|
||||
data_type = "Camera"
|
||||
data_type_plural = "Cameras"
|
||||
|
||||
data_path = "bpy.data.cameras"
|
||||
active_path = "bpy.context.scene.camera.data"
|
||||
|
||||
|
||||
|
||||
class SN_FontsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_FontsBlendDataNode"
|
||||
bl_label = "Fonts"
|
||||
|
||||
data_type = "Font"
|
||||
data_type_plural = "Fonts"
|
||||
|
||||
data_path = "bpy.data.fonts"
|
||||
|
||||
|
||||
|
||||
class SN_GreasePencilsBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_GreasePencilsBlendDataNode"
|
||||
bl_label = "Grease Pencils"
|
||||
|
||||
data_type = "Grease Pencil"
|
||||
data_type_plural = "Grease Pencils"
|
||||
|
||||
data_path = "bpy.data.grease_pencils"
|
||||
|
||||
|
||||
|
||||
class SN_LatticesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_LatticesBlendDataNode"
|
||||
bl_label = "Lattices"
|
||||
|
||||
data_type = "Lattice"
|
||||
data_type_plural = "Lattices"
|
||||
|
||||
data_path = "bpy.data.lattices"
|
||||
|
||||
|
||||
|
||||
class SN_ScenesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ScenesBlendDataNode"
|
||||
bl_label = "Scenes"
|
||||
|
||||
data_type = "Scene"
|
||||
data_type_plural = "Scenes"
|
||||
|
||||
data_path = "bpy.data.scenes"
|
||||
active_path = "bpy.context.scene"
|
||||
|
||||
|
||||
|
||||
class SN_ScreensBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ScreensBlendDataNode"
|
||||
bl_label = "Screens"
|
||||
|
||||
data_type = "Screen"
|
||||
data_type_plural = "Screens"
|
||||
|
||||
data_path = "bpy.data.screens"
|
||||
active_path = "bpy.context.screen"
|
||||
|
||||
|
||||
|
||||
class SN_ShapeKeysBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_ShapeKeysBlendDataNode"
|
||||
bl_label = "Shape Keys"
|
||||
|
||||
data_type = "Shape Key"
|
||||
data_type_plural = "Shape Keys"
|
||||
|
||||
data_path = "bpy.data.shape_keys"
|
||||
|
||||
|
||||
|
||||
class SN_VolumesBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_VolumesBlendDataNode"
|
||||
bl_label = "Volumes"
|
||||
|
||||
data_type = "Volume"
|
||||
data_type_plural = "Volumes"
|
||||
|
||||
data_path = "bpy.data.volumes"
|
||||
|
||||
|
||||
|
||||
class SN_WindowManagersBlendDataNode(bpy.types.Node, BlendDataBaseNode, SN_ScriptingBaseNode):
|
||||
|
||||
bl_idname = "SN_WindowManagersBlendDataNode"
|
||||
bl_label = "Window Managers"
|
||||
|
||||
data_type = "Window Manager"
|
||||
data_type_plural = "Window Managers"
|
||||
|
||||
data_path = "bpy.data.window_managers"
|
||||
active_path = "bpy.context.window_manager"
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BlenderDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BlenderDataNode"
|
||||
bl_label = "Blender Data"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_output("Current Context")
|
||||
self.add_property_output("Blend Data")
|
||||
self.add_property_output("App")
|
||||
self.add_property_output("Path")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Current Context"].python_value = "bpy.context"
|
||||
self.outputs["Blend Data"].python_value = "bpy.data"
|
||||
self.outputs["App"].python_value = "bpy.app"
|
||||
self.outputs["Path"].python_value = "bpy.path"
|
||||
@@ -0,0 +1,39 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CollectionsBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CollectionsBlendDataNode"
|
||||
bl_label = "Collections"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_collection_property_output("All Collections")
|
||||
self.add_collection_property_output("Scene Collections")
|
||||
self.add_property_output("Scene Collection")
|
||||
self.add_property_output("Active Collection")
|
||||
self.add_property_output("Indexed")
|
||||
self.add_integer_input("Index")
|
||||
|
||||
def update_index_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
|
||||
inp.name = "Index" if self.index_type == "Integer" else "Name"
|
||||
self._evaluate(context)
|
||||
|
||||
index_type: bpy.props.EnumProperty(name="Index Type",
|
||||
description="The type of index to use",
|
||||
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
|
||||
("String", "Name", "Refers to the name property of the element.")],
|
||||
update=update_index_type)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["All Collections"].python_value = f"bpy.data.collections"
|
||||
self.outputs["Scene Collections"].python_value = f"bpy.context.scene.collection.children"
|
||||
self.outputs["Scene Collection"].python_value = f"bpy.context.scene.collection"
|
||||
self.outputs["Active Collection"].python_value = f"bpy.context.collection"
|
||||
self.outputs["Indexed"].python_value = f"bpy.data.collections[{self.inputs[0].python_value}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "index_type", expand=True)
|
||||
@@ -0,0 +1,37 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_MeshBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_MeshBlendDataNode"
|
||||
bl_label = "Meshes"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_collection_property_output("All Meshes")
|
||||
self.add_list_output("Active Scene Meshes")
|
||||
self.add_property_output("Active Object Mesh")
|
||||
self.add_property_output("Indexed")
|
||||
self.add_integer_input("Index")
|
||||
|
||||
def update_index_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
|
||||
inp.name = "Index" if self.index_type == "Integer" else "Name"
|
||||
self._evaluate(context)
|
||||
|
||||
index_type: bpy.props.EnumProperty(name="Index Type",
|
||||
description="The type of index to use",
|
||||
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
|
||||
("String", "Name", "Refers to the name property of the element.")],
|
||||
update=update_index_type)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["All Meshes"].python_value = f"bpy.data.meshes"
|
||||
self.outputs["Active Scene Meshes"].python_value = f"list(filter(lambda obj: obj, [obj.data if obj.type == 'MESH' else None for obj in bpy.context.scene.objects]))"
|
||||
self.outputs["Active Object Mesh"].python_value = f"(bpy.context.active_object.data if bpy.context.active_object.type == 'MESH' else None)"
|
||||
self.outputs["Indexed"].python_value = f"bpy.data.meshes[{self.inputs[0].python_value}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "index_type", expand=True)
|
||||
@@ -0,0 +1,39 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ObjectBlendDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ObjectBlendDataNode"
|
||||
bl_label = "Objects"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_collection_property_output("All Objects")
|
||||
self.add_collection_property_output("Active Scene Objects")
|
||||
self.add_collection_property_output("Selected Objects")
|
||||
self.add_property_output("Active Object")
|
||||
self.add_property_output("Indexed")
|
||||
self.add_integer_input("Index")
|
||||
|
||||
def update_index_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
|
||||
inp.name = "Index" if self.index_type == "Integer" else "Name"
|
||||
self._evaluate(context)
|
||||
|
||||
index_type: bpy.props.EnumProperty(name="Index Type",
|
||||
description="The type of index to use",
|
||||
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
|
||||
("String", "Name", "Refers to the name property of the element.")],
|
||||
update=update_index_type)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["All Objects"].python_value = f"bpy.data.objects"
|
||||
self.outputs["Active Scene Objects"].python_value = f"bpy.context.scene.objects"
|
||||
self.outputs["Selected Objects"].python_value = f"bpy.context.view_layer.objects.selected"
|
||||
self.outputs["Active Object"].python_value = f"bpy.context.view_layer.objects.active"
|
||||
self.outputs["Indexed"].python_value = f"bpy.data.objects[{self.inputs[0].python_value}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "index_type", expand=True)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class BlendDataBaseNode():
|
||||
|
||||
data_type = ""
|
||||
data_type_plural = ""
|
||||
|
||||
active_path = ""
|
||||
data_path = ""
|
||||
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_collection_property_output(f"All {self.data_type_plural}")
|
||||
if self.active_path: self.add_property_output(f"Active {self.data_type}")
|
||||
self.add_property_output("Indexed")
|
||||
self.add_integer_input("Index")
|
||||
|
||||
def update_index_type(self, context):
|
||||
inp = self.convert_socket(self.inputs[0], self.socket_names[self.index_type])
|
||||
inp.name = "Index" if self.index_type == "Integer" else "Name"
|
||||
self._evaluate(context)
|
||||
|
||||
index_type: bpy.props.EnumProperty(name="Index Type",
|
||||
description="The type of index to use",
|
||||
items=[("Integer", "Index", "Starts at 0. Negative indices go to the back of the list."),
|
||||
("String", "Name", "Refers to the name property of the element.")],
|
||||
update=update_index_type)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[f"All {self.data_type_plural}"].python_value = self.data_path
|
||||
if self.active_path: self.outputs[f"Active {self.data_type}"].python_value = self.active_path
|
||||
self.outputs["Indexed"].python_value = f"{self.data_path}[{self.inputs[0].python_value}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "index_type", expand=True)
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BooleanVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BooleanVectorNode"
|
||||
bl_label = "Boolean Vector"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_vector_input("Boolean").set_hide(True)
|
||||
self.add_boolean_vector_output("Boolean")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Boolean"].python_value = self.inputs["Boolean"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Boolean"], "size")
|
||||
self.inputs["Boolean"].draw_socket(context, layout, self, "Value")
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BooleanNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BooleanNode"
|
||||
bl_label = "Boolean"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Boolean").set_hide(True)
|
||||
self.add_boolean_output("Boolean")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Boolean"].python_value = self.inputs["Boolean"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Boolean"], "default_value", text="Value")
|
||||
@@ -0,0 +1,35 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ColorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ColorNode"
|
||||
bl_label = "Color"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def update_size(self, context):
|
||||
if self.use_four:
|
||||
self.inputs[0].subtype = "COLOR_ALPHA"
|
||||
self.outputs[0].subtype = "COLOR_ALPHA"
|
||||
else:
|
||||
self.inputs[0].subtype = "COLOR"
|
||||
self.outputs[0].subtype = "COLOR"
|
||||
|
||||
use_four: bpy.props.BoolProperty(default=False,
|
||||
name="Use Alpha",
|
||||
update=update_size)
|
||||
|
||||
def on_create(self, context):
|
||||
socket = self.add_float_vector_input("Color")
|
||||
socket.set_hide(True)
|
||||
socket.subtype = "COLOR"
|
||||
self.add_float_vector_output("Color").subtype = "COLOR"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Color"].python_value = self.inputs["Color"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "use_four")
|
||||
self.inputs["Color"].draw_socket(context, layout, self, "")
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_FloatVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_FloatVectorNode"
|
||||
bl_label = "Float Vector"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_vector_input("Float").set_hide(True)
|
||||
self.add_float_vector_output("Float")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Float"].python_value = self.inputs["Float"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Float"], "size")
|
||||
self.inputs["Float"].draw_socket(context, layout, self, "Value")
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_FloatNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_FloatNode"
|
||||
bl_label = "Float"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("Float").set_hide(True)
|
||||
self.add_float_output("Float")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Float"].python_value = self.inputs["Float"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Float"], "default_value", text="")
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IntegerVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IntegerVectorNode"
|
||||
bl_label = "Integer Vector"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_integer_vector_input("Integer").set_hide(True)
|
||||
self.add_integer_vector_output("Integer")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Integer"].python_value = self.inputs["Integer"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Integer"], "size")
|
||||
self.inputs["Integer"].draw_socket(context, layout, self, "Value")
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IntegerNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IntegerNode"
|
||||
bl_label = "Integer"
|
||||
node_color = "INTEGER"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_integer_input("Integer").set_hide(True)
|
||||
self.add_integer_output("Integer")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Integer"].python_value = self.inputs["Integer"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["Integer"], "default_value", text="")
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ListNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ListNode"
|
||||
bl_label = "List"
|
||||
node_color = "LIST"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_dynamic_data_input("Data")
|
||||
self.add_list_output("List")
|
||||
|
||||
def evaluate(self, context):
|
||||
items = [inp.python_value if inp.is_linked else None for inp in self.inputs[:-1]]
|
||||
items = filter(lambda item: item != None, items)
|
||||
self.outputs["List"].python_value = f"[{', '.join(items)}]"
|
||||
@@ -0,0 +1,12 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_NoneNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_NoneNode"
|
||||
bl_label = "None"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_data_output("None")
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_StringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_StringNode"
|
||||
bl_label = "String"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String").set_hide(True)
|
||||
self.add_string_output("String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["String"].python_value = self.inputs["String"].python_value
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self.inputs["String"], "default_value", text="")
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BMeshDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BMeshDataNode"
|
||||
bl_label = "BMesh Object Data"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("BMesh")
|
||||
self.add_collection_property_output("Vertices")
|
||||
self.add_collection_property_output("Faces")
|
||||
self.add_collection_property_output("Edges")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["BMesh"].is_linked:
|
||||
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh'].python_value}.verts"
|
||||
self.outputs["Faces"].python_value = f"{self.inputs['BMesh'].python_value}.faces"
|
||||
self.outputs["Edges"].python_value = f"{self.inputs['BMesh'].python_value}.edges"
|
||||
else:
|
||||
self.outputs["Vertices"].reset_value()
|
||||
self.outputs["Faces"].reset_value()
|
||||
self.outputs["Edges"].reset_value()
|
||||
@@ -0,0 +1,58 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BMeshEdgeDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BMeshEdgeDataNode"
|
||||
bl_label = "BMesh Edge Data"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def update_with_transforms(self, context):
|
||||
self.inputs["Object"].set_hide(not self.with_transforms)
|
||||
self._evaluate(context)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("BMesh Edge")
|
||||
self.add_property_input("Object").set_hide(True)
|
||||
self.add_collection_property_output("Vertices")
|
||||
self.add_collection_property_output("Faces")
|
||||
self.add_boolean_output("Selected")
|
||||
self.add_boolean_output("Hidden")
|
||||
self.add_boolean_output("Smooth")
|
||||
self.add_integer_output("Index")
|
||||
self.add_boolean_output("Is Manifold")
|
||||
self.add_boolean_output("Is Boundary")
|
||||
self.add_boolean_output("Is Contiguous")
|
||||
self.add_boolean_output("Is Convex")
|
||||
self.add_boolean_output("Is Wire")
|
||||
self.add_boolean_output("Is Seam")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["BMesh Edge"].is_linked:
|
||||
self.outputs["Faces"].python_value = f"{self.inputs['BMesh Edge'].python_value}.link_faces"
|
||||
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh Edge'].python_value}.verts"
|
||||
self.outputs["Is Boundary"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_boundary"
|
||||
self.outputs["Is Contiguous"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_contiguous"
|
||||
self.outputs["Is Convex"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_convex"
|
||||
self.outputs["Is Wire"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_wire"
|
||||
self.outputs["Is Seam"].python_value = f"{self.inputs['BMesh Edge'].python_value}.seam"
|
||||
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Edge'].python_value}.select"
|
||||
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Edge'].python_value}.hide"
|
||||
self.outputs["Smooth"].python_value = f"{self.inputs['BMesh Edge'].python_value}.smooth"
|
||||
self.outputs["Index"].python_value = f"{self.inputs['BMesh Edge'].python_value}.index"
|
||||
self.outputs["Is Manifold"].python_value = f"{self.inputs['BMesh Edge'].python_value}.is_manifold"
|
||||
else:
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Selected"].reset_value()
|
||||
self.outputs["Hidden"].reset_value()
|
||||
self.outputs["Smooth"].reset_value()
|
||||
self.outputs["Is Boundary"].reset_value()
|
||||
self.outputs["Is Contiguous"].reset_value()
|
||||
self.outputs["Is Convex"].reset_value()
|
||||
self.outputs["Is Wire"].reset_value()
|
||||
self.outputs["Is Seam"].reset_value()
|
||||
self.outputs["Vertices"].reset_value()
|
||||
self.outputs["Faces"].reset_value()
|
||||
self.outputs["Is Manifold"].reset_value()
|
||||
@@ -0,0 +1,48 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BMeshFaceDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BMeshFaceDataNode"
|
||||
bl_label = "BMesh Face Data"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("BMesh Face")
|
||||
self.add_property_input("Object").set_hide(True)
|
||||
self.add_collection_property_output("Edges")
|
||||
self.add_collection_property_output("Vertices")
|
||||
self.add_float_vector_output("Center")
|
||||
self.add_float_vector_output("Center Weighted")
|
||||
self.add_float_vector_output("Normal")
|
||||
self.add_boolean_output("Selected")
|
||||
self.add_boolean_output("Hidden")
|
||||
self.add_boolean_output("Smooth")
|
||||
self.add_integer_output("Index")
|
||||
self.add_integer_output("Material Index")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["BMesh Face"].is_linked:
|
||||
self.outputs["Edges"].python_value = f"{self.inputs['BMesh Face'].python_value}.edges"
|
||||
self.outputs["Vertices"].python_value = f"{self.inputs['BMesh Face'].python_value}.verts"
|
||||
self.outputs["Center"].python_value = f"{self.inputs['BMesh Face'].python_value}.calc_center_median()"
|
||||
self.outputs["Center Weighted"].python_value = f"{self.inputs['BMesh Face'].python_value}.calc_center_median_weighted()"
|
||||
self.outputs["Normal"].python_value = f"{self.inputs['BMesh Face'].python_value}.normal"
|
||||
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Face'].python_value}.select"
|
||||
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Face'].python_value}.hide"
|
||||
self.outputs["Smooth"].python_value = f"{self.inputs['BMesh Face'].python_value}.smooth"
|
||||
self.outputs["Index"].python_value = f"{self.inputs['BMesh Face'].python_value}.index"
|
||||
self.outputs["Material Index"].python_value = f"{self.inputs['BMesh Face'].python_value}.material_index"
|
||||
else:
|
||||
self.outputs["Normal"].reset_value()
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Selected"].reset_value()
|
||||
self.outputs["Hidden"].reset_value()
|
||||
self.outputs["Center"].reset_value()
|
||||
self.outputs["Center Weighted"].reset_value()
|
||||
self.outputs["Smooth"].reset_value()
|
||||
self.outputs["Edges"].reset_value()
|
||||
self.outputs["Vertices"].reset_value()
|
||||
self.outputs["Material Index"].reset_value()
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BMeshVertexDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BMeshVertexDataNode"
|
||||
bl_label = "BMesh Vertex Data"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def update_with_transforms(self, context):
|
||||
self.inputs["Object"].set_hide(not self.with_transforms)
|
||||
self._evaluate(context)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("BMesh Vertex")
|
||||
self.add_property_input("Object").set_hide(True)
|
||||
self.add_float_vector_output("Location")
|
||||
self.add_float_vector_output("Normal")
|
||||
self.add_boolean_output("Selected")
|
||||
self.add_boolean_output("Hidden")
|
||||
self.add_integer_output("Index")
|
||||
self.add_boolean_output("Is Boundary")
|
||||
self.add_boolean_output("Is Manifold")
|
||||
self.add_boolean_output("Is Wire")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs["BMesh Vertex"].is_linked:
|
||||
self.outputs["Location"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.co"
|
||||
self.outputs["Normal"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.normal"
|
||||
self.outputs["Selected"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.select"
|
||||
self.outputs["Hidden"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.hide"
|
||||
self.outputs["Index"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.index"
|
||||
self.outputs["Is Boundary"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_boundary"
|
||||
self.outputs["Is Manifold"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_manifold"
|
||||
self.outputs["Is Wire"].python_value = f"{self.inputs['BMesh Vertex'].python_value}.is_wire"
|
||||
else:
|
||||
self.outputs["Location"].reset_value()
|
||||
self.outputs["Normal"].reset_value()
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Selected"].reset_value()
|
||||
self.outputs["Hidden"].reset_value()
|
||||
self.outputs["Index"].reset_value()
|
||||
self.outputs["Is Boundary"].reset_value()
|
||||
self.outputs["Is Manifold"].reset_value()
|
||||
self.outputs["Is Wire"].reset_value()
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CreateLineLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CreateLineLocationsNode"
|
||||
bl_label = "Create Line"
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def update_use3d(self, context):
|
||||
for input in self.inputs:
|
||||
if input.bl_label == "Float Vector" and input.subtype == "NONE":
|
||||
input.size = 3 if self.use_3d else 2
|
||||
self._evaluate(context)
|
||||
|
||||
use_3d: bpy.props.BoolProperty(name="Use 3D",
|
||||
description="Whether to use 3D or 2D coordinates",
|
||||
default=False,
|
||||
update=update_use3d)
|
||||
|
||||
def on_create(self, context):
|
||||
inp = self.add_float_vector_input("Point 1")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 0
|
||||
inp.default_value[1] = 0
|
||||
inp.default_value[2] = 0
|
||||
|
||||
inp = self.add_float_vector_input("Point 2")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 0
|
||||
inp.default_value[1] = 1
|
||||
inp.default_value[2] = 1
|
||||
|
||||
self.add_list_output("Line")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "use_3d", text="Use 3D")
|
||||
|
||||
def evaluate(self, context):
|
||||
p1 = self.inputs["Point 1"].python_value
|
||||
p2 = self.inputs["Point 2"].python_value
|
||||
|
||||
self.outputs[0].python_value = f"[{p1}, {p2}]"
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CreateQuadLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CreateQuadLocationsNode"
|
||||
bl_label = "Create Quad"
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def update_use3d(self, context):
|
||||
for input in self.inputs:
|
||||
if input.bl_label == "Float Vector" and input.subtype == "NONE":
|
||||
input.size = 3 if self.use_3d else 2
|
||||
self._evaluate(context)
|
||||
|
||||
use_3d: bpy.props.BoolProperty(name="Use 3D",
|
||||
description="Whether to use 3D or 2D coordinates",
|
||||
default=False,
|
||||
update=update_use3d)
|
||||
|
||||
def on_create(self, context):
|
||||
inp = self.add_float_vector_input("Bottom Left")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 0
|
||||
inp.default_value[1] = 0
|
||||
inp.default_value[2] = 0
|
||||
|
||||
inp = self.add_float_vector_input("Bottom Right")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 1
|
||||
inp.default_value[1] = 0
|
||||
inp.default_value[2] = 0
|
||||
|
||||
inp = self.add_float_vector_input("Top Right")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 1
|
||||
inp.default_value[1] = 1
|
||||
inp.default_value[2] = 1
|
||||
|
||||
inp = self.add_float_vector_input("Top Left")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 0
|
||||
inp.default_value[1] = 1
|
||||
inp.default_value[2] = 1
|
||||
|
||||
self.add_list_output("Quad")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "use_3d", text="Use 3D")
|
||||
|
||||
def evaluate(self, context):
|
||||
bl = self.inputs["Bottom Left"].python_value
|
||||
br = self.inputs["Bottom Right"].python_value
|
||||
tl = self.inputs["Top Left"].python_value
|
||||
tr = self.inputs["Top Right"].python_value
|
||||
|
||||
self.outputs[0].python_value = f"[{bl}, {br}, {tl}, {tr}]"
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CreateTriangleLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CreateTriangleLocationsNode"
|
||||
bl_label = "Create Triangle"
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def update_use3d(self, context):
|
||||
for input in self.inputs:
|
||||
if input.bl_label == "Float Vector" and input.subtype == "NONE":
|
||||
input.size = 3 if self.use_3d else 2
|
||||
self._evaluate(context)
|
||||
|
||||
use_3d: bpy.props.BoolProperty(name="Use 3D",
|
||||
description="Whether to use 3D or 2D coordinates",
|
||||
default=False,
|
||||
update=update_use3d)
|
||||
|
||||
def on_create(self, context):
|
||||
inp = self.add_float_vector_input("Corner 1")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 0
|
||||
inp.default_value[1] = 0
|
||||
inp.default_value[2] = 0
|
||||
|
||||
inp = self.add_float_vector_input("Corner 2")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 1
|
||||
inp.default_value[1] = 0
|
||||
inp.default_value[2] = 0
|
||||
|
||||
inp = self.add_float_vector_input("Corner 3")
|
||||
inp.size = 2
|
||||
inp.default_value[0] = 1
|
||||
inp.default_value[1] = 1
|
||||
inp.default_value[2] = 1
|
||||
|
||||
self.add_list_output("Triangle")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "use_3d", text="Use 3D")
|
||||
|
||||
def evaluate(self, context):
|
||||
c1 = self.inputs["Corner 1"].python_value
|
||||
c2 = self.inputs["Corner 2"].python_value
|
||||
c3 = self.inputs["Corner 3"].python_value
|
||||
|
||||
self.outputs[0].python_value = f"[{c1}, {c2}, {c3}]"
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_FacesToVertexLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_FacesToVertexLocationsNode"
|
||||
bl_label = "BMesh Faces To Vertex Locations"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_collection_property_input("Faces")
|
||||
self.add_list_output("All Locations")
|
||||
self.add_list_output("Quad Locations Only")
|
||||
self.add_list_output("Triangle Locations Only")
|
||||
self.add_list_output("Ngon Locations Only")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = f"""
|
||||
def get_bmesh_vertex_locations(faces):
|
||||
locations = []
|
||||
for face in faces:
|
||||
face_locations = []
|
||||
for vert in face.verts:
|
||||
face_locations.append(vert.co)
|
||||
locations.append(face_locations)
|
||||
return locations
|
||||
|
||||
def get_bmesh_vertex_locations_quads(faces):
|
||||
locations = get_bmesh_vertex_locations(faces)
|
||||
return [loc for loc in locations if len(loc) == 4]
|
||||
|
||||
def get_bmesh_vertex_locations_triangles(faces):
|
||||
locations = get_bmesh_vertex_locations(faces)
|
||||
return [loc for loc in locations if len(loc) == 3]
|
||||
|
||||
def get_bmesh_vertex_locations_ngons(faces):
|
||||
locations = get_bmesh_vertex_locations(faces)
|
||||
return [loc for loc in locations if len(loc) > 4]
|
||||
"""
|
||||
|
||||
self.outputs["All Locations"].python_value = f"get_bmesh_vertex_locations({self.inputs['Faces'].python_value})"
|
||||
self.outputs["Quad Locations Only"].python_value = f"get_bmesh_vertex_locations_quads({self.inputs['Faces'].python_value})"
|
||||
self.outputs["Triangle Locations Only"].python_value = f"get_bmesh_vertex_locations_triangles({self.inputs['Faces'].python_value})"
|
||||
self.outputs["Ngon Locations Only"].python_value = f"get_bmesh_vertex_locations_ngons({self.inputs['Faces'].python_value})"
|
||||
@@ -0,0 +1,43 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ObjectToBMeshNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ObjectToBMeshNode"
|
||||
bl_label = "Object To BMesh"
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_input()
|
||||
self.add_property_input("Object")
|
||||
self.add_boolean_input("Use Edit Mode").default_value = True
|
||||
self.add_boolean_input("Use Evaluated Mesh")
|
||||
self.add_boolean_input("Use Object Transforms").default_value = True
|
||||
self.add_execute_output()
|
||||
self.add_property_output("BMesh")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"import bmesh"
|
||||
|
||||
self.code = f"""
|
||||
bm_{self.static_uid} = bmesh.new()
|
||||
if {self.inputs["Object"].python_value}:
|
||||
if {self.inputs["Object"].python_value}.mode == 'EDIT' and {self.inputs["Use Edit Mode"].python_value}:
|
||||
bm_{self.static_uid} = bmesh.from_edit_mesh({self.inputs["Object"].python_value}.data)
|
||||
else:
|
||||
if {self.inputs["Use Evaluated Mesh"].python_value}:
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
bm_{self.static_uid}.from_mesh({self.inputs["Object"].python_value}.evaluated_get(dg).to_mesh())
|
||||
else:
|
||||
bm_{self.static_uid}.from_mesh({self.inputs["Object"].python_value}.data)
|
||||
if {self.inputs["Use Object Transforms"].python_value}:
|
||||
bm_{self.static_uid}.transform({self.inputs["Object"].python_value}.matrix_world)
|
||||
bm_{self.static_uid}.verts.ensure_lookup_table()
|
||||
bm_{self.static_uid}.faces.ensure_lookup_table()
|
||||
bm_{self.static_uid}.edges.ensure_lookup_table()
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
self.outputs["BMesh"].python_value = f"bm_{self.static_uid}"
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_NgonToTriangleLocationsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_NgonToTriangleLocationsNode"
|
||||
bl_label = "Ngon To Triangle Locations"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Ngon")
|
||||
self.add_list_output("Triangle Locations")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = """
|
||||
def ngon_to_triangle_locations(ngon):
|
||||
bm = bmesh.new()
|
||||
for v in ngon.verts:
|
||||
bm.verts.new(v.co)
|
||||
bm.faces.new(bm.verts)
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
bm.verts.ensure_lookup_table()
|
||||
new_faces = []
|
||||
for f in bm.faces:
|
||||
vert_locations = []
|
||||
for v in f.verts:
|
||||
vert_locations.append(tuple(v.co))
|
||||
new_faces.append(vert_locations)
|
||||
return new_faces
|
||||
"""
|
||||
|
||||
self.outputs["Triangle Locations"].python_value = f"ngon_to_triangle_locations({self.inputs['Ngon'].python_value})"
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_TriangulateBmeshNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_TriangulateBmeshNode"
|
||||
bl_label = "Triangulate BMesh"
|
||||
node_color = "PROGRAM"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_execute_input()
|
||||
self.add_property_input("BMesh")
|
||||
self.add_execute_output()
|
||||
self.add_property_output("BMesh")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code = f"""
|
||||
bm_{self.static_uid} = {self.inputs["BMesh"].python_value}.copy()
|
||||
bmesh.ops.triangulate(bm_{self.static_uid}, faces=bm_{self.static_uid}.faces)
|
||||
bm_{self.static_uid}.verts.ensure_lookup_table()
|
||||
bm_{self.static_uid}.faces.ensure_lookup_table()
|
||||
bm_{self.static_uid}.edges.ensure_lookup_table()
|
||||
{self.indent(self.outputs[0].python_value, 3)}
|
||||
"""
|
||||
|
||||
self.outputs["BMesh"].python_value = f"bm_{self.static_uid}"
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_GetEditSelectNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_GetEditSelectNode"
|
||||
bl_label = "Get Edit Select Mode"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_output("Vertex")
|
||||
self.add_boolean_output("Edge")
|
||||
self.add_boolean_output("Face")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"bpy.context.tool_settings.mesh_select_mode[0]"
|
||||
self.outputs[1].python_value = f"bpy.context.tool_settings.mesh_select_mode[1]"
|
||||
self.outputs[2].python_value = f"bpy.context.tool_settings.mesh_select_mode[2]"
|
||||
@@ -0,0 +1,111 @@
|
||||
import bpy
|
||||
import os
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IconNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IconNode"
|
||||
bl_label = "Icon"
|
||||
node_color = "ICON"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
inp = self.add_string_input("Image Path")
|
||||
inp.subtype = "FILE_PATH"
|
||||
inp.set_hide(True)
|
||||
self.add_icon_output("Icon")
|
||||
|
||||
|
||||
def update_icon_source(self, context):
|
||||
if self.icon_source == "PATH":
|
||||
self.inputs[0].set_hide(False)
|
||||
else:
|
||||
self.inputs[0].set_hide(True)
|
||||
self._evaluate(context)
|
||||
|
||||
icon_source: bpy.props.EnumProperty(name="Icon Source",
|
||||
description="The source of the icons",
|
||||
items=[("BLENDER","Blender","Blender",0),
|
||||
("CUSTOM","Image","Image",1),
|
||||
("PATH","Path","Path",2)],
|
||||
update=update_icon_source)
|
||||
|
||||
|
||||
icon: bpy.props.IntProperty(name="Value", description="Value of this socket", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def update_icon_file(self, context):
|
||||
if self.icon_file:
|
||||
self.icon_file.use_fake_user = True
|
||||
self.icon_file.preview_ensure()
|
||||
self._evaluate(context)
|
||||
|
||||
icon_file: bpy.props.PointerProperty(type=bpy.types.Image,
|
||||
name="Image File",
|
||||
description="The image you want to use as an icon",
|
||||
update=update_icon_file)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.icon_source == "BLENDER":
|
||||
self.outputs["Icon"].python_value = f"{self.icon}"
|
||||
elif self.icon_source == "CUSTOM":
|
||||
if self.icon_file:
|
||||
self.outputs["Icon"].python_value = f"bpy.data.images['{self.icon_file.name}'].preview.icon_id"
|
||||
else:
|
||||
self.outputs["Icon"].python_value = "0"
|
||||
elif self.icon_source == "PATH":
|
||||
self.code_import = "import os"
|
||||
self.code_imperative = f"""
|
||||
def load_preview_icon(path):
|
||||
global _icons
|
||||
if not path in _icons:
|
||||
if os.path.exists(path):
|
||||
_icons.load(path, path, "IMAGE")
|
||||
else:
|
||||
return 0
|
||||
return _icons[path].icon_id
|
||||
"""
|
||||
self.outputs["Icon"].python_value = f"load_preview_icon({self.inputs[0].python_value})"
|
||||
|
||||
|
||||
def evaluate_export(self, context):
|
||||
if self.icon_source == "BLENDER":
|
||||
self.outputs["Icon"].python_value = f"{self.icon}"
|
||||
elif self.icon_source == "CUSTOM":
|
||||
if self.icon_file:
|
||||
self.outputs["Icon"].python_value = f"_icons['{self.icon_file.name}'].icon_id"
|
||||
self.code_import = "import os"
|
||||
self.code_register = f"""
|
||||
if not '{self.icon_file.name}' in _icons: _icons.load('{self.icon_file.name}', os.path.join(os.path.dirname(__file__), 'icons', '{self.icon_file.name}'), "IMAGE")
|
||||
"""
|
||||
else:
|
||||
self.outputs["Icon"].python_value = "0"
|
||||
elif self.icon_source == "PATH":
|
||||
self.code_import = "import os"
|
||||
self.code_imperative = f"""
|
||||
def load_preview_icon(path):
|
||||
global _icons
|
||||
if not path in _icons:
|
||||
if os.path.exists(path):
|
||||
_icons.load(path, path, "IMAGE")
|
||||
else:
|
||||
return 0
|
||||
return _icons[path].icon_id
|
||||
"""
|
||||
self.outputs["Icon"].python_value = f"load_preview_icon({self.inputs[0].python_value})"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
row = layout.row()
|
||||
row.scale_y = 1.2
|
||||
row.prop(self,"icon_source",expand=True)
|
||||
|
||||
if self.icon_source == "BLENDER":
|
||||
op = layout.operator("sn.select_icon", text="Choose Icon", icon_value=self.icon)
|
||||
op.icon_data_path = f"bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}']"
|
||||
elif self.icon_source == "CUSTOM":
|
||||
layout.template_ID(self, "icon_file", new="image.new", open="image.open", live_icon=True)
|
||||
if self.icon_file and not self.icon_file.filepath:
|
||||
layout.label(text="Image not saved!", icon="ERROR")
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IsExportNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IsExportNode"
|
||||
bl_label = "Is Export"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_output("Is Export")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"False"
|
||||
|
||||
def evaluate_export(self, context):
|
||||
self.outputs[0].python_value = f"True"
|
||||
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_InModeNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_InModeNode"
|
||||
bl_label = "In Mode"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def get_modes(self,context):
|
||||
items = []
|
||||
modes = ["EDIT_MESH", "EDIT_CURVE", "EDIT_SURFACE", "EDIT_TEXT", "EDIT_ARMATURE",
|
||||
"EDIT_METABALL", "EDIT_LATTICE", "POSE", "SCULPT", "PAINT_WEIGHT", "PAINT_VERTEX",
|
||||
"PAINT_TEXTURE", "PARTICLE", "OBJECT", "PAINT_GPENCIL", "EDIT_GPENCIL",
|
||||
"SCULPT_GPENCIL" "WEIGHT_GPENCIL", "VERTEX_GPENCIL", "SCULPT_CURVES"]
|
||||
for mode in modes:
|
||||
items.append((mode,mode.replace("_"," ").title(),mode))
|
||||
return items
|
||||
|
||||
modes: bpy.props.EnumProperty(items=get_modes,
|
||||
update=SN_ScriptingBaseNode._evaluate,
|
||||
name="Mode",
|
||||
description="The mode which the active mode is compared to")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "modes", text="")
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_output("In Mode")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"'{self.modes}'==bpy.context.mode"
|
||||
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
from ..Input.Node_Idname import NodeType
|
||||
|
||||
class SN_NodeIsIdname(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_NodeIsIdname"
|
||||
bl_label = "Node Is Idname"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
|
||||
nodes: bpy.props.CollectionProperty(type=NodeType)
|
||||
|
||||
node: bpy.props.StringProperty(name="Node",
|
||||
description="The node to get the type for",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Node")
|
||||
self.add_boolean_output("Is ID Name")
|
||||
|
||||
# load internal nodes
|
||||
for name in dir(bpy.types):
|
||||
cls = getattr(bpy.types, name)
|
||||
if hasattr(cls, "bl_rna") and cls.bl_rna.base and "Node" in cls.bl_rna.base.bl_rna.name:
|
||||
item = self.nodes.add()
|
||||
item.name = f"{cls.bl_rna.name} ({cls.bl_rna.base.bl_rna.name})"
|
||||
item.identifier = cls.bl_rna.identifier
|
||||
|
||||
# load python nodes
|
||||
for cls in bpy.types.Node.__subclasses__():
|
||||
item = self.nodes.add()
|
||||
item.name = f"{cls.bl_rna.name} (Python)"
|
||||
item.identifier = cls.bl_rna.identifier
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.node:
|
||||
self.outputs[0].python_value = f"{self.inputs[0].python_value}.bl_rna.identifier == '{self.nodes[self.node].identifier}'"
|
||||
else:
|
||||
self.outputs[0].python_value = "False"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop_search(self, "node", self, "nodes", text="")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_IsObjectType(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IsObjectType"
|
||||
bl_label = "Is Object Type"
|
||||
node_color = "PROPERTY"
|
||||
|
||||
|
||||
types: bpy.props.EnumProperty(items=[('MESH', 'Mesh', ''), ('CURVE', 'Curve', ''), ('SURFACE', 'Surface', ''), ('META', 'Metaball', ''), ('FONT', 'Text', ''), ('HAIR', 'Hair', ''), ('POINTCLOUD', 'Point Cloud', ''), ('VOLUME', 'Volume', ''), ('GPENCIL', 'Grease Pencil', ''), ('ARMATURE', 'Armature', ''), ('LATTICE', 'Lattice', ''), ('EMPTY', 'Empty', ''), ('LIGHT', 'Light', ''), ('LIGHT_PROBE', 'Light Probe', ''), ('CAMERA', 'Camera', ''), ('SPEAKER', 'Speaker', '')],
|
||||
update=SN_ScriptingBaseNode._evaluate,
|
||||
name="Type",
|
||||
description="The type which the object is compared to")
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "types", text="")
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Object")
|
||||
self.add_boolean_output("Is Type")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"{self.inputs[0].python_value}.type == '{self.types}'"
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_NamedIconNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_NamedIconNode"
|
||||
bl_label = "Named Icon"
|
||||
node_color = "ICON"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_output("Icon")
|
||||
|
||||
|
||||
def update_selected(self, context):
|
||||
for icon in bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items:
|
||||
if icon.value == self.icon:
|
||||
self.icon_name = icon.name
|
||||
return
|
||||
self.icon_name = "NONE"
|
||||
self["icon"] = 0
|
||||
|
||||
icon: bpy.props.IntProperty(name="Value", description="Value of this socket", update=update_selected)
|
||||
icon_name: bpy.props.StringProperty(default="NONE" ,update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"'{self.icon_name}'"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
op = layout.operator("sn.select_icon", text="Choose Icon", icon_value=self.icon)
|
||||
op.icon_data_path = f"bpy.data.node_groups['{self.node_tree.name}'].nodes['{self.name}']"
|
||||
@@ -0,0 +1,53 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class NodeType(bpy.types.PropertyGroup):
|
||||
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
identifier: bpy.props.StringProperty()
|
||||
|
||||
|
||||
|
||||
class SN_NodeIdnameNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_NodeIdnameNode"
|
||||
bl_label = "Node Idname"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_output("Idname")
|
||||
|
||||
# load internal nodes
|
||||
for name in dir(bpy.types):
|
||||
cls = getattr(bpy.types, name)
|
||||
if hasattr(cls, "bl_rna") and cls.bl_rna.base and "Node" in cls.bl_rna.base.bl_rna.name:
|
||||
item = self.nodes.add()
|
||||
item.name = f"{cls.bl_rna.name} ({cls.bl_rna.base.bl_rna.name})"
|
||||
item.identifier = cls.bl_rna.identifier
|
||||
|
||||
# load python nodes
|
||||
for cls in bpy.types.Node.__subclasses__():
|
||||
item = self.nodes.add()
|
||||
item.name = f"{cls.bl_rna.name} (Python)"
|
||||
item.identifier = cls.bl_rna.identifier
|
||||
|
||||
|
||||
nodes: bpy.props.CollectionProperty(type=NodeType)
|
||||
|
||||
node: bpy.props.StringProperty(name="Node",
|
||||
description="The node to get the type for",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.node:
|
||||
self.outputs["Idname"].python_value = f"'{self.nodes[self.node].identifier}'"
|
||||
else:
|
||||
self.outputs["Idname"].python_value = "''"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop_search(self, "node", self, "nodes", text="")
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RandomColorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RandomColorNode"
|
||||
bl_label = "Random Color"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Use Alpha")
|
||||
inp = self.add_float_input("Fixed Alpha")
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
inp.default_value = 1.0
|
||||
inp = self.add_integer_input("Seed")
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
self.add_float_vector_output("Random Color").subtype = "COLOR"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import random"
|
||||
self.code_imperative = """
|
||||
def random_color(use_alpha, fixed_alpha, seed):
|
||||
random.seed(seed)
|
||||
if use_alpha:
|
||||
return (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1), fixed_alpha if fixed_alpha != None else random.uniform(0, 1))
|
||||
else:
|
||||
return (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))
|
||||
"""
|
||||
self.outputs[0].python_value = f"random_color({self.inputs['Use Alpha'].python_value}, {'None' if self.inputs['Fixed Alpha'].disabled else self.inputs['Fixed Alpha'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
|
||||
@@ -0,0 +1,47 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RandomNumberNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RandomNumberNode"
|
||||
bl_label = "Random Number"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("Minimum")
|
||||
self.add_float_input("Maximum")
|
||||
inp = self.add_integer_input("Seed")
|
||||
inp.can_be_disabled = True
|
||||
inp.disabled = True
|
||||
self.add_integer_output("Random Number")
|
||||
|
||||
def update_num_type(self, context):
|
||||
self.convert_socket(self.outputs[0], self.socket_names[self.number_type])
|
||||
self._evaluate(context)
|
||||
|
||||
number_type: bpy.props.EnumProperty(name="Type",
|
||||
description="Type of number",
|
||||
items=[("Integer", "Integer", "Integer"),
|
||||
("Float", "Float", "Float")],
|
||||
update=update_num_type)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import random"
|
||||
self.code_imperative = """
|
||||
def random_integer(min, max, seed):
|
||||
random.seed(seed)
|
||||
return random.randint(int(min), int(max))
|
||||
|
||||
def random_float(min, max, seed):
|
||||
random.seed(seed)
|
||||
return random.uniform(min, max)
|
||||
"""
|
||||
if self.number_type == "Integer":
|
||||
self.outputs[0].python_value = f"random_integer({self.inputs['Minimum'].python_value}, {self.inputs['Maximum'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
|
||||
else:
|
||||
self.outputs[0].python_value = f"random_float({self.inputs['Minimum'].python_value}, {self.inputs['Maximum'].python_value}, {'None' if self.inputs['Seed'].disabled else self.inputs['Seed'].python_value})"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "number_type")
|
||||
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SceneContextNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SceneContextNode"
|
||||
bl_label = "Scene Context"
|
||||
node_color = "DEFAULT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_output("Active Scene")
|
||||
self.add_property_output("Active Area")
|
||||
self.add_property_output("Active View Layer")
|
||||
self.add_property_output("Active Camera")
|
||||
self.add_property_output("Active Bone")
|
||||
self.add_property_output("Active Pose Bone")
|
||||
self.add_property_output("Preferences")
|
||||
self.add_property_output("Window Manager")
|
||||
self.add_property_output("Window")
|
||||
self.add_property_output("Screen")
|
||||
self.add_string_output("Engine")
|
||||
self.add_string_output("Mode")
|
||||
self.add_string_output("Blend Filepath")
|
||||
self.add_boolean_output("File Is Saved")
|
||||
self.add_boolean_output("File Has Changes")
|
||||
self.add_integer_output("Current Frame")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Active Scene"].python_value = f"bpy.context.scene"
|
||||
self.outputs["Active Area"].python_value = f"bpy.context.area"
|
||||
self.outputs["Active View Layer"].python_value = f"bpy.context.view_layer"
|
||||
self.outputs["Active Camera"].python_value = f"bpy.context.scene.camera"
|
||||
self.outputs["Active Bone"].python_value = f"bpy.context.active_bone"
|
||||
self.outputs["Active Pose Bone"].python_value = f"bpy.context.active_pose_bone"
|
||||
self.outputs["Preferences"].python_value = f"bpy.context.preferences"
|
||||
self.outputs["Window Manager"].python_value = f"bpy.context.window_manager"
|
||||
self.outputs["Window"].python_value = f"bpy.context.window"
|
||||
self.outputs["Screen"].python_value = f"bpy.context.screen"
|
||||
self.outputs["Engine"].python_value = f"bpy.context.engine"
|
||||
self.outputs["Mode"].python_value = f"bpy.context.mode"
|
||||
self.outputs["Blend Filepath"].python_value = f"bpy.data.filepath"
|
||||
self.outputs["File Is Saved"].python_value = f"bpy.data.is_saved"
|
||||
self.outputs["File Has Changes"].python_value = f"bpy.data.is_dirty"
|
||||
self.outputs["Current Frame"].python_value = f"bpy.context.scene.frame_current"
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_TimeNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_TimeNode"
|
||||
bl_label = "Time and Date"
|
||||
node_color = "INTEGER"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_output("Time")
|
||||
self.add_integer_output("Hours")
|
||||
self.add_integer_output("Minutes")
|
||||
self.add_integer_output("Seconds")
|
||||
self.add_integer_output("Milliseconds")
|
||||
self.add_string_output("Date")
|
||||
self.add_integer_output("Year")
|
||||
self.add_integer_output("Month")
|
||||
self.add_integer_output("Day")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = f"from datetime import datetime"
|
||||
self.outputs[0].python_value = "str(datetime.now().time()).split(\".\")[0]"
|
||||
self.outputs[1].python_value = "datetime.now().time().hour"
|
||||
self.outputs[2].python_value = "datetime.now().time().minute"
|
||||
self.outputs[3].python_value = "datetime.now().time().second"
|
||||
self.outputs[4].python_value = "datetime.now().time().microsecond//1000"
|
||||
self.outputs[5].python_value = "str(datetime.now().date())"
|
||||
self.outputs[6].python_value = "datetime.now().date().year"
|
||||
self.outputs[7].python_value = "datetime.now().date().month"
|
||||
self.outputs[8].python_value = "datetime.now().date().day"
|
||||
@@ -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)}
|
||||
"""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user