Files
blender-portable-repo/scripts/addons/asset_pipeline/ui.py
T
2026-03-17 14:58:51 -06:00

256 lines
9.2 KiB
Python

# 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)