black format

This commit is contained in:
Joseph HENRY 2025-12-01 15:34:07 +01:00
parent f1aaa1dfca
commit 2b692432c2
10 changed files with 739 additions and 452 deletions

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.12

209
Types.py
View File

@ -5,82 +5,75 @@ from bpy.props import *
from .properties import CustomShelfProps from .properties import CustomShelfProps
class Types(): class Types:
def initialyse(self, label) : def initialyse(self, label):
package = __package__.replace('_','').replace('-','').lower() package = __package__.replace("_", "").replace("-", "").lower()
idname = label.replace(' ','_').replace('-','_').lower() idname = label.replace(" ", "_").replace("-", "_").lower()
self.bl_label = title(label) self.bl_label = title(label)
self.bl_idname = '%s.%s'%(package, idname) self.bl_idname = "%s.%s" % (package, idname)
#self.bl_props = bpy.context.scene.CustomShelf
#self.prefs = ""
# self.bl_props = bpy.context.scene.CustomShelf
# self.prefs = ""
class CSHELF_OT_shelf_popup(Types, Operator): class CSHELF_OT_shelf_popup(Types, Operator):
#props = None # props = None
bl_options = {'REGISTER', 'UNDO'} bl_options = {"REGISTER", "UNDO"}
@property @property
def props(self) : def props(self):
#print(getattr(bpy.context.window_manager.CustomShelf,self.prop)) # print(getattr(bpy.context.window_manager.CustomShelf,self.prop))
return getattr(bpy.context.window_manager.CustomShelf, self.prop) return getattr(bpy.context.window_manager.CustomShelf, self.prop)
def get_props(self): def get_props(self):
properties = self.props.bl_rna.properties properties = self.props.bl_rna.properties
#props = # props =
return {k:v for k,v in self.args.items() if k in properties} return {k: v for k, v in self.args.items() if k in properties}
def set_props(self,prop,value): def set_props(self, prop, value):
setattr(bpy.context.window_manager.CustomShelf,prop,value) setattr(bpy.context.window_manager.CustomShelf, prop, value)
def initialyse(self,label,info) : def initialyse(self, label, info):
super().initialyse(CSHELF_OT_shelf_popup,label) super().initialyse(CSHELF_OT_shelf_popup, label)
self.args = dic_to_args(info) self.args = dic_to_args(info)
Props = type("props", (PropertyGroup,), {"__annotations__": dict(self.args)})
Props = type("props",(PropertyGroup,),{'__annotations__' : dict(self.args)})
bpy.utils.register_class(Props) bpy.utils.register_class(Props)
setattr(CustomShelfProps, arg_name(label), PointerProperty(type=Props)) setattr(CustomShelfProps, arg_name(label), PointerProperty(type=Props))
#self.props = getattr(bpy.context.window_manager,self.bl_idname) # self.props = getattr(bpy.context.window_manager,self.bl_idname)
self.info = info self.info = info
self.prop = arg_name(label) self.prop = arg_name(label)
# print(self.args)
# properties = self.get_props().bl_rna.properties
# props = {k:v for k,v in self.args.items() if k in properties or hasattr(v,"bl_idname")}
#print(self.args) return # props
#properties = self.get_props().bl_rna.properties
#props = {k:v for k,v in self.args.items() if k in properties or hasattr(v,"bl_idname")}
return #props
def invoke(self, context, event): def invoke(self, context, event):
if event.alt : if event.alt:
for t in [t for t in bpy.data.texts if t.filepath == self.script] : for t in [t for t in bpy.data.texts if t.filepath == self.script]:
bpy.data.texts.remove(t) bpy.data.texts.remove(t)
text = bpy.data.texts.load(self.script) text = bpy.data.texts.load(self.script)
areas = [] areas = []
text_editor = None text_editor = None
for area in context.screen.areas : for area in context.screen.areas:
if area.type == 'TEXT_EDITOR' : if area.type == "TEXT_EDITOR":
text_editor = area text_editor = area
else : else:
areas.append(area) areas.append(area)
if not text_editor : if not text_editor:
bpy.ops.screen.area_split(direction = "VERTICAL") bpy.ops.screen.area_split(direction="VERTICAL")
for area in context.screen.areas : for area in context.screen.areas:
if area not in areas : if area not in areas:
text_editor = area text_editor = area
text_editor.type = "TEXT_EDITOR" text_editor.type = "TEXT_EDITOR"
text_editor.spaces[0].show_syntax_highlight = True text_editor.spaces[0].show_syntax_highlight = True
@ -88,82 +81,77 @@ class CSHELF_OT_shelf_popup(Types, Operator):
text_editor.spaces[0].show_line_numbers = True text_editor.spaces[0].show_line_numbers = True
context_copy = context.copy() context_copy = context.copy()
context_copy['area'] = text_editor context_copy["area"] = text_editor
#bpy.ops.text.properties(context_copy) # bpy.ops.text.properties(context_copy)
#bpy.ops.view3d.toolshelf(context_copy) # bpy.ops.view3d.toolshelf(context_copy)
text_editor.spaces[0].text = text text_editor.spaces[0].text = text
return {"FINISHED"} return {"FINISHED"}
else : else:
if self.args : if self.args:
#set default value for collection # set default value for collection
for k,v in self.info.items() : for k, v in self.info.items():
if isinstance(v,dict) and v.get('collection') : if isinstance(v, dict) and v.get("collection"):
collection = getattr(bpy.data, id_type[v["collection"]]) collection = getattr(bpy.data, id_type[v["collection"]])
setattr(self.props, k,collection.get(v["value"])) setattr(self.props, k, collection.get(v["value"]))
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
else : else:
return self.execute(context) return self.execute(context)
def bl_report(self, *args):
def bl_report(self,*args) : if len(args) and args[0] in ("INFO", "ERROR", "WARNING"):
if len(args) and args[0] in ('INFO','ERROR', 'WARNING') : return self.report({args[0]}, " ".join(args[1:]))
return self.report({args[0]},' '.join(args[1:])) else:
else :
print(*args) print(*args)
def execute(self,context): def execute(self, context):
info, lines = read_info(self.script) info, lines = read_info(self.script)
values = dict(info) values = dict(info)
for arg in self.args : for arg in self.args:
value = getattr(self.props,arg) value = getattr(self.props, arg)
if hasattr(value,"name") : if hasattr(value, "name"):
values[arg] = {'value': value.name} values[arg] = {"value": value.name}
elif isinstance(info.get(arg),dict) : elif isinstance(info.get(arg), dict):
values[arg] = {'value': value} values[arg] = {"value": value}
else : else:
values[arg] = value values[arg] = value
info_str = "info = %s\n\n"%values info_str = "info = %s\n\n" % values
lines_str = '\n'.join(lines) lines_str = "\n".join(lines)
script = f"{info_str}\n\n{lines_str}" script = f"{info_str}\n\n{lines_str}"
exec(script, {'info': values, 'print': self.bl_report}) exec(script, {"info": values, "print": self.bl_report})
return {'FINISHED'} return {"FINISHED"}
def draw(self,context) : def draw(self, context):
layout = self.layout layout = self.layout
bl_props = context.scene.CustomShelf bl_props = context.scene.CustomShelf
#print(self.get_props()) # print(self.get_props())
for k in self.args : for k in self.args:
#filter # filter
row = layout.row(align = True) row = layout.row(align=True)
row.label(text=title(k)) row.label(text=title(k))
row.prop(self.props,k,text='') row.prop(self.props, k, text="")
#row.prop_search(self.props,k,self.props,k+"_prop_search",text='') # row.prop_search(self.props,k,self.props,k+"_prop_search",text='')
#op = self.args[k+"_prop_search"] # op = self.args[k+"_prop_search"]
#row.operator(op.bl_idname,text="",icon ="COLLAPSEMENU" ) # row.operator(op.bl_idname,text="",icon ="COLLAPSEMENU" )
def check(self, context):
def check(self,context) :
return True return True
""" """
class ShelfPropSearch(Types,Operator): class ShelfPropSearch(Types,Operator):
bl_property = "enum" bl_property = "enum"
@ -194,61 +182,60 @@ class ShelfPropSearch(Types,Operator):
""" """
class CSHELF_PT_shelf_panel(Types,Panel): class CSHELF_PT_shelf_panel(Types, Panel):
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
#bl_category = "SHELF" # bl_category = "SHELF"
settings = {} settings = {}
scripts = [] scripts = []
@staticmethod @staticmethod
def search_filter(search,str) : def search_filter(search, str):
return search.lower() in str.lower() return search.lower() in str.lower()
@classmethod @classmethod
def poll(self, context): def poll(self, context):
bl_props = getattr(context.scene.CustomShelf,self.cat) bl_props = getattr(context.scene.CustomShelf, self.cat)
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
search = bl_props.search search = bl_props.search
shelf = [self.search_filter(search,s["text"]) for s in self.scripts] shelf = [self.search_filter(search, s["text"]) for s in self.scripts]
#print("settings",self.settings) # print("settings",self.settings)
is_taged = True is_taged = True
if self.settings and self.settings.get('tags') : if self.settings and self.settings.get("tags"):
tag = prefs.tag_filter tag = prefs.tag_filter
if not tag : if not tag:
tag = os.getenv("CUSTOM_SHELF_TAG") tag = os.getenv("CUSTOM_SHELF_TAG")
is_taged = tag in self.settings['tags'] is_taged = tag in self.settings["tags"]
return any(shelf) and is_taged return any(shelf) and is_taged
def draw(self, context):
def draw(self,context) :
layout = self.layout layout = self.layout
bl_props = getattr(context.scene.CustomShelf,self.cat) bl_props = getattr(context.scene.CustomShelf, self.cat)
search = bl_props.search search = bl_props.search
for script in self.scripts : for script in self.scripts:
args = script.copy() args = script.copy()
if not self.search_filter(search,script["text"]) : continue if not self.search_filter(search, script["text"]):
continue
if bl_props.filter and script["text"].startswith('_') : continue if bl_props.filter and script["text"].startswith("_"):
continue
args["text"] = title(script["text"]) args["text"] = title(script["text"])
#print(script) # print(script)
layout.operator(**args) layout.operator(**args)
class CSHELF_PT_shelf_header(Panel): class CSHELF_PT_shelf_header(Panel):
bl_label = "Custom Shelf" bl_label = "Custom Shelf"
bl_space_type = 'VIEW_3D' bl_space_type = "VIEW_3D"
bl_region_type = 'UI' bl_region_type = "UI"
bl_options = {"HIDE_HEADER"} bl_options = {"HIDE_HEADER"}
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
bl_props = bpy.context.scene.CustomShelf bl_props = bpy.context.scene.CustomShelf
@ -256,16 +243,20 @@ class CSHELF_PT_shelf_header(Panel):
cat_props = getattr(bl_props, self.cat) cat_props = getattr(bl_props, self.cat)
row = layout.row(align = True) row = layout.row(align=True)
row.operator_menu_enum("customshelf.set_tag_filter",'tag_filter',icon= "DOWNARROW_HLT",text='') row.operator_menu_enum(
row.operator("customshelf.refresh", text='',emboss=True,icon= "FILE_REFRESH") "customshelf.set_tag_filter", "tag_filter", icon="DOWNARROW_HLT", text=""
#row.separator() )
row.operator("customshelf.refresh", text="", emboss=True, icon="FILE_REFRESH")
# row.separator()
row.prop(cat_props, "filter",text='',emboss=True,icon= "FILTER") row.prop(cat_props, "filter", text="", emboss=True, icon="FILTER")
#row.separator() # row.separator()
row.prop(cat_props,"search",icon = "VIEWZOOM",text='') row.prop(cat_props, "search", icon="VIEWZOOM", text="")
#row.separator() # row.separator()
row.operator("customshelf.open_shelf_folder", text='',emboss=True,icon= "FILE").path = self.path row.operator(
"customshelf.open_shelf_folder", text="", emboss=True, icon="FILE"
).path = self.path

View File

@ -8,23 +8,24 @@ bl_info = {
"doc_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/blob/main/README.md", "doc_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/blob/main/README.md",
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/issues", "tracker_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/issues",
"support": "COMMUNITY", "support": "COMMUNITY",
"category": "User"} "category": "User",
}
if "bpy" in locals(): if "bpy" in locals():
import importlib import importlib
importlib.reload(operators) importlib.reload(operators)
importlib.reload(panels) importlib.reload(panels)
importlib.reload(functions) importlib.reload(functions)
importlib.reload(properties) importlib.reload(properties)
from . utils import report from .utils import report
from . functions import read_shelves from .functions import read_shelves
from . import operators from . import operators
from . import properties from . import properties
from . import ui from . import ui
from .properties import CustomShelfSettings,CustomShelfProps from .properties import CustomShelfSettings, CustomShelfProps
import bpy import bpy
import os import os
@ -41,19 +42,22 @@ bl_classes = [
operators.CSHELF_OT_open_shelf_folder, operators.CSHELF_OT_open_shelf_folder,
operators.CSHELF_OT_add_script, operators.CSHELF_OT_add_script,
operators.CSHELF_OT_set_tag_filter, operators.CSHELF_OT_set_tag_filter,
ui.CSHELF_MT_text_editor ui.CSHELF_MT_text_editor,
] ]
def draw_menu(self, context): def draw_menu(self, context):
self.layout.menu('CSHELF_MT_text_editor') self.layout.menu("CSHELF_MT_text_editor")
def register(): def register():
for bl_class in bl_classes : for bl_class in bl_classes:
bpy.utils.register_class(bl_class) bpy.utils.register_class(bl_class)
bpy.types.Scene.CustomShelf = bpy.props.PointerProperty(type=CustomShelfSettings) bpy.types.Scene.CustomShelf = bpy.props.PointerProperty(type=CustomShelfSettings)
bpy.types.WindowManager.CustomShelf = bpy.props.PointerProperty(type=CustomShelfProps) bpy.types.WindowManager.CustomShelf = bpy.props.PointerProperty(
type=CustomShelfProps
)
bpy.types.TEXT_MT_editor_menus.append(draw_menu) bpy.types.TEXT_MT_editor_menus.append(draw_menu)
@ -72,10 +76,11 @@ def register():
read_shelves() read_shelves()
def unregister(): def unregister():
# unregister panel : # unregister panel :
for panel in CustomShelfSettings.panel_list : for panel in CustomShelfSettings.panel_list:
try : try:
bpy.utils.unregister_class(panel) bpy.utils.unregister_class(panel)
except Exception: except Exception:
pass pass
@ -84,5 +89,5 @@ def unregister():
del bpy.types.Scene.CustomShelf del bpy.types.Scene.CustomShelf
del bpy.types.WindowManager.CustomShelf del bpy.types.WindowManager.CustomShelf
for bl_class in bl_classes : for bl_class in bl_classes:
bpy.utils.unregister_class(bl_class) bpy.utils.unregister_class(bl_class)

