338 lines
12 KiB
Python
338 lines
12 KiB
Python
|
import bpy
|
|||
|
import re
|
|||
|
import json
|
|||
|
import os
|
|||
|
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
|||
|
from pathlib import Path
|
|||
|
from . import utils
|
|||
|
# from . import blendfile
|
|||
|
|
|||
|
from bpy.types import (
|
|||
|
Panel,
|
|||
|
Operator,
|
|||
|
PropertyGroup,
|
|||
|
UIList,
|
|||
|
)
|
|||
|
|
|||
|
from bpy.props import (
|
|||
|
IntProperty,
|
|||
|
BoolProperty,
|
|||
|
StringProperty,
|
|||
|
FloatProperty,
|
|||
|
EnumProperty,
|
|||
|
PointerProperty,
|
|||
|
)
|
|||
|
|
|||
|
class GPTB_UL_blend_list(UIList):
|
|||
|
# order_by_distance : BoolProperty(default=True)
|
|||
|
|
|||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
|||
|
layout.label(text=item.blend_name)
|
|||
|
|
|||
|
def draw_filter(self, context, layout):
|
|||
|
row = layout.row()
|
|||
|
subrow = row.row(align=True)
|
|||
|
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use ‘*’ as wildcard)
|
|||
|
|
|||
|
# reverse order
|
|||
|
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
|
|||
|
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
|
|||
|
|
|||
|
def filter_items(self, context, data, propname):
|
|||
|
# example : https://docs.blender.org/api/blender_python_api_current/bpy.types.UIList.html
|
|||
|
# This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
|
|||
|
# * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
|
|||
|
# matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
|
|||
|
# * The second one is for reordering, it must return a list containing the new indices of the items (which
|
|||
|
# gives us a mapping org_idx -> new_idx).
|
|||
|
# Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
|
|||
|
# If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
|
|||
|
# returning full lists doing nothing!).
|
|||
|
|
|||
|
collec = getattr(data, propname)
|
|||
|
helper_funcs = bpy.types.UI_UL_list
|
|||
|
|
|||
|
# Default return values.
|
|||
|
flt_flags = []
|
|||
|
flt_neworder = []
|
|||
|
|
|||
|
|
|||
|
# Filtering by name #not working damn !
|
|||
|
if self.filter_name:
|
|||
|
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, collec, "name",
|
|||
|
reverse=self.use_filter_sort_reverse)#self.use_filter_name_reverse)
|
|||
|
|
|||
|
return flt_flags, flt_neworder
|
|||
|
|
|||
|
class GPTB_UL_object_list(UIList):
|
|||
|
# order_by_distance : BoolProperty(default=True)
|
|||
|
|
|||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
|||
|
layout.label(text=item.name)
|
|||
|
|
|||
|
def draw_filter(self, context, layout):
|
|||
|
row = layout.row()
|
|||
|
subrow = row.row(align=True)
|
|||
|
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use ‘*’ as wildcard)
|
|||
|
# reverse order
|
|||
|
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
|
|||
|
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
|
|||
|
|
|||
|
def filter_items(self, context, data, propname):
|
|||
|
collec = getattr(data, propname)
|
|||
|
helper_funcs = bpy.types.UI_UL_list
|
|||
|
# Default return values.
|
|||
|
flt_flags = []
|
|||
|
flt_neworder = []
|
|||
|
if self.filter_name:
|
|||
|
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, collec, "name",
|
|||
|
reverse=self.use_filter_sort_reverse)
|
|||
|
return flt_flags, flt_neworder
|
|||
|
|
|||
|
|
|||
|
def reload_blends(self, context):
|
|||
|
scn = context.scene
|
|||
|
pl_prop = scn.bl_palettes_props
|
|||
|
uilist = scn.bl_palettes_props.blends
|
|||
|
uilist.clear()
|
|||
|
pl_prop['bl_idx'] = 0
|
|||
|
|
|||
|
prefs = utils.get_addon_prefs()
|
|||
|
|
|||
|
if pl_prop.use_project_path:
|
|||
|
palette_fp = prefs.palette_path
|
|||
|
else:
|
|||
|
palette_fp = pl_prop.custom_dir
|
|||
|
|
|||
|
if not palette_fp: # singular
|
|||
|
item = uilist.add()
|
|||
|
item.blend_name = 'No Palette Path Specified'
|
|||
|
reload_objects(self, context)
|
|||
|
return
|
|||
|
|
|||
|
palettes_dir = Path(palette_fp)
|
|||
|
if not palettes_dir.exists():
|
|||
|
item = uilist.add()
|
|||
|
item.blend_name = 'Palette Path not found'
|
|||
|
reload_objects(self, context)
|
|||
|
return
|
|||
|
|
|||
|
blends = [(o.path, Path(o).stem, "") for o in os.scandir(palettes_dir)
|
|||
|
if o.is_file()
|
|||
|
and o.name.endswith('.blend')
|
|||
|
and re.search(r'v\d{3}', o.name, re.I)]
|
|||
|
|
|||
|
blends.sort(key=lambda x: x[1], reverse=False)
|
|||
|
# print('blends found', len(blends))
|
|||
|
|
|||
|
for bl in blends: # populate list
|
|||
|
item = uilist.add()
|
|||
|
|
|||
|
scn.bl_palettes_props['bl_idx'] = len(uilist) - 1 # don't trigger updates
|
|||
|
item.blend_path = bl[0]
|
|||
|
item.blend_name = bl[1]
|
|||
|
|
|||
|
scn.bl_palettes_props.bl_idx = len(uilist) - 1 # trigger update ()
|
|||
|
# reload_objects(self, context) # triggered by above assignation
|
|||
|
|
|||
|
# return len(blends) # return value must be None
|
|||
|
|
|||
|
class GPTB_OT_palettes_reload_blends(Operator):
|
|||
|
bl_idname = "gp.palettes_reload_blends"
|
|||
|
bl_label = "Reload Palette Blends"
|
|||
|
bl_description = "Reload the blends in UI list of palettes linker"
|
|||
|
bl_options = {"REGISTER"} # , "INTERNAL"
|
|||
|
|
|||
|
def execute(self, context):
|
|||
|
reload_blends(self, context)
|
|||
|
# ret = reload_blends(self, context)
|
|||
|
# if ret is None:
|
|||
|
# self.report({'ERROR'}, 'No blend scanned, check palette path')
|
|||
|
# else:
|
|||
|
# self.report({'INFO'}, f'{ret} blends found')
|
|||
|
return {"FINISHED"}
|
|||
|
|
|||
|
def reload_objects(self, context):
|
|||
|
scn = context.scene
|
|||
|
prefs = utils.get_addon_prefs()
|
|||
|
pal_prop = scn.bl_palettes_props
|
|||
|
blend_uil = pal_prop.blends
|
|||
|
obj_uil = pal_prop.objects
|
|||
|
obj_uil.clear()
|
|||
|
pal_prop['ob_idx'] = 0
|
|||
|
|
|||
|
if not len(blend_uil) or (len(blend_uil) == 1 and not bool(blend_uil[0].blend_path)):
|
|||
|
item = obj_uil.add()
|
|||
|
item.name = 'No blend to list object'
|
|||
|
return
|
|||
|
|
|||
|
if not blend_uil[pal_prop.bl_idx].blend_path:
|
|||
|
item = obj_uil.add()
|
|||
|
item.name = 'Selected blend has no path'
|
|||
|
return
|
|||
|
|
|||
|
path_to_blend = Path(blend_uil[pal_prop.bl_idx].blend_path)
|
|||
|
|
|||
|
ob_list = utils.check_objects_in_blend(str(path_to_blend)) # get list of string of all object except camera
|
|||
|
|
|||
|
ob_list.sort(reverse=False) # filter object by name ?
|
|||
|
# remove camera
|
|||
|
# print('blends found', len(blends))
|
|||
|
|
|||
|
for ob_name in ob_list: # populate list
|
|||
|
item = obj_uil.add()
|
|||
|
item.name = ob_name
|
|||
|
item.path = str(path_to_blend / 'Object' / ob_name)
|
|||
|
|
|||
|
pal_prop.ob_idx = len(obj_uil) - 1
|
|||
|
# return len(ob_list) # must return None if used in update
|
|||
|
|
|||
|
|
|||
|
## PROPS
|
|||
|
|
|||
|
class GPTB_PG_blend_prop(PropertyGroup):
|
|||
|
blend_name : StringProperty() # stem of the path
|
|||
|
blend_path : StringProperty() # ful path
|
|||
|
# select: BoolProperty(update=update_select) # use and update to set the plane selection
|
|||
|
|
|||
|
class GPTB_PG_object_prop(PropertyGroup):
|
|||
|
name : StringProperty() # stem of the path
|
|||
|
path : StringProperty() # Object / Material ?
|
|||
|
|
|||
|
class GPTB_PG_palette_settings(PropertyGroup):
|
|||
|
bl_idx : IntProperty(update=reload_objects) # update_on_index_change to reload object
|
|||
|
blends : bpy.props.CollectionProperty(type=GPTB_PG_blend_prop)
|
|||
|
|
|||
|
ob_idx : IntProperty()
|
|||
|
objects : bpy.props.CollectionProperty(type=GPTB_PG_object_prop)
|
|||
|
|
|||
|
use_project_path : BoolProperty(name='Use Project Palettes',
|
|||
|
default=True, description='Use palettes directory specified in gp toolbox addon preferences',
|
|||
|
update=reload_blends)
|
|||
|
|
|||
|
show_path : BoolProperty(name='Show path',
|
|||
|
default=True, description='Show Palette directoty filepath')
|
|||
|
|
|||
|
custom_dir : StringProperty(name='Custom Palettes Directory', subtype='DIR_PATH',
|
|||
|
description='Use choosen directory to load blend palettes',
|
|||
|
update=reload_blends)
|
|||
|
|
|||
|
import_type : EnumProperty(
|
|||
|
name="Import Type", description="Choose inmport type: link, append, append reuse (keep existing materials)",
|
|||
|
default='LINK', options={'ANIMATABLE'}, update=None, get=None, set=None,
|
|||
|
items=(
|
|||
|
('LINK', 'Link', 'Link materials to selected object', 0),
|
|||
|
('APPEND', 'Append', 'Append materials to selected objects', 1),
|
|||
|
('APPEND_REUSE', 'Append (Reuse)', 'Append materials to selected objects\nkeep those already there', 2),
|
|||
|
)
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# fav_blend: StringProperty() ## mark a blend as prefered ? (need to be stored in prefereneces to restore in other blend...)
|
|||
|
|
|||
|
class GPTB_OT_import_obj_palette(Operator):
|
|||
|
bl_idname = "gp.import_obj_palette"
|
|||
|
bl_label = "Import Object Palette"
|
|||
|
bl_description = "Import object palette from blend"
|
|||
|
bl_options = {"REGISTER", "INTERNAL"}
|
|||
|
|
|||
|
def execute(self, context):
|
|||
|
pl_prop = context.scene.bl_palettes_props
|
|||
|
path = pl_prop.objects[pl_prop.ob_idx].path
|
|||
|
self.report({'INFO'}, f'Path to object: {path}')
|
|||
|
return {"FINISHED"}
|
|||
|
|
|||
|
## PANEL
|
|||
|
|
|||
|
class GPTB_PT_palettes_linker_ui(Panel):
|
|||
|
bl_idname = 'GPTB_PT_palettes_linker_ui'
|
|||
|
bl_space_type = "VIEW_3D"
|
|||
|
bl_region_type = "UI"
|
|||
|
bl_category = "Gpencil"#Dev
|
|||
|
bl_label = "Palettes Mat Linker"
|
|||
|
|
|||
|
def draw(self, context):
|
|||
|
layout = self.layout
|
|||
|
scn = bpy.context.scene
|
|||
|
pl_prop = scn.bl_palettes_props
|
|||
|
col= layout.column()
|
|||
|
prefs = utils.get_addon_prefs()
|
|||
|
## Here put the path thing (only to use a non-library)
|
|||
|
|
|||
|
# maybe in submenu...
|
|||
|
row = col.row()
|
|||
|
expand_icon = 'TRIA_DOWN' if pl_prop.show_path else 'TRIA_RIGHT'
|
|||
|
row.prop(pl_prop, 'show_path', text='', icon=expand_icon, emboss=False)
|
|||
|
row.prop(pl_prop, 'use_project_path', text='Use Project Palettes')
|
|||
|
row.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
|||
|
|
|||
|
if pl_prop.use_project_path:
|
|||
|
## gp toolbox addon prefs path
|
|||
|
if not prefs.palette_path:
|
|||
|
col.label(text='Gp Toolbox Palette Directory Needed')
|
|||
|
col.label(text='(saved with preferences)')
|
|||
|
if pl_prop.show_path or not prefs.palette_path:
|
|||
|
col.prop(prefs, 'palette_path', text='Project Dir')
|
|||
|
else:
|
|||
|
## local path
|
|||
|
if not pl_prop.custom_dir:
|
|||
|
col.label(text='Need to specify directory')
|
|||
|
if pl_prop.show_path or not pl_prop.custom_dir:
|
|||
|
col.prop(pl_prop, 'custom_dir', text='Custom Dir')
|
|||
|
|
|||
|
|
|||
|
row = col.row()
|
|||
|
row.template_list("GPTB_UL_blend_list", "", pl_prop, "blends", pl_prop, "bl_idx",
|
|||
|
rows=2)
|
|||
|
# side panel
|
|||
|
# subcol = row.column(align=True)
|
|||
|
# subcol.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
|||
|
|
|||
|
## Show object UI list only once blend Uilist is filled ?
|
|||
|
if not len(pl_prop.blends) or (len(pl_prop.blends) == 1 and not bool(pl_prop.blends[0].blend_path)):
|
|||
|
col.label(text='Select a blend to list available object')
|
|||
|
|
|||
|
row = col.row()
|
|||
|
row.template_list("GPTB_UL_object_list", "", pl_prop, "objects", pl_prop, "ob_idx",
|
|||
|
rows=4)
|
|||
|
|
|||
|
## Show link button in the border of the UI list ?
|
|||
|
# col.prop(pl_prop, 'import_type')
|
|||
|
split = col.split(align=True, factor=0.4 )
|
|||
|
split.prop(pl_prop, 'import_type', text='')
|
|||
|
|
|||
|
split.enabled = len(pl_prop.objects) and bool(pl_prop.objects[pl_prop.ob_idx].path)
|
|||
|
split.operator('gp.import_obj_palette', text='Palette')
|
|||
|
|
|||
|
# button to launch link with combined props (active only if the two items are valids)
|
|||
|
# str(Path(self.blends) / 'Object' / self.objects
|
|||
|
|
|||
|
|
|||
|
classes = (
|
|||
|
# blend list
|
|||
|
GPTB_PG_blend_prop,
|
|||
|
GPTB_UL_blend_list,
|
|||
|
GPTB_OT_palettes_reload_blends,
|
|||
|
|
|||
|
# object in blend list
|
|||
|
GPTB_PG_object_prop,
|
|||
|
GPTB_UL_object_list,
|
|||
|
|
|||
|
# prop containing two above
|
|||
|
GPTB_PG_palette_settings,
|
|||
|
|
|||
|
GPTB_OT_import_obj_palette,
|
|||
|
GPTB_PT_palettes_linker_ui,
|
|||
|
)
|
|||
|
|
|||
|
def register():
|
|||
|
for cls in classes:
|
|||
|
bpy.utils.register_class(cls)
|
|||
|
bpy.types.Scene.bl_palettes_props = bpy.props.PointerProperty(type=GPTB_PG_palette_settings)
|
|||
|
|
|||
|
def unregister():
|
|||
|
for cls in reversed(classes):
|
|||
|
bpy.utils.unregister_class(cls)
|
|||
|
|
|||
|
del bpy.types.Scene.bl_palettes_props
|