2026-02-16
This commit is contained in:
+192
-102
@@ -5,14 +5,14 @@ import sys
|
||||
import subprocess
|
||||
|
||||
# Import ghost buster functionality
|
||||
from ..ops.ghost_buster import GhostBuster, GhostDetector, ResyncEnforce
|
||||
from ..ops.ghost_buster import RBST_Bustin_OT_GhostBuster, RBST_Bustin_OT_GhostDetector, RBST_Bustin_OT_ResyncEnforce
|
||||
from ..utils import compat
|
||||
|
||||
# Regular expression to match numbered suffixes like .001, .002, _001, _0001, etc.
|
||||
NUMBERED_SUFFIX_PATTERN = re.compile(r'(.*?)[._](\d{3,})$')
|
||||
RBST_DatRem_NUMBERED_SUFFIX_PATTERN = re.compile(r'(.*?)[._](\d{3,})$')
|
||||
|
||||
# Function to check if any datablocks in a collection are linked from a library
|
||||
def has_linked_datablocks(data_collection):
|
||||
def RBST_DatRem_has_linked_datablocks(data_collection):
|
||||
"""Check if any datablocks in the collection are linked from a library"""
|
||||
for data in data_collection:
|
||||
if data.users > 0 and hasattr(data, 'library') and data.library is not None:
|
||||
@@ -20,7 +20,7 @@ def has_linked_datablocks(data_collection):
|
||||
return False
|
||||
|
||||
# Register properties for data remap settings
|
||||
def register_dataremap_properties():
|
||||
def RBST_DatRem_register_properties():
|
||||
bpy.types.Scene.dataremap_images = bpy.props.BoolProperty( # type: ignore
|
||||
name="Images",
|
||||
description="Find and remap duplicate images",
|
||||
@@ -136,7 +136,7 @@ def register_dataremap_properties():
|
||||
default=False
|
||||
)
|
||||
|
||||
def unregister_dataremap_properties():
|
||||
def RBST_DatRem_unregister_properties():
|
||||
del bpy.types.Scene.dataremap_images
|
||||
del bpy.types.Scene.dataremap_materials
|
||||
del bpy.types.Scene.dataremap_fonts
|
||||
@@ -162,14 +162,14 @@ def unregister_dataremap_properties():
|
||||
if hasattr(bpy.types.Scene, "ghost_buster_delete_low_priority"):
|
||||
del bpy.types.Scene.ghost_buster_delete_low_priority
|
||||
|
||||
def get_base_name(name):
|
||||
def RBST_DatRem_get_base_name(name):
|
||||
"""Extract the base name without numbered suffix"""
|
||||
match = NUMBERED_SUFFIX_PATTERN.match(name)
|
||||
match = RBST_DatRem_NUMBERED_SUFFIX_PATTERN.match(name)
|
||||
if match:
|
||||
return match.group(1) # Return the base name
|
||||
return name
|
||||
|
||||
def find_data_groups(data_collection):
|
||||
def RBST_DatRem_find_data_groups(data_collection):
|
||||
"""Group data blocks by their base name, excluding those with no users or linked from libraries"""
|
||||
groups = {}
|
||||
|
||||
@@ -182,7 +182,7 @@ def find_data_groups(data_collection):
|
||||
if hasattr(data, 'library') and data.library is not None:
|
||||
continue
|
||||
|
||||
base_name = get_base_name(data.name)
|
||||
base_name = RBST_DatRem_get_base_name(data.name)
|
||||
|
||||
# Only group local datablocks
|
||||
if base_name not in groups:
|
||||
@@ -194,7 +194,7 @@ def find_data_groups(data_collection):
|
||||
return {name: items for name, items in groups.items()
|
||||
if len(items) > 1 and any(not (hasattr(item, 'library') and item.library is not None) for item in items)}
|
||||
|
||||
def find_target_data(data_group):
|
||||
def RBST_DatRem_find_target_data(data_group):
|
||||
"""Find the target data block to remap to"""
|
||||
# Filter out linked datablocks
|
||||
local_data_group = [data for data in data_group if not (hasattr(data, 'library') and data.library is not None)]
|
||||
@@ -205,7 +205,7 @@ def find_target_data(data_group):
|
||||
|
||||
# First, try to find a data block without a numbered suffix
|
||||
for data in local_data_group:
|
||||
if get_base_name(data.name) == data.name:
|
||||
if RBST_DatRem_get_base_name(data.name) == data.name:
|
||||
return data
|
||||
|
||||
# If no unnumbered version exists, find the "youngest" version (highest number)
|
||||
@@ -213,7 +213,7 @@ def find_target_data(data_group):
|
||||
highest_suffix = 0
|
||||
|
||||
for data in local_data_group:
|
||||
match = NUMBERED_SUFFIX_PATTERN.match(data.name)
|
||||
match = RBST_DatRem_NUMBERED_SUFFIX_PATTERN.match(data.name)
|
||||
if match:
|
||||
suffix_num = int(match.group(2))
|
||||
if suffix_num > highest_suffix:
|
||||
@@ -222,7 +222,7 @@ def find_target_data(data_group):
|
||||
|
||||
return youngest
|
||||
|
||||
def clean_data_names(data_collection):
|
||||
def RBST_DatRem_clean_data_names(data_collection):
|
||||
"""Remove numbered suffixes from all data blocks with users"""
|
||||
cleaned_count = 0
|
||||
|
||||
@@ -235,14 +235,14 @@ def clean_data_names(data_collection):
|
||||
if hasattr(data, 'library') and data.library is not None:
|
||||
continue
|
||||
|
||||
base_name = get_base_name(data.name)
|
||||
base_name = RBST_DatRem_get_base_name(data.name)
|
||||
if base_name != data.name:
|
||||
data.name = base_name
|
||||
cleaned_count += 1
|
||||
|
||||
return cleaned_count
|
||||
|
||||
def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap_worlds):
|
||||
def RBST_DatRem_remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap_worlds):
|
||||
"""Remap redundant data blocks to their base versions like Blender's Remap Users function, and clean up names."""
|
||||
remapped_count = 0
|
||||
cleaned_count = 0
|
||||
@@ -250,18 +250,18 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
# Process images
|
||||
if remap_images:
|
||||
# First remap duplicates
|
||||
image_groups = find_data_groups(bpy.data.images)
|
||||
image_groups = RBST_DatRem_find_data_groups(bpy.data.images)
|
||||
for base_name, images in image_groups.items():
|
||||
# Skip excluded groups
|
||||
if f"images:{base_name}" in context.scene.excluded_remap_groups:
|
||||
continue
|
||||
|
||||
target_image = find_target_data(images)
|
||||
target_image = RBST_DatRem_find_target_data(images)
|
||||
|
||||
# Rename the target if it has a numbered suffix and is the youngest
|
||||
if get_base_name(target_image.name) != target_image.name:
|
||||
if RBST_DatRem_get_base_name(target_image.name) != target_image.name:
|
||||
try:
|
||||
target_image.name = get_base_name(target_image.name)
|
||||
target_image.name = RBST_DatRem_get_base_name(target_image.name)
|
||||
except AttributeError:
|
||||
# Skip if the target is linked and can't be renamed
|
||||
print(f"Warning: Cannot rename linked image {target_image.name}")
|
||||
@@ -323,23 +323,23 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
# This matches Blender's Remap Users behavior
|
||||
|
||||
# Then clean up any remaining numbered suffixes
|
||||
cleaned_count += clean_data_names(bpy.data.images)
|
||||
cleaned_count += RBST_DatRem_clean_data_names(bpy.data.images)
|
||||
|
||||
# Process materials
|
||||
if remap_materials:
|
||||
# First remap duplicates
|
||||
material_groups = find_data_groups(bpy.data.materials)
|
||||
material_groups = RBST_DatRem_find_data_groups(bpy.data.materials)
|
||||
for base_name, materials in material_groups.items():
|
||||
# Skip excluded groups
|
||||
if f"materials:{base_name}" in context.scene.excluded_remap_groups:
|
||||
continue
|
||||
|
||||
target_material = find_target_data(materials)
|
||||
target_material = RBST_DatRem_find_target_data(materials)
|
||||
|
||||
# Rename the target if it has a numbered suffix and is the youngest
|
||||
if get_base_name(target_material.name) != target_material.name:
|
||||
if RBST_DatRem_get_base_name(target_material.name) != target_material.name:
|
||||
try:
|
||||
target_material.name = get_base_name(target_material.name)
|
||||
target_material.name = RBST_DatRem_get_base_name(target_material.name)
|
||||
except AttributeError:
|
||||
# Skip if the target is linked and can't be renamed
|
||||
print(f"Warning: Cannot rename linked material {target_material.name}")
|
||||
@@ -443,23 +443,23 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
# This matches Blender's Remap Users behavior
|
||||
|
||||
# Then clean up any remaining numbered suffixes
|
||||
cleaned_count += clean_data_names(bpy.data.materials)
|
||||
cleaned_count += RBST_DatRem_clean_data_names(bpy.data.materials)
|
||||
|
||||
# Process fonts
|
||||
if remap_fonts:
|
||||
# First remap duplicates
|
||||
font_groups = find_data_groups(bpy.data.fonts)
|
||||
font_groups = RBST_DatRem_find_data_groups(bpy.data.fonts)
|
||||
for base_name, fonts in font_groups.items():
|
||||
# Skip excluded groups
|
||||
if f"fonts:{base_name}" in context.scene.excluded_remap_groups:
|
||||
continue
|
||||
|
||||
target_font = find_target_data(fonts)
|
||||
target_font = RBST_DatRem_find_target_data(fonts)
|
||||
|
||||
# Rename the target if it has a numbered suffix and is the youngest
|
||||
if get_base_name(target_font.name) != target_font.name:
|
||||
if RBST_DatRem_get_base_name(target_font.name) != target_font.name:
|
||||
try:
|
||||
target_font.name = get_base_name(target_font.name)
|
||||
target_font.name = RBST_DatRem_get_base_name(target_font.name)
|
||||
except AttributeError:
|
||||
# Skip if the target is linked and can't be renamed
|
||||
print(f"Warning: Cannot rename linked font {target_font.name}")
|
||||
@@ -519,23 +519,23 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
# This matches Blender's Remap Users behavior
|
||||
|
||||
# Then clean up any remaining numbered suffixes
|
||||
cleaned_count += clean_data_names(bpy.data.fonts)
|
||||
cleaned_count += RBST_DatRem_clean_data_names(bpy.data.fonts)
|
||||
|
||||
# Process worlds
|
||||
if remap_worlds:
|
||||
# First remap duplicates
|
||||
world_groups = find_data_groups(bpy.data.worlds)
|
||||
world_groups = RBST_DatRem_find_data_groups(bpy.data.worlds)
|
||||
for base_name, worlds in world_groups.items():
|
||||
# Skip excluded groups
|
||||
if f"worlds:{base_name}" in context.scene.excluded_remap_groups:
|
||||
continue
|
||||
|
||||
target_world = find_target_data(worlds)
|
||||
target_world = RBST_DatRem_find_target_data(worlds)
|
||||
|
||||
# Rename the target if it has a numbered suffix and is the youngest
|
||||
if get_base_name(target_world.name) != target_world.name:
|
||||
if RBST_DatRem_get_base_name(target_world.name) != target_world.name:
|
||||
try:
|
||||
target_world.name = get_base_name(target_world.name)
|
||||
target_world.name = RBST_DatRem_get_base_name(target_world.name)
|
||||
except AttributeError:
|
||||
# Skip if the target is linked and can't be renamed
|
||||
print(f"Warning: Cannot rename linked world {target_world.name}")
|
||||
@@ -585,7 +585,7 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
# This matches Blender's Remap Users behavior
|
||||
|
||||
# Then clean up any remaining numbered suffixes
|
||||
cleaned_count += clean_data_names(bpy.data.worlds)
|
||||
cleaned_count += RBST_DatRem_clean_data_names(bpy.data.worlds)
|
||||
|
||||
# Force an update of the dependency graph to ensure all users are properly updated
|
||||
if context.view_layer:
|
||||
@@ -593,7 +593,7 @@ def remap_data_blocks(context, remap_images, remap_materials, remap_fonts, remap
|
||||
|
||||
return remapped_count, cleaned_count
|
||||
|
||||
class DATAREMAP_OT_RemapData(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_RemapData(bpy.types.Operator):
|
||||
"""Remap redundant data blocks to reduce duplicates"""
|
||||
bl_idname = "bst.bulk_data_remap"
|
||||
bl_label = "Remap Data"
|
||||
@@ -607,10 +607,10 @@ class DATAREMAP_OT_RemapData(bpy.types.Operator):
|
||||
remap_worlds = context.scene.dataremap_worlds
|
||||
|
||||
# Count duplicates before remapping (only for local datablocks)
|
||||
image_groups = find_data_groups(bpy.data.images) if remap_images else {}
|
||||
material_groups = find_data_groups(bpy.data.materials) if remap_materials else {}
|
||||
font_groups = find_data_groups(bpy.data.fonts) if remap_fonts else {}
|
||||
world_groups = find_data_groups(bpy.data.worlds) if remap_worlds else {}
|
||||
image_groups = RBST_DatRem_find_data_groups(bpy.data.images) if remap_images else {}
|
||||
material_groups = RBST_DatRem_find_data_groups(bpy.data.materials) if remap_materials else {}
|
||||
font_groups = RBST_DatRem_find_data_groups(bpy.data.fonts) if remap_fonts else {}
|
||||
world_groups = RBST_DatRem_find_data_groups(bpy.data.worlds) if remap_worlds else {}
|
||||
|
||||
total_duplicates = sum(len(group) - 1 for groups in [image_groups, material_groups, font_groups, world_groups] for group in groups.values())
|
||||
|
||||
@@ -620,29 +620,29 @@ class DATAREMAP_OT_RemapData(bpy.types.Operator):
|
||||
total_numbered += sum(1 for img in bpy.data.images
|
||||
if img.users > 0
|
||||
and not (hasattr(img, 'library') and img.library is not None)
|
||||
and get_base_name(img.name) != img.name)
|
||||
and RBST_DatRem_get_base_name(img.name) != img.name)
|
||||
if remap_materials:
|
||||
total_numbered += sum(1 for mat in bpy.data.materials
|
||||
if mat.users > 0
|
||||
and not (hasattr(mat, 'library') and mat.library is not None)
|
||||
and get_base_name(mat.name) != mat.name)
|
||||
and RBST_DatRem_get_base_name(mat.name) != mat.name)
|
||||
if remap_fonts:
|
||||
total_numbered += sum(1 for font in bpy.data.fonts
|
||||
if font.users > 0
|
||||
and not (hasattr(font, 'library') and font.library is not None)
|
||||
and get_base_name(font.name) != font.name)
|
||||
and RBST_DatRem_get_base_name(font.name) != font.name)
|
||||
if remap_worlds:
|
||||
total_numbered += sum(1 for world in bpy.data.worlds
|
||||
if world.users > 0
|
||||
and not (hasattr(world, 'library') and world.library is not None)
|
||||
and get_base_name(world.name) != world.name)
|
||||
and RBST_DatRem_get_base_name(world.name) != world.name)
|
||||
|
||||
if total_duplicates == 0 and total_numbered == 0:
|
||||
self.report({'INFO'}, "No local data blocks to process")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Perform the remapping and cleaning
|
||||
remapped_count, cleaned_count = remap_data_blocks(
|
||||
remapped_count, cleaned_count = RBST_DatRem_remap_data_blocks(
|
||||
context,
|
||||
remap_images,
|
||||
remap_materials,
|
||||
@@ -662,8 +662,80 @@ class DATAREMAP_OT_RemapData(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a new operator for merging duplicates using data-block utilities
|
||||
class RBST_DatRem_OT_MergeDuplicatesWithDBU(bpy.types.Operator):
|
||||
"""Merge duplicates using data-block utilities addon for all supported data types"""
|
||||
bl_idname = "bst.merge_duplicates_dbu"
|
||||
bl_label = "Merge Duplicates (DBU)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
# Check if data-block utilities addon is installed
|
||||
if not hasattr(context.scene, 'dbu_similar_settings'):
|
||||
self.report({'ERROR'}, "Data-block utilities addon is not installed or enabled")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Data types to process in order
|
||||
data_types = ['NODETREE', 'MATERIAL', 'LIGHT', 'IMAGE', 'MESH']
|
||||
type_labels = {
|
||||
'NODETREE': 'Node Groups',
|
||||
'MATERIAL': 'Materials',
|
||||
'LIGHT': 'Lights',
|
||||
'IMAGE': 'Images',
|
||||
'MESH': 'Meshes'
|
||||
}
|
||||
|
||||
total_merged = 0
|
||||
processed_types = []
|
||||
|
||||
try:
|
||||
settings = context.scene.dbu_similar_settings
|
||||
|
||||
for id_type in data_types:
|
||||
# Set the id_type
|
||||
settings.id_type = id_type
|
||||
|
||||
# Find similar and duplicates
|
||||
try:
|
||||
bpy.ops.scene.dbu_find_similar_and_duplicates()
|
||||
except Exception as e:
|
||||
self.report({'WARNING'}, f"Failed to find duplicates for {type_labels[id_type]}: {str(e)}")
|
||||
continue
|
||||
|
||||
# Check if any duplicates were found
|
||||
if not settings.duplicates:
|
||||
continue
|
||||
|
||||
# Count items to be merged (each group has duplicates, so count items - groups)
|
||||
# Each group keeps one item, so we count (total items - number of groups)
|
||||
total_items = sum(len(group.group) for group in settings.duplicates)
|
||||
num_groups = len(settings.duplicates)
|
||||
items_to_remove = total_items - num_groups # One item per group is kept
|
||||
|
||||
# Merge duplicates
|
||||
try:
|
||||
bpy.ops.scene.dbu_merge_duplicates()
|
||||
total_merged += items_to_remove
|
||||
processed_types.append(f"{type_labels[id_type]} ({items_to_remove})")
|
||||
except Exception as e:
|
||||
self.report({'WARNING'}, f"Failed to merge duplicates for {type_labels[id_type]}: {str(e)}")
|
||||
continue
|
||||
|
||||
# Report results
|
||||
if total_merged > 0:
|
||||
types_str = ", ".join(processed_types)
|
||||
self.report({'INFO'}, f"Merged {total_merged} duplicate(s) across: {types_str}")
|
||||
else:
|
||||
self.report({'INFO'}, "No duplicates found to merge")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, f"Error during merge operation: {str(e)}")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Add a new operator for purging unused data
|
||||
class DATAREMAP_OT_PurgeUnused(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_PurgeUnused(bpy.types.Operator):
|
||||
"""Purge all unused data-blocks from the file (equivalent to File > Clean Up > Purge Unused Data)"""
|
||||
bl_idname = "bst.purge_unused_data"
|
||||
bl_label = "Purge Unused Data"
|
||||
@@ -678,7 +750,7 @@ class DATAREMAP_OT_PurgeUnused(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a new operator for toggling group exclusion
|
||||
class DATAREMAP_OT_ToggleGroupExclusion(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_ToggleGroupExclusion(bpy.types.Operator):
|
||||
"""Toggle whether this group should be included in remapping"""
|
||||
bl_idname = "bst.toggle_group_exclusion"
|
||||
bl_label = "Toggle Group"
|
||||
@@ -712,7 +784,7 @@ class DATAREMAP_OT_ToggleGroupExclusion(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class DATAREMAP_OT_SelectAllGroups(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_SelectAllGroups(bpy.types.Operator):
|
||||
"""Select or deselect all groups of a specific data type"""
|
||||
bl_idname = "bst.select_all_data_groups"
|
||||
bl_label = "Select All Groups"
|
||||
@@ -738,13 +810,13 @@ class DATAREMAP_OT_SelectAllGroups(bpy.types.Operator):
|
||||
# Get the appropriate data groups based on data_type
|
||||
data_groups = {}
|
||||
if self.data_type == "images":
|
||||
data_groups = find_data_groups(bpy.data.images)
|
||||
data_groups = RBST_DatRem_find_data_groups(bpy.data.images)
|
||||
elif self.data_type == "materials":
|
||||
data_groups = find_data_groups(bpy.data.materials)
|
||||
data_groups = RBST_DatRem_find_data_groups(bpy.data.materials)
|
||||
elif self.data_type == "fonts":
|
||||
data_groups = find_data_groups(bpy.data.fonts)
|
||||
data_groups = RBST_DatRem_find_data_groups(bpy.data.fonts)
|
||||
elif self.data_type == "worlds":
|
||||
data_groups = find_data_groups(bpy.data.worlds)
|
||||
data_groups = RBST_DatRem_find_data_groups(bpy.data.worlds)
|
||||
|
||||
# Process only groups with more than one item
|
||||
for base_name, items in data_groups.items():
|
||||
@@ -762,7 +834,7 @@ class DATAREMAP_OT_SelectAllGroups(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Update the toggle group selection operator to handle shift-click range selection
|
||||
class DATAREMAP_OT_ToggleGroupSelection(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_ToggleGroupSelection(bpy.types.Operator):
|
||||
"""Toggle whether this group should be included in remapping"""
|
||||
bl_idname = "bst.toggle_group_selection"
|
||||
bl_label = "Toggle Group Selection"
|
||||
@@ -803,13 +875,13 @@ class DATAREMAP_OT_ToggleGroupSelection(bpy.types.Operator):
|
||||
# Get all data groups for this data type
|
||||
data_groups = []
|
||||
if self.data_type == "images":
|
||||
data_groups = list(find_data_groups(bpy.data.images).keys())
|
||||
data_groups = list(RBST_DatRem_find_data_groups(bpy.data.images).keys())
|
||||
elif self.data_type == "materials":
|
||||
data_groups = list(find_data_groups(bpy.data.materials).keys())
|
||||
data_groups = list(RBST_DatRem_find_data_groups(bpy.data.materials).keys())
|
||||
elif self.data_type == "fonts":
|
||||
data_groups = list(find_data_groups(bpy.data.fonts).keys())
|
||||
data_groups = list(RBST_DatRem_find_data_groups(bpy.data.fonts).keys())
|
||||
elif self.data_type == "worlds":
|
||||
data_groups = list(find_data_groups(bpy.data.worlds).keys())
|
||||
data_groups = list(RBST_DatRem_find_data_groups(bpy.data.worlds).keys())
|
||||
|
||||
# Find the indices of the last clicked group and the current group
|
||||
try:
|
||||
@@ -869,7 +941,7 @@ class DATAREMAP_OT_ToggleGroupSelection(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a custom draw function for checkboxes that supports drag selection
|
||||
def draw_drag_selectable_checkbox(layout, context, data_type, group_key):
|
||||
def RBST_DatRem_draw_drag_selectable_checkbox(layout, context, data_type, group_key):
|
||||
"""Draw a checkbox that supports drag selection"""
|
||||
# Create a unique key for this group
|
||||
key = f"{data_type}:{group_key}"
|
||||
@@ -885,7 +957,7 @@ def draw_drag_selectable_checkbox(layout, context, data_type, group_key):
|
||||
op.group_key = group_key
|
||||
op.data_type = data_type
|
||||
|
||||
def search_matches_group(group, search_string):
|
||||
def RBST_DatRem_search_matches_group(group, search_string):
|
||||
"""Check if search string matches group base name or any item in group"""
|
||||
if not search_string:
|
||||
return True
|
||||
@@ -901,7 +973,7 @@ def search_matches_group(group, search_string):
|
||||
return False
|
||||
|
||||
# Update the UI code to use the custom draw function
|
||||
def draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
def RBST_DatRem_draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
"""Draw the list of duplicate data items with drag-selectable checkboxes and click to rename"""
|
||||
box_dup = layout.box()
|
||||
|
||||
@@ -945,7 +1017,7 @@ def draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
search_string = getattr(context.scene, search_prop_name)
|
||||
|
||||
if search_string:
|
||||
group_items = [group for group in group_items if search_matches_group(group, search_string)]
|
||||
group_items = [group for group in group_items if RBST_DatRem_search_matches_group(group, search_string)]
|
||||
|
||||
# Sort by selection if enabled
|
||||
sort_prop_name = f"dataremap_sort_{data_type}"
|
||||
@@ -965,7 +1037,7 @@ def draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
row = box_dup.row()
|
||||
|
||||
# Add checkbox to include/exclude this group using the custom draw function
|
||||
draw_drag_selectable_checkbox(row, context, data_type, base_name)
|
||||
RBST_DatRem_draw_drag_selectable_checkbox(row, context, data_type, base_name)
|
||||
|
||||
# Add dropdown toggle
|
||||
group_key = f"{data_type}:{base_name}"
|
||||
@@ -979,7 +1051,7 @@ def draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
exp_op.data_type = data_type
|
||||
|
||||
# Find the original data item (target)
|
||||
target_item = find_target_data(items)
|
||||
target_item = RBST_DatRem_find_target_data(items)
|
||||
|
||||
# Add icon based on data type
|
||||
if data_type == "images":
|
||||
@@ -1046,10 +1118,10 @@ def draw_data_duplicates(layout, context, data_type, data_groups):
|
||||
rename_op.data_type = data_type
|
||||
rename_op.old_name = item.name
|
||||
|
||||
class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
class RBST_DatRem_PT_BulkDataRemap(bpy.types.Panel):
|
||||
"""Bulk Data Remap Panel"""
|
||||
bl_label = "Bulk Data Remap"
|
||||
bl_idname = "VIEW3D_PT_bulk_data_remap"
|
||||
bl_idname = "RBST_DatRem_PT_bulk_data_remap"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Edit'
|
||||
@@ -1068,25 +1140,25 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
linked_types = []
|
||||
linked_paths = set()
|
||||
|
||||
if context.scene.dataremap_images and has_linked_datablocks(bpy.data.images):
|
||||
if context.scene.dataremap_images and RBST_DatRem_has_linked_datablocks(bpy.data.images):
|
||||
linked_datablocks_found = True
|
||||
linked_types.append("images")
|
||||
linked_paths.update(get_linked_file_paths(bpy.data.images))
|
||||
linked_paths.update(RBST_DatRem_get_linked_file_paths(bpy.data.images))
|
||||
|
||||
if context.scene.dataremap_materials and has_linked_datablocks(bpy.data.materials):
|
||||
if context.scene.dataremap_materials and RBST_DatRem_has_linked_datablocks(bpy.data.materials):
|
||||
linked_datablocks_found = True
|
||||
linked_types.append("materials")
|
||||
linked_paths.update(get_linked_file_paths(bpy.data.materials))
|
||||
linked_paths.update(RBST_DatRem_get_linked_file_paths(bpy.data.materials))
|
||||
|
||||
if context.scene.dataremap_fonts and has_linked_datablocks(bpy.data.fonts):
|
||||
if context.scene.dataremap_fonts and RBST_DatRem_has_linked_datablocks(bpy.data.fonts):
|
||||
linked_datablocks_found = True
|
||||
linked_types.append("fonts")
|
||||
linked_paths.update(get_linked_file_paths(bpy.data.fonts))
|
||||
linked_paths.update(RBST_DatRem_get_linked_file_paths(bpy.data.fonts))
|
||||
|
||||
if context.scene.dataremap_worlds and has_linked_datablocks(bpy.data.worlds):
|
||||
if context.scene.dataremap_worlds and RBST_DatRem_has_linked_datablocks(bpy.data.worlds):
|
||||
linked_datablocks_found = True
|
||||
linked_types.append("worlds")
|
||||
linked_paths.update(get_linked_file_paths(bpy.data.worlds))
|
||||
linked_paths.update(RBST_DatRem_get_linked_file_paths(bpy.data.worlds))
|
||||
|
||||
# Display warning about linked datablocks in a separate section if found
|
||||
if linked_datablocks_found:
|
||||
@@ -1116,20 +1188,20 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
col = box.column(align=True)
|
||||
|
||||
# Count duplicates and numbered suffixes for each type
|
||||
image_groups = find_data_groups(bpy.data.images)
|
||||
material_groups = find_data_groups(bpy.data.materials)
|
||||
font_groups = find_data_groups(bpy.data.fonts)
|
||||
world_groups = find_data_groups(bpy.data.worlds)
|
||||
image_groups = RBST_DatRem_find_data_groups(bpy.data.images)
|
||||
material_groups = RBST_DatRem_find_data_groups(bpy.data.materials)
|
||||
font_groups = RBST_DatRem_find_data_groups(bpy.data.fonts)
|
||||
world_groups = RBST_DatRem_find_data_groups(bpy.data.worlds)
|
||||
|
||||
image_duplicates = sum(len(group) - 1 for group in image_groups.values())
|
||||
material_duplicates = sum(len(group) - 1 for group in material_groups.values())
|
||||
font_duplicates = sum(len(group) - 1 for group in font_groups.values())
|
||||
world_duplicates = sum(len(group) - 1 for group in world_groups.values())
|
||||
|
||||
image_numbered = sum(1 for img in bpy.data.images if img.users > 0 and get_base_name(img.name) != img.name)
|
||||
material_numbered = sum(1 for mat in bpy.data.materials if mat.users > 0 and get_base_name(mat.name) != mat.name)
|
||||
font_numbered = sum(1 for font in bpy.data.fonts if font.users > 0 and get_base_name(font.name) != font.name)
|
||||
world_numbered = sum(1 for world in bpy.data.worlds if world.users > 0 and get_base_name(world.name) != world.name)
|
||||
image_numbered = sum(1 for img in bpy.data.images if img.users > 0 and RBST_DatRem_get_base_name(img.name) != img.name)
|
||||
material_numbered = sum(1 for mat in bpy.data.materials if mat.users > 0 and RBST_DatRem_get_base_name(mat.name) != mat.name)
|
||||
font_numbered = sum(1 for font in bpy.data.fonts if font.users > 0 and RBST_DatRem_get_base_name(font.name) != font.name)
|
||||
world_numbered = sum(1 for world in bpy.data.worlds if world.users > 0 and RBST_DatRem_get_base_name(world.name) != world.name)
|
||||
|
||||
# Initialize excluded_remap_groups if it doesn't exist
|
||||
if not hasattr(context.scene, "excluded_remap_groups"):
|
||||
@@ -1165,7 +1237,7 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
|
||||
# Show image duplicates if enabled
|
||||
if context.scene.show_image_duplicates and image_duplicates > 0 and context.scene.dataremap_images:
|
||||
draw_data_duplicates(col, context, "images", image_groups)
|
||||
RBST_DatRem_draw_data_duplicates(col, context, "images", image_groups)
|
||||
|
||||
# Materials
|
||||
row = col.row()
|
||||
@@ -1196,7 +1268,7 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
|
||||
# Show material duplicates if enabled
|
||||
if context.scene.show_material_duplicates and material_duplicates > 0 and context.scene.dataremap_materials:
|
||||
draw_data_duplicates(col, context, "materials", material_groups)
|
||||
RBST_DatRem_draw_data_duplicates(col, context, "materials", material_groups)
|
||||
|
||||
# Fonts
|
||||
row = col.row()
|
||||
@@ -1227,7 +1299,7 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
|
||||
# Show font duplicates if enabled
|
||||
if context.scene.show_font_duplicates and font_duplicates > 0 and context.scene.dataremap_fonts:
|
||||
draw_data_duplicates(col, context, "fonts", font_groups)
|
||||
RBST_DatRem_draw_data_duplicates(col, context, "fonts", font_groups)
|
||||
|
||||
# World
|
||||
row = col.row()
|
||||
@@ -1258,13 +1330,30 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
|
||||
# Show world duplicates if enabled
|
||||
if context.scene.show_world_duplicates and world_duplicates > 0 and context.scene.dataremap_worlds:
|
||||
draw_data_duplicates(col, context, "worlds", world_groups)
|
||||
RBST_DatRem_draw_data_duplicates(col, context, "worlds", world_groups)
|
||||
|
||||
# Add the operator button
|
||||
row = box.row()
|
||||
row.scale_y = 1.5
|
||||
row.operator("bst.bulk_data_remap")
|
||||
|
||||
# Add Data-Block Utils Integration section
|
||||
box.separator()
|
||||
dbu_box = box.box()
|
||||
dbu_box.label(text="Data-Block Utils Integration")
|
||||
dbu_col = dbu_box.column()
|
||||
dbu_col.label(text="Merge duplicates using data-block utilities addon")
|
||||
dbu_col.label(text="Processes: Node Groups, Materials, Lights, Images, Meshes")
|
||||
|
||||
# Check if data-block utilities addon is available
|
||||
if hasattr(context.scene, 'dbu_similar_settings'):
|
||||
dbu_row = dbu_box.row()
|
||||
dbu_row.scale_y = 1.5
|
||||
dbu_row.operator("bst.merge_duplicates_dbu", text="Merge Duplicates (DBU)", icon='FILE_PARENT')
|
||||
else:
|
||||
dbu_box.alert = True
|
||||
dbu_box.label(text="Data-block utilities addon not installed", icon='ERROR')
|
||||
|
||||
# Show total counts
|
||||
total_duplicates = image_duplicates + material_duplicates + font_duplicates + world_duplicates
|
||||
total_numbered = image_numbered + material_numbered + font_numbered + world_numbered
|
||||
@@ -1311,7 +1400,7 @@ class VIEW3D_PT_BulkDataRemap(bpy.types.Panel):
|
||||
row.operator("bst.resync_enforce", text="Resync Enforce", icon='FILE_REFRESH')
|
||||
|
||||
# Add a new operator for toggling data types
|
||||
class DATAREMAP_OT_ToggleDataType(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_ToggleDataType(bpy.types.Operator):
|
||||
"""Toggle whether this data type should be included in remapping"""
|
||||
bl_idname = "bst.toggle_data_type"
|
||||
bl_label = "Toggle Data Type"
|
||||
@@ -1336,7 +1425,7 @@ class DATAREMAP_OT_ToggleDataType(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a new operator for toggling group expansion
|
||||
class DATAREMAP_OT_ToggleGroupExpansion(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_ToggleGroupExpansion(bpy.types.Operator):
|
||||
"""Toggle whether this group should be expanded to show details"""
|
||||
bl_idname = "bst.toggle_group_expansion"
|
||||
bl_label = "Toggle Group Expansion"
|
||||
@@ -1371,7 +1460,7 @@ class DATAREMAP_OT_ToggleGroupExpansion(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Function to get unique linked file paths from datablocks
|
||||
def get_linked_file_paths(data_collection):
|
||||
def RBST_DatRem_get_linked_file_paths(data_collection):
|
||||
"""Get unique file paths of linked libraries from datablocks"""
|
||||
linked_paths = set()
|
||||
|
||||
@@ -1382,7 +1471,7 @@ def get_linked_file_paths(data_collection):
|
||||
|
||||
return linked_paths
|
||||
|
||||
class DATAREMAP_OT_OpenLinkedFile(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_OpenLinkedFile(bpy.types.Operator):
|
||||
"""Open the linked file in a new Blender instance"""
|
||||
bl_idname = "bst.open_linked_file"
|
||||
bl_label = "Open Linked File"
|
||||
@@ -1411,7 +1500,7 @@ class DATAREMAP_OT_OpenLinkedFile(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a new operator for renaming datablocks
|
||||
class DATAREMAP_OT_RenameDatablock(bpy.types.Operator):
|
||||
class RBST_DatRem_OT_RenameDatablock(bpy.types.Operator):
|
||||
"""Click to rename datablock"""
|
||||
bl_idname = "bst.rename_datablock_remap"
|
||||
bl_label = "Rename Datablock"
|
||||
@@ -1483,21 +1572,22 @@ class DATAREMAP_OT_RenameDatablock(bpy.types.Operator):
|
||||
|
||||
# List of all classes in this module
|
||||
classes = (
|
||||
DATAREMAP_OT_RemapData,
|
||||
DATAREMAP_OT_PurgeUnused,
|
||||
DATAREMAP_OT_ToggleDataType,
|
||||
DATAREMAP_OT_ToggleGroupExclusion,
|
||||
DATAREMAP_OT_SelectAllGroups,
|
||||
VIEW3D_PT_BulkDataRemap,
|
||||
DATAREMAP_OT_ToggleGroupExpansion,
|
||||
DATAREMAP_OT_ToggleGroupSelection,
|
||||
DATAREMAP_OT_OpenLinkedFile,
|
||||
DATAREMAP_OT_RenameDatablock,
|
||||
RBST_DatRem_OT_RemapData,
|
||||
RBST_DatRem_OT_MergeDuplicatesWithDBU,
|
||||
RBST_DatRem_OT_PurgeUnused,
|
||||
RBST_DatRem_OT_ToggleDataType,
|
||||
RBST_DatRem_OT_ToggleGroupExclusion,
|
||||
RBST_DatRem_OT_SelectAllGroups,
|
||||
RBST_DatRem_PT_BulkDataRemap,
|
||||
RBST_DatRem_OT_ToggleGroupExpansion,
|
||||
RBST_DatRem_OT_ToggleGroupSelection,
|
||||
RBST_DatRem_OT_OpenLinkedFile,
|
||||
RBST_DatRem_OT_RenameDatablock,
|
||||
)
|
||||
|
||||
# Registration
|
||||
def register():
|
||||
register_dataremap_properties()
|
||||
RBST_DatRem_register_properties()
|
||||
|
||||
for cls in classes:
|
||||
compat.safe_register_class(cls)
|
||||
@@ -1507,6 +1597,6 @@ def unregister():
|
||||
compat.safe_unregister_class(cls)
|
||||
# Unregister properties
|
||||
try:
|
||||
unregister_dataremap_properties()
|
||||
RBST_DatRem_unregister_properties()
|
||||
except Exception:
|
||||
pass
|
||||
+52
-52
@@ -5,7 +5,7 @@ import os
|
||||
import re
|
||||
from ..utils import compat
|
||||
|
||||
class REMOVE_EXT_OT_summary_dialog(bpy.types.Operator):
|
||||
class RBST_PathMan_OT_summary_dialog(bpy.types.Operator):
|
||||
"""Show remove extensions operation summary"""
|
||||
bl_idname = "remove_ext.summary_dialog"
|
||||
bl_label = "Remove Extensions Summary"
|
||||
@@ -204,7 +204,7 @@ def bulk_remap_paths(mapping_dict):
|
||||
return (success_count, failed_list)
|
||||
|
||||
# Properties for path management
|
||||
class BST_PathProperties(PropertyGroup):
|
||||
class RBST_PathMan_PG_PathProperties(PropertyGroup):
|
||||
# Active image pointer
|
||||
active_image: PointerProperty(
|
||||
name="Image",
|
||||
@@ -328,7 +328,7 @@ class BST_PathProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
# Operator to remap a single datablock path
|
||||
class BST_OT_remap_path(Operator):
|
||||
class RBST_PathMan_OT_remap_path(Operator):
|
||||
bl_idname = "bst.remap_path"
|
||||
bl_label = "Remap Path"
|
||||
bl_description = "Change the filepath and filepath_raw for a datablock"
|
||||
@@ -374,7 +374,7 @@ class BST_OT_remap_path(Operator):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
# Operator to toggle all image selections
|
||||
class BST_OT_toggle_select_all(Operator):
|
||||
class RBST_PathMan_OT_toggle_select_all(Operator):
|
||||
bl_idname = "bst.toggle_select_all"
|
||||
bl_label = "Toggle All"
|
||||
bl_description = "Toggle selection of all datablocks"
|
||||
@@ -394,7 +394,7 @@ class BST_OT_toggle_select_all(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Operator to remap multiple paths at once
|
||||
class BST_OT_bulk_remap(Operator):
|
||||
class RBST_PathMan_OT_bulk_remap(Operator):
|
||||
bl_idname = "bst.bulk_remap"
|
||||
bl_label = "Remap Paths"
|
||||
bl_description = "Apply the new path to all selected datablocks"
|
||||
@@ -493,7 +493,7 @@ class BST_OT_bulk_remap(Operator):
|
||||
return 0.05 # Process next item in 0.05 seconds (50ms) for better stability
|
||||
|
||||
# Operator to toggle path editing mode
|
||||
class BST_OT_toggle_path_edit(Operator):
|
||||
class RBST_PathMan_OT_toggle_path_edit(Operator):
|
||||
bl_idname = "bst.toggle_path_edit"
|
||||
bl_label = "Toggle Path Edit"
|
||||
bl_description = "Toggle between view and edit mode for paths"
|
||||
@@ -535,7 +535,7 @@ class BST_OT_toggle_path_edit(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Operator to select all images used in the current material
|
||||
class BST_OT_select_material_images(Operator):
|
||||
class RBST_PathMan_OT_select_material_images(Operator):
|
||||
bl_idname = "bst.select_material_images"
|
||||
bl_label = "Select Material Images"
|
||||
bl_description = "Select all images used in the current material in the node editor"
|
||||
@@ -569,7 +569,7 @@ class BST_OT_select_material_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Operator to select active/selected image texture nodes
|
||||
class BST_OT_select_active_images(Operator):
|
||||
class RBST_PathMan_OT_select_active_images(Operator):
|
||||
bl_idname = "bst.select_active_images"
|
||||
bl_label = "Select Active Images"
|
||||
bl_description = "Select all images from currently selected texture nodes in the node editor"
|
||||
@@ -603,7 +603,7 @@ class BST_OT_select_active_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Operator to select all images with absolute paths
|
||||
class BST_OT_select_absolute_images(Operator):
|
||||
class RBST_PathMan_OT_select_absolute_images(Operator):
|
||||
bl_idname = "bst.select_absolute_images"
|
||||
bl_label = "Select Absolute Images"
|
||||
bl_description = "Select all images with absolute file paths"
|
||||
@@ -658,7 +658,7 @@ class BST_OT_select_absolute_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add a class for renaming datablocks
|
||||
class BST_OT_rename_datablock(Operator):
|
||||
class RBST_PathMan_OT_rename_datablock(Operator):
|
||||
"""Click to rename datablock"""
|
||||
bl_idname = "bst.rename_datablock"
|
||||
bl_label = "Rename Datablock"
|
||||
@@ -706,7 +706,7 @@ class BST_OT_rename_datablock(Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Update class for shift+click selection
|
||||
class BST_OT_toggle_image_selection(Operator):
|
||||
class RBST_PathMan_OT_toggle_image_selection(Operator):
|
||||
"""Toggle whether this image should be included in bulk operations"""
|
||||
bl_idname = "bst.toggle_image_selection"
|
||||
bl_label = "Toggle Image Selection"
|
||||
@@ -759,7 +759,7 @@ class BST_OT_toggle_image_selection(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Add new operator for reusing material name in path
|
||||
class BST_OT_reuse_material_path(Operator):
|
||||
class RBST_PathMan_OT_reuse_material_path(Operator):
|
||||
"""Use the active material's name in the path"""
|
||||
bl_idname = "bst.reuse_material_path"
|
||||
bl_label = "Use Material Path"
|
||||
@@ -793,7 +793,7 @@ class BST_OT_reuse_material_path(Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Add new operator for reusing blend file name in path
|
||||
class BST_OT_reuse_blend_name(Operator):
|
||||
class RBST_PathMan_OT_reuse_blend_name(Operator):
|
||||
"""Use the current blend file name in the path"""
|
||||
bl_idname = "bst.reuse_blend_name"
|
||||
bl_label = "Use Blend Name"
|
||||
@@ -817,7 +817,7 @@ class BST_OT_reuse_blend_name(Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Make Paths Relative Operator
|
||||
class BST_OT_make_paths_relative(Operator):
|
||||
class RBST_PathMan_OT_make_paths_relative(Operator):
|
||||
bl_idname = "bst.make_paths_relative"
|
||||
bl_label = "Make Paths Relative"
|
||||
bl_description = "Convert absolute paths to relative paths for all datablocks"
|
||||
@@ -829,7 +829,7 @@ class BST_OT_make_paths_relative(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Make Paths Absolute Operator
|
||||
class BST_OT_make_paths_absolute(Operator):
|
||||
class RBST_PathMan_OT_make_paths_absolute(Operator):
|
||||
bl_idname = "bst.make_paths_absolute"
|
||||
bl_label = "Make Paths Absolute"
|
||||
bl_description = "Convert relative paths to absolute paths for all datablocks"
|
||||
@@ -841,7 +841,7 @@ class BST_OT_make_paths_absolute(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Pack Images Operator
|
||||
class BST_OT_pack_images(Operator):
|
||||
class RBST_PathMan_OT_pack_images(Operator):
|
||||
bl_idname = "bst.pack_images"
|
||||
bl_label = "Pack Images"
|
||||
bl_description = "Pack selected images into the .blend file"
|
||||
@@ -883,7 +883,7 @@ class BST_OT_pack_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Unpack Images Operator
|
||||
class BST_OT_unpack_images(Operator):
|
||||
class RBST_PathMan_OT_unpack_images(Operator):
|
||||
bl_idname = "bst.unpack_images"
|
||||
bl_label = "Unpack Images (Use Local)"
|
||||
bl_description = "Unpack selected images to their file paths using the 'USE_LOCAL' option"
|
||||
@@ -918,7 +918,7 @@ class BST_OT_unpack_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Remove Packed Images Operator
|
||||
class BST_OT_remove_packed_images(Operator):
|
||||
class RBST_PathMan_OT_remove_packed_images(Operator):
|
||||
bl_idname = "bst.remove_packed_images"
|
||||
bl_label = "Remove Packed Data"
|
||||
bl_description = "Remove packed image data without saving to disk"
|
||||
@@ -953,7 +953,7 @@ class BST_OT_remove_packed_images(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
# Save All Images Operator
|
||||
class BST_OT_save_all_images(Operator):
|
||||
class RBST_PathMan_OT_save_all_images(Operator):
|
||||
bl_idname = "bst.save_all_images"
|
||||
bl_label = "Save All Images"
|
||||
bl_description = "Save all selected images to image paths"
|
||||
@@ -1059,7 +1059,7 @@ class BST_OT_save_all_images(Operator):
|
||||
return 0.05 # Process next item in 0.05 seconds (50ms) for better stability
|
||||
|
||||
# Remove Extensions Operator
|
||||
class BST_OT_remove_extensions(Operator):
|
||||
class RBST_PathMan_OT_remove_extensions(Operator):
|
||||
bl_idname = "bst.remove_extensions"
|
||||
bl_label = "Remove Extensions"
|
||||
bl_description = "Remove common file extensions from selected image datablock names."
|
||||
@@ -1148,7 +1148,7 @@ class BST_OT_remove_extensions(Operator):
|
||||
removal_details=details_text.strip())
|
||||
|
||||
# Add new operator for flat color texture renaming
|
||||
class BST_OT_rename_flat_colors(Operator):
|
||||
class RBST_PathMan_OT_rename_flat_colors(Operator):
|
||||
"""Rename flat color textures to their hex color values"""
|
||||
bl_idname = "bst.rename_flat_colors"
|
||||
bl_label = "Rename Flat Colors"
|
||||
@@ -1354,7 +1354,7 @@ class BST_OT_rename_flat_colors(Operator):
|
||||
return 0.05 # Process next item in 0.05 seconds (50ms) for better stability
|
||||
|
||||
# Cancel Operation Operator
|
||||
class BST_OT_cancel_operation(Operator):
|
||||
class RBST_PathMan_OT_cancel_operation(Operator):
|
||||
bl_idname = "bst.cancel_operation"
|
||||
bl_label = "Cancel Operation"
|
||||
bl_description = "Cancel the currently running operation"
|
||||
@@ -1433,9 +1433,9 @@ def get_combined_path(context, datablock_name, extension=""):
|
||||
return path + datablock_name + extension
|
||||
|
||||
# Panel for Shader Editor sidebar
|
||||
class NODE_PT_bulk_path_tools(Panel):
|
||||
class RBST_PathMan_PT_bulk_path_tools(Panel):
|
||||
bl_label = "Bulk Pathing"
|
||||
bl_idname = "NODE_PT_bulk_path_tools"
|
||||
bl_idname = "RBST_PathMan_PT_bulk_path_tools"
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Node'
|
||||
@@ -1643,9 +1643,9 @@ class NODE_PT_bulk_path_tools(Panel):
|
||||
box.label(text="No images in blend file")
|
||||
|
||||
# Sub-panel for existing Bulk Scene Tools
|
||||
class VIEW3D_PT_bulk_path_subpanel(Panel):
|
||||
class RBST_PathMan_PT_bulk_path_subpanel(Panel):
|
||||
bl_label = "Bulk Path Management"
|
||||
bl_idname = "VIEW3D_PT_bulk_path_subpanel"
|
||||
bl_idname = "RBST_PathMan_PT_bulk_path_subpanel"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Edit'
|
||||
@@ -1655,34 +1655,34 @@ class VIEW3D_PT_bulk_path_subpanel(Panel):
|
||||
|
||||
def draw(self, context):
|
||||
# Use the same draw function as the NODE_EDITOR panel
|
||||
NODE_PT_bulk_path_tools.draw(self, context)
|
||||
RBST_PathMan_PT_bulk_path_tools.draw(self, context)
|
||||
|
||||
# Registration function for this module
|
||||
classes = (
|
||||
REMOVE_EXT_OT_summary_dialog,
|
||||
BST_PathProperties,
|
||||
BST_OT_remap_path,
|
||||
BST_OT_toggle_select_all,
|
||||
BST_OT_bulk_remap,
|
||||
BST_OT_toggle_path_edit,
|
||||
BST_OT_select_material_images,
|
||||
BST_OT_select_active_images,
|
||||
BST_OT_select_absolute_images,
|
||||
BST_OT_rename_datablock,
|
||||
BST_OT_toggle_image_selection,
|
||||
BST_OT_reuse_material_path,
|
||||
BST_OT_reuse_blend_name,
|
||||
BST_OT_make_paths_relative,
|
||||
BST_OT_make_paths_absolute,
|
||||
BST_OT_pack_images,
|
||||
BST_OT_unpack_images,
|
||||
BST_OT_remove_packed_images,
|
||||
BST_OT_save_all_images,
|
||||
BST_OT_remove_extensions,
|
||||
BST_OT_rename_flat_colors,
|
||||
BST_OT_cancel_operation,
|
||||
NODE_PT_bulk_path_tools,
|
||||
VIEW3D_PT_bulk_path_subpanel,
|
||||
RBST_PathMan_OT_summary_dialog,
|
||||
RBST_PathMan_PG_PathProperties,
|
||||
RBST_PathMan_OT_remap_path,
|
||||
RBST_PathMan_OT_toggle_select_all,
|
||||
RBST_PathMan_OT_bulk_remap,
|
||||
RBST_PathMan_OT_toggle_path_edit,
|
||||
RBST_PathMan_OT_select_material_images,
|
||||
RBST_PathMan_OT_select_active_images,
|
||||
RBST_PathMan_OT_select_absolute_images,
|
||||
RBST_PathMan_OT_rename_datablock,
|
||||
RBST_PathMan_OT_toggle_image_selection,
|
||||
RBST_PathMan_OT_reuse_material_path,
|
||||
RBST_PathMan_OT_reuse_blend_name,
|
||||
RBST_PathMan_OT_make_paths_relative,
|
||||
RBST_PathMan_OT_make_paths_absolute,
|
||||
RBST_PathMan_OT_pack_images,
|
||||
RBST_PathMan_OT_unpack_images,
|
||||
RBST_PathMan_OT_remove_packed_images,
|
||||
RBST_PathMan_OT_save_all_images,
|
||||
RBST_PathMan_OT_remove_extensions,
|
||||
RBST_PathMan_OT_rename_flat_colors,
|
||||
RBST_PathMan_OT_cancel_operation,
|
||||
RBST_PathMan_PT_bulk_path_tools,
|
||||
RBST_PathMan_PT_bulk_path_subpanel,
|
||||
)
|
||||
|
||||
def register():
|
||||
@@ -1690,7 +1690,7 @@ def register():
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
# Register properties
|
||||
bpy.types.Scene.bst_path_props = PointerProperty(type=BST_PathProperties)
|
||||
bpy.types.Scene.bst_path_props = PointerProperty(type=RBST_PathMan_PG_PathProperties)
|
||||
|
||||
# Add custom property to images for selection
|
||||
bpy.types.Image.bst_selected = BoolProperty(
|
||||
|
||||
+8
-18
@@ -4,15 +4,15 @@ from ..ops.remove_custom_split_normals import RemoveCustomSplitNormals
|
||||
from ..ops.create_ortho_camera import CreateOrthoCamera
|
||||
from ..ops.spawn_scene_structure import SpawnSceneStructure
|
||||
from ..ops.delete_single_keyframe_actions import DeleteSingleKeyframeActions
|
||||
from ..ops.find_material_users import FindMaterialUsers, MATERIAL_USERS_OT_summary_dialog
|
||||
from ..ops.remove_unused_material_slots import RemoveUnusedMaterialSlots
|
||||
from ..ops.convert_relations_to_constraint import ConvertRelationsToConstraint
|
||||
from ..ops.white_world import WhiteWorld
|
||||
from ..utils import compat
|
||||
|
||||
class BulkSceneGeneral(bpy.types.Panel):
|
||||
class RBST_SceneGen_PT_BulkSceneGeneral(bpy.types.Panel):
|
||||
"""Bulk Scene General Panel"""
|
||||
bl_label = "Scene General"
|
||||
bl_idname = "VIEW3D_PT_bulk_scene_general"
|
||||
bl_idname = "RBST_SceneGen_PT_bulk_scene_general"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Edit'
|
||||
@@ -28,6 +28,8 @@ class BulkSceneGeneral(bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.scale_y = 1.2
|
||||
row.operator("bst.spawn_scene_structure", text="Spawn Scene Structure", icon='OUTLINER_COLLECTION')
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.white_world", text="White World", icon='WORLD')
|
||||
|
||||
# Mesh section
|
||||
box = layout.box()
|
||||
@@ -49,8 +51,6 @@ class BulkSceneGeneral(bpy.types.Panel):
|
||||
box.label(text="Materials")
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.remove_unused_material_slots", text="Remove Unused Material Slots", icon='MATERIAL')
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.find_material_users", text="Find Material Users", icon='VIEWZOOM')
|
||||
|
||||
# Animation Data section
|
||||
box = layout.box()
|
||||
@@ -62,14 +62,13 @@ class BulkSceneGeneral(bpy.types.Panel):
|
||||
|
||||
# List of all classes in this module
|
||||
classes = (
|
||||
BulkSceneGeneral,
|
||||
RBST_SceneGen_PT_BulkSceneGeneral,
|
||||
NoSubdiv, # Add NoSubdiv operator class
|
||||
RemoveCustomSplitNormals,
|
||||
CreateOrthoCamera,
|
||||
SpawnSceneStructure,
|
||||
WhiteWorld,
|
||||
DeleteSingleKeyframeActions,
|
||||
FindMaterialUsers,
|
||||
MATERIAL_USERS_OT_summary_dialog,
|
||||
RemoveUnusedMaterialSlots,
|
||||
ConvertRelationsToConstraint,
|
||||
)
|
||||
@@ -84,19 +83,10 @@ def register():
|
||||
description="Apply only to selected objects",
|
||||
default=True
|
||||
)
|
||||
# Register temporary material property for Find Material Users operator
|
||||
bpy.types.Scene.bst_temp_material = bpy.props.PointerProperty(
|
||||
name="Temporary Material",
|
||||
description="Temporary material selection for Find Material Users operator",
|
||||
type=bpy.types.Material
|
||||
)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
# Unregister the window manager property
|
||||
if hasattr(bpy.types.WindowManager, "bst_no_subdiv_only_selected"):
|
||||
del bpy.types.WindowManager.bst_no_subdiv_only_selected
|
||||
# Unregister temporary material property
|
||||
if hasattr(bpy.types.Scene, "bst_temp_material"):
|
||||
del bpy.types.Scene.bst_temp_material
|
||||
del bpy.types.WindowManager.bst_no_subdiv_only_selected
|
||||
+153
-67
@@ -6,9 +6,10 @@ from enum import Enum
|
||||
import colorsys # Add colorsys for RGB to HSV conversion
|
||||
from ..ops.select_diffuse_nodes import select_diffuse_nodes # Import the specific function
|
||||
from ..utils import compat
|
||||
from ..utils import version
|
||||
|
||||
# Material processing status enum
|
||||
class MaterialStatus(Enum):
|
||||
class RBST_ViewDisp_MaterialStatus(Enum):
|
||||
PENDING = 0
|
||||
PROCESSING = 1
|
||||
COMPLETED = 2
|
||||
@@ -26,7 +27,7 @@ material_queue = []
|
||||
current_index = 0
|
||||
|
||||
# Scene properties for viewport display settings
|
||||
def register_viewport_properties():
|
||||
def RBST_ViewDisp_register_properties():
|
||||
bpy.types.Scene.viewport_colors_selected_only = bpy.props.BoolProperty( # type: ignore
|
||||
name="Selected Objects Only",
|
||||
description="Apply viewport colors only to materials in selected objects",
|
||||
@@ -104,7 +105,7 @@ def unregister_viewport_properties():
|
||||
del bpy.types.Scene.viewport_colors_show_advanced
|
||||
del bpy.types.Scene.show_material_results
|
||||
|
||||
class VIEWPORT_OT_SetViewportColors(bpy.types.Operator):
|
||||
class RBST_ViewDisp_OT_SetViewportColors(bpy.types.Operator):
|
||||
"""Set Viewport Display colors from BSDF base color or texture"""
|
||||
bl_idname = "bst.set_viewport_colors"
|
||||
bl_label = "Set Viewport Colors"
|
||||
@@ -252,11 +253,11 @@ class VIEWPORT_OT_SetViewportColors(bpy.types.Operator):
|
||||
failed_count = 0
|
||||
|
||||
for _, status in material_results.values():
|
||||
if status == MaterialStatus.PREVIEW_BASED:
|
||||
if status == RBST_ViewDisp_MaterialStatus.PREVIEW_BASED:
|
||||
preview_count += 1
|
||||
elif status == MaterialStatus.COMPLETED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.COMPLETED:
|
||||
node_count += 1
|
||||
elif status == MaterialStatus.FAILED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.FAILED:
|
||||
failed_count += 1
|
||||
|
||||
# Use a popup menu instead of self.report since this might be called from a timer
|
||||
@@ -268,7 +269,7 @@ class VIEWPORT_OT_SetViewportColors(bpy.types.Operator):
|
||||
bpy.context.window_manager.popup_menu(draw_popup, title="Processing Complete", icon='INFO')
|
||||
|
||||
|
||||
class VIEWPORT_OT_RefreshMaterialPreviews(bpy.types.Operator):
|
||||
class RBST_ViewDisp_OT_RefreshMaterialPreviews(bpy.types.Operator):
|
||||
"""Regenerate material previews to avoid stale thumbnails"""
|
||||
bl_idname = "bst.refresh_material_previews"
|
||||
bl_label = "Refresh Material Previews"
|
||||
@@ -276,22 +277,37 @@ class VIEWPORT_OT_RefreshMaterialPreviews(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
forced_count = 0
|
||||
try:
|
||||
bpy.ops.wm.previews_clear()
|
||||
bpy.ops.wm.previews_batch_generate()
|
||||
bpy.ops.wm.previews_ensure()
|
||||
except Exception as exc:
|
||||
self.report({'WARNING'}, f"Pre-clearing previews failed: {exc}")
|
||||
|
||||
# Skip the preview clearing operators in Blender 5.0 - they can cause crashes
|
||||
# Instead, we'll just regenerate previews for each material individually
|
||||
# This is safer and avoids the EXCEPTION_ACCESS_VIOLATION crashes
|
||||
if not version.is_version_5_0():
|
||||
# Only use these operators in older Blender versions
|
||||
try:
|
||||
if hasattr(bpy.context, 'temp_override'):
|
||||
with bpy.context.temp_override():
|
||||
if hasattr(bpy.ops.wm, 'previews_clear'):
|
||||
bpy.ops.wm.previews_clear()
|
||||
else:
|
||||
if hasattr(bpy.ops.wm, 'previews_clear'):
|
||||
bpy.ops.wm.previews_clear()
|
||||
except Exception as exc:
|
||||
# These operations are optional - continue even if they fail
|
||||
print(f"BST preview refresh: Pre-clearing previews failed (non-fatal): {exc}")
|
||||
|
||||
temp_obj = self._create_preview_object(context)
|
||||
|
||||
if not temp_obj:
|
||||
self.report({'ERROR'}, "Failed to create preview object. Cannot refresh material previews.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
try:
|
||||
for material in bpy.data.materials:
|
||||
if not material or material.is_grease_pencil:
|
||||
continue
|
||||
|
||||
try:
|
||||
self._force_preview(material, temp_obj)
|
||||
self._force_preview(material, temp_obj, context)
|
||||
forced_count += 1
|
||||
except Exception as exc:
|
||||
print(f"BST preview refresh: failed for {material.name}: {exc}")
|
||||
@@ -303,36 +319,106 @@ class VIEWPORT_OT_RefreshMaterialPreviews(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
def _create_preview_object(self, context):
|
||||
mesh = bpy.data.meshes.new("BST_PreviewMesh")
|
||||
mesh.from_pydata(
|
||||
[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)],
|
||||
[],
|
||||
[(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 3, 2)]
|
||||
)
|
||||
obj = bpy.data.objects.new("BST_PreviewObject", mesh)
|
||||
obj.hide_viewport = True
|
||||
obj.hide_render = True
|
||||
context.scene.collection.objects.link(obj)
|
||||
return obj
|
||||
"""Create a temporary preview object for material preview generation."""
|
||||
try:
|
||||
mesh = bpy.data.meshes.new("RBST_PreviewMesh")
|
||||
mesh.from_pydata(
|
||||
[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)],
|
||||
[],
|
||||
[(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 3, 2)]
|
||||
)
|
||||
obj = bpy.data.objects.new("RBST_PreviewObject", mesh)
|
||||
obj.hide_viewport = True
|
||||
obj.hide_render = True
|
||||
|
||||
# Link to scene collection - may fail in some contexts
|
||||
try:
|
||||
context.scene.collection.objects.link(obj)
|
||||
except Exception:
|
||||
# Fallback: try master collection or view layer
|
||||
if hasattr(context, 'view_layer') and hasattr(context.view_layer, 'active_layer_collection'):
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
elif hasattr(bpy.context, 'scene') and hasattr(bpy.context.scene, 'collection'):
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
|
||||
return obj
|
||||
except Exception as exc:
|
||||
print(f"BST preview refresh: Failed to create preview object: {exc}")
|
||||
return None
|
||||
|
||||
def _cleanup_preview_object(self, obj):
|
||||
"""Safely cleanup the preview object with proper error handling."""
|
||||
if not obj:
|
||||
return
|
||||
mesh = obj.data
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
if mesh:
|
||||
bpy.data.meshes.remove(mesh, do_unlink=True)
|
||||
|
||||
try:
|
||||
# Clear any material assignments first to avoid dangling references
|
||||
if hasattr(obj, 'data') and obj.data and hasattr(obj.data, 'materials'):
|
||||
try:
|
||||
obj.data.materials.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get mesh reference before removing object
|
||||
mesh = obj.data if hasattr(obj, 'data') else None
|
||||
|
||||
# Remove object
|
||||
try:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
except Exception as exc:
|
||||
print(f"BST preview refresh: Failed to remove preview object: {exc}")
|
||||
|
||||
# Remove mesh if it still exists
|
||||
if mesh and mesh.name in bpy.data.meshes:
|
||||
try:
|
||||
bpy.data.meshes.remove(mesh, do_unlink=True)
|
||||
except Exception as exc:
|
||||
print(f"BST preview refresh: Failed to remove preview mesh: {exc}")
|
||||
except Exception as exc:
|
||||
print(f"BST preview refresh: Error during cleanup: {exc}")
|
||||
|
||||
def _force_preview(self, material, temp_obj):
|
||||
if temp_obj.data.materials:
|
||||
temp_obj.data.materials[0] = material
|
||||
else:
|
||||
temp_obj.data.materials.append(material)
|
||||
material.preview_render_type = 'SPHERE'
|
||||
preview = material.preview_ensure()
|
||||
if preview:
|
||||
# Touch icon id to ensure generation
|
||||
_ = preview.icon_id
|
||||
def _force_preview(self, material, temp_obj, context):
|
||||
"""Force preview generation for a material with proper error handling."""
|
||||
try:
|
||||
# Assign material to temp object
|
||||
if temp_obj.data.materials:
|
||||
temp_obj.data.materials[0] = material
|
||||
else:
|
||||
temp_obj.data.materials.append(material)
|
||||
|
||||
# Set preview render type if available
|
||||
if hasattr(material, 'preview_render_type'):
|
||||
material.preview_render_type = 'SPHERE'
|
||||
|
||||
# Ensure preview exists - this may fail in some Blender versions
|
||||
# In Blender 5.0, accessing icon_id immediately after preview_ensure() can cause crashes
|
||||
if hasattr(material, 'preview_ensure'):
|
||||
try:
|
||||
preview = material.preview_ensure()
|
||||
# In Blender 4.2/4.5, accessing icon_id is safe and helps ensure generation
|
||||
# In Blender 5.0+, we skip this to avoid crashes
|
||||
if not version.is_version_5_0() and preview and hasattr(preview, 'icon_id'):
|
||||
try:
|
||||
_ = preview.icon_id
|
||||
except Exception:
|
||||
# If icon_id access fails, that's okay - preview_ensure() is enough
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Preview generation may fail for some materials - this is acceptable
|
||||
print(f"BST preview refresh: Could not generate preview for {material.name}: {exc}")
|
||||
# Try alternative method: force update by accessing preview property
|
||||
if hasattr(material, 'preview'):
|
||||
try:
|
||||
_ = material.preview
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Clear material assignment to avoid keeping references
|
||||
if temp_obj.data.materials:
|
||||
temp_obj.data.materials.clear()
|
||||
except Exception as exc:
|
||||
# Re-raise to be caught by caller
|
||||
raise exc
|
||||
|
||||
|
||||
def correct_viewport_color(color):
|
||||
@@ -373,11 +459,11 @@ def process_material(material, use_vectorized=True):
|
||||
"""Process a material to determine its viewport color"""
|
||||
if not material:
|
||||
print(f"Material is None, using fallback color")
|
||||
return (1, 1, 1), MaterialStatus.PREVIEW_BASED
|
||||
return (1, 1, 1), RBST_ViewDisp_MaterialStatus.PREVIEW_BASED
|
||||
|
||||
if material.is_grease_pencil:
|
||||
print(f"Material {material.name}: is a grease pencil material, using fallback color")
|
||||
return (1, 1, 1), MaterialStatus.PREVIEW_BASED
|
||||
return (1, 1, 1), RBST_ViewDisp_MaterialStatus.PREVIEW_BASED
|
||||
|
||||
try:
|
||||
# Get color from material thumbnail
|
||||
@@ -393,14 +479,14 @@ def process_material(material, use_vectorized=True):
|
||||
corrected_color = correct_viewport_color(color)
|
||||
print(f"Material {material.name}: Corrected thumbnail color = {corrected_color}")
|
||||
|
||||
return corrected_color, MaterialStatus.PREVIEW_BASED
|
||||
return corrected_color, RBST_ViewDisp_MaterialStatus.PREVIEW_BASED
|
||||
else:
|
||||
print(f"Material {material.name}: Could not extract color from thumbnail, using fallback color")
|
||||
return (1, 1, 1), MaterialStatus.PREVIEW_BASED
|
||||
return (1, 1, 1), RBST_ViewDisp_MaterialStatus.PREVIEW_BASED
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing material {material.name}: {e}")
|
||||
return (1, 1, 1), MaterialStatus.FAILED
|
||||
return (1, 1, 1), RBST_ViewDisp_MaterialStatus.FAILED
|
||||
|
||||
def get_average_color(image, use_vectorized=True):
|
||||
"""Calculate the average color of an image"""
|
||||
@@ -719,38 +805,38 @@ def find_diffuse_texture(material):
|
||||
|
||||
def get_status_icon(status):
|
||||
"""Get the icon for a material status"""
|
||||
if status == MaterialStatus.PENDING:
|
||||
if status == RBST_ViewDisp_MaterialStatus.PENDING:
|
||||
return 'TRIA_RIGHT'
|
||||
elif status == MaterialStatus.PROCESSING:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.PROCESSING:
|
||||
return 'SORTTIME'
|
||||
elif status == MaterialStatus.COMPLETED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.COMPLETED:
|
||||
return 'CHECKMARK'
|
||||
elif status == MaterialStatus.PREVIEW_BASED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.PREVIEW_BASED:
|
||||
return 'IMAGE_DATA'
|
||||
elif status == MaterialStatus.FAILED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.FAILED:
|
||||
return 'ERROR'
|
||||
else:
|
||||
return 'QUESTION'
|
||||
|
||||
def get_status_text(status):
|
||||
"""Get the text for a material status"""
|
||||
if status == MaterialStatus.PENDING:
|
||||
if status == RBST_ViewDisp_MaterialStatus.PENDING:
|
||||
return "Pending"
|
||||
elif status == MaterialStatus.PROCESSING:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.PROCESSING:
|
||||
return "Processing"
|
||||
elif status == MaterialStatus.COMPLETED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.COMPLETED:
|
||||
return "Node-based"
|
||||
elif status == MaterialStatus.PREVIEW_BASED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.PREVIEW_BASED:
|
||||
return "Thumbnail-based"
|
||||
elif status == MaterialStatus.FAILED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.FAILED:
|
||||
return "Failed"
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
class VIEW3D_PT_BulkViewportDisplay(bpy.types.Panel):
|
||||
class RBST_ViewDisp_PT_BulkViewportDisplay(bpy.types.Panel):
|
||||
"""Bulk Viewport Display Panel"""
|
||||
bl_label = "Bulk Viewport Display"
|
||||
bl_idname = "VIEW3D_PT_bulk_viewport_display"
|
||||
bl_idname = "RBST_ViewDisp_PT_bulk_viewport_display"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Edit'
|
||||
@@ -827,9 +913,9 @@ class VIEW3D_PT_BulkViewportDisplay(bpy.types.Panel):
|
||||
color, status = material_results[material_name]
|
||||
|
||||
# Update counts
|
||||
if status == MaterialStatus.PREVIEW_BASED:
|
||||
if status == RBST_ViewDisp_MaterialStatus.PREVIEW_BASED:
|
||||
preview_count += 1
|
||||
elif status == MaterialStatus.FAILED:
|
||||
elif status == RBST_ViewDisp_MaterialStatus.FAILED:
|
||||
failed_count += 1
|
||||
|
||||
row = col.row(align=True)
|
||||
@@ -868,7 +954,7 @@ class VIEW3D_PT_BulkViewportDisplay(bpy.types.Panel):
|
||||
layout.separator()
|
||||
layout.operator("bst.select_diffuse_nodes", icon='NODE_TEXTURE')
|
||||
|
||||
class MATERIAL_OT_SelectInEditor(bpy.types.Operator):
|
||||
class RBST_ViewDisp_OT_SelectInEditor(bpy.types.Operator):
|
||||
"""Select this material in the editor"""
|
||||
bl_idname = "bst.select_in_editor"
|
||||
bl_label = "Select Material"
|
||||
@@ -989,7 +1075,7 @@ def get_color_from_preview(material, use_vectorized=True):
|
||||
else:
|
||||
return None
|
||||
|
||||
class VIEWPORT_OT_SelectDiffuseNodes(bpy.types.Operator):
|
||||
class RBST_ViewDisp_OT_SelectDiffuseNodes(bpy.types.Operator):
|
||||
bl_idname = "bst.select_diffuse_nodes"
|
||||
bl_label = "Set Texture Display"
|
||||
bl_description = "Select the most relevant diffuse/base color image texture node in each material"
|
||||
@@ -1005,11 +1091,11 @@ class VIEWPORT_OT_SelectDiffuseNodes(bpy.types.Operator):
|
||||
|
||||
# List of all classes in this module
|
||||
classes = (
|
||||
VIEWPORT_OT_SetViewportColors,
|
||||
VIEWPORT_OT_RefreshMaterialPreviews,
|
||||
VIEW3D_PT_BulkViewportDisplay,
|
||||
MATERIAL_OT_SelectInEditor,
|
||||
VIEWPORT_OT_SelectDiffuseNodes,
|
||||
RBST_ViewDisp_OT_SetViewportColors,
|
||||
RBST_ViewDisp_OT_RefreshMaterialPreviews,
|
||||
RBST_ViewDisp_PT_BulkViewportDisplay,
|
||||
RBST_ViewDisp_OT_SelectInEditor,
|
||||
RBST_ViewDisp_OT_SelectDiffuseNodes,
|
||||
)
|
||||
|
||||
# Registration
|
||||
@@ -1018,12 +1104,12 @@ def register():
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
# Register properties
|
||||
register_viewport_properties()
|
||||
RBST_ViewDisp_register_properties()
|
||||
|
||||
def unregister():
|
||||
# Unregister properties
|
||||
try:
|
||||
unregister_viewport_properties()
|
||||
RBST_ViewDisp_unregister_properties()
|
||||
except Exception:
|
||||
pass
|
||||
# Unregister classes
|
||||
|
||||
Reference in New Issue
Block a user