2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,186 @@
import bpy
from bpy_extras.io_utils import ImportHelper
import os
class SN_OT_AddAsset(bpy.types.Operator):
bl_idname = "sn.add_asset"
bl_label = "Add Asset"
bl_description = "Adds a asset to the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
add_type: bpy.props.EnumProperty(default="FILE",
items=[("FILE", "File", "Import a single file"),
("DIRECTORY", "Directory", "Import a full directory")],
name="Type",
description="Add this directory or this file as an asset",
options={"SKIP_SAVE"})
def execute(self, context):
bpy.ops.sn.load_asset("INVOKE_DEFAULT", add_type=self.add_type)
return {"FINISHED"}
def draw(self, context):
layout = self.layout
layout.prop(self, "add_type", expand=True)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=200)
class SN_OT_LoadAsset(bpy.types.Operator, ImportHelper):
bl_idname = "sn.load_asset"
bl_label = "Add Asset"
bl_description = "Adds a asset to the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
add_type: bpy.props.EnumProperty(default="FILE",
items=[("FILE", "File", "Import a single file"),
("DIRECTORY", "Directory", "Import a full directory")],
name="Type",
description="Add this directory or this file as an asset",
options={"SKIP_SAVE"})
def execute(self, context):
sn = context.scene.sn
new_asset = sn.assets.add()
if self.add_type == "DIRECTORY":
new_asset.path = os.path.dirname(self.filepath)
else:
new_asset.path = self.filepath
for index, asset in enumerate(sn.assets):
if asset == new_asset:
sn.asset_index = index
return {"FINISHED"}
class SN_OT_RemoveAsset(bpy.types.Operator):
bl_idname = "sn.remove_asset"
bl_label = "Remove Asset"
bl_description = "Removes this asset from the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
@classmethod
def poll(cls, context):
return context.scene.sn.asset_index < len(context.scene.sn.assets)
def execute(self, context):
sn = context.scene.sn
asset = sn.assets[sn.asset_index]
# remove removed from asset nodes
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
for node in ntree.nodes:
if node.bl_idname == "SN_AssetNode":
if node.asset == asset.name:
node.asset = ""
# remove asset
sn.assets.remove(sn.asset_index)
sn.asset_index -= 1
return {"FINISHED"}
class SN_OT_FindNode(bpy.types.Operator):
bl_idname = "sn.find_node"
bl_label = "Find Node"
bl_description = "Find Node"
bl_options = {"REGISTER", "INTERNAL"}
node_tree: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
node: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
def execute(self, context):
ntree = bpy.data.node_groups[self.node_tree]
# set active graph and select
context.space_data.node_tree = ntree
for index, group in enumerate(bpy.data.node_groups):
if group == ntree:
context.scene.sn.node_tree_index = index
# select node and frame
for node in ntree.nodes:
node.select = node.name == self.node
bpy.ops.node.view_selected("INVOKE_DEFAULT")
return {"FINISHED"}
class SN_OT_FindAsset(bpy.types.Operator):
bl_idname = "sn.find_asset"
bl_label = "Find Asset"
bl_description = "Finds this asset in the addon"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
def execute(self, context):
return {"FINISHED"}
def draw(self, context):
layout = self.layout
# init asset nodes
empty_nodes = []
asset_nodes = []
asset = None
if context.scene.sn.asset_index < len(context.scene.sn.assets):
asset = context.scene.sn.assets[context.scene.sn.asset_index]
# find assets nodes
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
for node in ntree.nodes:
if node.bl_idname == "SN_AssetNode":
if asset and node.asset == asset.name:
asset_nodes.append(node)
elif not node.asset:
empty_nodes.append(node)
# draw nodes for selected asset
if context.scene.sn.asset_index < len(context.scene.sn.assets):
col = layout.column()
row = col.row()
row.enabled = False
row.label(text=f"Asset: {asset.name}")
for node in asset_nodes:
op = col.operator("sn.find_node", text=node.name, icon="RESTRICT_SELECT_OFF")
op.node_tree = node.node_tree.name
op.node = node.name
if not asset_nodes:
col.label(text="No nodes found for this asset", icon="INFO")
# draw nodes with empty asset
col = layout.column()
row = col.row()
row.label(text="Empty Asset Nodes")
row.enabled = False
for node in empty_nodes:
op = col.operator("sn.find_node", text=node.name, icon="RESTRICT_SELECT_OFF")
op.node_tree = node.node_tree.name
op.node = node.name
if not empty_nodes:
col.label(text="No empty asset nodes found", icon="INFO")
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=250)
class SN_OT_AddAssetNode(bpy.types.Operator):
bl_idname = "sn.add_asset_node"
bl_label = "Add Asset Node"
bl_description = "Adds an asset node"
bl_options = {"REGISTER", "INTERNAL"}
def execute(self, context):
bpy.ops.node.add_node("INVOKE_DEFAULT", type="SN_AssetNode", use_transform=True)
node = context.space_data.node_tree.nodes.active
if context.scene.sn.asset_index < len(context.scene.sn.assets):
node.asset = context.scene.sn.assets[context.scene.sn.asset_index].name
return {"FINISHED"}
@@ -0,0 +1,47 @@
import bpy
import os
class SN_AssetProperties(bpy.types.PropertyGroup):
def get_to_update(self):
to_update = []
for ntree in bpy.data.node_groups:
if ntree.bl_idname == "ScriptingNodesTree":
for node in ntree.nodes:
if node.bl_idname == "SN_AssetNode":
if node.asset == self.name:
to_update.append(node)
return to_update
def update_file_path(self, context):
if not self.path == bpy.path.abspath(self.path):
self["path"] = bpy.path.abspath(self.path)
else:
if self.name == "Asset" and self.path:
self.name = os.path.basename(self.path)
for node in self.get_to_update():
node._evaluate(context)
def get_asset_name(self):
return self.get("name", "Asset")
def set_asset_name(self, new_name):
# update asset nodes that had this asset
to_update = self.get_to_update()
self["name"] = new_name
for node in to_update:
node.asset = new_name
name: bpy.props.StringProperty(name="Name",
description="Name of the asset",
default="Asset",
get=get_asset_name,
set=set_asset_name)
path: bpy.props.StringProperty(name="Path",
description="Path to the asset file",
subtype="FILE_PATH",
update=update_file_path)