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
class Types():
def initialyse(self, label) :
package = __package__.replace('_','').replace('-','').lower()
idname = label.replace(' ','_').replace('-','_').lower()
class Types:
def initialyse(self, label):
package = __package__.replace("_", "").replace("-", "").lower()
idname = label.replace(" ", "_").replace("-", "_").lower()
self.bl_label = title(label)
self.bl_idname = '%s.%s'%(package, idname)
#self.bl_props = bpy.context.scene.CustomShelf
#self.prefs = ""
self.bl_idname = "%s.%s" % (package, idname)
# self.bl_props = bpy.context.scene.CustomShelf
# self.prefs = ""
class CSHELF_OT_shelf_popup(Types, Operator):
#props = None
bl_options = {'REGISTER', 'UNDO'}
# props = None
bl_options = {"REGISTER", "UNDO"}
@property
def props(self) :
#print(getattr(bpy.context.window_manager.CustomShelf,self.prop))
def props(self):
# print(getattr(bpy.context.window_manager.CustomShelf,self.prop))
return getattr(bpy.context.window_manager.CustomShelf, self.prop)
def get_props(self):
properties = self.props.bl_rna.properties
#props =
return {k:v for k,v in self.args.items() if k in properties}
# props =
return {k: v for k, v in self.args.items() if k in properties}
def set_props(self,prop,value):
setattr(bpy.context.window_manager.CustomShelf,prop,value)
def set_props(self, prop, value):
setattr(bpy.context.window_manager.CustomShelf, prop, value)
def initialyse(self,label,info) :
super().initialyse(CSHELF_OT_shelf_popup,label)
def initialyse(self, label, info):
super().initialyse(CSHELF_OT_shelf_popup, label)
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)
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.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)
#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
return # props
def invoke(self, context, event):
if event.alt :
for t in [t for t in bpy.data.texts if t.filepath == self.script] :
if event.alt:
for t in [t for t in bpy.data.texts if t.filepath == self.script]:
bpy.data.texts.remove(t)
text = bpy.data.texts.load(self.script)
areas = []
text_editor = None
for area in context.screen.areas :
if area.type == 'TEXT_EDITOR' :
for area in context.screen.areas:
if area.type == "TEXT_EDITOR":
text_editor = area
else :
else:
areas.append(area)
if not text_editor :
bpy.ops.screen.area_split(direction = "VERTICAL")
if not text_editor:
bpy.ops.screen.area_split(direction="VERTICAL")
for area in context.screen.areas :
if area not in areas :
for area in context.screen.areas:
if area not in areas:
text_editor = area
text_editor.type = "TEXT_EDITOR"
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
context_copy = context.copy()
context_copy['area'] = text_editor
#bpy.ops.text.properties(context_copy)
#bpy.ops.view3d.toolshelf(context_copy)
context_copy["area"] = text_editor
# bpy.ops.text.properties(context_copy)
# bpy.ops.view3d.toolshelf(context_copy)
text_editor.spaces[0].text = text
return {"FINISHED"}
else :
if self.args :
#set default value for collection
for k,v in self.info.items() :
if isinstance(v,dict) and v.get('collection') :
else:
if self.args:
# set default value for collection
for k, v in self.info.items():
if isinstance(v, dict) and v.get("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)
else :
else:
return self.execute(context)
def bl_report(self,*args) :
if len(args) and args[0] in ('INFO','ERROR', 'WARNING') :
return self.report({args[0]},' '.join(args[1:]))
else :
def bl_report(self, *args):
if len(args) and args[0] in ("INFO", "ERROR", "WARNING"):
return self.report({args[0]}, " ".join(args[1:]))
else:
print(*args)
def execute(self,context):
def execute(self, context):
info, lines = read_info(self.script)
values = dict(info)
for arg in self.args :
value = getattr(self.props,arg)
for arg in self.args:
value = getattr(self.props, arg)
if hasattr(value,"name") :
values[arg] = {'value': value.name}
if hasattr(value, "name"):
values[arg] = {"value": value.name}
elif isinstance(info.get(arg),dict) :
values[arg] = {'value': value}
elif isinstance(info.get(arg), dict):
values[arg] = {"value": value}
else :
else:
values[arg] = value
info_str = "info = %s\n\n"%values
lines_str = '\n'.join(lines)
info_str = "info = %s\n\n" % values
lines_str = "\n".join(lines)
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
bl_props = context.scene.CustomShelf
#print(self.get_props())
# print(self.get_props())
for k in self.args :
#filter
for k in self.args:
# filter
row = layout.row(align = True)
row = layout.row(align=True)
row.label(text=title(k))
row.prop(self.props,k,text='')
#row.prop_search(self.props,k,self.props,k+"_prop_search",text='')
#op = self.args[k+"_prop_search"]
#row.operator(op.bl_idname,text="",icon ="COLLAPSEMENU" )
row.prop(self.props, k, text="")
# row.prop_search(self.props,k,self.props,k+"_prop_search",text='')
# op = self.args[k+"_prop_search"]
# row.operator(op.bl_idname,text="",icon ="COLLAPSEMENU" )
def check(self,context) :
def check(self, context):
return True
"""
class ShelfPropSearch(Types,Operator):
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_region_type = "UI"
#bl_category = "SHELF"
# bl_category = "SHELF"
settings = {}
scripts = []
@staticmethod
def search_filter(search,str) :
def search_filter(search, str):
return search.lower() in str.lower()
@classmethod
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
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
if self.settings and self.settings.get('tags') :
if self.settings and self.settings.get("tags"):
tag = prefs.tag_filter
if not tag :
if not 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
def draw(self,context) :
def draw(self, context):
layout = self.layout
bl_props = getattr(context.scene.CustomShelf,self.cat)
bl_props = getattr(context.scene.CustomShelf, self.cat)
search = bl_props.search
for script in self.scripts :
for script in self.scripts:
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"])
#print(script)
# print(script)
layout.operator(**args)
class CSHELF_PT_shelf_header(Panel):
bl_label = "Custom Shelf"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
layout = self.layout
bl_props = bpy.context.scene.CustomShelf
@ -256,16 +243,20 @@ class CSHELF_PT_shelf_header(Panel):
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("customshelf.refresh", text='',emboss=True,icon= "FILE_REFRESH")
#row.separator()
row.operator_menu_enum(
"customshelf.set_tag_filter", "tag_filter", icon="DOWNARROW_HLT", text=""
)
row.operator("customshelf.refresh", text="", emboss=True, icon="FILE_REFRESH")
# row.separator()
row.prop(cat_props, "filter",text='',emboss=True,icon= "FILTER")
#row.separator()
row.prop(cat_props, "filter", text="", emboss=True, icon="FILTER")
# row.separator()
row.prop(cat_props,"search",icon = "VIEWZOOM",text='')
#row.separator()
row.prop(cat_props, "search", icon="VIEWZOOM", text="")
# 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",
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/issues",
"support": "COMMUNITY",
"category": "User"}
"category": "User",
}
if "bpy" in locals():
import importlib
importlib.reload(operators)
importlib.reload(panels)
importlib.reload(functions)
importlib.reload(properties)
from . utils import report
from . functions import read_shelves
from .utils import report
from .functions import read_shelves
from . import operators
from . import properties
from . import ui
from .properties import CustomShelfSettings,CustomShelfProps
from .properties import CustomShelfSettings, CustomShelfProps
import bpy
import os
@ -41,19 +42,22 @@ bl_classes = [
operators.CSHELF_OT_open_shelf_folder,
operators.CSHELF_OT_add_script,
operators.CSHELF_OT_set_tag_filter,
ui.CSHELF_MT_text_editor
ui.CSHELF_MT_text_editor,
]
def draw_menu(self, context):
self.layout.menu('CSHELF_MT_text_editor')
self.layout.menu("CSHELF_MT_text_editor")
def register():
for bl_class in bl_classes :
for bl_class in bl_classes:
bpy.utils.register_class(bl_class)
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)
@ -72,10 +76,11 @@ def register():
read_shelves()
def unregister():
# unregister panel :
for panel in CustomShelfSettings.panel_list :
try :
for panel in CustomShelfSettings.panel_list:
try:
bpy.utils.unregister_class(panel)
except Exception:
pass
@ -84,5 +89,5 @@ def unregister():
del bpy.types.Scene.CustomShelf
del bpy.types.WindowManager.CustomShelf
for bl_class in bl_classes :
for bl_class in bl_classes:
bpy.utils.unregister_class(bl_class)

View File

@ -1,197 +1,222 @@
from .utils import *
from bpy.props import *
from bpy.types import Panel,Operator
from .properties import CustomShelfSettings,CustomShelfPrefs
from bpy.types import Panel, Operator
from .properties import CustomShelfSettings, CustomShelfPrefs
from .Types import *
SHELF_DIR = join(dirname(__file__),'shelves')
SHELF_DIR = join(dirname(__file__), "shelves")
def operator(idname,args) :
op_execute = args['execute']
def operator(idname, args):
op_execute = args["execute"]
def execute(self, context):
op_execute(context,self)
op_execute(context, self)
return {"FINISHED"}
package = __package__.replace('_','').replace('-','').lower()
package = __package__.replace("_", "").replace("-", "").lower()
args['bl_idname'] = "%s.%s"%(package,idname)
args['bl_label'] = title(idname)
args['execute'] = execute
args["bl_idname"] = "%s.%s" % (package, idname)
args["bl_label"] = title(idname)
args["execute"] = execute
op = type('operator',(Operator,),args)
op = type("operator", (Operator,), args)
bpy.utils.register_class(op)
return op
def register_bl_class(bl_class,args) :
a = type('test',(bl_class,),{})
a.initialyse(a,*args)
def register_bl_class(bl_class, args):
a = type("test", (bl_class,), {})
a.initialyse(a, *args)
bpy.utils.register_class(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():
prefs = bpy.context.preferences.addons[__package__].preferences
shelves_path = prefs.global_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) :
if isinstance(paths,(tuple,list)) :
def get_dirs(paths):
if isinstance(paths, (tuple, list)):
dirs = []
for dir in paths:
if not isinstance(dir, str) :
if not isinstance(dir, str):
dir = dir.path
dirs += filter_dir(scandir(dir))
return dirs
else :
else:
return filter_dir(scandir(paths))
def get_categories():
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()))]
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)]]
if category_dir :
return join(category_dir[0],category)
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)]
]
if category_dir:
return join(category_dir[0], category)
def get_category_tabs(category):
return [d.name for d in get_dirs(get_category_path(category))]
def read_shelves() :
def read_shelves():
tag_filter_items = []
# unregister panel :
for panel in CustomShelfSettings.panel_list:
print(panel)
if panel.__name__ not in get_tabs():
try :
try:
bpy.utils.unregister_class(panel)
except Exception:
pass
CustomShelfSettings.panel_list.remove(panel)
for cat in get_dirs(get_shelves_folder()) :
cat_name = cat.name.replace('_',' ').title()
for cat in get_dirs(get_shelves_folder()):
cat_name = cat.name.replace("_", " ").title()
header_info = {
"bl_region_type" : "UI",#petit ajout pour voir si foncitonne...
"bl_category" : cat_name,
"bl_idname" : "%s_PT_%s_header"%(__package__.upper(),cat.name),# -> "%s.%s_header" makes _PT_ warning
"cat" : cat.name,
"path" : cat.path
"bl_region_type": "UI", # petit ajout pour voir si foncitonne...
"bl_category": cat_name,
"bl_idname": "%s_PT_%s_header"
% (__package__.upper(), cat.name), # -> "%s.%s_header" makes _PT_ warning
"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)
#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
## 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)}}
PanelProps = type("CustomShelfPrefs",(PropertyGroup,),panel_props)
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)
setattr(CustomShelfPrefs, cat.name, PointerProperty(type=PanelProps))
#search and filter
# search and filter
props = {
"search" : StringProperty(options = {"TEXTEDIT_UPDATE"}),
"filter" : BoolProperty(default = True)
"search": StringProperty(options={"TEXTEDIT_UPDATE"}),
"filter": BoolProperty(default=True),
}
# Props = type("props",(PropertyGroup,), props)# annotation error
Props = type("props",(PropertyGroup,),{'__annotations__': props})
Props = type("props", (PropertyGroup,), {"__annotations__": props})
bpy.utils.register_class(Props)
setattr(CustomShelfSettings,cat.name,PointerProperty(type = Props))
setattr(CustomShelfSettings, cat.name, PointerProperty(type=Props))
#enable bool prefs
#Prefs = type("CustomShelfPrefs",(PropertyGroup,),{})
#bpy.utils.register_class(Prefs)
# enable bool prefs
# Prefs = type("CustomShelfPrefs",(PropertyGroup,),{})
# 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) :
#get_settings
settings_json = join(tab.path,'settings.json')
for tab in get_dirs(cat.path):
# get_settings
settings_json = join(tab.path, "settings.json")
tab_settings = {}
if exists(settings_json) :
if exists(settings_json):
tab_settings = read_json(settings_json)
if not tab_settings or not tab_settings.get('tags') : continue
for tag in tab_settings['tags'] :
if tag not in tag_filter_items :
if not tab_settings or not tab_settings.get("tags"):
continue
for tag in tab_settings["tags"]:
if tag not in tag_filter_items:
tag_filter_items.append(tag)
panel_info = {
"bl_region_type" : "UI",#Added, maybe need it in 2.8+...
"bl_category" : cat_name,
"bl_idname" : "%s_PT_%s_%s"%(__package__.upper(),cat.name,tab.name),#"%s.%s_%s" -> makes _PT_ warning
"bl_label" : tab.name.replace('_',' ').title(),
"cat" : cat.name,
"settings" :tab_settings
"bl_region_type": "UI", # Added, maybe need it in 2.8+...
"bl_category": cat_name,
"bl_idname": "%s_PT_%s_%s"
% (
__package__.upper(),
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 = type("Panel",(CSHELF_PT_shelf_panel,),panel_info)
panel = type("Panel", (CSHELF_PT_shelf_panel,), panel_info)
bpy.utils.register_class(panel)
CustomShelfSettings.panel_list.append(panel)
scripts = []
for script in scandir(tab.path) :
if not script.name.endswith('.py'):
for script in scandir(tab.path):
if not script.name.endswith(".py"):
continue
print(script.path)
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,),
{"script": script.path,
'bl_description': info["description"]
})
popup = type(
script_name,
(CSHELF_OT_shelf_popup,),
{"script": script.path, "bl_description": info["description"]},
)
popup.initialyse(popup, script_name, info)
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
#setattr(Pre, v)
# Register tag filter enum
# setattr(Pre, v)
prefs = bpy.context.preferences.addons[__package__].preferences
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.register_class(CustomShelfPrefs)
#CustomShelfSettings.folders = list(get_shelves_folder().keys())
# bpy.utils.unregister_class(CustomShelfPrefs)
# bpy.utils.register_class(CustomShelfPrefs)
# CustomShelfSettings.folders = list(get_shelves_folder().keys())
#folder_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in CustomShelfSettings.folders])
#setattr(CustomShelfSettings,'folders_enum',folder_enum)
# folder_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in CustomShelfSettings.folders])
# setattr(CustomShelfSettings,'folders_enum',folder_enum)

View File

@ -7,232 +7,323 @@ from .properties import CustomShelfSettings
class CSHELF_OT_refresh(Operator):
bl_idname = "customshelf.refresh"
bl_label = 'Refresh Shelves'
bl_label = "Refresh Shelves"
def execute(self,context) :
def execute(self, context):
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
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):
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):
prefs = bpy.context.preferences.addons[__package__].preferences
tag_filter = self.tag_filter
if tag_filter == '__clear__' :
if tag_filter == "__clear__":
tag_filter = ""
prefs.tag_filter = tag_filter
return {'FINISHED'}
return {"FINISHED"}
class CSHELF_OT_add_shelf_folder(Operator):
bl_idname = "customshelf.add_shelves_folder"
bl_label = 'Refresh Shelves'
bl_label = "Refresh Shelves"
def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
path = prefs.additionnal_shelves.add()
return {'FINISHED'}
return {"FINISHED"}
class CSHELF_OT_remove_shelf_folder(Operator):
bl_idname = "customshelf.remove_shelves_folder"
bl_label = 'Refresh Shelves'
bl_label = "Refresh Shelves"
index : IntProperty()
index: IntProperty()
def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
prefs.additionnal_shelves.remove(self.index)
return {'FINISHED'}
return {"FINISHED"}
class CSHELF_OT_open_shelf_folder(Operator):
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)
return {'FINISHED'}
return {"FINISHED"}
class CSHELF_OT_add_script(Operator) :
class CSHELF_OT_add_script(Operator):
bl_idname = "customshelf.add_script"
bl_label = 'Add script to a shelf'
bl_label = "Add script to a shelf"
add_category : BoolProperty()
add_tab : BoolProperty()
new_category : StringProperty()
new_tab : StringProperty()
add_category: BoolProperty()
add_tab: BoolProperty()
new_category: StringProperty()
new_tab: StringProperty()
name : StringProperty()
description : StringProperty()
icon : StringProperty()
show_icons : BoolProperty()
name: StringProperty()
description: StringProperty()
icon: StringProperty()
show_icons: BoolProperty()
@classmethod
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.name = self.name
#CustomShelfSettings
# CustomShelfSettings
def remove_folder(self,context,op) :
def remove_folder(self, context, op):
bl_props = context.scene.CustomShelf
index = self.folders.find(self.folders_enum)
self.folders.remove(index)
def get_all_icons (self) :
def get_all_icons(self):
ui_layout = bpy.types.UILayout
icons = ui_layout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys()
prefixes = ('BRUSH_','MATCAP_','COLORSET_')
exception = ('BRUSH_DATA')
prefixes = ("BRUSH_", "MATCAP_", "COLORSET_")
exception = "BRUSH_DATA"
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 = [
["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",
"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"],
[
"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",
"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
def set_icon(self,context,op) :
#bl_props = context.scene.CustomShelf
def set_icon(self, context, op):
# bl_props = context.scene.CustomShelf
self.icon = op.icon
self.icons = []
def draw(self,context) :
def draw(self, context):
bl_props = context.scene.CustomShelf
layout = self.layout
row = layout.row()
row.operator("customshelf.get_icons",text= '',icon = self.icon)
row.prop(self,"name",text ="")
row.operator("customshelf.get_icons", text="", icon=self.icon)
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 :
i=0
for icon in icon_list :
if not i%12 :
row = col.row(align= True)
row.operator("customshelf.set_icon", icon=icon, emboss=False, text="").icon=icon
row.operator(
"customshelf.set_icon", icon=icon, emboss=False, text=""
).icon = icon
i += 1
row = col.row(align= True)
row = col.row(align=True)
layout.prop(self, "description", text="")
layout.separator()
#Category Row
folder_row = layout.row(align = True)
folder_row.label(text="", icon='FILE_FOLDER')
# Category Row
folder_row = layout.row(align=True)
folder_row.label(text="", icon="FILE_FOLDER")
folder_row.separator()
if not self.add_category :
folder_row.prop(bl_props,"category_enum",expand = True)
if not self.add_category:
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
tab_row = layout.row(align = True)
tab_row.label(text='', icon='MENU_PANEL')
tab_row = layout.row(align=True)
tab_row.label(text="", icon="MENU_PANEL")
tab_row.separator()
if not self.add_tab :
if not self.add_tab:
category_tabs = get_category_tabs(bl_props.category_enum)
for t in [t for t in category_tabs if t in self.tabs] :
tab_row.prop_enum(bl_props,"tab_enum",t)
for t in [t for t in category_tabs if t in self.tabs]:
tab_row.prop_enum(bl_props, "tab_enum", t)
else :
tab_row.prop(self,"new_tab",text='')
else:
tab_row.prop(self, "new_tab", text="")
tab_row.prop(self,"add_tab",icon = 'ADD',text='')
#folder_row.operator("customshelf.remove_folder",icon = 'REMOVE',text='')
tab_row.prop(self, "add_tab", icon="ADD", text="")
# folder_row.operator("customshelf.remove_folder",icon = 'REMOVE',text='')
def write_script(self,f) :
keys = ['icon','description']
def write_script(self, f):
keys = ["icon", "description"]
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)
f.write('\n'.join(self.lines))
f.write("\n".join(self.lines))
def execute(self,context) :
def execute(self, context):
preferences = context.preferences
bl_props = context.scene.CustomShelf
addon_prefs = preferences.addons[__package__].preferences
if self.new_category :
if self.new_category:
category_path = get_category_path(self.new_category)
if not exists(category_path):
mkdir(new_category)
else :
else:
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)
if not exists(tab_path):
mkdir(tab_path)
else :
tab_path = join(category_path,bl_props.tab_enum)
else:
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):
os.remove(script_path)
self.info["icon"] = self.icon
self.info["description"] = self.description
self.info['icon'] = self.icon
self.info['description'] = self.description
with open(script_path,"w") as f :
with open(script_path, "w") as f:
self.write_script(f)
line_index = self.active_text.current_line_index
@ -246,21 +337,21 @@ class CSHELF_OT_add_script(Operator) :
return {"FINISHED"}
def check(self,context) :
def check(self, context):
return True
def invoke(self,context,event) :
def invoke(self, context, event):
bl_props = context.scene.CustomShelf
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"
if self.info.get('icon') :
if self.info.get("icon"):
icon = self.info["icon"]
description = "Some description"
if self.info.get('description') :
if self.info.get("description"):
description = self.info["description"]
self.icon = icon
@ -272,25 +363,32 @@ class CSHELF_OT_add_script(Operator) :
self.new_shelf = ""
self.icons = []
operator("get_icons", {'execute': self.get_icons})
operator("set_icon", {'execute': self.set_icon, '__annotations__': {'icon' : StringProperty()}})
operator("add_folder", {'execute': self.add_folder})
#operator("remove_folder",{'execute':self.remove_folder})
operator("get_icons", {"execute": self.get_icons})
operator(
"set_icon",
{"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.categories = get_categories()
CustomShelfSettings.category_enum = EnumProperty(items=[(i,i,"") for i in self.categories])
CustomShelfSettings.tab_enum = EnumProperty(items=[(i,i,"") for i in self.tabs])
CustomShelfSettings.category_enum = EnumProperty(
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)
category = basename(dirname(tab_path))
tab = basename(tab_path)
if category in self.categories :
if category in self.categories:
bl_props.category_enum = category
if tab in self.tabs :
if tab in self.tabs:
bl_props.tab_enum = tab
return context.window_manager.invoke_props_dialog(self, width=500)

View File

@ -2,47 +2,53 @@ from .utils import *
from bpy.props import *
class CustomShelfSettings(bpy.types.PropertyGroup) :
class CustomShelfSettings(bpy.types.PropertyGroup):
panel_list = []
#category_enum = EnumProperty(items = )
#tab_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in s.tabs])
# category_enum = EnumProperty(items = )
# tab_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in s.tabs])
#search = StringProperty(options = {"TEXTEDIT_UPDATE"})
#filter = BoolProperty(default = True)
#folders = PointerProperty(type= CustomShelfFolders)
#variables = bpy.props.PointerProperty(type= CustomShelfVariables)
# search = StringProperty(options = {"TEXTEDIT_UPDATE"})
# filter = BoolProperty(default = True)
# folders = PointerProperty(type= CustomShelfFolders)
# variables = bpy.props.PointerProperty(type= CustomShelfVariables)
class CustomShelfProps(bpy.types.PropertyGroup) :
class CustomShelfProps(bpy.types.PropertyGroup):
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):
bl_idname = __package__
global_path = join(dirname(__file__),'shelves')
global_shelves : StringProperty(name="Shelves Path",subtype = 'DIR_PATH',default =global_path)
additionnal_shelves : CollectionProperty(type = AdditionnalShelves)
tag_filter : StringProperty()
global_path = join(dirname(__file__), "shelves")
global_shelves: StringProperty(
name="Shelves Path", subtype="DIR_PATH", default=global_path
)
additionnal_shelves: CollectionProperty(type=AdditionnalShelves)
tag_filter: StringProperty()
def draw(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
layout = self.layout
layout.prop(self, "global_shelves")
row = layout.row(align = True)
row.operator("customshelf.add_shelves_folder",icon="ADD",text="")
row.label(text='Additionnal Shelves folder')
row = layout.row(align=True)
row.operator("customshelf.add_shelves_folder", icon="ADD", text="")
row.label(text="Additionnal Shelves folder")
for i,shelf in enumerate(prefs.additionnal_shelves) :
row = layout.row(align = True)
for i, shelf in enumerate(prefs.additionnal_shelves):
row = layout.row(align=True)
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.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
@ -7,9 +6,7 @@ class CSHELF_MT_text_editor(bpy.types.Menu):
def draw(self, context):
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 os
from os import listdir,mkdir,scandir
from os.path import join,dirname,splitext,isdir,exists,basename
from os import listdir, mkdir, scandir
from os.path import join, dirname, splitext, isdir, exists, basename
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import *
import subprocess
import json
id_type = {"Action" : "actions", "Armature":"armatures", "Brush":"brushes", "CacheFile":"cache_files", "Camera":"cameras",
"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"}
id_type = {
"Action": "actions",
"Armature": "armatures",
"Brush": "brushes",
"CacheFile": "cache_files",
"Camera": "cameras",
"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 globals()])
if "self" in var:
var["self"].report({type}, message)
if 'self' in var :
var['self'].report({type}, message)
else :
print('')
else:
print("")
print(type)
print(message)
def read_json(path):
jsonFile = path
if os.path.exists(jsonFile):
try :
try:
with open(jsonFile) as data_file:
return (json.load(data_file))
except :
print("the file %s not json readable"%jsonFile)
return json.load(data_file)
except:
print("the file %s not json readable" % jsonFile)
return
def search_filter(search,str) :
def search_filter(search, str):
return search.lower() in str.lower()
def title(string) :
return string.replace('_',' ').replace('-',' ').title()
def arg_name(string) :
return string.replace(' ','_').replace('-','_').lower().strip('_')
def title(string):
return string.replace("_", " ").replace("-", " ").title()
def open_folder(path) :
def arg_name(string):
return string.replace(" ", "_").replace("-", "_").lower().strip("_")
def open_folder(path):
try:
os.startfile(path)
except:
subprocess.Popen(['xdg-open', path])
subprocess.Popen(["xdg-open", path])
def dic_to_args(dic):
import collections
args = collections.OrderedDict()
prop_type = {
str: StringProperty,
bool: BoolProperty,
float: FloatProperty,
int : IntProperty
int: IntProperty,
}
for k, v in dic.items() :
if k in ('icon','description') or not isinstance(k,str):
for k, v in dic.items():
if k in ("icon", "description") or not isinstance(k, str):
continue
if isinstance(v,str) :
if '/' in v or '\\' in v :
args[k] = StringProperty(default=v,subtype = "FILE_PATH")
else :
if isinstance(v, str):
if "/" in v or "\\" in v:
args[k] = StringProperty(default=v, subtype="FILE_PATH")
else:
args[k] = StringProperty(default=v)
elif isinstance(v,bool) :
elif isinstance(v, bool):
args[k] = BoolProperty(default=v)
elif isinstance(v, float) :
args[k] = FloatProperty(default=v,precision=3)
elif isinstance(v, float):
args[k] = FloatProperty(default=v, precision=3)
elif isinstance(v,int) :
elif isinstance(v, int):
args[k] = IntProperty(default=v)
elif isinstance(v, dict) :
if v.get("items") and isinstance(v["items"],(tuple,list)) :
args[k] = EnumProperty(items = [(i,i,"") for i in v["items"]])
elif isinstance(v, dict):
if v.get("items") and isinstance(v["items"], (tuple, list)):
args[k] = EnumProperty(items=[(i, i, "") for i in v["items"]])
elif v.get("collection") and isinstance(v["collection"],str) :
if v["collection"] not in id_type :
print("Collection %s not supported must be in %s"%(v["collection"],id_type))
#args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]))
else :
args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]),poll = v.get('poll'))
elif v.get("collection") and isinstance(v["collection"], str):
if v["collection"] not in id_type:
print(
"Collection %s not supported must be in %s"
% (v["collection"], id_type)
)
# args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]))
else:
args[k] = PointerProperty(
type=getattr(bpy.types, v["collection"]), poll=v.get("poll")
)
else:
args[k] = prop_type[type(v['default'])](**v)
args[k] = prop_type[type(v["default"])](**v)
return args
def read_info(script_path) :
def read_info(script_path):
import collections
if isinstance(script_path,list) :
lines =script_path
else :
with open(script_path,encoding="utf-8") as f:
if isinstance(script_path, list):
lines = script_path
else:
with open(script_path, encoding="utf-8") as f:
lines = f.read().splitlines()
info = {}
for i,l in enumerate(lines) :
if i >= 10 :
return info,lines
info = {}
for i, l in enumerate(lines):
if i >= 10:
return info, lines
if l.startswith("info") :
if l.startswith("info"):
info_start = i
info_str = l
while info_str.endswith(',') or not info_str.endswith('}') or i == len(lines)-1:
i+=1
info_str+= lines[i]
while (
info_str.endswith(",")
or not info_str.endswith("}")
or i == len(lines) - 1
):
i += 1
info_str += lines[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:])
except :
print('The info header in the file %s cannot be read'%script_path)
except:
print("The info header in the file %s cannot be read" % script_path)
break
info = [(k,v) for k,v in info.items()]
info.sort(key = lambda x : info_str.find(x[0]))
info = [(k, v) for k, v in info.items()]
info.sort(key=lambda x: info_str.find(x[0]))
info = collections.OrderedDict(info)
del lines[info_start:info_end+1]
del lines[info_start : info_end + 1]
break
# remove backspace
for i in range(len(lines)) :
if lines[0] :
for i in range(len(lines)):
if lines[0]:
break
else :
else:
lines.pop(0)
for i in range(len(lines)) :
if lines[-1] :
for i in range(len(lines)):
if lines[-1]:
break
else :
else:
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)
line = '{\n'
for k in keys :
line = "{\n"
for k in keys:
v = dic[k]
line += ' '*spaces
line += " " * spaces
if isinstance(k,str) :
line +="'%s'"%k
else :
line += "%s"%k
if isinstance(k, str):
line += "'%s'" % k
else:
line += "%s" % k
line += ' : '
line += " : "
if isinstance(v,str) :
line +="'%s'"%v
else :
line += "%s"%v
if isinstance(v, str):
line += "'%s'" % v
else:
line += "%s" % v
line += ',\n'
line += ",\n"
line += '}\n\n'
line += "}\n\n"
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" },
]