overhaul: character migrator integration
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
from .operators import OPERATOR_CLASSES
|
||||
from .panels import DLM_PT_main_panel, DLM_UL_library_list
|
||||
from .preferences import DynamicLinkManagerPreferences
|
||||
from . import properties
|
||||
|
||||
PANEL_CLASSES = [DLM_PT_main_panel, DLM_UL_library_list]
|
||||
|
||||
CLASSES = (
|
||||
properties.SearchPathItem,
|
||||
properties.LinkedDatablockItem,
|
||||
properties.LinkedLibraryItem,
|
||||
properties.DynamicLinkManagerProperties,
|
||||
DynamicLinkManagerPreferences,
|
||||
DLM_UL_library_list,
|
||||
DLM_PT_main_panel,
|
||||
*OPERATOR_CLASSES,
|
||||
)
|
||||
|
||||
__all__ = ["CLASSES", "OPERATOR_CLASSES", "PANEL_CLASSES", "DynamicLinkManagerPreferences", "properties"]
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
import bpy
|
||||
import os
|
||||
from bpy.types import Operator
|
||||
from bpy.props import StringProperty, IntProperty
|
||||
|
||||
ADDON_NAME = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
|
||||
|
||||
|
||||
def _prefs(context):
|
||||
return context.preferences.addons.get(ADDON_NAME)
|
||||
|
||||
|
||||
class DLM_OT_replace_linked_asset(Operator):
|
||||
bl_idname = "dlm.replace_linked_asset"
|
||||
bl_label = "Replace Linked Asset"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: StringProperty(name="File Path", description="Path to the new asset file", subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
self.report({"ERROR"}, "No object selected")
|
||||
return {"CANCELLED"}
|
||||
if getattr(obj, "library", None):
|
||||
self.report({"INFO"}, f"Object '{obj.name}' is linked from: {obj.library.filepath}")
|
||||
return {"FINISHED"}
|
||||
if obj.data and getattr(obj.data, "library", None) and obj.data.library:
|
||||
self.report({"INFO"}, f"Object '{obj.name}' has linked data from: {obj.data.library.filepath}")
|
||||
return {"FINISHED"}
|
||||
if obj.type == "ARMATURE" and obj.data and obj.data.name in bpy.data.armatures:
|
||||
ad = bpy.data.armatures[obj.data.name]
|
||||
if getattr(ad, "library", None) and ad.library:
|
||||
self.report({"INFO"}, f"Armature '{obj.name}' data is linked from: {ad.library.filepath}")
|
||||
return {"FINISHED"}
|
||||
self.report({"ERROR"}, "Selected object is not a linked asset")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class DLM_OT_scan_linked_assets(Operator):
|
||||
bl_idname = "dlm.scan_linked_assets"
|
||||
bl_label = "Scan Linked Libraries"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
from ..ops import library
|
||||
|
||||
return library.scan_linked_assets(context, self.report)
|
||||
|
||||
|
||||
class DLM_OT_find_libraries_in_folders(Operator):
|
||||
bl_idname = "dlm.find_libraries_in_folders"
|
||||
bl_label = "Find Libraries in Folders"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
from ..ops import library
|
||||
|
||||
return library.find_libraries_in_folders(context, self.report, ADDON_NAME)
|
||||
|
||||
|
||||
class DLM_OT_open_linked_file(Operator):
|
||||
bl_idname = "dlm.open_linked_file"
|
||||
bl_label = "Open Linked File"
|
||||
bl_options = {"REGISTER"}
|
||||
filepath: StringProperty(name="File Path", default="")
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath:
|
||||
self.report({"ERROR"}, "No file path specified")
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
bpy.ops.wm.path_open(filepath=self.filepath)
|
||||
self.report({"INFO"}, f"Opening linked file: {self.filepath}")
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to open linked file: {e}")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_add_search_path(Operator):
|
||||
bl_idname = "dlm.add_search_path"
|
||||
bl_label = "Add Search Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
prefs = _prefs(context)
|
||||
if prefs:
|
||||
p = prefs.preferences.search_paths.add()
|
||||
p.path = "//"
|
||||
self.report({"INFO"}, f"Added search path: {p.path}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_remove_search_path(Operator):
|
||||
bl_idname = "dlm.remove_search_path"
|
||||
bl_label = "Remove Search Path"
|
||||
bl_options = {"REGISTER"}
|
||||
index: IntProperty(name="Index", default=0)
|
||||
|
||||
def execute(self, context):
|
||||
prefs = _prefs(context)
|
||||
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
|
||||
prefs.preferences.search_paths.remove(self.index)
|
||||
self.report({"INFO"}, f"Removed search path at index {self.index}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_attempt_relink(Operator):
|
||||
bl_idname = "dlm.attempt_relink"
|
||||
bl_label = "Attempt Relink"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
from ..ops import library
|
||||
|
||||
return library.attempt_relink(context, self.report, ADDON_NAME)
|
||||
|
||||
|
||||
class DLM_OT_browse_search_path(Operator):
|
||||
bl_idname = "dlm.browse_search_path"
|
||||
bl_label = "Browse Search Path"
|
||||
bl_options = {"REGISTER"}
|
||||
index: IntProperty(name="Index", default=0)
|
||||
filepath: StringProperty(name="Search Path", subtype="DIR_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = _prefs(context)
|
||||
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
|
||||
prefs.preferences.search_paths[self.index].path = self.filepath
|
||||
self.report({"INFO"}, f"Updated search path {self.index + 1}: {self.filepath}")
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
prefs = _prefs(context)
|
||||
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
|
||||
self.filepath = prefs.preferences.search_paths[self.index].path
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class DLM_OT_reload_libraries(Operator):
|
||||
bl_idname = "dlm.reload_libraries"
|
||||
bl_label = "Reload Libraries"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
bpy.ops.outliner.lib_operation(type="RELOAD")
|
||||
self.report({"INFO"}, "Library reload operation completed")
|
||||
except Exception:
|
||||
try:
|
||||
for lib in bpy.data.libraries:
|
||||
if lib.filepath and os.path.exists(bpy.path.abspath(lib.filepath)):
|
||||
lib.reload()
|
||||
self.report({"INFO"}, "Libraries reloaded manually")
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to reload libraries: {e}")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_make_paths_relative(Operator):
|
||||
bl_idname = "dlm.make_paths_relative"
|
||||
bl_label = "Make Paths Relative"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
bpy.ops.file.make_paths_relative()
|
||||
self.report({"INFO"}, "All file paths made relative")
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to make paths relative: {e}")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_make_paths_absolute(Operator):
|
||||
bl_idname = "dlm.make_paths_absolute"
|
||||
bl_label = "Make Paths Absolute"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
bpy.ops.file.make_paths_absolute()
|
||||
self.report({"INFO"}, "All file paths made absolute")
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to make paths absolute: {e}")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_relocate_single_library(Operator):
|
||||
bl_idname = "dlm.relocate_single_library"
|
||||
bl_label = "Relocate Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
target_filepath: StringProperty(name="Current Library Path", default="")
|
||||
filepath: StringProperty(name="New Library File", subtype="FILE_PATH", default="")
|
||||
|
||||
def execute(self, context):
|
||||
if not self.target_filepath or not self.filepath:
|
||||
self.report({"ERROR"}, "No target or new file specified")
|
||||
return {"CANCELLED"}
|
||||
abs_match = bpy.path.abspath(self.target_filepath) if self.target_filepath else ""
|
||||
library = None
|
||||
for lib in bpy.data.libraries:
|
||||
try:
|
||||
if lib.filepath and bpy.path.abspath(lib.filepath) == abs_match:
|
||||
library = lib
|
||||
break
|
||||
except Exception:
|
||||
if lib.filepath == self.target_filepath:
|
||||
library = lib
|
||||
break
|
||||
if not library:
|
||||
self.report({"ERROR"}, "Could not resolve the selected library")
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
library.filepath = self.filepath
|
||||
library.reload()
|
||||
self.report({"INFO"}, f"Relocated to: {self.filepath}")
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to relocate: {e}")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.target_filepath:
|
||||
try:
|
||||
self.filepath = bpy.path.abspath(self.target_filepath)
|
||||
except Exception:
|
||||
self.filepath = self.target_filepath
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class DLM_OT_run_character_migration(Operator):
|
||||
bl_idname = "dlm.run_character_migration"
|
||||
bl_label = "Run Character Migration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
from ..ops.migrator import run_full_migration
|
||||
|
||||
ok, msg = run_full_migration(context)
|
||||
if ok:
|
||||
self.report({"INFO"}, msg)
|
||||
return {"FINISHED"}
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class DLM_OT_picker_original_character(Operator):
|
||||
bl_idname = "dlm.picker_original_character"
|
||||
bl_label = "Pick Original"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj or obj.type != "ARMATURE":
|
||||
self.report({"WARNING"}, "Select an armature")
|
||||
return {"CANCELLED"}
|
||||
context.scene.dynamic_link_manager.original_character = obj
|
||||
self.report({"INFO"}, f"Original: {obj.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_picker_replacement_character(Operator):
|
||||
bl_idname = "dlm.picker_replacement_character"
|
||||
bl_label = "Pick Replacement"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj or obj.type != "ARMATURE":
|
||||
self.report({"WARNING"}, "Select an armature")
|
||||
return {"CANCELLED"}
|
||||
context.scene.dynamic_link_manager.replacement_character = obj
|
||||
self.report({"INFO"}, f"Replacement: {obj.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
OPERATOR_CLASSES = [
|
||||
DLM_OT_replace_linked_asset,
|
||||
DLM_OT_scan_linked_assets,
|
||||
DLM_OT_find_libraries_in_folders,
|
||||
DLM_OT_open_linked_file,
|
||||
DLM_OT_add_search_path,
|
||||
DLM_OT_remove_search_path,
|
||||
DLM_OT_browse_search_path,
|
||||
DLM_OT_attempt_relink,
|
||||
DLM_OT_reload_libraries,
|
||||
DLM_OT_make_paths_relative,
|
||||
DLM_OT_make_paths_absolute,
|
||||
DLM_OT_relocate_single_library,
|
||||
DLM_OT_run_character_migration,
|
||||
DLM_OT_picker_original_character,
|
||||
DLM_OT_picker_replacement_character,
|
||||
]
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel, UIList
|
||||
from . import properties
|
||||
|
||||
|
||||
class DLM_UL_library_list(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
if self.layout_type in {"DEFAULT", "COMPACT"}:
|
||||
layout.scale_x = 0.4
|
||||
layout.label(text=item.name)
|
||||
layout.scale_x = 0.3
|
||||
if item.is_missing:
|
||||
layout.label(text="MISSING", icon="ERROR")
|
||||
elif item.is_indirect:
|
||||
layout.label(text="INDIRECT", icon="INFO")
|
||||
else:
|
||||
layout.label(text="OK", icon="FILE_BLEND")
|
||||
layout.scale_x = 0.3
|
||||
path_text = item.filepath or ""
|
||||
if path_text.startswith("\\\\"):
|
||||
parts = path_text.split("\\")
|
||||
short_path = f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
|
||||
elif len(path_text) >= 2 and path_text[1] == ":":
|
||||
short_path = f"{path_text[:2]}\\"
|
||||
elif path_text.startswith("//"):
|
||||
short_path = "// (relative)"
|
||||
else:
|
||||
short_path = path_text[:15] + "..." if len(path_text) > 15 else path_text
|
||||
layout.label(text=short_path, icon="FILE_FOLDER")
|
||||
elif self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", icon="FILE_BLEND")
|
||||
|
||||
|
||||
def _get_short_path(filepath):
|
||||
if not filepath:
|
||||
return "Unknown"
|
||||
if filepath.startswith("//"):
|
||||
return "// (relative)"
|
||||
if filepath.startswith("\\\\"):
|
||||
parts = filepath.split("\\")
|
||||
return f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
|
||||
if len(filepath) >= 2 and filepath[1] == ":":
|
||||
return f"{filepath[:2]}\\"
|
||||
if filepath.startswith("/"):
|
||||
parts = filepath.split("/")
|
||||
return f"/{parts[1]}/" if len(parts) >= 2 else "/ (root)"
|
||||
return "Unknown"
|
||||
|
||||
|
||||
class DLM_PT_main_panel(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Dynamic Link Manager"
|
||||
bl_label = "Dynamic Link Manager"
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("preferences.addon_show", text="", icon="PREFERENCES").module = __package__.rsplit(".", 1)[0]
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.dynamic_link_manager
|
||||
|
||||
# Path management
|
||||
row = layout.row()
|
||||
row.operator("dlm.make_paths_relative", text="Make Paths Relative", icon="FILE_PARENT")
|
||||
row.operator("dlm.make_paths_absolute", text="Make Paths Absolute", icon="FILE_FOLDER")
|
||||
|
||||
# CharMig section (placeholder; full UI in implementation step 5)
|
||||
box = layout.box()
|
||||
box.label(text="Character Migrator")
|
||||
row = box.row()
|
||||
row.prop(props, "migrator_mode", text="Automatic pair discovery")
|
||||
row = box.row()
|
||||
row.prop(props, "original_character", text="Original")
|
||||
row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER")
|
||||
row = box.row()
|
||||
row.prop(props, "replacement_character", text="Replacement")
|
||||
row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")
|
||||
row = box.row()
|
||||
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
|
||||
|
||||
# Linked Libraries (own dropdown)
|
||||
box = layout.box()
|
||||
box.label(text="Linked Libraries Analysis")
|
||||
row = box.row()
|
||||
row.operator("dlm.scan_linked_assets", text="Scan Linked Assets", icon="FILE_REFRESH")
|
||||
missing_count = sum(1 for lib in props.linked_libraries if lib.is_missing)
|
||||
if missing_count > 0:
|
||||
row.label(text=f"({props.linked_assets_count} libraries, {missing_count} missing)", icon="ERROR")
|
||||
else:
|
||||
row.label(text=f"({props.linked_assets_count} libraries)")
|
||||
if props.linked_assets_count > 0:
|
||||
row = box.row(align=True)
|
||||
icon = "DISCLOSURE_TRI_DOWN" if props.linked_libraries_expanded else "DISCLOSURE_TRI_RIGHT"
|
||||
row.prop(props, "linked_libraries_expanded", text="", icon=icon, icon_only=True)
|
||||
row.label(text="Linked Libraries:")
|
||||
row.label(text=f"({props.linked_assets_count} libraries)")
|
||||
if props.linked_libraries_expanded:
|
||||
row = box.row()
|
||||
row.template_list("DLM_UL_library_list", "", props, "linked_libraries", props, "linked_libraries_index")
|
||||
row = box.row()
|
||||
row.operator("dlm.reload_libraries", text="Reload Libraries", icon="FILE_REFRESH")
|
||||
prefs = context.preferences.addons.get(__package__.rsplit(".", 1)[0])
|
||||
if prefs and prefs.preferences.search_paths:
|
||||
for i, path_item in enumerate(prefs.preferences.search_paths):
|
||||
row = box.row()
|
||||
row.prop(path_item, "path", text=f"Search path {i+1}")
|
||||
row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i
|
||||
row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i
|
||||
row = box.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("dlm.add_search_path", text="Add search path", icon="ADD")
|
||||
if missing_count > 0:
|
||||
row = box.row()
|
||||
row.operator("dlm.find_libraries_in_folders", text="Find libraries in these folders", icon="VIEWZOOM")
|
||||
if props.linked_libraries and 0 <= props.linked_libraries_index < len(props.linked_libraries):
|
||||
selected_lib = props.linked_libraries[props.linked_libraries_index]
|
||||
info_box = box.box()
|
||||
if selected_lib.is_missing:
|
||||
info_box.alert = True
|
||||
info_box.label(text=f"Selected: {selected_lib.name}")
|
||||
if selected_lib.is_missing:
|
||||
info_box.label(text="Status: MISSING", icon="ERROR")
|
||||
elif selected_lib.is_indirect:
|
||||
info_box.label(text="Status: INDIRECT", icon="INFO")
|
||||
else:
|
||||
info_box.label(text="Status: OK", icon="FILE_BLEND")
|
||||
info_box.label(text=f"Path: {selected_lib.filepath}", icon="FILE_FOLDER")
|
||||
info_box.operator("dlm.open_linked_file", text="Open Blend", icon="FILE_BLEND").filepath = selected_lib.filepath
|
||||
op = info_box.operator("dlm.relocate_single_library", text="Relocate Library", icon="FILE_FOLDER")
|
||||
op.target_filepath = selected_lib.filepath
|
||||
@@ -0,0 +1,32 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
import bpy
|
||||
from bpy.types import AddonPreferences
|
||||
from bpy.props import StringProperty, CollectionProperty
|
||||
from . import properties
|
||||
|
||||
|
||||
class DynamicLinkManagerPreferences(AddonPreferences):
|
||||
bl_idname = __package__.rsplit(".", 1)[0]
|
||||
|
||||
search_paths: CollectionProperty(
|
||||
type=properties.SearchPathItem,
|
||||
name="Search Paths",
|
||||
description="Paths to search for missing linked libraries",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Default Search Paths for Missing Libraries")
|
||||
row = box.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("dlm.add_search_path", text="Add search path", icon="ADD")
|
||||
for i, path_item in enumerate(self.search_paths):
|
||||
row = box.row()
|
||||
row.prop(path_item, "path", text=f"Search path {i+1}")
|
||||
row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i
|
||||
row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i
|
||||
@@ -0,0 +1,57 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import IntProperty, StringProperty, BoolProperty, CollectionProperty, PointerProperty
|
||||
|
||||
|
||||
class SearchPathItem(PropertyGroup):
|
||||
path: StringProperty(
|
||||
name="Search Path",
|
||||
description="Path to search for missing linked libraries",
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
|
||||
|
||||
class LinkedDatablockItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", description="Name of the linked datablock")
|
||||
type: StringProperty(name="Type", description="Type of the linked datablock")
|
||||
|
||||
|
||||
class LinkedLibraryItem(PropertyGroup):
|
||||
filepath: StringProperty(name="File Path", description="Path to the linked .blend file")
|
||||
name: StringProperty(name="Name", description="Name of the linked .blend file")
|
||||
is_missing: BoolProperty(name="Missing", description="True if the linked file is not found")
|
||||
is_indirect: BoolProperty(name="Is Indirect", description="True if this is an indirectly linked library")
|
||||
is_expanded: BoolProperty(name="Expanded", default=True)
|
||||
linked_datablocks: CollectionProperty(type=LinkedDatablockItem, name="Linked Datablocks")
|
||||
|
||||
|
||||
class DynamicLinkManagerProperties(PropertyGroup):
|
||||
linked_libraries: CollectionProperty(type=LinkedLibraryItem, name="Linked Libraries")
|
||||
linked_libraries_index: IntProperty(name="Linked Libraries Index", default=0)
|
||||
linked_assets_count: IntProperty(name="Linked Assets Count", default=0)
|
||||
linked_libraries_expanded: BoolProperty(name="Linked Libraries Expanded", default=True)
|
||||
selected_asset_path: StringProperty(name="Selected Asset Path", default="")
|
||||
|
||||
# Character migrator (manual mode)
|
||||
migrator_mode: BoolProperty(
|
||||
name="Automatic",
|
||||
description="Automatic: discover pair by Name_Rigify / Name_Rigify.001. Manual: use fields below",
|
||||
default=False,
|
||||
)
|
||||
original_character: PointerProperty(
|
||||
name="Original Character",
|
||||
description="Armature to migrate from",
|
||||
type=bpy.types.Object,
|
||||
poll=lambda self, obj: obj and obj.type == "ARMATURE",
|
||||
)
|
||||
replacement_character: PointerProperty(
|
||||
name="Replacement Character",
|
||||
description="Armature to migrate to",
|
||||
type=bpy.types.Object,
|
||||
poll=lambda self, obj: obj and obj.type == "ARMATURE",
|
||||
)
|
||||
Reference in New Issue
Block a user