2025-07-01
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
import os
|
||||
|
||||
|
||||
class SN_OT_ClearConsole(bpy.types.Operator):
|
||||
bl_idname = "sn.clear_console"
|
||||
bl_label = "Clear System Console"
|
||||
bl_description = "This operator clears the system console."
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
if os.name == "nt":
|
||||
os.system("cls")
|
||||
else:
|
||||
os.system("clear")
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,92 @@
|
||||
import bpy
|
||||
import platform
|
||||
|
||||
|
||||
class SN_PT_HeaderSettings(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_HeaderSettings"
|
||||
bl_label = "Settings"
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_region_type = "HEADER"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(context.scene.sn, "insert_sockets")
|
||||
layout.prop(
|
||||
context.preferences.view,
|
||||
"show_tooltips_python",
|
||||
text="Show Python Tooltips",
|
||||
)
|
||||
|
||||
|
||||
def header_prepend(self, context):
|
||||
if (
|
||||
context.space_data.node_tree
|
||||
and context.space_data.node_tree.bl_idname == "ScriptingNodesTree"
|
||||
):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
|
||||
if len(context.space_data.node_tree.nodes) == 0:
|
||||
row.operator(
|
||||
"node.add_node", text="Tutorial", icon="PLAY", depress=True
|
||||
).type = "SN_TutorialNode"
|
||||
|
||||
row.operator("sn.show_data_overview", text="Blend Data", icon="RNA")
|
||||
|
||||
subrow = row.row(align=True)
|
||||
if platform.system() == "Windows":
|
||||
subrow.operator("sn.clear_console", text="", icon="TRASH")
|
||||
subrow.operator("wm.console_toggle", text="Console", icon="CONSOLE")
|
||||
|
||||
row.operator("screen.userpref_show", text="", icon="PREFERENCES")
|
||||
row.popover("SN_PT_HeaderSettings", text="", icon="WINDOW")
|
||||
|
||||
if context.scene.sn.has_update:
|
||||
row.separator()
|
||||
row.operator("sn.update_message", text="Update!", icon="INFO", depress=True)
|
||||
|
||||
|
||||
def header_append(self, context):
|
||||
if (
|
||||
context.space_data.node_tree
|
||||
and context.space_data.node_tree.bl_idname == "ScriptingNodesTree"
|
||||
):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
sub_row = row.row(align=True)
|
||||
col = sub_row.column(align=True)
|
||||
col.scale_x = 1.5
|
||||
col.operator("sn.force_compile", text="", icon="FILE_REFRESH")
|
||||
sub_row.operator("sn.force_unregister", text="", icon="UNLINKED")
|
||||
sub_row = row.row(align=True)
|
||||
sub_row.operator(
|
||||
"wm.url_open",
|
||||
text="",
|
||||
icon_value=bpy.context.scene.sn_icons["discord"].icon_id,
|
||||
).url = "https://discord.com/invite/NK6kyae"
|
||||
sub_row.operator(
|
||||
"wm.url_open", text="", icon="HELP"
|
||||
).url = "https://joshuaknauber.notion.site/Serpens-Documentation-d44c98df6af64d7c9a7925020af11233"
|
||||
ms = round(context.scene.sn.compile_time * 1000, 2)
|
||||
row.label(text=str(ms) + "ms")
|
||||
row.prop(
|
||||
context.scene.sn,
|
||||
"pause_reregister",
|
||||
text="",
|
||||
icon="PLAY" if context.scene.sn.pause_reregister else "PAUSE",
|
||||
)
|
||||
|
||||
|
||||
def node_info_append(self, context):
|
||||
layout = self.layout
|
||||
node = context.space_data.node_tree.nodes.active
|
||||
if getattr(node, "is_sn", False):
|
||||
layout.operator(
|
||||
"wm.url_open", text="Node Documentation", icon="QUESTION"
|
||||
).url = "https://joshuaknauber.notion.site/555efb921f50426ea4d5812f1aa3e462?v=d781b590cc8f47449cb20812deab0cc6"
|
||||
|
||||
|
||||
def footer_status(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
@@ -0,0 +1,154 @@
|
||||
import json
|
||||
import bpy
|
||||
from ...nodes.base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_OT_RemovePreset(bpy.types.Operator):
|
||||
bl_idname = "sn.remove_preset"
|
||||
bl_label = "Remove Preset"
|
||||
bl_description = "Removes this preset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
index: bpy.props.IntProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
prefs = bpy.context.preferences.addons[__name__.partition('.')[ 0]].preferences
|
||||
prefs.presets.remove(self.index)
|
||||
bpy.ops.wm.save_userpref()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_RemovePresets(bpy.types.Operator):
|
||||
bl_idname = "sn.remove_presets"
|
||||
bl_label = "Remove Presets"
|
||||
bl_description = "Remove presets"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
prefs = bpy.context.preferences.addons[__name__.partition('.')[ 0]].preferences
|
||||
layout.label(text="Presets")
|
||||
|
||||
for i, preset in enumerate(prefs.presets):
|
||||
layout.operator("sn.remove_preset", text=f"Remove '{preset.name}'", icon="REMOVE").index = i
|
||||
|
||||
if not len(prefs.presets):
|
||||
layout.label(text="No presets", icon="INFO")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=300)
|
||||
|
||||
|
||||
|
||||
class SN_OT_AddPreset(bpy.types.Operator):
|
||||
bl_idname = "sn.add_preset"
|
||||
bl_label = "Add Preset"
|
||||
bl_description = "Adds the active node as a preset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.node_tree.nodes.active
|
||||
|
||||
def get_save_value(self, data, attr):
|
||||
value = getattr(data, attr)
|
||||
if "bpy_prop_array" in str(type(value)) or "Color" in str(type(value)):
|
||||
return tuple(value)
|
||||
return value
|
||||
|
||||
def execute(self, context):
|
||||
node = context.space_data.node_tree.nodes.active
|
||||
prefs = bpy.context.preferences.addons[__name__.partition('.')[ 0]].preferences
|
||||
|
||||
item = prefs.presets.add()
|
||||
item.name = node.label if node.label else node.name
|
||||
item.idname = node.bl_idname
|
||||
|
||||
data = { "node": {}, "inputs": [], "outputs": [] }
|
||||
|
||||
# get node attributes
|
||||
attributes = [a for a in dir(node) if not callable(getattr(node, a))]
|
||||
data_attributes = ["width", "color", "use_custom_color"]
|
||||
for attr in attributes:
|
||||
if not attr.startswith("__") and not attr.startswith("bl_")\
|
||||
and not attr == "code" and not attr.startswith("code_") and not attr.startswith("ref_")\
|
||||
and not hasattr(SN_ScriptingBaseNode, attr) and not attr in bpy.types.Node.bl_rna.properties.keys()\
|
||||
and not attr in ["active_layout", "disable_evaluation", "skip_export", "static_uid",]:
|
||||
data_attributes.append(attr)
|
||||
|
||||
# save node attributes
|
||||
for attr in data_attributes:
|
||||
data["node"][attr] = self.get_save_value(node, attr)
|
||||
|
||||
socket_save_attributes = ["name", "disabled", "index_type", "data_type", "default_value"]
|
||||
# get input attributes
|
||||
for inp in node.inputs:
|
||||
input_data = {}
|
||||
if not inp.is_program:
|
||||
for attr in socket_save_attributes:
|
||||
if hasattr(inp, attr):
|
||||
input_data[attr] = self.get_save_value(inp, attr)
|
||||
data["inputs"].append(input_data)
|
||||
|
||||
# get output attributes
|
||||
for out in node.outputs:
|
||||
output_data = {}
|
||||
if not out.is_program:
|
||||
for attr in socket_save_attributes:
|
||||
if hasattr(out, attr):
|
||||
output_data[attr] = self.get_save_value(out, attr)
|
||||
data["outputs"].append(output_data)
|
||||
|
||||
item.data = json.dumps(data)
|
||||
|
||||
bpy.ops.wm.save_userpref()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_LoadPreset(bpy.types.Operator):
|
||||
bl_idname = "sn.load_preset"
|
||||
bl_label = "Load Preset"
|
||||
bl_description = "Loads this preset node"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
index: bpy.props.IntProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def get_write_value(self, value):
|
||||
return list(value) if type(value) == list else value
|
||||
|
||||
def execute(self, context):
|
||||
prefs = bpy.context.preferences.addons[__name__.partition('.')[ 0]].preferences
|
||||
preset = prefs.presets[self.index]
|
||||
|
||||
bpy.ops.node.add_node("INVOKE_DEFAULT", type=preset.idname, use_transform=True)
|
||||
node = context.space_data.node_tree.nodes.active
|
||||
|
||||
node.label = preset.name
|
||||
|
||||
data = json.loads(preset.data)
|
||||
# load node data
|
||||
node.disable_evaluation = True
|
||||
for attr in data["node"].keys():
|
||||
setattr(node, attr, self.get_write_value(data["node"][attr]))
|
||||
|
||||
# load input data
|
||||
for i, inp_data in enumerate(data["inputs"]):
|
||||
node.disable_evaluation = True
|
||||
for attr in inp_data.keys():
|
||||
setattr(node.inputs[i], attr, self.get_write_value(inp_data[attr]))
|
||||
|
||||
# load output data
|
||||
for i, out_data in enumerate(data["outputs"]):
|
||||
node.disable_evaluation = True
|
||||
for attr in out_data.keys():
|
||||
setattr(node.outputs[i], attr, self.get_write_value(out_data[attr]))
|
||||
|
||||
node.disable_evaluation = False
|
||||
node._evaluate(context)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,28 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class SN_MT_PresetMenu(bpy.types.Menu):
|
||||
bl_idname = "SN_MT_PresetMenu"
|
||||
bl_label = "Presets"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
prefs = bpy.context.preferences.addons[__name__.partition('.')[ 0]].preferences
|
||||
|
||||
for i, preset in enumerate(prefs.presets):
|
||||
layout.operator("sn.load_preset", text=preset.name).index = i
|
||||
|
||||
if not len(prefs.presets):
|
||||
layout.label(text="No presets", icon="INFO")
|
||||
|
||||
layout.separator()
|
||||
|
||||
node = context.space_data.node_tree.nodes.active
|
||||
if node:
|
||||
layout.operator("sn.add_preset", icon="ADD", text=f"Add '{node.label if node.label else node.name}'")
|
||||
else:
|
||||
layout.operator("sn.add_preset", icon="ADD")
|
||||
|
||||
row = layout.row()
|
||||
row.enabled = len(prefs.presets) > 0
|
||||
row.operator("sn.remove_presets", text="Remove Preset", icon="REMOVE")
|
||||
@@ -0,0 +1,30 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class WM_MT_button_context(bpy.types.Menu):
|
||||
bl_label = ""
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def serpens_right_click(self, context):
|
||||
layout = self.layout
|
||||
|
||||
property_pointer = getattr(context, "button_pointer", None)
|
||||
property_value = getattr(context, "button_prop", None)
|
||||
button_value = getattr(context, "button_operator", None)
|
||||
|
||||
if property_value or button_value:
|
||||
layout.separator()
|
||||
|
||||
if property_value and property_pointer:
|
||||
layout.operator("sn.copy_property", text="Get Serpens Property", icon="FILE_SCRIPT")
|
||||
|
||||
if button_value:
|
||||
layout.operator("sn.copy_operator", text="Get Serpens Operator", icon="FILE_SCRIPT")
|
||||
|
||||
if context:
|
||||
layout.operator("sn.copy_context", text="Copy Context", icon="COPYDOWN")
|
||||
@@ -0,0 +1,147 @@
|
||||
import bpy
|
||||
|
||||
|
||||
REPLACE_NAMES = {
|
||||
"ObjectBase": "bpy.data.objects['Object']", # outliner object hide
|
||||
"LayerCollection": "bpy.context.view_layer.active_layer_collection", # outliner collection hide
|
||||
"SpaceView3D": "bpy.context.screen.areas[0].spaces[0]", # 3d space data
|
||||
"ToolSettings": "bpy.context.scene.tool_settings", # any space tool settings
|
||||
}
|
||||
|
||||
|
||||
class SN_OT_CopyProperty(bpy.types.Operator):
|
||||
bl_idname = "sn.copy_property"
|
||||
bl_label = "Copy Property"
|
||||
bl_description = "Copy the path of this property"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
# get property details
|
||||
property_pointer = getattr(context, "button_pointer", None)
|
||||
property_value = getattr(context, "button_prop", None)
|
||||
|
||||
# copy data path if available
|
||||
if bpy.ops.ui.copy_data_path_button.poll():
|
||||
bpy.ops.ui.copy_data_path_button("INVOKE_DEFAULT", full_path=True)
|
||||
|
||||
context.scene.sn.last_copied_datatype = property_value.type.title()
|
||||
path = context.window_manager.clipboard.replace('"', "'")
|
||||
|
||||
if path and path[-1] == "]" and path[:-1].split("[")[-1].isdigit():
|
||||
path = "[".join(path.split("[")[:-1])
|
||||
context.scene.sn.last_copied_datatype += " Vector"
|
||||
elif getattr(property_value, "subtype", None) == "COLOR":
|
||||
context.scene.sn.last_copied_datatype += " Vector"
|
||||
|
||||
context.window_manager.clipboard = path
|
||||
if property_value.type == "ENUM" and property_value.is_enum_flag:
|
||||
context.scene.sn.last_copied_datatype += " Set"
|
||||
|
||||
context.scene.sn.last_copied_datapath = context.window_manager.clipboard
|
||||
|
||||
self.report({"INFO"}, message="Copied!")
|
||||
return {"FINISHED"}
|
||||
|
||||
# check if replacement is available
|
||||
if property_pointer and property_value:
|
||||
if property_pointer.bl_rna.identifier in REPLACE_NAMES:
|
||||
context.window_manager.clipboard = f"{REPLACE_NAMES[property_pointer.bl_rna.identifier]}.{property_value.identifier}"
|
||||
context.window_manager.clipboard = (
|
||||
context.window_manager.clipboard.replace('"', "'")
|
||||
)
|
||||
context.scene.sn.last_copied_datatype = property_value.type.title()
|
||||
if property_value.type == "ENUM" and property_value.is_enum_flag:
|
||||
context.scene.sn.last_copied_datatype += " Set"
|
||||
context.scene.sn.last_copied_datapath = context.window_manager.clipboard
|
||||
self.report({"INFO"}, message="Copied!")
|
||||
return {"FINISHED"}
|
||||
|
||||
# error when property not available
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
message="We can't copy this property yet! Please use the Blend Data browser to find it!",
|
||||
)
|
||||
print("Serpens Log: ", property_pointer, property_value)
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class SN_OT_CopyOperator(bpy.types.Operator):
|
||||
bl_idname = "sn.copy_operator"
|
||||
bl_label = "Copy Operator"
|
||||
bl_description = "Copy the path of this operator"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def find_ops_path_from_rna(self, rna_identifier):
|
||||
for cat_name in dir(bpy.ops):
|
||||
if cat_name[0].isalpha() and not cat_name == "class":
|
||||
try:
|
||||
cat = eval(f"bpy.ops.{cat_name}")
|
||||
except:
|
||||
cat = None
|
||||
if cat:
|
||||
for op_name in dir(cat):
|
||||
if op_name[0].isalpha():
|
||||
try:
|
||||
op = eval(f"bpy.ops.{cat_name}.{op_name}")
|
||||
except:
|
||||
op = None
|
||||
if op and op.get_rna_type().identifier == rna_identifier:
|
||||
return f"bpy.ops.{cat_name}.{op_name}()"
|
||||
return None
|
||||
|
||||
def execute(self, context):
|
||||
# copy operator if available
|
||||
if bpy.ops.ui.copy_python_command_button.poll():
|
||||
bpy.ops.ui.copy_python_command_button("INVOKE_DEFAULT")
|
||||
self.report({"INFO"}, message="Copied!")
|
||||
return {"FINISHED"}
|
||||
|
||||
# get button details
|
||||
button_value = getattr(context, "button_operator", None)
|
||||
|
||||
# check if value exists
|
||||
if button_value:
|
||||
op_path = self.find_ops_path_from_rna(button_value.bl_rna.identifier)
|
||||
if op_path:
|
||||
context.window_manager.clipboard = op_path
|
||||
self.report({"INFO"}, message="Copied!")
|
||||
return {"FINISHED"}
|
||||
|
||||
# error when button not available
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
message="We can't copy this operator yet! Please report this to the developers!",
|
||||
)
|
||||
print("Serpens Log: ", button_value)
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
def copy_context():
|
||||
context = {}
|
||||
for attribute in dir(bpy.context):
|
||||
if (
|
||||
attribute[0].isalpha()
|
||||
and not attribute in ["property"]
|
||||
and hasattr(bpy.context, attribute)
|
||||
and not callable(getattr(bpy.context, attribute))
|
||||
):
|
||||
context[attribute] = getattr(bpy.context, attribute)
|
||||
return context
|
||||
|
||||
|
||||
class SN_OT_CopyContext(bpy.types.Operator):
|
||||
bl_idname = "sn.copy_context"
|
||||
bl_label = "Copy Context"
|
||||
bl_description = "Copy the context from this area"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
copied = copy_context()
|
||||
context.scene.sn.copied_context.clear()
|
||||
context.scene.sn.copied_context.append(copied)
|
||||
context.scene.sn.hide_preferences = True
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
area.tag_redraw()
|
||||
self.report({"INFO"}, message="Copied and reloaded!")
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,45 @@
|
||||
import bpy
|
||||
import os
|
||||
from ...extensions import snippet_ops
|
||||
|
||||
|
||||
class SN_MT_SnippetsMenu(bpy.types.Menu):
|
||||
bl_idname = "SN_MT_SnippetsMenu"
|
||||
bl_label = "Snippets"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
op = layout.operator("node.add_node", text="Snippet")
|
||||
op.type = "SN_SnippetNode"
|
||||
op.use_transform = True
|
||||
|
||||
no_cat_snippets = False
|
||||
for snippet in snippet_ops.loaded_snippets:
|
||||
if type(snippet) != str:
|
||||
row = layout.row()
|
||||
row.context_pointer_set("snippet", context.scene.sn.snippet_categories[snippet["name"]])
|
||||
row.menu("SN_MT_SnippetMenu", text=snippet["name"])
|
||||
else:
|
||||
no_cat_snippets = True
|
||||
if no_cat_snippets:
|
||||
layout.menu("SN_MT_SnippetMenu", text="Others")
|
||||
|
||||
|
||||
|
||||
class SN_MT_SnippetMenu(bpy.types.Menu):
|
||||
bl_idname = "SN_MT_SnippetMenu"
|
||||
bl_label = "Snippets"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if hasattr(context, "snippet"):
|
||||
for data in snippet_ops.loaded_snippets:
|
||||
if not type(data) == str and data["name"] == context.snippet.name:
|
||||
for snippet in data["snippets"]:
|
||||
layout.operator("sn.add_snippet", text=snippet.split(".")[0]).path = os.path.join(context.snippet.path, snippet)
|
||||
else:
|
||||
path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "extensions", "snippets")
|
||||
for name in snippet_ops.loaded_snippets:
|
||||
if type(name) == str:
|
||||
layout.operator("sn.add_snippet", text=name.split(".")[0]).path = os.path.join(path, name)
|
||||
@@ -0,0 +1,50 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_PT_AddonInfoPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_AddonInfoPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
bl_order = 4
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Addon")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Export-496335f1abe44262885bde330efe59c0"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
layout.prop(sn, "addon_name")
|
||||
layout.prop(sn, "description")
|
||||
layout.prop(sn, "author")
|
||||
layout.prop(sn, "location")
|
||||
layout.prop(sn, "warning")
|
||||
layout.prop(sn, "doc_url")
|
||||
layout.prop(sn, "tracker_url")
|
||||
col = layout.column(align=True)
|
||||
col.prop(sn, "category")
|
||||
if sn.category == "CUSTOM":
|
||||
col.prop(sn, "custom_category", text=" ")
|
||||
layout.prop(sn, "version")
|
||||
layout.prop(sn, "blender")
|
||||
# layout.prop(sn, "multifile")
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1.5
|
||||
col = row.column(align=True)
|
||||
col.operator("sn.export_addon", text="Save Addon", icon="EXPORT")
|
||||
row = col.row()
|
||||
row.scale_y = 0.7
|
||||
row.operator("sn.export_to_marketplace",text="Add to Marketplace",icon_value=bpy.context.scene.sn_icons[ "discord" ].icon_id)
|
||||
@@ -0,0 +1,88 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_PT_AddonSettingsPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_AddonSettingsPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
bl_order = 8
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Settings")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Workflow-Introduction-d235d03178124dc9b752088d75a25192"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
col = layout.column(heading="General")
|
||||
col.prop(sn, "watch_script_changes")
|
||||
col.prop(sn, "show_graph_categories")
|
||||
col.prop(sn, "show_property_categories")
|
||||
col.prop(sn, "overwrite_variable_graph")
|
||||
col.prop(sn, "compile_on_load")
|
||||
|
||||
layout.separator()
|
||||
col = layout.column(heading="Generated Code")
|
||||
col.prop(sn, "debug_code", text="Keep Code File")
|
||||
subcol = col.column()
|
||||
subcol.enabled = sn.debug_code
|
||||
subcol.prop(sn, "remove_duplicate_code")
|
||||
subcol.prop(sn, "format_code")
|
||||
|
||||
layout.separator()
|
||||
col = layout.column(heading="Debug")
|
||||
col.prop(sn, "debug_compile_time", text="Log Compile Time")
|
||||
col.prop(sn, "debug_python_nodes")
|
||||
col.prop(sn, "debug_python_sockets")
|
||||
subrow = col.row()
|
||||
subrow.active = sn.debug_python_nodes or sn.debug_python_sockets
|
||||
subrow.prop(sn, "debug_selected_only")
|
||||
col.prop(sn, "debug_python_properties")
|
||||
|
||||
|
||||
|
||||
class SN_PT_EasyBpyPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_EasyBpyPanel"
|
||||
bl_parent_id = "SN_PT_AddonSettingsPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_order = 0
|
||||
bl_options={"HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Easy BPY")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Easy-BPY-e3a894c7bf4c469389e6caa7640c3219"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
if sn.easy_bpy_path:
|
||||
layout.label(text="Easy BPY installed", icon="CHECKMARK")
|
||||
layout.operator("sn.open_explorer", text="Open Install", icon="FILE_FOLDER").path = sn.easy_bpy_path
|
||||
else:
|
||||
layout.label(text="Easy BPY not installed", icon="CANCEL")
|
||||
layout.operator("wm.url_open", text="Documentation", icon="URL").url = "https://curtisholt.online/easybpy"
|
||||
@@ -0,0 +1,11 @@
|
||||
import bpy
|
||||
import os
|
||||
|
||||
|
||||
|
||||
class SN_UL_AssetList(bpy.types.UIList):
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
row = layout.row()
|
||||
row.label(text="", icon="ASSET_MANAGER" if "." in item.path else "FILEBROWSER")
|
||||
row.prop(item, "name", text="", emboss=False)
|
||||
@@ -0,0 +1,44 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_PT_AssetsPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_AssetsPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_order = 2
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Assets")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Assets-c013c317a1b840b8824a4161da296614"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
# draw asset list
|
||||
row = layout.row(align=False)
|
||||
col = row.column(align=True)
|
||||
col.template_list("SN_UL_AssetList", "Assets", sn, "assets", sn, "asset_index", rows=3)
|
||||
col.operator("sn.add_asset_node", text="Add Node", icon="ADD")
|
||||
col = row.column(align=True)
|
||||
col.operator("sn.add_asset", text="", icon="ADD")
|
||||
col.operator("sn.find_asset", text="", icon="VIEWZOOM")
|
||||
col.operator("sn.remove_asset", text="", icon="REMOVE")
|
||||
|
||||
# draw asset settings
|
||||
if sn.asset_index < len(sn.assets):
|
||||
asset = sn.assets[sn.asset_index]
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
|
||||
col.prop(asset, "path", text="")
|
||||
@@ -0,0 +1,252 @@
|
||||
import bpy
|
||||
from ...settings.data_properties import get_data_items, item_from_path, filter_items, filter_defaults
|
||||
|
||||
|
||||
|
||||
class SN_OT_ShowDataOverview(bpy.types.Operator):
|
||||
bl_idname = "sn.show_data_overview"
|
||||
bl_label = "Show Data Overview"
|
||||
bl_description = "Opens a window that shows a data overview"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
for area in context.screen.areas:
|
||||
if area.type == "PREFERENCES":
|
||||
break
|
||||
else:
|
||||
bpy.ops.screen.userpref_show("INVOKE_DEFAULT")
|
||||
if not context.scene.sn.hide_preferences:
|
||||
context.scene.sn.hide_preferences = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
|
||||
class SN_OT_ExitDataSearch(bpy.types.Operator):
|
||||
bl_idname = "sn.exit_search"
|
||||
bl_label = "Exit Data Search"
|
||||
bl_description = "Exits the data search mode"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sn.hide_preferences = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_ExpandData(bpy.types.Operator):
|
||||
bl_idname = "sn.expand_data"
|
||||
bl_label = "Expand Data"
|
||||
bl_description = "Loads the items for the given item"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
sn = context.scene.sn
|
||||
|
||||
if "bpy.ops" in self.path:
|
||||
item = sn.ops_items["operators"][self.path.split(".")[-1]]
|
||||
item["expanded"] = not item["expanded"]
|
||||
|
||||
else:
|
||||
item = item_from_path(sn.data_items, self.path)
|
||||
item["expanded"] = not item["expanded"]
|
||||
if not item["properties"]:
|
||||
try:
|
||||
item["data"]
|
||||
item["properties"] = get_data_items(self.path, item["data"])
|
||||
except:
|
||||
item["has_properties"] = False
|
||||
item["expanded"] = False
|
||||
self.report({"ERROR"}, message="This data doesn't exist anymore!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_ExpandAllOperators(bpy.types.Operator):
|
||||
bl_idname = "sn.expand_operators"
|
||||
bl_label = "Expand Operators"
|
||||
bl_description = "Expands all operators"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
sn = context.scene.sn
|
||||
if sn.ops_items["operators"]:
|
||||
expand = not list(sn.ops_items["operators"].values())[0]["expanded"]
|
||||
|
||||
for item in sn.ops_items["operators"].values():
|
||||
item["expanded"] = expand
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_FilterData(bpy.types.Operator):
|
||||
bl_idname = "sn.filter_data"
|
||||
bl_label = "Filter Data"
|
||||
bl_description = "Filters this items data"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def update_filters(self, context):
|
||||
item = item_from_path(context.scene.sn.data_items, self.path)
|
||||
item["data_search"] = self.data_search
|
||||
item["data_filter"] = self.data_filter
|
||||
|
||||
data_search: bpy.props.StringProperty(default="",
|
||||
options={"SKIP_SAVE", "HIDDEN", "TEXTEDIT_UPDATE"},
|
||||
update=update_filters)
|
||||
|
||||
data_filter: bpy.props.EnumProperty(name="Type",
|
||||
options={"ENUM_FLAG"},
|
||||
description="Filter by data type",
|
||||
items=filter_items,
|
||||
default=filter_defaults,
|
||||
update=update_filters)
|
||||
|
||||
def update_reset(self, context):
|
||||
if not self.reset:
|
||||
self["reset"] = True
|
||||
self.data_search = ""
|
||||
self.data_filter = filter_defaults
|
||||
|
||||
reset: bpy.props.BoolProperty(name="Reset", default=True,
|
||||
description="Reset the filters",
|
||||
update=update_reset)
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Search:")
|
||||
row = layout.row()
|
||||
row.prop(self, "data_search", text="")
|
||||
row.prop(self, "reset", text="", icon="LOOP_BACK", invert_checkbox=True)
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.prop(self, "data_filter", expand=True)
|
||||
|
||||
def invoke(self, context, event):
|
||||
item = item_from_path(context.scene.sn.data_items, self.path)
|
||||
last_filter = item["data_filter"]
|
||||
last_search = item["data_search"]
|
||||
self.data_filter = last_filter
|
||||
self.data_search = last_search
|
||||
return context.window_manager.invoke_popup(self, width=300)
|
||||
|
||||
|
||||
|
||||
class SN_OT_ResetFilters(bpy.types.Operator):
|
||||
bl_idname = "sn.reset_filters"
|
||||
bl_label = "Reset Filters"
|
||||
bl_description = "Resets these filters"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sn.data_filter = filter_defaults
|
||||
context.scene.sn.data_search = ""
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_ReloadData(bpy.types.Operator):
|
||||
bl_idname = "sn.reload_data"
|
||||
bl_label = "Reload Data"
|
||||
bl_description = "Reloads the listed scene data"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sn.hide_preferences = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_ResetItemFilters(bpy.types.Operator):
|
||||
bl_idname = "sn.reset_item_filters"
|
||||
bl_label = "Reset Item Filters"
|
||||
bl_description = "Reset this items filters"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
item = item_from_path(context.scene.sn.data_items, self.path)
|
||||
try:
|
||||
item["data"]
|
||||
item["data_filter"] = filter_defaults
|
||||
item["data_search"] = ""
|
||||
except:
|
||||
item["has_properties"] = False
|
||||
item["expanded"] = False
|
||||
self.report({"ERROR"}, message="This data doesn't exist anymore!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_ReloadItemData(bpy.types.Operator):
|
||||
bl_idname = "sn.reload_item_data"
|
||||
bl_label = "Reload Item Data"
|
||||
bl_description = "Reloads this items data"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
item = item_from_path(context.scene.sn.data_items, self.path)
|
||||
try:
|
||||
item["data"]
|
||||
item["properties"] = get_data_items(self.path, item["data"])
|
||||
except:
|
||||
item["has_properties"] = False
|
||||
item["expanded"] = False
|
||||
self.report({"ERROR"}, message="This data doesn't exist anymore!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_CopyDataPath(bpy.types.Operator):
|
||||
bl_idname = "sn.copy_data_path"
|
||||
bl_label = "Copy Data Path"
|
||||
bl_description = "Copy data path to paste in a node"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
type: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
required: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
context.window_manager.clipboard = self.path
|
||||
context.scene.sn.last_copied_datapath = self.path
|
||||
context.scene.sn.last_copied_datatype = self.type
|
||||
context.scene.sn.last_copied_required = self.required
|
||||
self.report({"INFO"}, message="Copied!")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_AddToSearch(bpy.types.Operator):
|
||||
bl_idname = "sn.add_to_search"
|
||||
bl_label = "Add To Search"
|
||||
bl_description = "Adds this section to the search"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
section: bpy.props.StringProperty(options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
sn = context.scene.sn
|
||||
if sn.discover_search.startswith(self.section):
|
||||
sn.discover_search = sn.discover_search[len(self.section)+1:]
|
||||
elif sn.discover_search.endswith(self.section):
|
||||
sn.discover_search = sn.discover_search[:-len(self.section)-1]
|
||||
elif f",{self.section}," in sn.discover_search:
|
||||
sn.discover_search = sn.discover_search.replace(f",{self.section},", ",")
|
||||
else:
|
||||
if sn.discover_search:
|
||||
sn.discover_search += f",{self.section}"
|
||||
else:
|
||||
sn.discover_search = self.section
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,284 @@
|
||||
import bpy
|
||||
from ...addon.properties.settings.settings import property_icons
|
||||
from ...settings.data_properties import filter_defaults
|
||||
from ...settings import global_search
|
||||
|
||||
|
||||
|
||||
class SN_PT_navigation_bar(bpy.types.Panel):
|
||||
bl_label = "Preferences Navigation"
|
||||
bl_space_type = 'PREFERENCES'
|
||||
bl_region_type = 'NAVIGATION_BAR'
|
||||
bl_options = {'HIDE_HEADER'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.sn.hide_preferences
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1.4
|
||||
row.alert = True
|
||||
row.operator("sn.exit_search", text="Exit", icon="PANEL_CLOSE")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("wm.url_open", text="How to", icon="QUESTION").url = "https://joshuaknauber.notion.site/Blend-Data-33e9f2ea40f44c2498cb26838662b621"
|
||||
|
||||
layout.separator(factor=2)
|
||||
col = layout.column(align=True)
|
||||
col.scale_y = 1.4
|
||||
col.operator("sn.reload_data", text="Reload", icon="FILE_REFRESH")
|
||||
col.separator()
|
||||
col.prop_enum(sn, "data_category", value="discover", text="Discover (BETA)", icon="WORLD")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Source:")
|
||||
col = layout.column(align=True)
|
||||
col.scale_y = 1.4
|
||||
col.prop_enum(sn, "data_category", value="app")
|
||||
col.prop_enum(sn, "data_category", value="context")
|
||||
col.prop_enum(sn, "data_category", value="data")
|
||||
col.separator()
|
||||
col.prop_enum(sn, "data_category", value="ops")
|
||||
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
row = col.row()
|
||||
row.label(text="Filter Overview:")
|
||||
row.operator("sn.reset_filters", text="", icon="LOOP_BACK", emboss=False)
|
||||
|
||||
if sn.data_category == "discover":
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text=f"Total: {len(global_search.data_flat)} items")
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text=f"Full Matches: {sn.discover_data['full_matches']} items")
|
||||
|
||||
row = col.row()
|
||||
row.scale_y = 1.2
|
||||
if sn.data_category == "discover":
|
||||
row.prop(sn, "discover_search", text="", icon="VIEWZOOM")
|
||||
else:
|
||||
row.prop(sn, "data_search", text="", icon="VIEWZOOM")
|
||||
subcol = col.column()
|
||||
subcol.enabled = sn.data_category != "ops"
|
||||
subcol.prop(sn, "data_filter", expand=True)
|
||||
|
||||
layout.separator()
|
||||
layout.prop(sn, "show_path")
|
||||
if sn.data_category == "discover":
|
||||
layout.prop(sn, "discover_full_only")
|
||||
layout.prop(sn, "discover_show_amount", text="Max Amount")
|
||||
|
||||
|
||||
|
||||
class SN_PT_FilterDataSettings(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_FilterDataSettings"
|
||||
bl_label = "Filter"
|
||||
bl_space_type = "PREFERENCES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if getattr(context, "sn_filter_path", None):
|
||||
row = layout.row()
|
||||
row.prop(context.sn_filter_path, "data_search", text="", icon="VIEWZOOM")
|
||||
col = layout.column()
|
||||
col.prop(context.sn_filter_path, "data_filter")
|
||||
|
||||
|
||||
|
||||
path_notes = {
|
||||
"bpy.context.preferences.keymap": "Copy shortcuts from Context -> Window Manager -> Keyconfigs -> Your Shortcut -> Type",
|
||||
"bpy.context.window_manager.keyconfigs": "To display a shortcut, find it in the User Key Config below, copy its Type property and check Full Shortcut on the node",
|
||||
"bpy.context.active_object": "To set the active object use the active object output on the Objects node or copy the active object from the active view layer",
|
||||
}
|
||||
|
||||
class SN_PT_data_search(bpy.types.Panel):
|
||||
bl_space_type = 'PREFERENCES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_label = "Display"
|
||||
bl_options = {'HIDE_HEADER'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.sn.hide_preferences
|
||||
|
||||
def should_draw(self, item, search_value, filters):
|
||||
if search_value.lower() in item["name"].lower():
|
||||
return item["type"] in filters
|
||||
return False
|
||||
|
||||
def draw_item(self, layout, item):
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
|
||||
if not item["has_properties"]:
|
||||
row.scale_y = 0.75
|
||||
else:
|
||||
op = row.operator("sn.expand_data", text="", icon="TRIA_DOWN" if item["expanded"] else "TRIA_RIGHT", emboss=False)
|
||||
op.path = item["path"]
|
||||
|
||||
subrow = row.row(align=True)
|
||||
has_filters = item["data_search"] != "" or item["data_filter"] != filter_defaults
|
||||
op = subrow.operator("sn.filter_data", text="", icon="FILTER", emboss=has_filters, depress=has_filters)
|
||||
op.path = item["path"]
|
||||
if has_filters:
|
||||
op = subrow.operator("sn.reset_item_filters", text="", icon="LOOP_BACK", depress=True)
|
||||
op.path = item["path"]
|
||||
|
||||
row.label(text=item["name"])
|
||||
|
||||
icon = property_icons[item["type"]] if item["type"] in property_icons else "ERROR"
|
||||
subrow = row.row()
|
||||
subrow.enabled = False
|
||||
subrow.label(text=item["type"], icon=icon)
|
||||
|
||||
if bpy.context.scene.sn.show_path:
|
||||
subrow = row.row()
|
||||
subrow.enabled = False
|
||||
subrow.label(text=item["path"])
|
||||
|
||||
if item["has_properties"]:
|
||||
op = row.operator("sn.reload_item_data", text="", icon="FILE_REFRESH", emboss=False)
|
||||
op.path = item["path"]
|
||||
|
||||
op = row.operator("sn.copy_data_path", text="", icon="COPYDOWN", emboss=False)
|
||||
op.path = item["path"]
|
||||
op.type = item["type"]
|
||||
op.required = item["required"]
|
||||
|
||||
if item["expanded"]:
|
||||
row = box.row()
|
||||
split = row.split(factor=0.015)
|
||||
split.label(text="")
|
||||
col = split.column(align=True)
|
||||
|
||||
if item["path"] in path_notes:
|
||||
box = col.box()
|
||||
box.scale_y = 0.75
|
||||
box.label(text=path_notes[item["path"]], icon="INFO")
|
||||
|
||||
is_empty = True
|
||||
for key in item["properties"].keys():
|
||||
sub_item = item["properties"][key]
|
||||
if self.should_draw(sub_item, item["data_search"], item["data_filter"]):
|
||||
self.draw_item(col, sub_item)
|
||||
if sub_item["clamped"]:
|
||||
box = col.box()
|
||||
box.scale_y = 0.75
|
||||
box.label(text="... Shortened because of too many items", icon="PLUS")
|
||||
col.separator()
|
||||
is_empty = False
|
||||
|
||||
if is_empty:
|
||||
col.label(text="No Items for these filters!", icon="INFO")
|
||||
|
||||
def draw_operator_category(self, layout, category):
|
||||
sn = bpy.context.scene.sn
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
op = row.operator("sn.expand_data", text="", icon="TRIA_DOWN" if sn.ops_items["operators"][category]["expanded"] else "TRIA_RIGHT", emboss=False)
|
||||
op.path = f"bpy.ops.{category}"
|
||||
if category == "sn":
|
||||
row.label(text="Serpens")
|
||||
elif category == "sna":
|
||||
row.label(text="Serpens Addon")
|
||||
else:
|
||||
row.label(text=category.replace("_", " ").title())
|
||||
|
||||
if sn.ops_items["operators"][category]["expanded"]:
|
||||
row = box.row()
|
||||
split = row.split(factor=0.015)
|
||||
split.label(text="")
|
||||
col = split.column(align=True)
|
||||
|
||||
for operator in sn.ops_items["operators"][category]["items"]:
|
||||
if operator["operator"] in sn.ops_items["filtered"][category]:
|
||||
path = f"bpy.ops.{category}.{operator['operator']}()"
|
||||
box = col.box()
|
||||
box.scale_y = 0.75
|
||||
row = box.row()
|
||||
row.label(text=operator["name"])
|
||||
|
||||
if bpy.context.scene.sn.show_path:
|
||||
subrow = row.row()
|
||||
subrow.enabled = False
|
||||
subrow.label(text=path)
|
||||
|
||||
op = row.operator("sn.copy_python_name", text="", icon="COPYDOWN", emboss=False)
|
||||
op.name = path
|
||||
|
||||
def draw_global_search(self, layout):
|
||||
sn = bpy.context.scene.sn
|
||||
|
||||
def is_section_in_search(section):
|
||||
if sn.discover_search.startswith(section) or \
|
||||
sn.discover_search.endswith(section) or \
|
||||
f",{section}," in sn.discover_search:
|
||||
return True
|
||||
return False
|
||||
|
||||
col = layout.column(align=True)
|
||||
for path in bpy.context.scene.sn.discover_data["items"]:
|
||||
item = global_search.data_flat[path]
|
||||
|
||||
box = col.box()
|
||||
row = box.row()
|
||||
|
||||
subrow = row.row(align=True)
|
||||
subrow.alignment = "LEFT"
|
||||
for section in path.split("."):
|
||||
if not section == "bpy":
|
||||
display = section.replace("_", " ").title()
|
||||
if "[" in display and "]" in display:
|
||||
display = display.split("[")[0] + ": " + display.split("[")[1].replace("]", "")
|
||||
subrow.operator("sn.add_to_search", text=display, emboss=not is_section_in_search(section)).section = section
|
||||
|
||||
row.label(text="")
|
||||
|
||||
if bpy.context.scene.sn.show_path:
|
||||
subcol = row.column()
|
||||
subcol.enabled = False
|
||||
subcol.label(text=path)
|
||||
|
||||
row.label(text="")
|
||||
|
||||
op = row.operator("sn.copy_python_name", text="", icon="COPYDOWN", emboss=False)
|
||||
op.name = path
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
col = layout.column(align=True)
|
||||
|
||||
if sn.data_category == "discover":
|
||||
self.draw_global_search(col)
|
||||
|
||||
else:
|
||||
is_empty = True
|
||||
if sn.data_category == "ops":
|
||||
row = col.row()
|
||||
row.label(text="Use property functions instead of operators when possible!", icon="INFO")
|
||||
row.operator("sn.expand_operators", text="", icon="FULLSCREEN_ENTER", emboss=False)
|
||||
col.separator()
|
||||
for cat in sn.ops_items["operators"].keys():
|
||||
if cat in sn.ops_items["filtered"].keys():
|
||||
self.draw_operator_category(col, cat)
|
||||
is_empty = False
|
||||
else:
|
||||
for key in sn.data_items[sn.data_category].keys():
|
||||
item = sn.data_items[sn.data_category][key]
|
||||
if self.should_draw(item, sn.data_search, sn.data_filter):
|
||||
self.draw_item(col, item)
|
||||
is_empty = False
|
||||
|
||||
if is_empty:
|
||||
layout.label(text="No Items for these filters!", icon="INFO")
|
||||
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
import subprocess
|
||||
|
||||
|
||||
|
||||
class SN_OT_OpenExplorer(bpy.types.Operator):
|
||||
bl_idname = "sn.open_explorer"
|
||||
bl_label = "Open Explorer"
|
||||
bl_description = "Open the explorer"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
path: bpy.props.StringProperty({"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
subprocess.Popen(f'explorer /select,"{self.path}"')
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,67 @@
|
||||
import bpy
|
||||
from .graph_ui_list import get_selected_graph, get_selected_graph_offset
|
||||
|
||||
|
||||
class SN_OT_GetPythonName(bpy.types.Operator):
|
||||
bl_idname = "sn.get_python_name"
|
||||
bl_label = "Get Python Name"
|
||||
bl_description = "Get the python name for this element"
|
||||
bl_options = {"REGISTER","UNDO","INTERNAL"}
|
||||
|
||||
to_copy: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.window_manager.clipboard = self.to_copy
|
||||
self.report({"INFO"},message="Python path copied")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SN_PT_GraphPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_GraphPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_order = 0
|
||||
bl_options = {"HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Node Trees")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Workflow-Introduction-d235d03178124dc9b752088d75a25192"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
|
||||
tree = get_selected_graph()
|
||||
before = get_selected_graph_offset(-1)
|
||||
after = get_selected_graph_offset(1)
|
||||
|
||||
row = layout.row(align=False)
|
||||
col = row.column(align=True)
|
||||
|
||||
if sn.show_graph_categories:
|
||||
subrow = col.row(align=True)
|
||||
subrow.prop(sn, "active_graph_category", text="")
|
||||
subrow.operator("sn.edit_graph_categories", text="", icon="GREASEPENCIL")
|
||||
|
||||
col.template_list("SN_UL_GraphList", "Graphs", bpy.data, "node_groups", sn, "node_tree_index", rows=4)
|
||||
|
||||
col = row.column(align=True)
|
||||
col.operator("sn.add_graph", text="", icon="ADD")
|
||||
col.operator("sn.append_graph", text="", icon="APPEND_BLEND")
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = tree != None
|
||||
subrow.operator("sn.remove_graph", text="", icon="REMOVE")
|
||||
col.separator()
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = tree != None and before != None
|
||||
subrow.operator("sn.move_node_tree", text="", icon="TRIA_UP").move_up = True
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = tree != None and after != None
|
||||
subrow.operator("sn.move_node_tree", text="", icon="TRIA_DOWN").move_up = False
|
||||
@@ -0,0 +1,110 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def get_selected_graph():
|
||||
sn = bpy.context.scene.sn
|
||||
if sn.node_tree_index < len(bpy.data.node_groups):
|
||||
ntree = bpy.data.node_groups[sn.node_tree_index]
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
cat_list = list(map(lambda cat: cat.name, sn.graph_categories))
|
||||
|
||||
if sn.active_graph_category == "ALL":
|
||||
return ntree
|
||||
elif sn.active_graph_category == "OTHER":
|
||||
if ntree.category == "OTHER" or not ntree.category or not ntree.category in cat_list:
|
||||
return ntree
|
||||
elif ntree.category == sn.active_graph_category:
|
||||
return ntree
|
||||
return None
|
||||
|
||||
|
||||
filtered_cache = {}
|
||||
|
||||
|
||||
def get_filtered_graphs():
|
||||
sn = bpy.context.scene.sn
|
||||
key = "|".join(list(map(lambda ntree: getattr(ntree, "category", "SHADER") + "," + str(getattr(ntree, "index", 0)), bpy.data.node_groups))) + "|" + bpy.context.scene.sn.active_graph_category
|
||||
if key in filtered_cache:
|
||||
return filtered_cache[key]
|
||||
filtered = []
|
||||
cat_list = list(map(lambda cat: cat.name, sn.graph_categories))
|
||||
for ntree in bpy.data.node_groups:
|
||||
if ntree.bl_idname == "ScriptingNodesTree":
|
||||
if sn.active_graph_category == "ALL":
|
||||
filtered.append(ntree)
|
||||
elif sn.active_graph_category == "OTHER":
|
||||
if ntree.category == "OTHER" or not ntree.category or not ntree.category in cat_list:
|
||||
filtered.append(ntree)
|
||||
elif ntree.category == sn.active_graph_category:
|
||||
filtered.append(ntree)
|
||||
|
||||
filtered = list(sorted(filtered, key=lambda n: n.index))
|
||||
filtered_cache[key] = filtered
|
||||
return filtered
|
||||
|
||||
|
||||
def get_selected_graph_offset(offset):
|
||||
global filtered_cache
|
||||
selected = get_selected_graph()
|
||||
filtered = get_filtered_graphs()
|
||||
if selected:
|
||||
if not selected in filtered:
|
||||
filtered_cache = {}
|
||||
filtered = get_filtered_graphs()
|
||||
i = filtered.index(selected)
|
||||
i += offset
|
||||
if i >= 0 and i < len(filtered):
|
||||
return filtered[i]
|
||||
return None
|
||||
|
||||
|
||||
class SN_UL_GraphList(bpy.types.UIList):
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
row = layout.row()
|
||||
row.label(text="", icon="SCRIPT")
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
if context.scene.sn.show_graph_categories:
|
||||
row.operator("sn.move_graph_category", text="",
|
||||
icon="FORWARD", emboss=False).index = index
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
sn = context.scene.sn
|
||||
node_trees = getattr(data, propname)
|
||||
helper_funcs = bpy.types.UI_UL_list
|
||||
|
||||
_sort = [(idx, frame)
|
||||
for idx, frame in enumerate(bpy.data.node_groups)]
|
||||
flt_neworder = helper_funcs.sort_items_helper(
|
||||
_sort, lambda e: getattr(e[1], "index", 0), False)
|
||||
|
||||
if sn.active_graph_category == "ALL":
|
||||
flt_flags = helper_funcs.filter_items_by_name(
|
||||
"ScriptingNodesTree", self.bitflag_filter_item, node_trees, "bl_idname", reverse=False)
|
||||
|
||||
elif sn.active_graph_category == "OTHER":
|
||||
flt_flags = []
|
||||
cat_list = list(map(lambda cat: cat.name, sn.graph_categories))
|
||||
for tree in node_trees:
|
||||
if not hasattr(tree, "category"):
|
||||
flt_flags.append(0)
|
||||
elif tree.category == "OTHER" or not tree.category or not tree.category in cat_list:
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
|
||||
else:
|
||||
flt_flags = []
|
||||
for tree in node_trees:
|
||||
if not hasattr(tree, "category"):
|
||||
flt_flags.append(0)
|
||||
elif tree.category == sn.active_graph_category:
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
|
||||
for i in range(len(node_trees)):
|
||||
if self.filter_name and not self.filter_name.lower() in node_trees[i].name.lower():
|
||||
flt_flags[i] = 0
|
||||
|
||||
return flt_flags, flt_neworder
|
||||
@@ -0,0 +1,205 @@
|
||||
import bpy
|
||||
from ...extensions import package_ops
|
||||
from ...extensions import snippet_ops
|
||||
|
||||
|
||||
class SN_PT_ExtensionsPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_ExtensionsPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Serpens"
|
||||
bl_order = 6
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
context.space_data.tree_type == "ScriptingNodesTree"
|
||||
and context.space_data.node_tree
|
||||
)
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Node Extensions")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = (
|
||||
"https://joshuaknauber.notion.site/Packages-Snippets-5fc9492b640146a2bcafb269d4a9e876"
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
|
||||
class SN_PT_SnippetsPanel(bpy.types.Panel):
|
||||
bl_parent_id = "SN_PT_ExtensionsPanel"
|
||||
bl_idname = "SN_PT_SnippetsPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Serpens"
|
||||
bl_options = {"HEADER_LAYOUT_EXPAND"}
|
||||
bl_order = 1
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
context.space_data.tree_type == "ScriptingNodesTree"
|
||||
and context.space_data.node_tree
|
||||
)
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Snippets")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.scale_y = 1.1
|
||||
row.operator(
|
||||
"sn.open_preferences", text="Get Snippets", icon="URL"
|
||||
).navigation = "MARKET"
|
||||
|
||||
node = context.space_data.node_tree.nodes.active
|
||||
row = layout.row()
|
||||
row.scale_y = 1.1
|
||||
if (
|
||||
node
|
||||
and node.select
|
||||
and node.bl_idname
|
||||
in ["SN_RunFunctionNode", "SN_RunInterfaceFunctionNodeNew"]
|
||||
):
|
||||
if getattr(node, "ref_SN_FunctionNode", None) or getattr(
|
||||
node, "ref_SN_RunInterfaceFunctionNodeNew", None
|
||||
):
|
||||
op = row.operator(
|
||||
"sn.draw_export_snippet",
|
||||
text="Export Snippet",
|
||||
icon="EXPORT",
|
||||
depress=True,
|
||||
)
|
||||
op.node = node.name
|
||||
op.tree = node.node_tree.name
|
||||
|
||||
else:
|
||||
box = row.box()
|
||||
box.label(
|
||||
text="Select a valid Run Function node to export a snippet",
|
||||
icon="EXPORT",
|
||||
)
|
||||
else:
|
||||
box = row.box()
|
||||
box.label(
|
||||
text="Select Run Function node to export a snippet", icon="EXPORT"
|
||||
)
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1.1
|
||||
row.operator("sn.install_snippet", text="Install Snippets", icon="FILE_FOLDER")
|
||||
|
||||
for i, snippet in enumerate(snippet_ops.loaded_snippets):
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
if type(snippet) == str:
|
||||
row.label(text=snippet.split(".")[0])
|
||||
row.operator(
|
||||
"sn.uninstall_snippet", text="", icon="PANEL_CLOSE", emboss=False
|
||||
).index = i
|
||||
else:
|
||||
cat = context.scene.sn.snippet_categories.get(snippet["name"])
|
||||
row.prop(
|
||||
cat,
|
||||
"expand",
|
||||
text="",
|
||||
emboss=False,
|
||||
icon=(
|
||||
"DISCLOSURE_TRI_DOWN" if cat.expand else "DISCLOSURE_TRI_RIGHT"
|
||||
),
|
||||
)
|
||||
row.label(text=snippet["name"])
|
||||
row.operator(
|
||||
"sn.uninstall_snippet", text="", icon="PANEL_CLOSE", emboss=False
|
||||
).index = i
|
||||
if cat.expand:
|
||||
row = box.row()
|
||||
split = row.split(factor=0.1)
|
||||
split.label(text="")
|
||||
col = split.column(align=True)
|
||||
col.enabled = False
|
||||
for name in snippet["snippets"]:
|
||||
col.label(text=name.split(".")[0])
|
||||
|
||||
if not snippet_ops.loaded_snippets:
|
||||
box = layout.box()
|
||||
box.label(text="No snippets installed!", icon="INFO")
|
||||
|
||||
|
||||
class SN_PT_PackagesPanel(bpy.types.Panel):
|
||||
bl_parent_id = "SN_PT_ExtensionsPanel"
|
||||
bl_idname = "SN_PT_PackagesPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Serpens"
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
bl_order = 2
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
context.space_data.tree_type == "ScriptingNodesTree"
|
||||
and context.space_data.node_tree
|
||||
)
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Packages")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.scale_y = 1.1
|
||||
row.operator(
|
||||
"sn.open_preferences", text="Get Packages", icon="URL"
|
||||
).navigation = "MARKET"
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.1
|
||||
row.operator("sn.install_package", text="Install Package", icon="FILE_FOLDER")
|
||||
row.operator("sn.reload_packages", text="", icon="FILE_REFRESH")
|
||||
|
||||
for i, package in enumerate(package_ops.loaded_packages):
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
row = col.row()
|
||||
row.label(text=package["name"])
|
||||
if package["wiki"]:
|
||||
row.operator("wm.url_open", text="", icon="URL", emboss=False).url = (
|
||||
package["wiki"]
|
||||
)
|
||||
row.operator(
|
||||
"sn.uninstall_package", text="", icon="PANEL_CLOSE", emboss=False
|
||||
).index = i
|
||||
|
||||
if package["description"]:
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text=package["description"])
|
||||
if package["author"]:
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text="By: " + package["author"])
|
||||
if package["version"]:
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text=package["version"])
|
||||
|
||||
if not package_ops.loaded_packages:
|
||||
box = layout.box()
|
||||
box.label(text="No packages installed!", icon="INFO")
|
||||
|
||||
if package_ops.require_reload:
|
||||
row = layout.row()
|
||||
row.alert = True
|
||||
row.label(text="Restart blender to see package!", icon="INFO")
|
||||
@@ -0,0 +1,93 @@
|
||||
import bpy
|
||||
from .property_ui_list import get_selected_property, get_selected_property_offset
|
||||
|
||||
|
||||
|
||||
class SN_PT_PropertyPanel(bpy.types.Panel):
|
||||
bl_idname = "SN_PT_PropertyPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_order = 1
|
||||
bl_options = {"HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Properties")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Properties-6f7567be7bff4256b9bb0311e8d79f9d"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
prop = get_selected_property()
|
||||
before = get_selected_property_offset(-1)
|
||||
after = get_selected_property_offset(1)
|
||||
|
||||
# draw property ui list
|
||||
row = layout.row(align=False)
|
||||
col = row.column(align=True)
|
||||
|
||||
if sn.show_property_categories:
|
||||
subrow = col.row(align=True)
|
||||
subrow.prop(sn, "active_prop_category", text="")
|
||||
subrow.operator("sn.edit_property_categories", text="", icon="GREASEPENCIL")
|
||||
|
||||
col.template_list("SN_UL_PropertyList", "Properties", sn, "properties", sn, "property_index", rows=5)
|
||||
col.operator("sn.add_property_node_popup", text="Add Node", icon="ADD")
|
||||
col = row.column(align=True)
|
||||
col.operator("sn.add_property", text="", icon="ADD")
|
||||
col.operator("sn.find_property", text="", icon="VIEWZOOM")
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = prop != None
|
||||
subrow.operator("sn.remove_property", text="", icon="REMOVE")
|
||||
col.separator()
|
||||
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = prop != None
|
||||
op = subrow.operator("sn.duplicate_property", text="", icon="DUPLICATE")
|
||||
|
||||
col.separator()
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = before != None and prop != None
|
||||
op = subrow.operator("sn.move_property", text="", icon="TRIA_UP")
|
||||
op.move_up = True
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = after != None and prop != None
|
||||
op = subrow.operator("sn.move_property", text="", icon="TRIA_DOWN")
|
||||
op.move_up = False
|
||||
|
||||
|
||||
|
||||
if prop:
|
||||
# draw property debug
|
||||
if sn.debug_python_properties:
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text="Register")
|
||||
for line in prop.register_code.split("\n"):
|
||||
col.label(text=line)
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.label(text="Unregister")
|
||||
for line in prop.unregister_code.split("\n"):
|
||||
col.label(text=line)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
|
||||
# draw general property settings
|
||||
prop.draw(context, col)
|
||||
|
||||
# draw property specific settings
|
||||
col.separator()
|
||||
prop.settings.draw(context, col)
|
||||
@@ -0,0 +1,86 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def get_filtered_properties():
|
||||
filtered = []
|
||||
sn = bpy.context.scene.sn
|
||||
for prop in sn.properties:
|
||||
cat_list = list(map(lambda cat: cat.name, sn.property_categories))
|
||||
if sn.active_prop_category == "ALL":
|
||||
filtered.append(prop)
|
||||
elif sn.active_prop_category == "OTHER":
|
||||
if prop.category == "OTHER" or not prop.category or not prop.category in cat_list:
|
||||
filtered.append(prop)
|
||||
elif prop.category == sn.active_prop_category:
|
||||
filtered.append(prop)
|
||||
return filtered
|
||||
|
||||
def get_selected_property():
|
||||
sn = bpy.context.scene.sn
|
||||
if sn.property_index < len(sn.properties):
|
||||
prop = sn.properties[sn.property_index]
|
||||
cat_list = list(map(lambda cat: cat.name, sn.property_categories))
|
||||
|
||||
if sn.active_prop_category == "ALL":
|
||||
return prop
|
||||
elif sn.active_prop_category == "OTHER":
|
||||
if prop.category == "OTHER" or not prop.category or not prop.category in cat_list:
|
||||
return prop
|
||||
elif prop.category == sn.active_prop_category:
|
||||
return prop
|
||||
return None
|
||||
|
||||
def get_selected_property_offset(offset):
|
||||
selected = get_selected_property()
|
||||
filtered = get_filtered_properties()
|
||||
if selected:
|
||||
i = filtered.index(selected)
|
||||
i += offset
|
||||
if i >= 0 and i < len(filtered):
|
||||
return filtered[i]
|
||||
return None
|
||||
|
||||
|
||||
class SN_UL_PropertyList(bpy.types.UIList):
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
row = layout.row()
|
||||
row.label(text="", icon=item.icon)
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
if not item.property_type == "Group":
|
||||
row.operator("sn.copy_python_name", text="", icon="COPYDOWN", emboss=False).name = item.data_path
|
||||
if context.scene.sn.show_property_categories and item.prop_collection_origin == context.scene.sn:
|
||||
row.operator("sn.move_property_category", text="", icon="FORWARD", emboss=False).index = index
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
sn = context.scene.sn
|
||||
helper_funcs = bpy.types.UI_UL_list
|
||||
|
||||
if sn.active_prop_category == "ALL" or data != context.scene.sn:
|
||||
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, sn.properties, "name", reverse=False)
|
||||
return flt_flags, []
|
||||
|
||||
elif sn.active_prop_category == "OTHER":
|
||||
flt_flags = []
|
||||
cat_list = list(map(lambda cat: cat.name, sn.property_categories))
|
||||
for prop in sn.properties:
|
||||
if prop.category == "OTHER" or not prop.category or not prop.category in cat_list:
|
||||
if not self.filter_name or self.filter_name.lower() in prop.name.lower():
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
return flt_flags, []
|
||||
|
||||
else:
|
||||
flt_flags = []
|
||||
for prop in sn.properties:
|
||||
if prop.category == sn.active_prop_category:
|
||||
if not self.filter_name or self.filter_name.lower() in prop.name.lower():
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
return flt_flags, []
|
||||
@@ -0,0 +1,10 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class SN_UL_VariableList(bpy.types.UIList):
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
row = layout.row()
|
||||
row.label(text="", icon=item.icon)
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
row.operator("sn.copy_python_name", text="", icon="COPYDOWN", emboss=False).name = item.data_path
|
||||
@@ -0,0 +1,79 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_PT_VariablePanel(bpy.types.Panel):
|
||||
bl_parent_id = "SN_PT_GraphPanel"
|
||||
bl_idname = "SN_PT_VariablePanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Serpens"
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
bl_order = 1
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == "ScriptingNodesTree" and context.space_data.node_tree
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Variables")
|
||||
layout.operator("wm.url_open", text="", icon="QUESTION", emboss=False).url = "https://joshuaknauber.notion.site/Variables-ff5e8ae2e4154c8fa9eed43ecaa0c165"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
sn = context.scene.sn
|
||||
ntree = context.space_data.node_tree
|
||||
|
||||
# draw variable ui list
|
||||
row = layout.row(align=False)
|
||||
col = row.column(align=True)
|
||||
|
||||
if sn.overwrite_variable_graph:
|
||||
col.prop(sn, "variable_graph", text="")
|
||||
ntree = bpy.data.node_groups[sn.variable_graph]
|
||||
|
||||
col.template_list("SN_UL_VariableList", "Variables", ntree, "variables", ntree, "variable_index", rows=4)
|
||||
|
||||
op = col.operator("sn.add_variable_node_popup", text="Add Node", icon="ADD")
|
||||
op.node_tree = ntree.name
|
||||
|
||||
col = row.column(align=True)
|
||||
col.operator("sn.add_variable", text="", icon="ADD").node_tree = ntree.name
|
||||
col.operator("sn.find_variable", text="", icon="VIEWZOOM").node_tree = ntree.name
|
||||
col.operator("sn.remove_variable", text="", icon="REMOVE").node_tree = ntree.name
|
||||
|
||||
col.separator()
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = ntree.variable_index > 0
|
||||
op = subrow.operator("sn.move_variable", text="", icon="TRIA_UP")
|
||||
op.move_up = True
|
||||
op.node_tree = ntree.name
|
||||
subrow = col.row(align=True)
|
||||
subrow.enabled = ntree.variable_index < len(ntree.variables)-1
|
||||
op = subrow.operator("sn.move_variable", text="", icon="TRIA_DOWN")
|
||||
op.move_up = False
|
||||
op.node_tree = ntree.name
|
||||
layout.separator()
|
||||
|
||||
if ntree.variable_index < len(ntree.variables):
|
||||
var = ntree.variables[ntree.variable_index]
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
|
||||
col.prop(var, "variable_type")
|
||||
|
||||
if var.variable_type == "String":
|
||||
col.separator()
|
||||
col.prop(var, "string_default")
|
||||
elif var.variable_type == "Boolean":
|
||||
col.separator()
|
||||
col.prop(var, "boolean_default")
|
||||
elif var.variable_type == "Float":
|
||||
col.separator()
|
||||
col.prop(var, "float_default")
|
||||
elif var.variable_type == "Integer":
|
||||
col.separator()
|
||||
col.prop(var, "integer_default")
|
||||
@@ -0,0 +1,10 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def append_warning(self, context):
|
||||
if context.space_data.node_tree and context.space_data.node_tree.bl_idname == "ScriptingNodesTree":
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.alert = True
|
||||
|
||||
row.label(text="Do not edit these settings!", icon="ERROR")
|
||||
Reference in New Issue
Block a user