View File

@ -1,197 +1,222 @@
from .utils import * from .utils import *
from bpy.props import * from bpy.props import *
from bpy.types import Panel,Operator from bpy.types import Panel, Operator
from .properties import CustomShelfSettings,CustomShelfPrefs from .properties import CustomShelfSettings, CustomShelfPrefs
from .Types import * from .Types import *
SHELF_DIR = join(dirname(__file__),'shelves') SHELF_DIR = join(dirname(__file__), "shelves")
def operator(idname,args) : def operator(idname, args):
op_execute = args['execute'] op_execute = args["execute"]
def execute(self, context): def execute(self, context):
op_execute(context,self) op_execute(context, self)
return {"FINISHED"} return {"FINISHED"}
package = __package__.replace('_','').replace('-','').lower() package = __package__.replace("_", "").replace("-", "").lower()
args['bl_idname'] = "%s.%s"%(package,idname) args["bl_idname"] = "%s.%s" % (package, idname)
args['bl_label'] = title(idname) args["bl_label"] = title(idname)
args['execute'] = execute args["execute"] = execute
op = type('operator',(Operator,),args) op = type("operator", (Operator,), args)
bpy.utils.register_class(op) bpy.utils.register_class(op)
return op return op
def register_bl_class(bl_class,args) :
a = type('test',(bl_class,),{}) def register_bl_class(bl_class, args):
a.initialyse(a,*args) a = type("test", (bl_class,), {})
a.initialyse(a, *args)
bpy.utils.register_class(a) bpy.utils.register_class(a)
return a return a
def filter_dir(dir_list) :
return [d for d in dir_list if d.is_dir() and not d.name.startswith('_')] def filter_dir(dir_list):
return [d for d in dir_list if d.is_dir() and not d.name.startswith("_")]
def get_shelves_folder(): def get_shelves_folder():
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
shelves_path = prefs.global_shelves shelves_path = prefs.global_shelves
additionnal_shelves_path = [s.path for s in prefs.additionnal_shelves] additionnal_shelves_path = [s.path for s in prefs.additionnal_shelves]
return [p for p in additionnal_shelves_path+[shelves_path] if exists(p)] return [p for p in additionnal_shelves_path + [shelves_path] if exists(p)]
def get_dirs(paths):
def get_dirs(paths) : if isinstance(paths, (tuple, list)):
if isinstance(paths,(tuple,list)) :
dirs = [] dirs = []
for dir in paths: for dir in paths:
if not isinstance(dir, str) : if not isinstance(dir, str):
dir = dir.path dir = dir.path
dirs += filter_dir(scandir(dir)) dirs += filter_dir(scandir(dir))
return dirs return dirs
else : else:
return filter_dir(scandir(paths)) return filter_dir(scandir(paths))
def get_categories(): def get_categories():
return [d.name for d in get_dirs(get_shelves_folder())] return [d.name for d in get_dirs(get_shelves_folder())]
def get_tabs() :
def get_tabs():
return [d.name for d in get_dirs(get_dirs(get_shelves_folder()))] return [d.name for d in get_dirs(get_dirs(get_shelves_folder()))]
def get_category_path(category) :
category_dir = [f for f in get_shelves_folder() if category in [d.name for d in get_dirs(f)]] def get_category_path(category):
if category_dir : category_dir = [
return join(category_dir[0],category) f for f in get_shelves_folder() if category in [d.name for d in get_dirs(f)]
]
if category_dir:
return join(category_dir[0], category)
def get_category_tabs(category): def get_category_tabs(category):
return [d.name for d in get_dirs(get_category_path(category))] return [d.name for d in get_dirs(get_category_path(category))]
def read_shelves() : def read_shelves():
tag_filter_items = [] tag_filter_items = []
# unregister panel : # unregister panel :
for panel in CustomShelfSettings.panel_list: for panel in CustomShelfSettings.panel_list:
print(panel) print(panel)
if panel.__name__ not in get_tabs(): if panel.__name__ not in get_tabs():
try : try:
bpy.utils.unregister_class(panel) bpy.utils.unregister_class(panel)
except Exception: except Exception:
pass pass
CustomShelfSettings.panel_list.remove(panel) CustomShelfSettings.panel_list.remove(panel)
for cat in get_dirs(get_shelves_folder()):
for cat in get_dirs(get_shelves_folder()) : cat_name = cat.name.replace("_", " ").title()
cat_name = cat.name.replace('_',' ').title()
header_info = { header_info = {
"bl_region_type" : "UI",#petit ajout pour voir si foncitonne... "bl_region_type": "UI", # petit ajout pour voir si foncitonne...
"bl_category" : cat_name, "bl_category": cat_name,
"bl_idname" : "%s_PT_%s_header"%(__package__.upper(),cat.name),# -> "%s.%s_header" makes _PT_ warning "bl_idname": "%s_PT_%s_header"
"cat" : cat.name, % (__package__.upper(), cat.name), # -> "%s.%s_header" makes _PT_ warning
"path" : cat.path "cat": cat.name,
"path": cat.path,
} }
header = type("Header",(CSHELF_PT_shelf_header,),header_info) header = type("Header", (CSHELF_PT_shelf_header,), header_info)
bpy.utils.register_class(header) bpy.utils.register_class(header)
# enable bool prefs
#enable bool prefs
# panel_props = {t.name : BoolProperty(default = False) for t in get_dirs(cat.path)}# give "should be an annotation" field warning # panel_props = {t.name : BoolProperty(default = False) for t in get_dirs(cat.path)}# give "should be an annotation" field warning
## need annoattion type : https://blender.stackexchange.com/questions/118118/blender-2-8-field-property-declaration-and-dynamic-class-creation ## need annoattion type : https://blender.stackexchange.com/questions/118118/blender-2-8-field-property-declaration-and-dynamic-class-creation
panel_props = {'__annotations__': {t.name : BoolProperty(default = False) for t in get_dirs(cat.path)}} panel_props = {
PanelProps = type("CustomShelfPrefs",(PropertyGroup,),panel_props) "__annotations__": {
t.name: BoolProperty(default=False) for t in get_dirs(cat.path)
}
}
PanelProps = type("CustomShelfPrefs", (PropertyGroup,), panel_props)
bpy.utils.register_class(PanelProps) bpy.utils.register_class(PanelProps)
setattr(CustomShelfPrefs, cat.name, PointerProperty(type=PanelProps)) setattr(CustomShelfPrefs, cat.name, PointerProperty(type=PanelProps))
#search and filter # search and filter
props = { props = {
"search" : StringProperty(options = {"TEXTEDIT_UPDATE"}), "search": StringProperty(options={"TEXTEDIT_UPDATE"}),
"filter" : BoolProperty(default = True) "filter": BoolProperty(default=True),
} }
# Props = type("props",(PropertyGroup,), props)# annotation error # Props = type("props",(PropertyGroup,), props)# annotation error
Props = type("props",(PropertyGroup,),{'__annotations__': props}) Props = type("props", (PropertyGroup,), {"__annotations__": props})
bpy.utils.register_class(Props) bpy.utils.register_class(Props)
setattr(CustomShelfSettings,cat.name,PointerProperty(type = Props)) setattr(CustomShelfSettings, cat.name, PointerProperty(type=Props))
#enable bool prefs # enable bool prefs
#Prefs = type("CustomShelfPrefs",(PropertyGroup,),{}) # Prefs = type("CustomShelfPrefs",(PropertyGroup,),{})
#bpy.utils.register_class(Prefs) # bpy.utils.register_class(Prefs)
#setattr(CustomShelfPrefs,cat.name,PointerProperty(type = Prefs)) # setattr(CustomShelfPrefs,cat.name,PointerProperty(type = Prefs))
for tab in get_dirs(cat.path) : for tab in get_dirs(cat.path):
#get_settings # get_settings
settings_json = join(tab.path,'settings.json') settings_json = join(tab.path, "settings.json")
tab_settings = {} tab_settings = {}
if exists(settings_json) : if exists(settings_json):
tab_settings = read_json(settings_json) tab_settings = read_json(settings_json)
if not tab_settings or not tab_settings.get('tags') : continue if not tab_settings or not tab_settings.get("tags"):
for tag in tab_settings['tags'] : continue
if tag not in tag_filter_items : for tag in tab_settings["tags"]:
if tag not in tag_filter_items:
tag_filter_items.append(tag) tag_filter_items.append(tag)
panel_info = { panel_info = {
"bl_region_type" : "UI",#Added, maybe need it in 2.8+... "bl_region_type": "UI", # Added, maybe need it in 2.8+...
"bl_category" : cat_name, "bl_category": cat_name,
"bl_idname" : "%s_PT_%s_%s"%(__package__.upper(),cat.name,tab.name),#"%s.%s_%s" -> makes _PT_ warning "bl_idname": "%s_PT_%s_%s"
"bl_label" : tab.name.replace('_',' ').title(), % (
"cat" : cat.name, __package__.upper(),
"settings" :tab_settings cat.name,
tab.name,
), # "%s.%s_%s" -> makes _PT_ warning
"bl_label": tab.name.replace("_", " ").title(),
"cat": cat.name,
"settings": tab_settings,
} }
space_info = {k:v for k,v in tab_settings.items() if k in ("bl_space_type","bl_region_type")} space_info = {
k: v
for k, v in tab_settings.items()
if k in ("bl_space_type", "bl_region_type")
}
panel_info.update(space_info) panel_info.update(space_info)
panel = type("Panel",(CSHELF_PT_shelf_panel,),panel_info) panel = type("Panel", (CSHELF_PT_shelf_panel,), panel_info)
bpy.utils.register_class(panel) bpy.utils.register_class(panel)
CustomShelfSettings.panel_list.append(panel) CustomShelfSettings.panel_list.append(panel)
scripts = [] scripts = []
for script in scandir(tab.path) : for script in scandir(tab.path):
if not script.name.endswith('.py'): if not script.name.endswith(".py"):
continue continue
print(script.path) print(script.path)
script_name = splitext(script.name)[0] script_name = splitext(script.name)[0]
info,lines = read_info(script.path) info, lines = read_info(script.path)
info["description"] = info.get('description',"") info["description"] = info.get("description", "")
icon = info.get('icon', "WORDWRAP_OFF") icon = info.get("icon", "WORDWRAP_OFF")
popup = type(script_name, (CSHELF_OT_shelf_popup,), popup = type(
{"script": script.path, script_name,
'bl_description': info["description"] (CSHELF_OT_shelf_popup,),
}) {"script": script.path, "bl_description": info["description"]},
)
popup.initialyse(popup, script_name, info) popup.initialyse(popup, script_name, info)
bpy.utils.register_class(popup) bpy.utils.register_class(popup)
scripts.append({"operator" : popup.bl_idname,"text" : script_name,"icon" : icon}) scripts.append(
{"operator": popup.bl_idname, "text": script_name, "icon": icon}
)
panel.scripts = sorted(scripts,key = lambda x :x['text']) panel.scripts = sorted(scripts, key=lambda x: x["text"])
#Register tag filter enum # Register tag filter enum
#setattr(Pre, v) # setattr(Pre, v)
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
tag_filter_items.sort() tag_filter_items.sort()
tag_filter_items.insert(0, '__clear__') tag_filter_items.insert(0, "__clear__")
prefs['tag_filter_items'] = tag_filter_items prefs["tag_filter_items"] = tag_filter_items
#bpy.utils.unregister_class(CustomShelfPrefs) # bpy.utils.unregister_class(CustomShelfPrefs)
#bpy.utils.register_class(CustomShelfPrefs) # bpy.utils.register_class(CustomShelfPrefs)
#CustomShelfSettings.folders = list(get_shelves_folder().keys()) # CustomShelfSettings.folders = list(get_shelves_folder().keys())
#folder_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in CustomShelfSettings.folders]) # folder_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in CustomShelfSettings.folders])
#setattr(CustomShelfSettings,'folders_enum',folder_enum) # setattr(CustomShelfSettings,'folders_enum',folder_enum)

