gp_toolbox/OP_palettes.py

259 lines
8.9 KiB
Python
Raw Normal View History

2021-01-10 16:47:17 +01:00
import bpy
import json
import os
from bpy_extras.io_utils import ImportHelper, ExportHelper
from pathlib import Path
from .utils import convert_attr, get_addon_prefs
### --- Json serialized material load/save
def load_palette(context, filepath):
with open(filepath, 'r') as fd:
mat_dic = json.load(fd)
# from pprint import pprint
# pprint(mat_dic)
ob = context.object
for mat_name, attrs in mat_dic.items():
curmat = bpy.data.materials.get(mat_name)
if curmat:#exists
if curmat.is_grease_pencil:
if curmat not in ob.data.materials[:]:# add only if it's not already there
ob.data.materials.append(curmat)
continue
else:
mat_name = mat_name+'.01'#rename to avoid conflict
## to create a GP mat (from https://developer.blender.org/T67102)
mat = bpy.data.materials.new(name=mat_name)
bpy.data.materials.create_gpencil_data(mat)#cast to GP mat
ob.data.materials.append(mat)
for attr, value in attrs.items():
setattr(mat.grease_pencil, attr, value)
class GPTB_OT_load_default_palette(bpy.types.Operator):
bl_idname = "gp.load_default_palette"
bl_label = "Load basic palette"
bl_description = "Load a material palette on the current GP object\nif material name already exists in scene it will uses these"
bl_options = {"REGISTER", "INTERNAL"}
# path_to_pal : bpy.props.StringProperty(name="paht to palette", description="path to the palette", default="")
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
def execute(self, context):
# Start Clean (delete unuesed sh*t)
bpy.ops.object.material_slot_remove_unused()
#Rename default solid stroke if still there
line = context.object.data.materials.get('Black')
if line:
line.name = 'line'
if not line:
line = context.object.data.materials.get('Solid Stroke')
if line:
line.name = 'line'
# load json
pfp = Path(bpy.path.abspath(get_addon_prefs().palette_path))
if not pfp.exists():
self.report({'ERROR'}, f'Palette path not found')
return {"CANCELLED"}
base = pfp / 'base.json'
if not base.exists():
self.report({'ERROR'}, f'base.json palette not found in {pfp.as_posix()}')
return {"CANCELLED"}
load_palette(context, base)
self.report({'INFO'}, f'Loaded base Palette')
return {"FINISHED"}
class GPTB_OT_load_palette(bpy.types.Operator, ImportHelper):
bl_idname = "gp.load_palette"
bl_label = "Load palette"
bl_description = "Load a material palette on the current GP object\nif material name already exists in scene it will uses these"
#bl_options = {"REGISTER", "INTERNAL"}
# path_to_pal : bpy.props.StringProperty(name="paht to palette", description="path to the palette", default="")
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
filename_ext = '.json'
filter_glob: bpy.props.StringProperty(default='*.json', options={'HIDDEN'} )#*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp
filepath : bpy.props.StringProperty(
name="File Path",
description="File path used for import",
maxlen= 1024)
def execute(self, context):
# load json
load_palette(context, self.filepath)
self.report({'INFO'}, f'settings loaded from: {os.path.basename(self.filepath)}')
return {"FINISHED"}
class GPTB_OT_save_palette(bpy.types.Operator, ExportHelper):
bl_idname = "gp.save_palette"
bl_label = "save palette"
bl_description = "Save a material palette from material on current GP object."
#bl_options = {"REGISTER", "INTERNAL"}
# path_to_pal : bpy.props.StringProperty(name="paht to palette", description="path to the palette", default="")
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
filter_glob: bpy.props.StringProperty(default='*.json', options={'HIDDEN'})#*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp
filename_ext = '.json'
filepath : bpy.props.StringProperty(
name="File Path",
description="File path used for export",
maxlen= 1024)
def execute(self, context):
ob = context.object
exclusions = ('bl_rna', 'rna_type')
# save json
dic = {}
allmat=[]
for mat in ob.data.materials:
if not mat.is_grease_pencil:
continue
if mat in allmat:
continue
allmat.append(mat)
dic[mat.name] = {}
for attr in dir(mat.grease_pencil):
if attr.startswith('__'):
continue
if attr in exclusions:
continue
if mat.grease_pencil.bl_rna.properties[attr].is_readonly:#avoid readonly
continue
dic[mat.name][attr] = convert_attr(getattr(mat.grease_pencil, attr))
if not dic:
self.report({'ERROR'}, f'No materials on this GP object')
return {"CANCELLED"}
# export
with open(self.filepath, 'w') as fd:
json.dump(dic, fd, indent='\t')
self.report({'INFO'}, f'Palette saved: {self.filepath}')#WARNING, ERROR
return {"FINISHED"}
### --- Direct material append/link from blend file
def load_blend_palette(context, filepath):
'''Load materials on current active object from current chosen blend'''
#from pathlib import Path
#palette_fp = C.preferences.addons['GP_toolbox'].preferences['palette_path']
#fp = Path(palette_fp) / 'christina.blend'
print(f'-- import palette from : {filepath} --')
for ob in context.selected_objects:
if ob.type != 'GPENCIL':
print(f'{ob.name} not a GP object')
continue
print('\n', ob.name, ':')
obj_mats = [m.name for m in ob.data.materials if m]# can found Nonetype
scene_mats = [m.name for m in bpy.data.materials]
# Link into the blend file
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
for name in data_from.materials:
if name.lower() in ('bg', 'line', 'dots stroke'):
continue
if name in obj_mats:
print(f"!- {name} already in object materials")
continue
if name in scene_mats:
print(f'- {name} (found in scene)')
ob.data.materials.append(bpy.data.materials[name])
continue
## TODO find a way to Update color !... complex...
data_to.materials.append(name)
if not data_to.materials:
# print('Nothing to link/append from lib palette!')
continue
print('From palette append:')
for mat in data_to.materials:
print(f'- {mat.name}')
ob.data.materials.append(mat)
print(f'-- import Done --')
## list sources in a palette txt data block
palette_txt = bpy.data.texts.get('palettes')
if not palette_txt:
palette_txt = bpy.data.texts.new('palettes')
lines = [l.body for l in palette_txt.lines]
if not os.path.basename(filepath) in lines:
palette_txt.write('\n' + os.path.basename(filepath))
class GPTB_OT_load_blend_palette(bpy.types.Operator, ImportHelper):
bl_idname = "gp.load_blend_palette"
bl_label = "Load colo palette"
bl_description = "Load a material palette from blend file on the current GP object\nif material name already exists in scene it will uses these"
#bl_options = {"REGISTER", "INTERNAL"}
# path_to_pal : bpy.props.StringProperty(name="paht to palette", description="path to the palette", default="")
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
filename_ext = '.blend'
filter_glob: bpy.props.StringProperty(default='*.blend', options={'HIDDEN'} )#*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp
filepath : bpy.props.StringProperty(
name="File Path",
description="File path used for import",
maxlen= 1024)
def execute(self, context):
# load json
load_blend_palette(context, self.filepath)
self.report({'INFO'}, f'materials loaded from: {os.path.basename(self.filepath)}')
return {"FINISHED"}
classes = (
GPTB_OT_load_palette,
GPTB_OT_save_palette,
GPTB_OT_load_default_palette,
GPTB_OT_load_blend_palette,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)