# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors # # SPDX-License-Identifier: GPL-3.0-or-later import bpy from addon_utils import check as check_addon from pathlib import Path from .merge.transfer_data.transfer_ui import draw_transfer_data from .merge.task_layer import draw_task_layer_selection from .config import verify_task_layer_json_data from .prefs import get_addon_prefs from . import constants from .merge.publish import is_staged_publish from bpy.types import UILayout, Context, Panel class ASSETPIPE_PT_sync(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Asset Management" def draw_collection_selection(self, layout: UILayout, context: Context) -> None: layout.prop_search( context.scene.asset_pipeline, 'asset_collection_name', bpy.data, 'collections' ) def draw(self, context: bpy.types.Context) -> None: layout = self.layout asset_pipe = context.scene.asset_pipeline if not asset_pipe.is_asset_pipeline_file: layout.prop(asset_pipe, "new_file_mode", expand=True) layout.prop(asset_pipe, "task_layer_config_type") if asset_pipe.new_file_mode == "BLANK": layout.prop(asset_pipe, "name") layout.prop(asset_pipe, "prefix") layout.prop(asset_pipe, "dir") else: layout.prop_search(asset_pipe, 'asset_collection_name', bpy.data, 'collections') layout.operator("assetpipe.create_new_asset") return if not Path(bpy.data.filepath).exists: layout.label(text="File is not saved", icon="ERROR") return if asset_pipe.asset_collection is not None and ( asset_pipe.sync_error or asset_pipe.asset_collection.name.endswith(constants.LOCAL_SUFFIX) ): layout.alert = True row = layout.row() row.label(text="Merge Process has Failed", icon='ERROR') row.operator("assetpipe.revert_file", text="Revert", icon="FILE_TICK") return # TODO Move this call out of the UI because we keep re-loading this file every draw if not verify_task_layer_json_data() and not asset_pipe.is_published: layout.label(text="Task Layer Config is invalid", icon="ERROR") return if asset_pipe.is_published: layout.label(text="Current File is Published") col = layout.column() col.active = False self.draw_collection_selection(col, context) return layout.label(text="Local Task Layers:") box = layout.box() row = box.row(align=True) for task_layer in asset_pipe.local_task_layers: row.label(text=task_layer.name) self.draw_collection_selection(layout, context) staged = is_staged_publish(Path(bpy.data.filepath)) sync_target_name = "Staged" if staged else "Active" layout.operator( "assetpipe.sync_pull", text=f"Pull from {sync_target_name}", icon="TRIA_DOWN", ) sync_text = f"Sync from {sync_target_name}" push_text = f"Force Push to {sync_target_name}" if check_addon('blender_log')[1]: log_count = len(list(context.scene.blender_log.all_logs)) if log_count > 0: issues_text = f" ({log_count} issues)" sync_text += issues_text push_text += issues_text layout.operator("assetpipe.sync_push", text=sync_text, icon="FILE_REFRESH").pull = True layout.operator("assetpipe.sync_push", text=push_text, icon="TRIA_UP").pull = False class ASSETPIPE_PT_publish(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Publish" bl_parent_id = "ASSETPIPE_PT_sync" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context: bpy.types.Context) -> bool: return bool(not context.scene.asset_pipeline.is_published) def draw(self, context: bpy.types.Context) -> None: staged = is_staged_publish(Path(bpy.data.filepath)) layout = self.layout if staged: layout.operator("assetpipe.publish_staged_as_active", icon="LOOP_FORWARDS") layout.operator("assetpipe.publish_new_version", icon="PLUS") layout.operator("assetpipe.open_publish", icon="FILE") class ASSETPIPE_PT_working_files(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Working Files" bl_parent_id = "ASSETPIPE_PT_sync" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context: bpy.types.Context) -> bool: return context.scene.asset_pipeline.is_published def draw(self, context: bpy.types.Context) -> None: for file in Path(bpy.data.filepath).parent.parent.glob("*.blend"): name = f"Open {file.name.strip('.blend')}" self.layout.operator("assetpipe.open_file", text=name).filepath = str(file) class ASSETPIPE_PT_sync_tools(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Tools" bl_parent_id = "ASSETPIPE_PT_sync" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context: bpy.types.Context) -> bool: return bool(not context.scene.asset_pipeline.is_published) def draw(self, context: bpy.types.Context) -> None: layout = self.layout cat_row = layout.row(align=True) cat_row.prop(context.scene.asset_pipeline, 'asset_catalog_name') cat_row.operator("assetpipe.refresh_asset_cat", icon='FILE_REFRESH', text="") layout.operator("assetpipe.batch_ownership_change") layout.operator("assetpipe.revert_file", icon="FILE_TICK") layout.separator() col = layout.column(align=True) col.operator("assetpipe.save_production_hook", text="Create Production Hook").mode = 'PROD' col.operator("assetpipe.save_production_hook", text="Create Asset Hook").mode = 'ASSET' class ASSETPIPE_PT_sync_advanced(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Advanced" bl_parent_id = "ASSETPIPE_PT_sync" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context: bpy.types.Context) -> bool: prefs = get_addon_prefs() return prefs.is_advanced_mode def draw(self, context: bpy.types.Context) -> None: layout = self.layout box = layout.box() box.operator("assetpipe.prepare_sync") box.operator("assetpipe.reset_ownership", icon="LOOP_BACK") box = layout.box() box.operator("assetpipe.fix_prefixes", icon="CHECKMARK") # Task Layer Updater box = layout.box() box.label(text="Change Local Task Layers") row = box.row() asset_pipe = context.scene.asset_pipeline all_task_layers = asset_pipe.all_task_layers for task_layer in all_task_layers: row.prop(task_layer, "is_local", text=task_layer.name) box.operator("assetpipe.update_local_task_layers") class ASSETPIPE_PT_ownership_inspector(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Assset Pipeline' bl_label = "Ownership Inspector" def draw(self, context: bpy.types.Context) -> None: layout = self.layout asset_pipe = context.scene.asset_pipeline if not asset_pipe.is_asset_pipeline_file: layout.label(text="Open valid 'Asset Pipeline' file", icon="ERROR") return if ( asset_pipe.asset_collection and context.collection in list(asset_pipe.asset_collection.children) ): col = context.collection tl_row = layout.row() tl_row.label( text=f"{col.name}: ", icon="OUTLINER_COLLECTION", ) draw_task_layer_selection(context, layout=tl_row, data=col) if not context.active_object: layout.label(text="Set an Active Object to Inspect", icon="OBJECT_DATA") return obj = context.active_object transfer_data = obj.transfer_data_ownership box = layout.box() main_row = box.row() name_row = main_row.row() name_row.prop(obj, 'name', icon="OBJECT_DATA", text="", emboss=False) if obj.asset_id_surrender: name_row.operator("assetpipe.update_surrendered_object") draw_task_layer_selection(context, layout=main_row.row(), data=obj) surrender_row = main_row.row() surrender_row.enabled = obj.asset_id_owner in asset_pipe.local_task_layers surrender_row.prop(obj, "asset_id_surrender", text="", icon="ORPHAN_DATA" if obj.asset_id_surrender else "HEART") draw_transfer_data(context, transfer_data, box) classes = ( ASSETPIPE_PT_sync, ASSETPIPE_PT_sync_advanced, ASSETPIPE_PT_working_files, ASSETPIPE_PT_sync_tools, ASSETPIPE_PT_publish, ASSETPIPE_PT_ownership_inspector, ) def register(): for i in classes: bpy.utils.register_class(i) def unregister(): for i in classes: bpy.utils.unregister_class(i)