import os import traceback import bpy from bpy.types import Menu preview_collection = None logo_icon_id = 0 splash_icon_id = 0 original_splash_cls = None def load_icons(): global preview_collection, logo_icon_id, splash_icon_id if preview_collection is not None: return preview_collection = bpy.utils.previews.new() theme_dir = os.path.dirname(__file__) logo_path = os.path.join(theme_dir, "LeenkxLogo.png") splash_path = os.path.join(theme_dir, "splash.png") if os.path.exists(logo_path): preview_collection.load("logo", logo_path, 'IMAGE', force_reload=True) logo_icon_id = preview_collection["logo"].icon_id else: print("Leenkx theme: logo not found at", logo_path) if os.path.exists(splash_path): preview_collection.load("splash", splash_path, 'IMAGE', force_reload=True) splash_icon_id = preview_collection["splash"].icon_id else: print("Leenkx theme: splash not found at", splash_path) def install_theme(sdk_path: str): theme_xml = os.path.join(sdk_path, "leenkx", "blender", "theme", "BlenderLeenkxTheme.xml") if not os.path.exists(theme_xml): print("Leenkx theme: theme XML not found at", theme_xml) return bpy.ops.preferences.theme_install( filepath=theme_xml, overwrite=True) def setup_splash(sdk_path: str): splash_png = os.path.join(sdk_path, "leenkx", "blender", "theme", "splash.png") if os.path.exists(splash_png): os.environ["BLENDER_CUSTOM_SPLASH"] = splash_png else: print("Leenkx splash: splash.png not found at", splash_png) class LEENKX_MT_splash(Menu): bl_label = "Splash" bl_idname = "WM_MT_splash" def draw(self, context): layout = self.layout layout.operator_context = 'EXEC_DEFAULT' layout.emboss = 'PULLDOWN_MENU' split = layout.split() col1 = split.column() col1.label(text="New File") bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True) col2 = split.column() col2_title = col2.row() found_recent = col2.template_recent_files(rows=5) if found_recent: col2_title.label(text="Recent Files") col_more = col2.column() col_more.operator_context = 'INVOKE_DEFAULT' more_props = col_more.operator("wm.search_single_menu", text="More...", icon='VIEWZOOM') more_props.menu_idname = "TOPBAR_MT_file_open_recent" else: col2_title.label(text="Getting Started") col2.operator("wm.url_open", text="Leenkx Docs", icon='URL').url = "https://leenkx.com/docs" col2.operator("wm.url_open", text="Support", icon='URL').url = "https://leenkx.com/support" col2.operator("wm.url_open", text="Community", icon='URL').url = "https://leenkx.com/community" col2.operator("wm.url_open", text="GitHub", icon='URL').url = "https://github.com/leenkx/leenkx" col_sep = layout.column() col_sep.separator() col_sep.separator(type='LINE') col_sep.separator() split = layout.split() col1 = split.column() sub = col1.row() sub.operator_context = 'INVOKE_DEFAULT' sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER') col1.operator("wm.recover_last_session", icon='RECOVER_LAST') col2 = split.column() col2.operator("wm.url_open", text="Support Leenkx", icon='FUND').url = "https://leenkx.com/donate" col2.operator("wm.url_open", text="Leenkx Website", icon='URL').url = "https://leenkx.com" layout.separator() if (not bpy.app.online_access) and bpy.app.online_access_override: self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE') layout.separator() def register_splash_menu(): global original_splash_cls cls = getattr(bpy.types, "WM_MT_splash", None) if cls is not None and cls is not LEENKX_MT_splash: original_splash_cls = cls bpy.utils.unregister_class(cls) bpy.utils.register_class(LEENKX_MT_splash) def unregister_splash_menu(): global original_splash_cls bpy.utils.unregister_class(LEENKX_MT_splash) if original_splash_cls is not None: bpy.utils.register_class(original_splash_cls) original_splash_cls = None saved_draws = {} def override_draw(bl_idname, new_draw): cls = getattr(bpy.types, bl_idname, None) if cls is None: print("Leenkx menu: class not found", bl_idname) return if bl_idname not in saved_draws: saved_draws[bl_idname] = cls.draw elif cls.draw is new_draw: return cls.draw = new_draw def restore_draw(bl_idname): cls = getattr(bpy.types, bl_idname, None) if cls is not None and bl_idname in saved_draws: cls.draw = saved_draws[bl_idname] def editor_menus_draw(self, context): layout = self.layout if logo_icon_id: layout.menu("TOPBAR_MT_blender", text="", icon_value=logo_icon_id) else: layout.menu("TOPBAR_MT_blender", text="Leenkx") layout.menu("TOPBAR_MT_file") layout.menu("TOPBAR_MT_edit") layout.menu("TOPBAR_MT_render") layout.menu("TOPBAR_MT_window") layout.menu("TOPBAR_MT_help") def blender_draw(self, _context): layout = self.layout layout.operator("wm.splash", text="Leenkx Splash") layout.separator() layout.operator("preferences.app_template_install", text="Install Application Template...") layout.separator() layout.menu("TOPBAR_MT_blender_system") def help_draw(self, context): layout = self.layout show_developer = context.preferences.view.show_developer_ui layout.operator("wm.url_open", text="Leenkx Docs", icon='URL').url = "https://leenkx.com/api" layout.operator("wm.url_open", text="Leenkx Support", icon='URL').url = "https://leenkx.com/contact" layout.operator("wm.url_open", text="Tutorials", icon='URL').url = "https://leenkx.com/tutorials" layout.operator("wm.url_open", text="Community", icon='URL').url = "https://leenkx.com/community" layout.operator("wm.url_open", text="Support Leenkx (Donate)", icon='FUND').url = "https://leenkx.com/donate" layout.separator() if show_developer: layout.operator("wm.url_open", text="Developer Docs", icon='URL').url = "https://leenkx.com/wiki" layout.operator("wm.url_open_preset", text="Python API Reference").type = 'API' layout.separator() layout.operator("wm.url_open", text="Report a Bug", icon='URL').url = "https://leenkx.com/contact" layout.operator("wm.sysinfo") def register(sdk_path: str): install_theme(sdk_path) setup_splash(sdk_path) load_icons() register_splash_menu() override_draw("TOPBAR_MT_editor_menus", editor_menus_draw) override_draw("TOPBAR_MT_blender", blender_draw) override_draw("TOPBAR_MT_help", help_draw) def unregister(): restore_draw("TOPBAR_MT_editor_menus") restore_draw("TOPBAR_MT_blender") restore_draw("TOPBAR_MT_help") saved_draws.clear() unregister_splash_menu() global preview_collection if preview_collection is not None: bpy.utils.previews.remove(preview_collection) preview_collection = None os.environ.pop("BLENDER_CUSTOM_SPLASH", None) window = bpy.context.window_manager.windows[0] if bpy.app.version >= (3, 0, 0): with bpy.context.temp_override(window=window): bpy.ops.preferences.reset_default_theme() else: override = bpy.context.copy() override['window'] = window bpy.ops.preferences.reset_default_theme(override_context=override)