707 lines
24 KiB
Python
707 lines
24 KiB
Python
|
|
import importlib
|
|
from pathlib import Path
|
|
import bpy
|
|
import subprocess
|
|
import gpu
|
|
from gpu_extras.batch import batch_for_shader
|
|
from mathutils import Vector
|
|
from math import sqrt
|
|
|
|
from bpy_extras.io_utils import ExportHelper
|
|
from bpy.types import Operator, PropertyGroup
|
|
from bpy.props import (BoolProperty, EnumProperty, StringProperty, IntProperty, CollectionProperty)
|
|
from .core.catalog import read_catalog
|
|
from .core.bl_utils import get_addon_prefs, unique_name, get_asset_type, get_bl_cmd, get_viewport
|
|
from .core.lib_utils import get_asset_full_path, get_asset_catalog_path, find_asset_data, clear_time_tag
|
|
from . import constants
|
|
|
|
|
|
class ASSETLIB_OT_reload_addon(Operator):
|
|
bl_idname = "assetlibrary.reload_addon"
|
|
bl_options = {"UNDO"}
|
|
bl_label = 'Reload Asset Library Addon'
|
|
bl_description = 'Reload The Asset Library Addon and the addapters'
|
|
|
|
def execute(self, context):
|
|
|
|
print('Execute reload', __package__)
|
|
|
|
addon = importlib.import_module(__package__)
|
|
|
|
addon.unregister()
|
|
importlib.reload(addon)
|
|
for mod in addon.modules:
|
|
importlib.reload(mod)
|
|
addon.register()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_remove_library(Operator):
|
|
bl_idname = "assetlibrary.remove_library"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Remove Library'
|
|
bl_description = 'Remove Library'
|
|
|
|
index : IntProperty(default=-1)
|
|
|
|
def execute(self, context):
|
|
prefs = get_addon_prefs()
|
|
|
|
addon_lib = prefs.libraries[self.index]
|
|
|
|
bl_libs = context.preferences.filepaths.asset_libraries
|
|
if (bl_lib := bl_libs.get(addon_lib.name)) and bl_lib.path == addon_lib.path:
|
|
index = list(bl_libs).index(bl_lib)
|
|
bpy.ops.preferences.asset_library_remove(index=index)
|
|
|
|
prefs.libraries.remove(self.index)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_add_library(Operator):
|
|
bl_idname = "assetlibrary.add_library"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Add Library'
|
|
bl_description = 'Add Library'
|
|
|
|
def execute(self, context):
|
|
prefs = get_addon_prefs()
|
|
|
|
lib = prefs.libraries.add()
|
|
lib.expand = True
|
|
lib.name = unique_name('Asset Library', [l.name for l in prefs.libraries])
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_synchronize(Operator):
|
|
bl_idname = "assetlibrary.synchronize"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Synchronize'
|
|
bl_description = 'Synchronize Action Lib to Local Directory'
|
|
|
|
name : StringProperty()
|
|
diff : StringProperty()
|
|
blocking : BoolProperty(default=False)
|
|
mode : EnumProperty(items=[(i.replace(' ', '_').upper(), i, '') for i in ('None', 'All', 'Auto Bundle')], default='NONE')
|
|
directory : StringProperty(subtype='DIR_PATH')
|
|
#conform : BoolProperty(default=False)
|
|
#def refresh(self):
|
|
# for area in suitable_areas(bpy.context.screen):
|
|
# bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
|
|
#space_data.activate_asset_by_id(asset, deferred=deferred)
|
|
|
|
def execute(self, context):
|
|
prefs = get_addon_prefs()
|
|
|
|
libs = []
|
|
if self.name:
|
|
libs += [prefs.libraries[self.name]]
|
|
|
|
if self.mode == 'ALL':
|
|
libs += prefs.libraries.values()
|
|
elif self.mode == 'AUTO_BUNDLE':
|
|
libs += [l for l in prefs.libraries if l.auto_bundle]
|
|
|
|
if not libs:
|
|
return {"CANCELLED"}
|
|
|
|
lib_datas = [l.to_dict() for l in libs]
|
|
|
|
print(f'Bundle Libraries: {[l.name for l in libs]}')
|
|
|
|
script_code = dedent(f"""
|
|
import bpy
|
|
prefs = bpy.context.preferences.addons["asset_library"].preferences
|
|
|
|
for lib_data in {lib_datas}:
|
|
lib = prefs.env_libraries.add()
|
|
lib.set_dict(lib_data)
|
|
lib.plugin.bundle(cache_diff='{self.diff}')
|
|
|
|
bpy.ops.wm.quit_blender()
|
|
""")
|
|
|
|
script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
|
|
script_path.write_text(script_code)
|
|
|
|
print(script_code)
|
|
|
|
#raise Exception()
|
|
|
|
cmd = get_bl_cmd(script=str(script_path), background=True)
|
|
|
|
#print(cmd)
|
|
if self.blocking:
|
|
subprocess.call(cmd)
|
|
bpy.app.timers.register(refresh_asset_browsers, first_interval=0.2)
|
|
else:
|
|
subprocess.Popen(cmd)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_save_asset_preview(Operator):
|
|
bl_idname = "assetlibrary.save_asset_preview"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Save Asset Preview'
|
|
bl_description = 'Save Asset Preview'
|
|
|
|
filepath: StringProperty(
|
|
name="File Path",
|
|
description="Filepath used for exporting the image",
|
|
subtype='FILE_PATH',
|
|
)
|
|
check_existing: BoolProperty(
|
|
name="Check Existing",
|
|
description="Check and warn on overwriting existing files",
|
|
default=True,
|
|
options={'HIDDEN'},
|
|
)
|
|
|
|
quality: IntProperty(subtype='PERCENTAGE', min=0, max=100, default=90, name='Quality')
|
|
|
|
def execute(self, context):
|
|
prefs = get_addon_prefs()
|
|
|
|
preview = None
|
|
|
|
if context.asset.local_id:
|
|
preview = context.asset.local_id.preview
|
|
width, height = preview.image_size
|
|
pixels = [0] * width * height * 4
|
|
preview.image_pixels_float.foreach_get(pixels)
|
|
|
|
else:
|
|
asset_path = context.asset.full_library_path
|
|
asset_type, asset_name = Path(context.asset.full_path).parts[-2:]
|
|
asset_type = get_asset_type(asset_type)
|
|
|
|
with bpy.data.temp_data(filepath=asset_path) as temp_data:
|
|
with temp_data.libraries.load(asset_path, assets_only=True, link=True) as (data_from, data_to):
|
|
setattr(data_to, asset_type, [asset_name])
|
|
if assets := getattr(data_to, asset_type):
|
|
preview = assets[0].preview
|
|
width, height = preview.image_size
|
|
# Has to read pixel in the with statement for it to work
|
|
pixels = [0] * width * height * 4
|
|
preview.image_pixels_float.foreach_get(pixels)
|
|
|
|
if not preview:
|
|
self.report({'ERROR'}, 'Cannot retrieve preview')
|
|
return {"CANCELLED"}
|
|
|
|
image = bpy.data.images.new('Asset Preview', width=width, height=height, alpha=True)
|
|
image.pixels.foreach_set(pixels)
|
|
try:
|
|
image.save(filepath=self.filepath, quality=self.quality)
|
|
except Exception as e:
|
|
print(e)
|
|
self.report({'ERROR'}, 'Cannot write preview')
|
|
return {"CANCELLED"}
|
|
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
path = Path(context.asset.name)
|
|
if bpy.data.filepath:
|
|
path = Path(bpy.data.filepath, context.asset.name)
|
|
|
|
self.filepath = str(path.with_suffix('.webp'))
|
|
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
|
|
|
|
class ASSETLIB_OT_make_custom_preview(Operator):
|
|
bl_idname = "assetlibrary.make_custom_preview"
|
|
bl_label = "Custom Preview"
|
|
bl_description = "Make a preview"
|
|
|
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
|
|
|
def draw_border(self, context):
|
|
if not self.is_down:
|
|
return
|
|
|
|
# 50% alpha, 2 pixel width line
|
|
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
|
#gpu.state.line_width_set(1.0)
|
|
batch = batch_for_shader(shader, 'LINE_LOOP', {"pos": self.border})
|
|
shader.uniform_float("color", (1.0, 0.0, 0.0, 1))
|
|
batch.draw(shader)
|
|
|
|
# restore opengl defaults
|
|
#gpu.state.line_width_set(1.0)
|
|
#gpu.state.blend_set('NONE')
|
|
|
|
def grab_view3d(self, context):
|
|
width = int(self.release_window_pos.x - self.press_window_pos.x)
|
|
height = width#int(self.press_window_pos.y - self.release_window_pos.y)
|
|
x = int(self.press_window_pos.x)
|
|
y = int(self.press_window_pos.y - width)
|
|
|
|
print(x, y, width, height)
|
|
|
|
scene = context.scene
|
|
|
|
fb = gpu.state.active_framebuffer_get()
|
|
buffer = fb.read_color(x, y, width, height, 4, 0, 'FLOAT')
|
|
|
|
buffer.dimensions = width * height * 4
|
|
|
|
img = bpy.data.images.get('.Asset Preview')
|
|
if img:
|
|
bpy.data.images.remove(img)
|
|
img = bpy.data.images.new('.Asset Preview', width, height)
|
|
#img.scale(width, height)
|
|
img.pixels.foreach_set(buffer)
|
|
img.scale(256, 256)
|
|
|
|
pixels = [0] * 256 * 256 * 4
|
|
img.pixels.foreach_get(pixels)
|
|
|
|
bpy.data.images.remove(img)
|
|
return pixels
|
|
|
|
def modal(self, context, event):
|
|
context.area.tag_redraw()
|
|
|
|
self.mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))
|
|
|
|
if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
|
|
self.press_window_pos = Vector((event.mouse_x, event.mouse_y))
|
|
self.press_pos = Vector((event.mouse_region_x, event.mouse_region_y))
|
|
print('Start Border')
|
|
|
|
self.is_down = True
|
|
|
|
elif event.type == 'MOUSEMOVE' and self.is_down:
|
|
|
|
|
|
width = int(self.mouse_pos.x - self.press_pos.x)
|
|
X = (self.press_pos.x-1, self.mouse_pos.x +2)
|
|
Y = (self.press_pos.y+1, self.press_pos.y-width-2)
|
|
#print(self.mouse_pos, self.press_pos )
|
|
|
|
#X = sorted((self.press_pos.x, self.mouse_pos.x))
|
|
#Y = sorted((self.press_pos.y, self.mouse_pos.y))
|
|
#Constraint to square
|
|
#Y[0] = Y[1] - (X[1] - X[0])
|
|
|
|
self.border = [(X[0], Y[0]), (X[1], Y[0]), (X[1], Y[1]), (X[0], Y[1])]
|
|
|
|
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
|
self.release_window_pos = Vector((event.mouse_x, event.mouse_y))
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
context.area.tag_redraw()
|
|
self.store_preview(context)
|
|
|
|
return {'FINISHED'}
|
|
|
|
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
return {'CANCELLED'}
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def store_preview(self, context):
|
|
asset = context.window_manager.asset_library.asset
|
|
pixels = self.grab_view3d(context)
|
|
asset.preview.image_size = 256, 256
|
|
asset.preview.image_pixels_float.foreach_set(pixels)
|
|
|
|
def invoke(self, context, event):
|
|
self.press_window_pos = Vector((0, 0))
|
|
self.release_window_pos = Vector((0, 0))
|
|
|
|
self.press_pos = Vector((0, 0))
|
|
|
|
self.is_down = False
|
|
self.border = []
|
|
|
|
# Add the region OpenGL drawing callback
|
|
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
|
|
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_border, (context,), 'WINDOW', 'POST_PIXEL')
|
|
|
|
area = get_viewport()
|
|
region = next(r for r in area.regions if r.type =="WINDOW")
|
|
with context.temp_override(area=area, space_data=area.spaces.active, region=region):
|
|
context.window_manager.modal_handler_add(self)
|
|
|
|
return {'RUNNING_MODAL'}
|
|
#else:
|
|
# self.report({'WARNING'}, "View3D not found, cannot run operator")
|
|
# return {'CANCELLED'}
|
|
|
|
|
|
class ASSETLIB_OT_add_tag(Operator):
|
|
bl_idname = "assetlibrary.tag_add"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Add Tag'
|
|
bl_description = 'Add Tag'
|
|
|
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
|
|
|
def execute(self, context):
|
|
asset = context.window_manager.asset_library.asset
|
|
|
|
new_tag = asset.asset_data.tags.new(name='Tag')
|
|
index = list(asset.asset_data.tags).index(new_tag)
|
|
asset.asset_data.active_tag = index
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class ASSETLIB_OT_remove_tag(Operator):
|
|
bl_idname = "assetlibrary.tag_remove"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Remove Tag'
|
|
bl_description = 'Remove Tag'
|
|
|
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
|
|
|
def execute(self, context):
|
|
asset = context.window_manager.asset_library.asset
|
|
|
|
if asset.asset_data.active_tag == -1:
|
|
return {"CANCELLED"}
|
|
|
|
active_tag = asset.asset_data.tags[asset.asset_data.active_tag]
|
|
asset.asset_data.tags.remove(active_tag)
|
|
asset.asset_data.active_tag -=1
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
class ASSETLIB_OT_publish_asset(Operator):
|
|
bl_idname = "assetlibrary.publish_asset"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Publish Asset'
|
|
bl_description = 'Publish Asset'
|
|
|
|
name : StringProperty(name='Name')
|
|
library : EnumProperty(name="Library", items=lambda s, c: constants.LIB_ITEMS)
|
|
#description : StringProperty(name='Description')
|
|
catalog : StringProperty(name='Catalog')
|
|
data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
|
catalog_items : CollectionProperty(type=PropertyGroup)
|
|
|
|
new_asset = False
|
|
is_asset = False
|
|
viewport = None
|
|
use_overlay = False
|
|
|
|
# @classmethod
|
|
# def poll(self, context):
|
|
# return context.space_data.type == 'NODE_EDITOR' and context.space_data.edit_tree
|
|
|
|
def invoke(self, context, event):
|
|
if self.data_type == 'NodeTree':
|
|
asset = context.space_data.edit_tree
|
|
elif self.data_type == 'Material':
|
|
asset = context.object.active_material
|
|
elif self.data_type == 'Object':
|
|
asset = context.object
|
|
|
|
if asset.asset_data:
|
|
self.is_asset = True
|
|
else:
|
|
asset.asset_mark()
|
|
asset.preview_ensure()
|
|
asset.preview.image_size = 256, 256
|
|
|
|
self.viewport = get_viewport()
|
|
if self.viewport:
|
|
self.use_overlay = self.viewport.spaces.active.overlay.show_overlays
|
|
self.viewport.spaces.active.overlay.show_overlays = False
|
|
|
|
bl_libs = context.preferences.filepaths.asset_libraries
|
|
constants.LIB_ITEMS[:] = [(lib.name, lib.name, "") for lib in bl_libs if lib.name]
|
|
|
|
asset_type = get_asset_type(self.data_type)
|
|
asset_data = find_asset_data(asset.name, asset_type=asset_type, preview=True)
|
|
|
|
for lib in bl_libs:
|
|
for catalog_item in read_catalog(lib.path):
|
|
c = self.catalog_items.add()
|
|
c.name = catalog_item.path
|
|
|
|
self.name = asset.name
|
|
self.new_asset = True
|
|
if asset_data:
|
|
catalog = read_catalog(asset_data['library'].path)
|
|
if catalog_item := catalog.get(id=asset_data["catalog_id"]):
|
|
self.catalog = catalog_item.path
|
|
|
|
self.new_asset = False
|
|
self.library = asset_data['library'].name
|
|
|
|
if not self.is_asset:
|
|
if asset_data.get('preview_size'):
|
|
asset.preview.image_size = asset_data['preview_size']
|
|
asset.preview.image_pixels_float.foreach_set(asset_data['preview_pixels'])
|
|
|
|
asset.asset_data.description = asset_data['description']
|
|
|
|
for tag in asset_data['tags']:
|
|
asset.asset_data.tags.new(name=tag, skip_if_exists=True)
|
|
clear_time_tag(asset)
|
|
|
|
#asset.preview_ensure()
|
|
context.window_manager.asset_library.asset = asset
|
|
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def check(self, context):
|
|
return True
|
|
|
|
def cancel(self, context):
|
|
asset = context.window_manager.asset_library.asset
|
|
if self.viewport:
|
|
self.viewport.spaces.active.overlay.show_overlays = self.use_overlay
|
|
if not self.is_asset:
|
|
asset.asset_clear()
|
|
|
|
def split_row(self, layout, name):
|
|
split = layout.split(factor=0.225)
|
|
split.alignment = 'RIGHT'
|
|
split.label(text=name)
|
|
return split
|
|
|
|
def draw(self, context):
|
|
asset = context.window_manager.asset_library.asset
|
|
layout = self.layout
|
|
|
|
col = layout.column()
|
|
col.use_property_split = True
|
|
col.use_property_decorate = False
|
|
|
|
split = self.split_row(layout, "Name")
|
|
split.prop(self, "name", text='')
|
|
|
|
split = self.split_row(layout, "Library")
|
|
split.prop(self, "library", text='')
|
|
|
|
split = self.split_row(layout, "Catalog")
|
|
split.prop_search(self, "catalog", self, "catalog_items", results_are_suggestions=True, text='')
|
|
|
|
split = self.split_row(layout, "Description")
|
|
split.prop(asset.asset_data, "description", text='')
|
|
|
|
split = self.split_row(layout, "Tags")
|
|
row = split.row()
|
|
row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset.asset_data, "tags",
|
|
asset.asset_data, "active_tag", rows=3)
|
|
|
|
col = row.column(align=True)
|
|
col.operator("assetlibrary.tag_add", icon='ADD', text="")
|
|
col.operator("assetlibrary.tag_remove", icon='REMOVE', text="")
|
|
|
|
split = self.split_row(layout, "Preview")
|
|
row = split.row()
|
|
box = row.box()
|
|
box.template_icon(icon_value=asset.preview.icon_id, scale=5.0)
|
|
|
|
col = row.column(align=False)
|
|
if self.viewport:
|
|
col.prop(self.viewport.spaces.active.overlay, 'show_overlays', icon="OVERLAY", text="")
|
|
col.operator("assetlibrary.make_custom_preview", icon='SCENE', text="")
|
|
#op.data_type = self.data_type
|
|
|
|
def execute(self, context):
|
|
bl_libs = context.preferences.filepaths.asset_libraries
|
|
asset = context.window_manager.asset_library.asset
|
|
|
|
publish_library = bl_libs[self.library]
|
|
asset_temp_blend = Path(bpy.app.tempdir, self.name).with_suffix('.blend')
|
|
|
|
bpy.data.libraries.write(str(asset_temp_blend), {asset}, path_remap="ABSOLUTE")
|
|
|
|
self.cancel(context) # To clear the asset mark and restore overlay
|
|
|
|
asset_type = get_asset_type(self.data_type)
|
|
asset_full_path = Path(asset_temp_blend, asset_type, self.name)
|
|
|
|
cmd = get_bl_cmd(
|
|
background=True,
|
|
factory_startup=True,
|
|
blendfile=constants.RESOURCES_DIR / 'asset_preview.blend',
|
|
script=constants.SCRIPTS_DIR / 'publish_library_assets.py',
|
|
library=publish_library.path,
|
|
assets=[asset_full_path.as_posix()],
|
|
catalogs=[self.catalog]
|
|
)
|
|
|
|
print(cmd)
|
|
subprocess.call(cmd)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_publish_assets(Operator):
|
|
bl_idname = "assetlibrary.publish_assets"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
bl_label = 'Publish Assets'
|
|
bl_description = 'Publish Assets'
|
|
|
|
library : EnumProperty(name="Library", items=lambda s, c: constants.LIB_ITEMS)
|
|
override : BoolProperty(default=True)
|
|
|
|
# @classmethod
|
|
# def poll(self, context):
|
|
# return context.space_data.edit_tree
|
|
|
|
def invoke(self, context, event):
|
|
bl_libs = context.preferences.filepaths.asset_libraries
|
|
constants.LIB_ITEMS[:] = [(lib.name, lib.name, "") for lib in bl_libs]
|
|
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column()
|
|
col.use_property_split = True
|
|
col.use_property_decorate = False
|
|
|
|
layout.prop(self, "library")
|
|
layout.prop(self, "override")
|
|
|
|
def execute(self, context):
|
|
bl_libs = context.preferences.filepaths.asset_libraries
|
|
publish_library = bl_libs[self.library]
|
|
preview_blend = constants.RESOURCES_DIR / 'asset_preview.blend'
|
|
|
|
cmd = get_bl_cmd(
|
|
background=True,
|
|
factory_startup=True,
|
|
blendfile=preview_blend,
|
|
script=constants.SCRIPTS_DIR / 'publish_library_assets.py',
|
|
library=publish_library.path,
|
|
assets=[get_asset_full_path(a) for a in context.selected_assets],
|
|
catalogs=[get_asset_catalog_path(a) for a in context.selected_assets]
|
|
)
|
|
|
|
print(cmd)
|
|
subprocess.call(cmd)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ASSETLIB_OT_update_assets(Operator):
|
|
bl_idname = 'assetlibrary.update_assets'
|
|
bl_label = 'Update node'
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
|
selection : EnumProperty( items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'CURRENT')],
|
|
default="CURRENT", name='All Nodes')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.space_data.edit_tree
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
asset_libraries = context.preferences.filepaths.asset_libraries
|
|
|
|
if self.data_type == 'NodeTree':
|
|
assets = [context.space_data.edit_tree]
|
|
blend_data = bpy.data.node_groups
|
|
|
|
if self.selection == 'SELECTED':
|
|
assets = [ n.node_tree for n in context.space_data.edit_tree.nodes
|
|
if n.type == "GROUP" and n.select]
|
|
elif self.selection == 'ALL':
|
|
assets = list(bpy.data.node_groups)
|
|
|
|
elif self.data_type == 'Material':
|
|
asset = context.object.active_material
|
|
blend_data = bpy.data.materials
|
|
if self.selection == 'ALL':
|
|
assets = list(bpy.data.materials)
|
|
|
|
elif self.data_type == 'Object':
|
|
return {"CANCELLED"}
|
|
|
|
elif self.selection == 'CURRENT':
|
|
active_node = context.space_data.edit_tree
|
|
assets = [active_node]
|
|
else:
|
|
assets = list(bpy.data.node_groups)
|
|
|
|
node_names = set(n.name for n in nodes)
|
|
|
|
for asset_library in asset_libraries:
|
|
library_path = Path(asset_library.path)
|
|
blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()]
|
|
|
|
node_groups = list(bpy.data.node_groups)# Storing original node_geoup to compare with imported
|
|
|
|
link = (asset_library.import_method == 'LINK')
|
|
for blend_file in blend_files:
|
|
print(blend_file)
|
|
with bpy.data.libraries.load(str(blend_file), assets_only=True, link=link) as (data_from, data_to):
|
|
|
|
import_node_groups = [n for n in data_from.node_groups if n in node_names]
|
|
print("import_node_groups", import_node_groups)
|
|
data_to.node_groups = import_node_groups
|
|
|
|
node_names -= set(import_node_groups) # Store already updated nodes
|
|
|
|
new_node_groups = set(n for n in bpy.data.node_groups if n not in node_groups)
|
|
|
|
for new_node_group in new_node_groups:
|
|
new_node_group_name = new_node_group.library_weak_reference.id_name[2:]
|
|
local_node_group = next((n for n in bpy.data.node_groups if n.name == new_node_group_name and n != new_node_group), None)
|
|
|
|
if not local_node_group:
|
|
print(f'No local node_group {new_node_group_name}')
|
|
continue
|
|
|
|
print(f'Merge node {local_node_group.name} into {new_node_group.name}')
|
|
|
|
local_node_group.user_remap(new_node_group)
|
|
new_node_group.interface_update(context)
|
|
bpy.data.node_groups.remove(local_node_group)
|
|
|
|
new_node_group.name = new_node_group_name
|
|
new_node_group.asset_clear()
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop(self, "selection", expand=True)
|
|
|
|
classes = (
|
|
ASSETLIB_OT_reload_addon,
|
|
#ASSETLIB_OT_add_library,
|
|
#ASSETLIB_OT_remove_library,
|
|
#ASSETLIB_OT_synchronize,
|
|
ASSETLIB_OT_save_asset_preview,
|
|
ASSETLIB_OT_make_custom_preview,
|
|
ASSETLIB_OT_publish_asset,
|
|
ASSETLIB_OT_publish_assets,
|
|
ASSETLIB_OT_add_tag,
|
|
ASSETLIB_OT_remove_tag
|
|
)
|
|
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |