New material cleaner
1.4.2 - feat: new material cleaner in GP layer menu with 3 options - clean material duplication (with sub option to not clear if color settings are different) - fuse material slots that have the same materials - remove empty slotsgpv2
parent
f10f572bdc
commit
94e3b7a7ad
|
@ -1,6 +1,13 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
1.4.2
|
||||||
|
|
||||||
|
- feat: new material cleaner in GP layer menu with 3 options
|
||||||
|
- clean material duplication (with sub option to not clear if color settings are different)
|
||||||
|
- fuse material slots that have the same materials
|
||||||
|
- remove empty slots
|
||||||
|
|
||||||
1.4.1
|
1.4.1
|
||||||
|
|
||||||
- fix: custom passepartout size limit when dezooming in camera
|
- fix: custom passepartout size limit when dezooming in camera
|
||||||
|
|
180
OP_palettes.py
180
OP_palettes.py
|
@ -282,12 +282,192 @@ class GPTB_OT_copy_active_to_selected_palette(bpy.types.Operator):
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_clean_material_stack(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.clean_material_stack"
|
||||||
|
bl_label = "Clean Material Stack"
|
||||||
|
bl_description = "Clean materials duplication in active GP object stack"
|
||||||
|
bl_options = {"REGISTER", "UNDO"} # , "INTERNAL"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use_clean_mats : bpy.props.BoolProperty(name="Remove Duplication",
|
||||||
|
description="All duplicated material (with suffix .001, .002 ...) will be replaced by the material with clean name (if found in scene)" ,
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
skip_different_materials : bpy.props.BoolProperty(name="Skip Different Material",
|
||||||
|
description="Will not touch duplication if color settings are different (and show infos about skipped materials)",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
use_fuses_mats : bpy.props.BoolProperty(name="Fuse Materials Slots",
|
||||||
|
description="Fuse materials slots when multiple uses same materials",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
remove_empty_slots : bpy.props.BoolProperty(name="Remove Empty Slots",
|
||||||
|
description="Remove slots that haven't any material attached ",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
# skip_binded_empty_slots : bpy.props.BoolProperty(name="Skip Binded Empty slots",
|
||||||
|
# description="Remove only empty slots that haven't any material attached",
|
||||||
|
# default=False)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.ob = context.object
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'use_clean_mats')
|
||||||
|
if self.use_clean_mats:
|
||||||
|
box.prop(self, 'skip_different_materials')
|
||||||
|
|
||||||
|
# layout.separator()
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'use_fuses_mats')
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'remove_empty_slots')
|
||||||
|
# if self.remove_empty_slots:
|
||||||
|
# box.prop(self, 'skip_binded_empty_slots')
|
||||||
|
|
||||||
|
|
||||||
|
def different_gp_mat(self, mata, matb):
|
||||||
|
a = mata.grease_pencil
|
||||||
|
b = matb.grease_pencil
|
||||||
|
if a.color[:] != b.color[:]:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} stroke color is different'
|
||||||
|
if a.fill_color[:] != b.fill_color[:]:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} fill_color color is different'
|
||||||
|
if a.show_stroke != b.show_stroke:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} stroke has different state'
|
||||||
|
if a.show_fill != b.show_fill:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} fill has different state'
|
||||||
|
|
||||||
|
## Clean dups
|
||||||
|
|
||||||
|
def clean_mats_duplication(self, ob):
|
||||||
|
import re
|
||||||
|
diff_ct = 0
|
||||||
|
todel = []
|
||||||
|
if ob.type != 'GPENCIL':
|
||||||
|
return
|
||||||
|
if not hasattr(ob, 'material_slots'):
|
||||||
|
return
|
||||||
|
for i, ms in enumerate(ob.material_slots):
|
||||||
|
mat = ms.material
|
||||||
|
if not mat:
|
||||||
|
continue
|
||||||
|
match = re.search(r'(.*)\.\d{3}$', mat.name)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
basemat = bpy.data.materials.get(match.group(1))
|
||||||
|
if not basemat:
|
||||||
|
continue
|
||||||
|
diff = self.different_gp_mat(mat, basemat)
|
||||||
|
if diff:
|
||||||
|
print(diff)
|
||||||
|
diff_ct += 1
|
||||||
|
if self.skip_different_materials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if mat not in todel:
|
||||||
|
todel.append(mat)
|
||||||
|
ms.material = basemat
|
||||||
|
print(f'{ob.name} : slot {i} >> replaced {mat.name}')
|
||||||
|
mat.use_fake_user = False
|
||||||
|
|
||||||
|
### delete (only when using on all objects loop, else can delete another objects mat...)
|
||||||
|
## for m in reversed(todel):
|
||||||
|
## bpy.data.materials.remove(m)
|
||||||
|
|
||||||
|
if diff_ct:
|
||||||
|
return('INFO', f'{diff_ct} mat skipped >> same name but different color settings!')
|
||||||
|
|
||||||
|
## fuse
|
||||||
|
|
||||||
|
def fuse_object_mats(self, ob):
|
||||||
|
for i in range(len(ob.material_slots))[::-1]:
|
||||||
|
ms = ob.material_slots[i]
|
||||||
|
mat = ms.material
|
||||||
|
# if not mat:
|
||||||
|
# # remove empty slots
|
||||||
|
# if self.remove_empty_slots:
|
||||||
|
# ob.active_material_index = i
|
||||||
|
# bpy.ops.object.material_slot_remove()
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# update mat list
|
||||||
|
mlist = [ms.material for ms in ob.material_slots if ms.material]
|
||||||
|
if mlist.count(mat) > 1:
|
||||||
|
# get first material in list
|
||||||
|
new_mat_id = mlist.index(mat)
|
||||||
|
|
||||||
|
# iterate in all strokes and replace with new_mat_id
|
||||||
|
for l in ob.data.layers:
|
||||||
|
for f in l.frames:
|
||||||
|
for s in f.strokes:
|
||||||
|
if s.material_index == i:
|
||||||
|
s.material_index = new_mat_id
|
||||||
|
|
||||||
|
# delete slot (or add to the remove_slot list
|
||||||
|
ob.active_material_index = i
|
||||||
|
bpy.ops.object.material_slot_remove()
|
||||||
|
|
||||||
|
def delete_empty_material_slots(self, ob):
|
||||||
|
for i in range(len(ob.material_slots))[::-1]:
|
||||||
|
ms = ob.material_slots[i]
|
||||||
|
mat = ms.material
|
||||||
|
if not mat:
|
||||||
|
# is_binded=False
|
||||||
|
# if self.skip_binded_empty_slots:
|
||||||
|
# for l in ob.data.layers:
|
||||||
|
# for f in l.frames:
|
||||||
|
# for s in f.strokes:
|
||||||
|
# if s.material_index == i:
|
||||||
|
# is_binded = True
|
||||||
|
# break
|
||||||
|
# if is_binded:
|
||||||
|
# continue
|
||||||
|
|
||||||
|
ob.active_material_index = i
|
||||||
|
bpy.ops.object.material_slot_remove()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ob = context.object
|
||||||
|
info = None
|
||||||
|
|
||||||
|
if not self.use_clean_mats and not self.use_fuses_mats and not self.remove_empty_slots:
|
||||||
|
self.report({'ERROR'}, 'At least one operation should be selected')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if self.use_clean_mats:
|
||||||
|
info = self.clean_mats_duplication(ob)
|
||||||
|
if self.use_fuses_mats:
|
||||||
|
self.fuse_object_mats(ob)
|
||||||
|
if self.remove_empty_slots:
|
||||||
|
self.delete_empty_material_slots(ob)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
self.report({info[0]}, info[1])
|
||||||
|
# else:
|
||||||
|
# self.report({'WARNING'}, '')
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_OT_load_palette,
|
GPTB_OT_load_palette,
|
||||||
GPTB_OT_save_palette,
|
GPTB_OT_save_palette,
|
||||||
GPTB_OT_load_default_palette,
|
GPTB_OT_load_default_palette,
|
||||||
GPTB_OT_load_blend_palette,
|
GPTB_OT_load_blend_palette,
|
||||||
GPTB_OT_copy_active_to_selected_palette,
|
GPTB_OT_copy_active_to_selected_palette,
|
||||||
|
GPTB_OT_clean_material_stack,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|
|
@ -351,6 +351,7 @@ def palette_manager_menu(self, context):
|
||||||
layout.operator("gp.load_palette", text='Load json Palette', icon='IMPORT').filepath = prefs.palette_path
|
layout.operator("gp.load_palette", text='Load json Palette', icon='IMPORT').filepath = prefs.palette_path
|
||||||
layout.operator("gp.save_palette", text='Save json Palette', icon='EXPORT').filepath = prefs.palette_path
|
layout.operator("gp.save_palette", text='Save json Palette', icon='EXPORT').filepath = prefs.palette_path
|
||||||
layout.operator("gp.load_blend_palette", text='Load color Palette', icon='COLOR').filepath = prefs.palette_path
|
layout.operator("gp.load_blend_palette", text='Load color Palette', icon='COLOR').filepath = prefs.palette_path
|
||||||
|
layout.operator("gp.clean_material_stack", text='Clean material Stack', icon='NODE_MATERIAL')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
|
|
@ -15,7 +15,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Set of tools for Grease Pencil in animation production",
|
"description": "Set of tools for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (1, 4, 1),
|
"version": (1, 4, 2),
|
||||||
"blender": (2, 91, 0),
|
"blender": (2, 91, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
Loading…
Reference in New Issue