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