View File

@ -7,232 +7,323 @@ from .properties import CustomShelfSettings
class CSHELF_OT_refresh(Operator): class CSHELF_OT_refresh(Operator):
bl_idname = "customshelf.refresh" bl_idname = "customshelf.refresh"
bl_label = 'Refresh Shelves' bl_label = "Refresh Shelves"
def execute(self,context) : def execute(self, context):
read_shelves() read_shelves()
return {'FINISHED'} return {"FINISHED"}
def get_tag_items(self,context) : def get_tag_items(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
return [(i,i.title(),"") for i in prefs['tag_filter_items']] return [(i, i.title(), "") for i in prefs["tag_filter_items"]]
class CSHELF_OT_set_tag_filter(Operator): class CSHELF_OT_set_tag_filter(Operator):
bl_idname = "customshelf.set_tag_filter" bl_idname = "customshelf.set_tag_filter"
bl_label = 'Refresh Shelves' bl_label = "Refresh Shelves"
tag_filter : EnumProperty(items = get_tag_items) tag_filter: EnumProperty(items=get_tag_items)
def execute(self, context): def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
tag_filter = self.tag_filter tag_filter = self.tag_filter
if tag_filter == '__clear__' : if tag_filter == "__clear__":
tag_filter = "" tag_filter = ""
prefs.tag_filter = tag_filter prefs.tag_filter = tag_filter
return {'FINISHED'} return {"FINISHED"}
class CSHELF_OT_add_shelf_folder(Operator): class CSHELF_OT_add_shelf_folder(Operator):
bl_idname = "customshelf.add_shelves_folder" bl_idname = "customshelf.add_shelves_folder"
bl_label = 'Refresh Shelves' bl_label = "Refresh Shelves"
def execute(self, context): def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
path = prefs.additionnal_shelves.add() path = prefs.additionnal_shelves.add()
return {'FINISHED'} return {"FINISHED"}
class CSHELF_OT_remove_shelf_folder(Operator): class CSHELF_OT_remove_shelf_folder(Operator):
bl_idname = "customshelf.remove_shelves_folder" bl_idname = "customshelf.remove_shelves_folder"
bl_label = 'Refresh Shelves' bl_label = "Refresh Shelves"
index : IntProperty() index: IntProperty()
def execute(self, context): def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
prefs.additionnal_shelves.remove(self.index) prefs.additionnal_shelves.remove(self.index)
return {'FINISHED'} return {"FINISHED"}
class CSHELF_OT_open_shelf_folder(Operator): class CSHELF_OT_open_shelf_folder(Operator):
bl_idname = "customshelf.open_shelf_folder" bl_idname = "customshelf.open_shelf_folder"
bl_label = 'Run Function' bl_label = "Run Function"
path : StringProperty() path: StringProperty()
def execute(self,context) : def execute(self, context):
open_folder(self.path) open_folder(self.path)
return {'FINISHED'} return {"FINISHED"}
class CSHELF_OT_add_script(Operator) : class CSHELF_OT_add_script(Operator):
bl_idname = "customshelf.add_script" bl_idname = "customshelf.add_script"
bl_label = 'Add script to a shelf' bl_label = "Add script to a shelf"
add_category : BoolProperty() add_category: BoolProperty()
add_tab : BoolProperty() add_tab: BoolProperty()
new_category : StringProperty() new_category: StringProperty()
new_tab : StringProperty() new_tab: StringProperty()
name : StringProperty() name: StringProperty()
description : StringProperty() description: StringProperty()
icon : StringProperty() icon: StringProperty()
show_icons : BoolProperty() show_icons: BoolProperty()
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.area.type == 'TEXT_EDITOR' and context.space_data.text return context.area.type == "TEXT_EDITOR" and context.space_data.text
def add_folder(self,context,op) : def add_folder(self, context, op):
folder = self.folder.add() folder = self.folder.add()
folder.name = self.name folder.name = self.name
#CustomShelfSettings # CustomShelfSettings
def remove_folder(self,context,op) : def remove_folder(self, context, op):
bl_props = context.scene.CustomShelf bl_props = context.scene.CustomShelf
index = self.folders.find(self.folders_enum) index = self.folders.find(self.folders_enum)
self.folders.remove(index) self.folders.remove(index)
def get_all_icons (self) : def get_all_icons(self):
ui_layout = bpy.types.UILayout ui_layout = bpy.types.UILayout
icons = ui_layout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys() icons = ui_layout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys()
prefixes = ('BRUSH_','MATCAP_','COLORSET_') prefixes = ("BRUSH_", "MATCAP_", "COLORSET_")
exception = ('BRUSH_DATA') exception = "BRUSH_DATA"
return [i for i in icons if not i.startswith(prefixes) or i in exception] return [i for i in icons if not i.startswith(prefixes) or i in exception]
def get_icons(self,context,op) : def get_icons(self, context, op):
icons = [ icons = [
["SCENE_DATA","RENDERLAYERS","MATERIAL_DATA","GROUP_UVS","TEXTURE","WORLD","SPEAKER","TEXT","NODETREE","NODE_INSERT_OFF","PARTICLES","SORTALPHA"], [
["MODIFIER","MOD_WAVE","MOD_SUBSURF","MOD_FLUIDSIM","MOD_OCEAN","BLANK1", "SCENE_DATA",
"ARMATURE_DATA","BONE_DATA","GROUP_BONE"], "RENDERLAYERS",
["SEQUENCE","CAMERA_DATA","SCENE","BLANK1", "MATERIAL_DATA",
"FILE_NEW","CONSOLE","BLENDER","APPEND_BLEND"], "GROUP_UVS",
["GROUP","MESH_CUBE","MESH_PLANE","MESH_CIRCLE","MESH_UVSPHERE","MESH_GRID","EMPTY_DATA","OUTLINER_DATA_MESH", "TEXTURE",
"LIGHT_SUN","LIGHT_SPOT","LIGHT"], "WORLD",
["TRIA_RIGHT_BAR","REC","PLAY","PREV_KEYFRAME","NEXT_KEYFRAME","PAUSE","X","ADD","REMOVE","RESTRICT_VIEW_OFF","RESTRICT_VIEW_ON","RESTRICT_SELECT_OFF",], "SPEAKER",
["BRUSH_DATA","GREASEPENCIL","LINE_DATA","PARTICLEMODE","SCULPTMODE_HLT","WPAINT_HLT","TPAINT_HLT","VIEWZOOM","HAND","KEY_HLT","KEY_DEHLT",], "TEXT",
["PLUGIN","SCRIPT","PREFERENCES", "NODETREE",
"ACTION","SOLO_OFF", "NODE_INSERT_OFF",
"RNDCURVE","SNAP_ON", "PARTICLES",
"FORCE_WIND","COPY_ID","EYEDROPPER","AUTO","UNLOCKED","LOCKED", "SORTALPHA",
"UNPINNED","PINNED","PLUGIN","HELP","GHOST_ENABLED","GHOST_DISABLED","COLOR", ],
"LINKED","UNLINKED","LINKED","ZOOM_ALL", [
"FREEZE","STYLUS_PRESSURE","FILE_TICK", "MODIFIER",
"QUIT","RECOVER_LAST","TIME","PREVIEW_RANGE", "MOD_WAVE",
"OUTLINER","NLA","EDITMODE_HLT", "MOD_SUBSURF",
"BOIDS","RNA","CAMERA_STEREO"], "MOD_FLUIDSIM",
"MOD_OCEAN",
"BLANK1",
"ARMATURE_DATA",
"BONE_DATA",
"GROUP_BONE",
],
[
"SEQUENCE",
"CAMERA_DATA",
"SCENE",
"BLANK1",
"FILE_NEW",
"CONSOLE",
"BLENDER",
"APPEND_BLEND",
],
[
"GROUP",
"MESH_CUBE",
"MESH_PLANE",
"MESH_CIRCLE",
"MESH_UVSPHERE",
"MESH_GRID",
"EMPTY_DATA",
"OUTLINER_DATA_MESH",
"LIGHT_SUN",
"LIGHT_SPOT",
"LIGHT",
],
[
"TRIA_RIGHT_BAR",
"REC",
"PLAY",
"PREV_KEYFRAME",
"NEXT_KEYFRAME",
"PAUSE",
"X",
"ADD",
"REMOVE",
"RESTRICT_VIEW_OFF",
"RESTRICT_VIEW_ON",
"RESTRICT_SELECT_OFF",
],
[
"BRUSH_DATA",
"GREASEPENCIL",
"LINE_DATA",
"PARTICLEMODE",
"SCULPTMODE_HLT",
"WPAINT_HLT",
"TPAINT_HLT",
"VIEWZOOM",
"HAND",
"KEY_HLT",
"KEY_DEHLT",
],
[
"PLUGIN",
"SCRIPT",
"PREFERENCES",
"ACTION",
"SOLO_OFF",
"RNDCURVE",
"SNAP_ON",
"FORCE_WIND",
"COPY_ID",
"EYEDROPPER",
"AUTO",
"UNLOCKED",
"LOCKED",
"UNPINNED",
"PINNED",
"PLUGIN",
"HELP",
"GHOST_ENABLED",
"GHOST_DISABLED",
"COLOR",
"LINKED",
"UNLINKED",
"LINKED",
"ZOOM_ALL",
"FREEZE",
"STYLUS_PRESSURE",
"FILE_TICK",
"QUIT",
"RECOVER_LAST",
"TIME",
"PREVIEW_RANGE",
"OUTLINER",
"NLA",
"EDITMODE_HLT",
"BOIDS",
"RNA",
"CAMERA_STEREO",
],
] ]
self.icons = icons self.icons = icons
def set_icon(self, context, op):
def set_icon(self,context,op) : # bl_props = context.scene.CustomShelf
#bl_props = context.scene.CustomShelf
self.icon = op.icon self.icon = op.icon
self.icons = [] self.icons = []
def draw(self, context):
def draw(self,context) :
bl_props = context.scene.CustomShelf bl_props = context.scene.CustomShelf
layout = self.layout layout = self.layout
row = layout.row() row = layout.row()
row.operator("customshelf.get_icons",text= '',icon = self.icon) row.operator("customshelf.get_icons", text="", icon=self.icon)
row.prop(self,"name",text ="") row.prop(self, "name", text="")
col = layout.column(align = True) col = layout.column(align=True)
for icon_list in self.icons:
i = 0
for icon in icon_list:
if not i % 12:
row = col.row(align=True)
for icon_list in self.icons : row.operator(
i=0 "customshelf.set_icon", icon=icon, emboss=False, text=""
for icon in icon_list : ).icon = icon
if not i%12 :
row = col.row(align= True)
row.operator("customshelf.set_icon", icon=icon, emboss=False, text="").icon=icon
i += 1 i += 1
row = col.row(align= True) row = col.row(align=True)
layout.prop(self, "description", text="") layout.prop(self, "description", text="")
layout.separator() layout.separator()
#Category Row # Category Row
folder_row = layout.row(align = True) folder_row = layout.row(align=True)
folder_row.label(text="", icon='FILE_FOLDER') folder_row.label(text="", icon="FILE_FOLDER")
folder_row.separator() folder_row.separator()
if not self.add_category : if not self.add_category:
folder_row.prop(bl_props,"category_enum",expand = True) folder_row.prop(bl_props, "category_enum", expand=True)
else : else:
folder_row.prop(self,"new_category",text='') folder_row.prop(self, "new_category", text="")
folder_row.prop(self,"add_category",icon = 'ADD',text='') folder_row.prop(self, "add_category", icon="ADD", text="")
# Tabs row # Tabs row
tab_row = layout.row(align = True) tab_row = layout.row(align=True)
tab_row.label(text='', icon='MENU_PANEL') tab_row.label(text="", icon="MENU_PANEL")
tab_row.separator() tab_row.separator()
if not self.add_tab : if not self.add_tab:
category_tabs = get_category_tabs(bl_props.category_enum) category_tabs = get_category_tabs(bl_props.category_enum)
for t in [t for t in category_tabs if t in self.tabs] : for t in [t for t in category_tabs if t in self.tabs]:
tab_row.prop_enum(bl_props,"tab_enum",t) tab_row.prop_enum(bl_props, "tab_enum", t)
else : else:
tab_row.prop(self,"new_tab",text='') tab_row.prop(self, "new_tab", text="")
tab_row.prop(self,"add_tab",icon = 'ADD',text='') tab_row.prop(self, "add_tab", icon="ADD", text="")
#folder_row.operator("customshelf.remove_folder",icon = 'REMOVE',text='') # folder_row.operator("customshelf.remove_folder",icon = 'REMOVE',text='')
def write_script(self,f) : def write_script(self, f):
keys = ['icon','description'] keys = ["icon", "description"]
keys += [k for k in self.info if k not in keys] keys += [k for k in self.info if k not in keys]
f.write("info = "+dic_to_str(self.info,keys)) f.write("info = " + dic_to_str(self.info, keys))
print(self.lines) print(self.lines)
f.write('\n'.join(self.lines)) f.write("\n".join(self.lines))
def execute(self, context):
def execute(self,context) :
preferences = context.preferences preferences = context.preferences
bl_props = context.scene.CustomShelf bl_props = context.scene.CustomShelf
addon_prefs = preferences.addons[__package__].preferences addon_prefs = preferences.addons[__package__].preferences
if self.new_category : if self.new_category:
category_path = get_category_path(self.new_category) category_path = get_category_path(self.new_category)
if not exists(category_path): if not exists(category_path):
mkdir(new_category) mkdir(new_category)
else : else:
category_path = get_category_path(bl_props.category_enum) category_path = get_category_path(bl_props.category_enum)
if self.new_tab : if self.new_tab:
tab_path = join(category_path, self.new_tab) tab_path = join(category_path, self.new_tab)
if not exists(tab_path): if not exists(tab_path):
mkdir(tab_path) mkdir(tab_path)
else : else:
tab_path = join(category_path,bl_props.tab_enum) tab_path = join(category_path, bl_props.tab_enum)
script_name = self.name.replace(" ", "_").replace("-", "_")
script_name = self.name.replace(' ','_').replace('-','_') script_path = join(tab_path, script_name + ".py")
script_path = join(tab_path,script_name+'.py')
if os.path.exists(script_path): if os.path.exists(script_path):
os.remove(script_path) os.remove(script_path)
self.info["icon"] = self.icon
self.info["description"] = self.description
self.info['icon'] = self.icon with open(script_path, "w") as f:
self.info['description'] = self.description
with open(script_path,"w") as f :
self.write_script(f) self.write_script(f)
line_index = self.active_text.current_line_index line_index = self.active_text.current_line_index
@ -246,21 +337,21 @@ class CSHELF_OT_add_script(Operator) :
return {"FINISHED"} return {"FINISHED"}
def check(self,context) : def check(self, context):
return True return True
def invoke(self,context,event) : def invoke(self, context, event):
bl_props = context.scene.CustomShelf bl_props = context.scene.CustomShelf
self.active_text = context.space_data.text self.active_text = context.space_data.text
self.info,self.lines = read_info([l.body for l in self.active_text.lines]) self.info, self.lines = read_info([l.body for l in self.active_text.lines])
icon = "LONGDISPLAY" icon = "LONGDISPLAY"
if self.info.get('icon') : if self.info.get("icon"):
icon = self.info["icon"] icon = self.info["icon"]
description = "Some description" description = "Some description"
if self.info.get('description') : if self.info.get("description"):
description = self.info["description"] description = self.info["description"]
self.icon = icon self.icon = icon
@ -272,25 +363,32 @@ class CSHELF_OT_add_script(Operator) :
self.new_shelf = "" self.new_shelf = ""
self.icons = [] self.icons = []
operator("get_icons", {'execute': self.get_icons}) operator("get_icons", {"execute": self.get_icons})
operator("set_icon", {'execute': self.set_icon, '__annotations__': {'icon' : StringProperty()}}) operator(
operator("add_folder", {'execute': self.add_folder}) "set_icon",
#operator("remove_folder",{'execute':self.remove_folder}) {"execute": self.set_icon, "__annotations__": {"icon": StringProperty()}},
)
operator("add_folder", {"execute": self.add_folder})
# operator("remove_folder",{'execute':self.remove_folder})
self.tabs = get_tabs() self.tabs = get_tabs()
self.categories = get_categories() self.categories = get_categories()
CustomShelfSettings.category_enum = EnumProperty(items=[(i,i,"") for i in self.categories]) CustomShelfSettings.category_enum = EnumProperty(
CustomShelfSettings.tab_enum = EnumProperty(items=[(i,i,"") for i in self.tabs]) items=[(i, i, "") for i in self.categories]
)
CustomShelfSettings.tab_enum = EnumProperty(
items=[(i, i, "") for i in self.tabs]
)
if self.active_text.filepath : if self.active_text.filepath:
tab_path = dirname(self.active_text.filepath) tab_path = dirname(self.active_text.filepath)
category = basename(dirname(tab_path)) category = basename(dirname(tab_path))
tab = basename(tab_path) tab = basename(tab_path)
if category in self.categories : if category in self.categories:
bl_props.category_enum = category bl_props.category_enum = category
if tab in self.tabs : if tab in self.tabs:
bl_props.tab_enum = tab bl_props.tab_enum = tab
return context.window_manager.invoke_props_dialog(self, width=500) return context.window_manager.invoke_props_dialog(self, width=500)

View File

@ -2,47 +2,53 @@ from .utils import *
from bpy.props import * from bpy.props import *
class CustomShelfSettings(bpy.types.PropertyGroup):
class CustomShelfSettings(bpy.types.PropertyGroup) :
panel_list = [] panel_list = []
#category_enum = EnumProperty(items = ) # category_enum = EnumProperty(items = )
#tab_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in s.tabs]) # tab_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in s.tabs])
#search = StringProperty(options = {"TEXTEDIT_UPDATE"}) # search = StringProperty(options = {"TEXTEDIT_UPDATE"})
#filter = BoolProperty(default = True) # filter = BoolProperty(default = True)
#folders = PointerProperty(type= CustomShelfFolders) # folders = PointerProperty(type= CustomShelfFolders)
#variables = bpy.props.PointerProperty(type= CustomShelfVariables) # variables = bpy.props.PointerProperty(type= CustomShelfVariables)
class CustomShelfProps(bpy.types.PropertyGroup) :
class CustomShelfProps(bpy.types.PropertyGroup):
pass pass
class AdditionnalShelves(bpy.types.PropertyGroup) :
path: StringProperty(name="Path",subtype = 'FILE_PATH') class AdditionnalShelves(bpy.types.PropertyGroup):
path: StringProperty(name="Path", subtype="FILE_PATH")
class CustomShelfPrefs(bpy.types.AddonPreferences): class CustomShelfPrefs(bpy.types.AddonPreferences):
bl_idname = __package__ bl_idname = __package__
global_path = join(dirname(__file__),'shelves') global_path = join(dirname(__file__), "shelves")
global_shelves : StringProperty(name="Shelves Path",subtype = 'DIR_PATH',default =global_path) global_shelves: StringProperty(
additionnal_shelves : CollectionProperty(type = AdditionnalShelves) name="Shelves Path", subtype="DIR_PATH", default=global_path
tag_filter : StringProperty() )
additionnal_shelves: CollectionProperty(type=AdditionnalShelves)
tag_filter: StringProperty()
def draw(self, context): def draw(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
layout = self.layout layout = self.layout
layout.prop(self, "global_shelves") layout.prop(self, "global_shelves")
row = layout.row(align = True) row = layout.row(align=True)
row.operator("customshelf.add_shelves_folder",icon="ADD",text="") row.operator("customshelf.add_shelves_folder", icon="ADD", text="")
row.label(text='Additionnal Shelves folder') row.label(text="Additionnal Shelves folder")
for i,shelf in enumerate(prefs.additionnal_shelves) : for i, shelf in enumerate(prefs.additionnal_shelves):
row = layout.row(align = True) row = layout.row(align=True)
row.prop(shelf, "path") row.prop(shelf, "path")
row.operator("customshelf.remove_shelves_folder",icon="REMOVE",text="").index = i row.operator(
"customshelf.remove_shelves_folder", icon="REMOVE", text=""
).index = i
row = layout.row(align = True) row = layout.row(align=True)
row.prop(self, "tag_filter") row.prop(self, "tag_filter")
row.operator_menu_enum("customshelf.set_tag_filter",'tag_filter',icon= "DOWNARROW_HLT",text='') row.operator_menu_enum(
"customshelf.set_tag_filter", "tag_filter", icon="DOWNARROW_HLT", text=""
)

12
pyproject.toml Normal file
View File

@ -0,0 +1,12 @@
[project]
name = "custom-shelf"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = [
"black>=25.11.0",
]

5
ui.py
View File

@ -1,4 +1,3 @@
import bpy import bpy
@ -7,9 +6,7 @@ class CSHELF_MT_text_editor(bpy.types.Menu):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator("customshelf.add_script", text='Add Script', icon="FILE_NEW") layout.operator("customshelf.add_script", text="Add Script", icon="FILE_NEW")
""" """

227
utils.py
View File

@ -1,192 +1,231 @@
import bpy import bpy
import os import os
from os import listdir,mkdir,scandir from os import listdir, mkdir, scandir
from os.path import join,dirname,splitext,isdir,exists,basename from os.path import join, dirname, splitext, isdir, exists, basename
from bpy.types import Operator, Panel, PropertyGroup from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import * from bpy.props import *
import subprocess import subprocess
import json import json
id_type = {"Action" : "actions", "Armature":"armatures", "Brush":"brushes", "CacheFile":"cache_files", "Camera":"cameras", id_type = {
"Curve":"curves", "FreestyleLineStyle":"linestyles", "GreasePencil":"grease_pencil", "Group":"groups", "Action": "actions",
"Image":"images", "Key":"shape_keys", "Light":"lights", "Lattice":"lattices", "Library":"librairies", "Mask":"masks", "Armature": "armatures",
"Material":"materials", "Mesh":"meshes", "MetaBall":"metaballs", "MovieClip":"movieclips", "NodeTree":"node_groups", "Brush": "brushes",
"Object":"objects", "PaintCurve":"paint_curves", "Palette":"palettes", "ParticleSettings":"particles", "CacheFile": "cache_files",
"Scene":"scenes", "Screen":"screens", "Sound":"sounds", "Speaker":"speakers", "Text":"texts", "Texture":"textures", "Camera": "cameras",
"Collection":"collections", "VectorFont":"fonts", "WindowManager":"window_managers", "World":"worlds"} "Curve": "curves",
"FreestyleLineStyle": "linestyles",
"GreasePencil": "grease_pencil",
"Group": "groups",
"Image": "images",
"Key": "shape_keys",
"Light": "lights",
"Lattice": "lattices",
"Library": "librairies",
"Mask": "masks",
"Material": "materials",
"Mesh": "meshes",
"MetaBall": "metaballs",
"MovieClip": "movieclips",
"NodeTree": "node_groups",
"Object": "objects",
"PaintCurve": "paint_curves",
"Palette": "palettes",
"ParticleSettings": "particles",
"Scene": "scenes",
"Screen": "screens",
"Sound": "sounds",
"Speaker": "speakers",
"Text": "texts",
"Texture": "textures",
"Collection": "collections",
"VectorFont": "fonts",
"WindowManager": "window_managers",
"World": "worlds",
}
def report(var, message, type="INFO"):
def report(var,message,type='INFO') :
print([a for a in locals()]) print([a for a in locals()])
print([a for a in globals()]) print([a for a in globals()])
if "self" in var:
var["self"].report({type}, message)
if 'self' in var : else:
var['self'].report({type}, message) print("")
else :
print('')
print(type) print(type)
print(message) print(message)
def read_json(path): def read_json(path):
jsonFile = path jsonFile = path
if os.path.exists(jsonFile): if os.path.exists(jsonFile):
try : try:
with open(jsonFile) as data_file: with open(jsonFile) as data_file:
return (json.load(data_file)) return json.load(data_file)
except : except:
print("the file %s not json readable"%jsonFile) print("the file %s not json readable" % jsonFile)
return return
def search_filter(search, str):
def search_filter(search,str) :
return search.lower() in str.lower() return search.lower() in str.lower()
def title(string) :
return string.replace('_',' ').replace('-',' ').title()
def arg_name(string) : def title(string):
return string.replace(' ','_').replace('-','_').lower().strip('_') return string.replace("_", " ").replace("-", " ").title()
def open_folder(path) :
def arg_name(string):
return string.replace(" ", "_").replace("-", "_").lower().strip("_")
def open_folder(path):
try: try:
os.startfile(path) os.startfile(path)
except: except:
subprocess.Popen(['xdg-open', path]) subprocess.Popen(["xdg-open", path])
def dic_to_args(dic): def dic_to_args(dic):
import collections import collections
args = collections.OrderedDict() args = collections.OrderedDict()
prop_type = { prop_type = {
str: StringProperty, str: StringProperty,
bool: BoolProperty, bool: BoolProperty,
float: FloatProperty, float: FloatProperty,
int : IntProperty int: IntProperty,
} }
for k, v in dic.items() : for k, v in dic.items():
if k in ('icon','description') or not isinstance(k,str): if k in ("icon", "description") or not isinstance(k, str):
continue continue
if isinstance(v,str) : if isinstance(v, str):
if '/' in v or '\\' in v : if "/" in v or "\\" in v:
args[k] = StringProperty(default=v,subtype = "FILE_PATH") args[k] = StringProperty(default=v, subtype="FILE_PATH")
else : else:
args[k] = StringProperty(default=v) args[k] = StringProperty(default=v)
elif isinstance(v,bool) : elif isinstance(v, bool):
args[k] = BoolProperty(default=v) args[k] = BoolProperty(default=v)
elif isinstance(v, float) : elif isinstance(v, float):
args[k] = FloatProperty(default=v,precision=3) args[k] = FloatProperty(default=v, precision=3)
elif isinstance(v,int) : elif isinstance(v, int):
args[k] = IntProperty(default=v) args[k] = IntProperty(default=v)
elif isinstance(v, dict) : elif isinstance(v, dict):
if v.get("items") and isinstance(v["items"],(tuple,list)) : if v.get("items") and isinstance(v["items"], (tuple, list)):
args[k] = EnumProperty(items = [(i,i,"") for i in v["items"]]) args[k] = EnumProperty(items=[(i, i, "") for i in v["items"]])
elif v.get("collection") and isinstance(v["collection"],str) : elif v.get("collection") and isinstance(v["collection"], str):
if v["collection"] not in id_type : if v["collection"] not in id_type:
print("Collection %s not supported must be in %s"%(v["collection"],id_type)) print(
#args[k] = PointerProperty(type = getattr(bpy.types,v["collection"])) "Collection %s not supported must be in %s"
else : % (v["collection"], id_type)
args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]),poll = v.get('poll')) )
# args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]))
else:
args[k] = PointerProperty(
type=getattr(bpy.types, v["collection"]), poll=v.get("poll")
)
else: else:
args[k] = prop_type[type(v['default'])](**v) args[k] = prop_type[type(v["default"])](**v)
return args return args
def read_info(script_path) : def read_info(script_path):
import collections import collections
if isinstance(script_path,list) : if isinstance(script_path, list):
lines =script_path lines = script_path
else : else:
with open(script_path,encoding="utf-8") as f: with open(script_path, encoding="utf-8") as f:
lines = f.read().splitlines() lines = f.read().splitlines()
info = {} info = {}
for i,l in enumerate(lines) : for i, l in enumerate(lines):
if i >= 10 : if i >= 10:
return info,lines return info, lines
if l.startswith("info") : if l.startswith("info"):
info_start = i info_start = i
info_str = l info_str = l
while info_str.endswith(',') or not info_str.endswith('}') or i == len(lines)-1: while (
i+=1 info_str.endswith(",")
info_str+= lines[i] or not info_str.endswith("}")
or i == len(lines) - 1
):
i += 1
info_str += lines[i]
info_end = i info_end = i
opening_brace_index = info_str.find('{') opening_brace_index = info_str.find("{")
try : try:
info = eval(info_str[opening_brace_index:]) info = eval(info_str[opening_brace_index:])
except : except:
print('The info header in the file %s cannot be read'%script_path) print("The info header in the file %s cannot be read" % script_path)
break break
info = [(k,v) for k,v in info.items()] info = [(k, v) for k, v in info.items()]
info.sort(key = lambda x : info_str.find(x[0])) info.sort(key=lambda x: info_str.find(x[0]))
info = collections.OrderedDict(info) info = collections.OrderedDict(info)
del lines[info_start:info_end+1] del lines[info_start : info_end + 1]
break break
# remove backspace # remove backspace
for i in range(len(lines)) : for i in range(len(lines)):
if lines[0] : if lines[0]:
break break
else : else:
lines.pop(0) lines.pop(0)
for i in range(len(lines)) : for i in range(len(lines)):
if lines[-1] : if lines[-1]:
break break
else : else:
lines.pop(-1) lines.pop(-1)
return info,lines return info, lines
def dic_to_str(dic,keys= None,spaces = 4) :
if not keys : def dic_to_str(dic, keys=None, spaces=4):
if not keys:
keys = sorted(dic) keys = sorted(dic)
line = '{\n' line = "{\n"
for k in keys : for k in keys:
v = dic[k] v = dic[k]
line += ' '*spaces line += " " * spaces
if isinstance(k,str) : if isinstance(k, str):
line +="'%s'"%k line += "'%s'" % k
else : else:
line += "%s"%k line += "%s" % k
line += ' : ' line += " : "
if isinstance(v,str) : if isinstance(v, str):
line +="'%s'"%v line += "'%s'" % v
else : else:
line += "%s"%v line += "%s" % v
line += ',\n' line += ",\n"
line += '}\n\n' line += "}\n\n"
return line return line

113
uv.lock generated Normal file
View File

@ -0,0 +1,113 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "black"
version = "25.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" },
{ url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" },
{ url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" },
{ url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" },
{ url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" },
{ url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" },
{ url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" },
{ url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" },
{ url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" },
{ url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" },
{ url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" },
{ url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" },
{ url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "custom-shelf"
version = "0.1.0"
source = { virtual = "." }
[package.dev-dependencies]
dev = [
{ name = "black" },
]
[package.metadata]
[package.metadata.requires-dev]
dev = [{ name = "black", specifier = ">=25.11.0" }]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
]
[[package]]
name = "pytokens"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" },
]