-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathui_material_remap.py
157 lines (137 loc) · 6.57 KB
/
ui_material_remap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from bpy.types import Context, UILayout, Object, Event, SpaceProperties
from bpy.props import EnumProperty
from sys import intern
from typing import Union
from .extensions import MaterialRemap, ObjectPropertyGroup
from .registration import register_module_classes_factory, OperatorBase
from . import utils
_MAT_SLOT_ITEMS_CACHE = []
# self seems to be a sort of partial operator, it has access to only
# ['__doc__', '__module__', '__slots__', 'bl_rna', 'rna_type', 'slots_enum']
# when used as the items of the slots_enum property in KeepOnlyMaterialSlotSearch
def _material_slot_items(self, context: Context):
global _MAT_SLOT_ITEMS_CACHE
obj = context.object
items: list[tuple[str, str, str, Union[str, int], int]] = []
for idx, slot in enumerate(obj.material_slots):
unique_id = str(idx)
mat = slot.material
if mat:
label = intern(mat.name)
icon = utils.get_preview(mat).icon_id
else:
label = "(empty slot)"
icon = 'MATERIAL_DATA'
items.append((unique_id, label, label, icon, idx))
# It's important to always have at least one item
if not items:
items.append(('0', "(no material slots)", "Mesh has no material slots", 'ERROR', 0))
if items != _MAT_SLOT_ITEMS_CACHE:
_MAT_SLOT_ITEMS_CACHE = items
return items
class KeepOnlyMaterialSlotSearch(OperatorBase):
"""Pick Material Slot"""
bl_idname = 'keep_only_material_slot_search'
bl_label = "Pick Material Slot"
bl_property = 'slots_enum'
bl_options = {'UNDO', 'INTERNAL'}
slots_enum: EnumProperty(items=_material_slot_items, options={'HIDDEN'})
@classmethod
def poll(cls, context: Context) -> bool:
if not context.object:
return cls.poll_fail("An Object is required")
return True
def execute(self, context: Context) -> set[str]:
obj = context.object
# Get the index of the EnumProperty
material_slot_index = self.properties['slots_enum']
# Set the keep_only_mat_slot index property
group = ObjectPropertyGroup.get_group(obj)
object_settings = group.get_displayed_settings(context.scene)
object_settings.mesh_settings.material_settings.keep_only_mat_slot = material_slot_index
region = context.region
if region and isinstance(context.space_data, SpaceProperties):
# We've changed the keep_only_mat_slot property, update the UI region so that it displays the new value
# immediately. Without this, the UI won't show the property's new value until another part of the ui causes
# a redraw (this can be as simple as mousing over a property)
region.tag_redraw()
return {'FINISHED'}
def invoke(self, context: Context, event: Event) -> set[str]:
context.window_manager.invoke_search_popup(self)
return {'FINISHED'}
class RefreshRemapList(OperatorBase):
bl_idname = 'material_remap_refresh_list'
bl_label = "Refresh Remap List"
@classmethod
def poll(cls, context: Context) -> bool:
if not context.object:
return cls.poll_fail("An Object is required")
return True
def execute(self, context: Context) -> set[str]:
obj = context.object
object_build_settings = ObjectPropertyGroup.get_group(obj).get_displayed_settings(context.scene)
material_settings = object_build_settings.mesh_settings.material_settings
if material_settings.materials_main_op == 'REMAP':
data = material_settings.materials_remap.collection
material_slots = obj.material_slots
num_mappings = len(data)
num_slots = len(material_slots)
if num_mappings != num_slots:
if num_mappings > num_slots:
# Remove the excess mappings
# Iterate in reverse so that we remove the last element each time, so that the indices don't change
# while iterating
for i in reversed(range(num_slots, num_mappings)):
data.remove(i)
else:
# For each missing mapping, add a new mapping and set it to the current material in the
# corresponding slot
for slot in material_slots[num_mappings:num_slots]:
added = data.add()
added.to_mat = slot.material
return {'FINISHED'}
else:
return {'CANCELLED'}
def draw_material_remap_list(layout: UILayout, obj: Object, material_remap: MaterialRemap):
box = layout.box()
box.use_property_decorate = False
box.use_property_split = False
col_flow = box.column_flow(columns=2, align=True)
col1 = col_flow.column()
col2 = col_flow.column()
for idx, (slot, remap) in enumerate(zip(obj.material_slots, material_remap.collection)):
mat = slot.material
row = col1.row()
if mat:
label = f"{slot.material.name}:"
row.label(text=label, icon_value=utils.get_preview(mat).icon_id)
else:
row.label(text="(empty):", icon='MATERIAL_DATA')
row.label(text="", icon='FORWARD')
to_mat = remap.to_mat
if to_mat:
col2.prop(remap, 'to_mat', text="", icon_value=utils.get_preview(to_mat).icon_id)
else:
col2.prop(remap, 'to_mat', text="", icon='MATERIAL_DATA')
num_slots = len(obj.material_slots)
num_remaps = len(material_remap.collection)
if num_slots != num_remaps:
if num_slots > num_remaps:
# There are more slots than remaps, the user needs to refresh the list
box.label(text=f"There are {num_slots - num_remaps} material slots that don't have mappings", icon='ERROR')
box.label(text=f"Refresh the list to add the missing mappings", icon='BLANK1')
else:
# There are extra, unused slots, the user should refresh the list to remove the extras
box.label(text=f"Removed slots, please refresh to remove mappings:", icon='ERROR')
col_flow = box.column_flow(columns=2, align=True)
col_flow.alert = True
col1 = col_flow.column()
col2 = col_flow.column()
for remap in material_remap.collection[num_slots:]:
row = col1.row()
row.label(text="Removed slot:")
row.label(text="", icon='FORWARD')
col2.prop(remap, 'to_mat', text="", emboss=False)
# Draw operator button to refresh the list
box.operator(RefreshRemapList.bl_idname)
register_module_classes_factory(__name__, globals())