Source code for cgl.apps.alchemy.alchemy

# --- EARLY DLL LOAD TRACER ---
# print("Starting DLL Load Tracer...")
# import ctypes
#
# kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
#
# # Save originals
# _LoadLibraryW = kernel32.LoadLibraryW
# _LoadLibraryExW = kernel32.LoadLibraryExW
#
# _LoadLibraryW.argtypes = [ctypes.c_wchar_p]
# _LoadLibraryW.restype = ctypes.c_void_p
#
# _LoadLibraryExW.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_uint32]
# _LoadLibraryExW.restype = ctypes.c_void_p
#
# def hooked_LoadLibraryW(filename):
#     print(f"DLL Load: {filename}")
#     return _LoadLibraryW(filename)
#
# def hooked_LoadLibraryExW(filename, file_handle, flags):
#     print(f"DLL LoadEx: {filename}")
#     return _LoadLibraryExW(filename, file_handle, flags)
#
# # Patch BOTH APIs so all DLL loads print
# ctypes.windll.kernel32.LoadLibraryW = hooked_LoadLibraryW
# ctypes.windll.kernel32.LoadLibraryExW = hooked_LoadLibraryExW
#
# print("DLL tracer active")
#
# import os
# import glob
#
# print("Scanning for OTIO .pyd files...")
# otio_pyds = glob.glob(os.path.join(os.path.dirname(__file__), "..", "..", "**", "_otio*.pyd"), recursive=True)
# for f in otio_pyds:
#     print("Found OTIO binary:", f)
# # -----------------------------------------
#
# os.environ["OTIO_DEBUG"] = "1"
import sys
import os

if hasattr(sys, "_MEIPASS"):  # running as a frozen EXE
    # os.environ["OTIO_DEBUG"] = "1"
    from cgl.plugins.otio.edit import load_alchemy_otio_manifests

    load_alchemy_otio_manifests()
import opentimelineio as otio

# Force manifest rebuild (belt + suspenders)
manifest = otio.plugins.ActiveManifest()
print("OTIO registered adapters:")
print(sorted(a.name for a in manifest.adapters))
print("Alchemy running OTIO version:", otio.__version__)

from PySide6 import QtCore, QtGui, QtWidgets  # noqa: E402
from PySide6.QtCore import QThreadPool  # noqa: E402

import importlib  # noqa: E402
import logging  # noqa: E402
from functools import partial  # noqa: E402
from importlib import reload  # noqa: E402
from cgl.apps.alchemy.alchemy_startup import (  # noqa: E402
    VersionCheckWorker,
    init_alchemy,
)

from cgl.apps.alchemy.widgets.company_panel import CompanyPanel  # noqa: E402
from cgl.apps.css_editor.alchemy_theme_editor import ThemeManager  # noqa: E402
from cgl.core.config.query import (  # noqa: E402
    AlchemyConfigManager,
    get_fonts_root,
)
from cgl.core.path.object import PathObject  # noqa: E402
from cgl.core.utils.general import load_json  # noqa: E402
import cgl.apps.alchemy.widgets.asset_widget as asset_widget  # noqa: E402
import cgl.apps.alchemy.widgets.path_widget as path_widget  # noqa: E402
import cgl.apps.alchemy.widgets.project_panel as project_panel  # noqa: E402
import cgl.apps.alchemy.widgets.version_window as version_window  # noqa: E402
from cgl.core.utils.general import apply_theme  # noqa: E402
import resources.resources_rc  # noqa: F401, E402

CFG = AlchemyConfigManager()
reload(asset_widget)
reload(project_panel)
reload(path_widget)
reload(version_window)


[docs] class AlchemyWidget(QtWidgets.QWidget): """ This is the main widget for the Alchemy application. """ def __init__(self, ue=False): super(AlchemyWidget, self).__init__() apply_theme(self) # self.load_fonts() self.start_task_from_existing_dialog = None self.report_bug_dialog = None self.request_feature_dialog = None self.theme_manager = ThemeManager() self.theme_manager.themeUpdated.connect(self.setStyleSheet) self.skip_config_save = False self.thread_pool = QThreadPool() self.current_version = None self.cookbook_root = None self.open_windows = [] self.ue = ue self.company_panel = CompanyPanel(parent=self) if not self.ue: import cgl.apps.alchemy.widgets.io_widget as io_widget self.project_widget = project_panel.ProjectPanel( parent=self, company=CFG.company, project=CFG.project, ue=self.ue ) self.io_widget = io_widget.IOWidget(parent=self) else: # we'll set the UE logo with Alchemy Green self.set_alchemy_ue_logo() self.project_widget = project_panel.ProjectPanel( parent=self, company=CFG.company.lower(), project=CFG.project.lower(), ue=self.ue, ) self.project_widget.setSizePolicy( QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred ) self.asset_widget = asset_widget.AssetWidget(ue=self.ue) self.path_widget = path_widget.PathWidget(ue=self.ue) CFG.path_widget = self.path_widget self.version_widget = version_window.VersionWidget(parent=self, ue=self.ue) main_widget = QtWidgets.QWidget(self) # the horizontal layout for the main widget hlayout = QtWidgets.QHBoxLayout(main_widget) hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setSpacing( 1 ) # if i want to handle borders with this, i need to set a Frame around the main widget # The main Asset Panel self.asset_panel = QtWidgets.QWidget(self) asset_vlayout = QtWidgets.QVBoxLayout(self.asset_panel) asset_vlayout.setContentsMargins(0, 0, 0, 0) asset_vlayout.setSpacing(1) # ---- STACK ---- self.asset_stack = QtWidgets.QStackedWidget(self) self.asset_stack.addWidget(self.asset_widget) # index 0 self.asset_stack.addWidget(self.version_widget) # index 1 if not self.ue: self.asset_stack.addWidget(self.io_widget) # ---- Asset panel Layout ---- asset_vlayout.addWidget(self.asset_stack) asset_vlayout.addWidget(self.path_widget) hlayout.addWidget(self.company_panel) hlayout.addWidget(self.project_widget) hlayout.addWidget(self.asset_panel) self.setLayout(hlayout) ## set up connections self.project_widget.project_filter_widget.project_selection_widget.project_changed.connect( self.set_task_info ) self.project_widget.project_filter_widget.tree_updated.connect( self.set_task_info ) CFG.path_object_changed.connect( self.project_widget.project_filter_widget.refresh ) CFG.path_object_changed.connect(self.path_widget.update_path_text) # CFG.path_object_changed.connect(self.update_cookbook_root) if not self.ue: self.version_widget.files_widget.files_group.ue_source_widget.hide() else: CFG.refresh_assets = True self.company_panel.setVisible(False) self.version_widget.files_widget.files_group.source_widget.hide() self.version_widget.files_widget.files_group.ue_source_widget.show() self.project_widget.project_filter_widget.item_clicked.connect( self.edit_project_tags ) self.asset_widget.thumbnail_grid.show_files.connect(self.on_thumbnail_clicked) # load the tasks for the initial project self.version_widget.path_bar.back_clicked.connect(self.on_back_clicked) self.version_widget.files_widget.files_group.source_widget.fileSelected.connect( self.path_widget.update_path_text ) self.version_widget.files_widget.files_group.render_widget.fileSelected.connect( self.path_widget.update_path_text ) self.version_widget.files_widget.files_group.source_widget.fileSelected.connect( self.version_widget.notes_widget.thumbnail_widget.display_system_thumbnail ) self.version_widget.versions_widget.update_files.connect( self.path_widget.update_path_text ) self.company_panel.setCompanies() self.company_panel.joinCompany.connect(self.join_company) self.check_for_updates() self.load_tasks() # self.ingest_widget.hide()
[docs] def update_project_msd_from_sg(self): """ Updates the project metadata from shotgun Returns: """ sg = True company = CFG.company project = CFG.project if project: if sg: try: import cookbook.shotgrid.create as custom_sg_create custom_sg_create.project_msd(company, project) except ModuleNotFoundError: logging.warning("Cookbook override not found, using default") import cgl.plugins.shotgrid.create cgl.plugins.shotgrid.create.project_msd(company, project) logging.info("project MSD updated") # self.load_tasks() else: logging.info("Updating MSD based off files on disk") else: logging.warning("No Project Selected") # refresh the project widget with the new data self.load_tasks()
[docs] def load_tasks(self): company = CFG.company project = CFG.project print("Loading tasks for company:", company, "and project:", project) if not project and not company: return None # đźš« Don't proceed if no compan if not company: logging.warning("No company set. Skipping load_tasks.") return self.company_panel.select_company(company) if project and company: po_dict = { "root": CFG.prod_root, "project": project, "company": company, "sync_root": "VERSIONS", "season": "1", } po = PathObject().from_dict(po_dict) CFG.set_path_object(po) self.set_task_info() self.path_widget.update_path_text() else: # Avoid excessive empty task reloads logging.warning("No project selected. Doing Nothing.")
# po_dict = {"project": "", "company": company, "sync_root": "VERSIONS"} # po = PathObject().from_dict(po_dict) # self.po_changed.emit(po) # self.project_changed.emit([])
[docs] def join_company(self): """ This is a slot that is called when the user joins a company. It sets the path object to the company path object. Args: company: The company to join. """ from cgl.apps.alchemy.widgets.share import JoinDialog dialog = JoinDialog() dialog.exec()
# def on_project_changed(self): # sender = self.sender() # project_long_name = sender.currentText() # project_short_name = sender.project_dict["long_to_short"][project_long_name] # po_dict = { # "project": project_short_name, # "company": sender.company, # "sync_root": "VERSIONS", # } # po = PathObject().from_dict(po_dict) # CFG.set_path_object(po)
[docs] @QtCore.Slot() def show_versions_screen(self): self.asset_stack.setCurrentIndex(1)
[docs] @QtCore.Slot() def show_main_screen(self): self.asset_stack.setCurrentIndex(0)
[docs] def check_for_updates(self): worker = VersionCheckWorker() worker.signals.finished.connect(self.handle_version_result) self.thread_pool.start(worker)
[docs] def handle_version_result(self, latest_version, current_version): # logging.info("Current version:", current_version) # set the window tilte to the current version self.setWindowTitle(f"Alchemy - {current_version}") self.current_version = current_version if self.current_version == "dev": self.setWindowTitle( f"Alchemy - {current_version} ({latest_version} available)" ) logging.info("Skipping update check for dev version.") return if latest_version != self.current_version: self.setWindowTitle( f"Alchemy v{current_version} (v{latest_version} available)" ) self.show_update_notice(latest_version)
[docs] def show_update_notice(self, latest_version): return result = QtWidgets.QMessageBox.information( self, "Update Available", f"A new version of Alchemy is available:\n\n{latest_version}", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, ) if result == QtWidgets.QMessageBox.Ok: self.run_update() # replace with your update logic
[docs] def run_update(self): """ Launches the Alchemy installer from _internal and exits the application. """ import os import subprocess import sys if hasattr(sys, "_MEIPASS"): installer_path = os.path.join(sys._MEIPASS, "bin", "alchemy_installer.exe") else: logging.info("Running in development mode, using local installer path.") return if not os.path.exists(installer_path): QtWidgets.QMessageBox.critical( self, "Installer Not Found", f"❌ Unable to find the Alchemy installer at:\n{installer_path}", ) return try: # Launch installer with admin privileges via PowerShell subprocess.Popen( [ "powershell", "-Command", f"Start-Process -FilePath '{installer_path}' -Verb runAs", ] ) logging.info(f"🚀 Running installer: {installer_path}") except Exception as e: QtWidgets.QMessageBox.critical( self, "Update Failed", f"❌ Failed to launch installer:\n{e}" ) return # Gracefully close the app self.force_close()
[docs] def load_fonts(self): font_db = QtGui.QFontDatabase() font_directory = get_fonts_root() # Loop over all files in the font directory for font_file in os.listdir(font_directory): if font_file.endswith(".ttf") or font_file.endswith(".otf"): font_path = os.path.join(font_directory, font_file) font_id = font_db.addApplicationFont(font_path) if font_id == -1: logging.info(f"Failed to load font: {font_file}") else: logging.info(f"Font loaded: {font_file}")
[docs] def update_path_display(self, path_object): """ This updates the path display for the path widget. Args: path_object: Returns: """ self.path_widget.update_path_display(path_object)
[docs] def on_thumbnail_clicked(self): """ This is a slot that is called when a thumbnail is clicked. """ from cgl.plugins.alchemy.create import start_task logging.info("thumbnail clicked, starting task") print("ue value", self.ue) if self.ue: print("we're in unreal engine") path_object = CFG.path_object # launch the start recipe, for this we want the regular path_object. new_folder = start_task(path_object, software="unreal") if new_folder: self.show_versions_screen() return path_object = CFG.path_object version_path = path_object.get_path() # we have versions of this task - show the versions screen if os.path.exists(version_path): self.show_versions_widget() else: # Start a task from an existing publish/user version. users = path_object.get_users() if users and path_object.task != "tex": # skip this on substance. from cgl.ui.tools.new_task_from_existing.main import ( StartTaskWithOtherUsers, ) self.start_task_from_existing_dialog = StartTaskWithOtherUsers( parent=None, users=users, path_object=path_object ) self.start_task_from_existing_dialog.show() self.start_task_from_existing_dialog.raise_() # <-- important logging.info("Launching Widget for starting task from other users") new_folder = True # TODO - need to swithc this to proper signals coming from the dialog itself so that if we cancel we aren't creating folders. # only show this when triggered to show it from the UI. self.start_task_from_existing_dialog.task_started.connect( self.show_versions_widget ) # start a task the normal way. else: # We're starting a fresh task here. new_folder = start_task(path_object, software="alchemy") self.show_versions_widget()
[docs] def show_versions_widget(self): self.show_versions_screen() self.version_widget.versions_widget.version_combo_box.setCurrentIndex(0) self.version_widget.versions_widget.refresh_clicked()
[docs] def on_back_clicked(self): """ This is a slot that is called when the back button is clicked. """ # clean out the path_object logging.info("back clicked, did i set the path correctly?") self.show_main_screen()
[docs] def edit_project_tags(self, project_filter): """ This is a slot that is called when the project asset/shot filters are selected. It sets the tags for the asset widget. Args: data: Returns: """ if not self.project_widget.project_filter_widget.tree_widget.selectedItems(): return selected_item = ( self.project_widget.project_filter_widget.tree_widget.selectedItems()[0] ) if selected_item.path_variable == "ingests": from pathlib import Path # show the io widget instead of the asset widget IO_po = CFG.path_object.copy(sync_root="IO") io_path = Path(IO_po.get_path()) io_path = io_path / "ingest" / project_filter self.asset_stack.setCurrentIndex(2) self.io_widget.ingest_tab.load_folder(io_path.as_posix()) return self.asset_widget.show() tag = project_filter self.asset_widget.asset_filter_widget.remove_all_tags() if tag: self.asset_widget.asset_filter_widget.add_tag(tag)
[docs] def edit_task_tags(self, task_filters): """ This is a slot that is called when the radio buttons are selected. It sets the tags for the asset widget. Args: data: Returns: """ self.clean_tasks_from_filter() for task in task_filters: self.asset_widget.asset_filter_widget.add_tag(task)
[docs] def clean_tasks_from_filter(self): """ This removes anything in the "all_tasks" list from the task_filters list. Returns: """ all_tasks = self.project_widget.task_widget.all_tasks current_filters = ( self.asset_widget.asset_filter_widget.asset_filter_widget.frame.get_tags() ) # remove anything in all tasks from the task_filters for task in all_tasks: if task in current_filters: self.asset_widget.asset_filter_widget.remove_tag(task)
[docs] def set_task_info(self): """ This sets the task info for the asset widget Args: data: Returns: """ self.asset_widget.thumbnail_grid.get_current_filters() project_msd_path = CFG.path_object.get_project_msd_path() if not os.path.exists(project_msd_path): logging.warning( f"[set_task_info] Project MSD path does not exist, no task info to set. {project_msd_path}" ) # we want to hide the asset widget if there is no project MSD path. self.asset_widget.hide() return if CFG.path_object and os.path.exists(project_msd_path): print("updating grid") self.asset_widget.thumbnail_grid.load_grid() else: logging.warning("[set_task_info] No path object set, cannot set task info.")
[docs] class AlchemyMainWindow(QtWidgets.QMainWindow): def __init__(self): super(AlchemyMainWindow, self).__init__() apply_theme(self) self.menu_bar = self.menuBar() self.setWindowTitle("Alchemy") self.setCentralWidget(AlchemyWidget()) self.project_selection_widget = ( self.centralWidget().project_widget.project_filter_widget.project_selection_widget.project_selector ) self.windows = [] self.build_core_menus() print(1234, CFG.company, CFG.project, CFG.get_cookbook_path()) self.get_cookbook_menus() self.create_cookbook_menus() self.resize(1260, 740) # or whatever you want self.setFixedSize(self.size()) # prevents user resize self.icon = QtGui.QIcon(":/icons/Alchemy.iconset/icon_16x16@2x.png") self.setWindowIcon(self.icon) company_panel = self.centralWidget().company_panel company_panel.companyActivated.connect(self.close) company_panel.createCompany.connect( lambda: self.statusBar().showMessage("Create Company…", 3000) ) company_panel.joinCompany.connect( lambda: self.statusBar().showMessage("Join Company…", 3000) ) company_panel.companySettings.connect( lambda c: self.statusBar().showMessage(f"Settings for {c.name}", 3000) ) company_panel.companyInvite.connect( lambda c: self.statusBar().showMessage(f"Invite for {c.name}", 3000) ) company_panel.companyEditLogo.connect( lambda c: self.statusBar().showMessage(f"Edit Logo for {c.name}", 3000) ) CFG.path_object_changed.connect( lambda po: self.statusBar().showMessage( f"Path Object Changed: {po.get_path()}", 3000 ) )
[docs] def build_core_menus(self): """ This builds the core menu for the Alchemy application. It adds the File, Edit, View, and Help menus. """ import cgl.apps.alchemy.core_menus as core_menus # Import the core menus module (must be after CFG init) # TODO - this should just be the main window. # This is a hack that is no longer needed. menu_parent = self sep = QtGui.QAction(self.menu_bar) sep.setSeparator(True) file_menu = self.menu_bar.addMenu("File") tools_menu = self.menu_bar.addMenu("Tools") share_menu = self.menu_bar.addMenu("Sync") dashboard_menu = self.menu_bar.addMenu("Dashboard") help_menu = self.menu_bar.addMenu("Help") self.menu_bar.addMenu("|") self.menu_bar.addAction(sep) new_menu = file_menu.addMenu("New") open_menu = file_menu.addMenu("Open") plugins_menu = file_menu.addMenu("Plugins") config_menu = file_menu.addMenu("Config") # add actions to the File menu file_menu.addSeparator() file_menu.addAction("Preferences", core_menus.show_preferences) dashboard_menu.addAction("Studio Dashboard", core_menus.launch_dashboard) dashboard_menu.addAction( "Studio Documentation", core_menus.launch_studio_pipeline_docs ) dashboard_menu.addAction("Spec Sheets", core_menus.launch_studio_spec_sheets) # Add actions to the New menu new_menu.addAction( "Company", partial(core_menus.create_alchemy_company, menu_parent) ) new_menu.addAction( "Project", partial(core_menus.create_alchemy_project, menu_parent) ) new_menu.addAction( "Asset", partial(core_menus.create_alchemy_asset, menu_parent) ) new_menu.addAction("Shot", partial(core_menus.create_shot, menu_parent)) new_menu.addSeparator() new_menu.addAction( "Unreal Engine Location", partial(core_menus.create_unreal_location, menu_parent), ) new_menu.addAction( "Perforce Depot", partial(core_menus.create_perforce_depot, menu_parent) ) new_menu.addSeparator() new_menu.addAction("Library", partial(core_menus.create_library, menu_parent)) open_menu.addAction("Company", partial(core_menus.open_company, menu_parent)) open_menu.addAction("Project", partial(core_menus.open_project, menu_parent)) # Add actions to the Plugins menu plugins_menu.addAction( "Install Maya Plugin", partial(core_menus.install_maya_plugin, menu_parent) ) plugins_menu.addAction( "Install Blender Plugin", partial(core_menus.install_blender_plugin, menu_parent), ) plugins_menu.addAction( "Install Substance Painter Plugin", partial(core_menus.install_substance_painter_plugin, menu_parent), ) plugins_menu.addAction( "Install Unreal Engine Plugin", partial(core_menus.install_unreal_plugin, menu_parent), ) plugins_menu.addAction( "Install Motion Builder", partial(core_menus.install_motionbuilder_plugin, menu_parent), ) # Add actions to the Config menu config_menu.addAction( "User Config", partial(core_menus.show_config, menu_parent, "user") ) config_menu.addAction( "Alchemy Config", partial(core_menus.show_config, menu_parent, "alchemy") ) config_menu.addAction( "Shotgrid Config", partial(core_menus.show_config, menu_parent, "shotgrid") ) config_menu.addAction( "Task Config", partial(core_menus.show_config, menu_parent, "tasks") ) config_menu.addAction( "Project MSD", partial(core_menus.show_project_msd, menu_parent) ) # Add actions to the Share menu share_menu.addAction("Pull", partial(core_menus.pull_project, menu_parent)) share_menu.addAction( "Pull Monitor", partial(core_menus.pull_monitor, menu_parent) ) share_menu.addAction("Sync Manager", partial(core_menus.sync_manager, menu_parent)) share_menu.addSeparator() share_menu.addAction( "Share Project", partial(core_menus.share_project, menu_parent) ) share_menu.addAction( "Join Project", partial(core_menus.join_project, menu_parent) ) # Add actions to the Help menu help_menu.addAction( "About Alchemy", partial(core_menus.show_about_alchemy, menu_parent) ) help_menu.addAction( "Documentation", partial(core_menus.go_to_documentation, menu_parent) ) help_menu.addAction( "Report A Bug", partial(core_menus.report_issue, menu_parent) ) help_menu.addAction( "Request A Feature", partial(core_menus.request_feature, menu_parent) ) help_menu.addAction( "Alchemy Community", partial(core_menus.go_to_community, menu_parent) ) # Add actions to the menus file_menu.addAction("Exit", self.close) # edit_menu.addAction("Preferences", partial(core_menus.show_preferences, menu_parent)) tools_menu.addAction( "Cookbook", partial(core_menus.launch_cookbook, menu_parent) ) tools_menu.addAction( "Default Files", partial(core_menus.show_default_files, menu_parent) ) tools_menu.addAction( "Theme Editor", partial(core_menus.show_theme_editor, menu_parent) )
[docs] def get_cookbook_menus(self): """ Get the mensus to be added from the cgl file """ self.recipe_cgl_path = os.path.join( CFG.cookbook_dir, "alchemy", "menus.cgl" ).replace("\\", "/") self.menus_dict = load_json(self.recipe_cgl_path)["alchemy"]
[docs] def create_cookbook_menus(self): # self.menu_bar.clear() for menu_dict in self.menus_dict: name = menu_dict["name"] label = menu_dict["label"] menu = self.menuBar().addMenu(label) menu.name = name menu.label = label menu.dict = menu_dict self.populate_cookbook_menu(menu)
# menu.aboutToShow.connect(partial(self.populate_menu, menu))
[docs] def populate_cookbook_menu(self, menu): # path_object = self.centralWidget().path_widget.get_path_object() menu_parent = self menu.clear() menu_dict = menu.dict for button in menu_dict["buttons"]: label = button["label"] module = button["module"] if "separator" in label: menu.addSeparator() continue # name = button["name"] module_path = module.split(" as ")[0].replace("import ", "") module_class = importlib.import_module(module_path) action = menu.addAction(label) action.triggered.connect(partial(module_class.run, menu_parent))
[docs] def get_path_object(self): return self.centralWidget().path_widget.get_path_object()
[docs] def refresh_current_project(self): """ Refreshes the current project by reloading the project widget. This is useful if the project has changed or needs to be reloaded. """ self.project_selection_widget.reload_alchemy_config()
[docs] def closeEvent(self, event): if not getattr(self, "skip_config_save", False): logging.info(1) # self.centralWidget().header_widget.close() else: logging.info(2) # self.skip_config_save = False # Reset for next time event.accept()
[docs] def force_close(self): """ This is a method to force close the alchemy widget. It will close all the open windows and reset the path object. Args: company: The company to reset the path object to. """ # self.skip_config_save = True self.close()
[docs] def main(): import sys if not init_alchemy(): return from cgl.core.utils.general import apply_theme # ✅ Add this early exit for CI testing if "--test" in sys.argv: logging.info("TEST MODE: Alchemy test mode — minimal init successful.") sys.exit(0) # add the python path for the cgl folder to PATH root_path = CFG.get_code_root() if root_path not in sys.path: sys.path.append(root_path) # sync_ui.main() init_alchemy() # Set before creating the app (and before any QStyle/theme init) QtCore.QCoreApplication.setOrganizationName("CGLumberjack") QtCore.QCoreApplication.setOrganizationDomain( "alchemystudio.com" ) # optional but nice on macOS QtCore.QCoreApplication.setApplicationName("Alchemy") QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus, True) app = QtWidgets.QApplication(sys.argv) apply_theme(app) window = AlchemyMainWindow() window.setWindowTitle("Alchemy") window.show() sys.exit(app.exec())
if __name__ == "__main__": try: main() except RuntimeError as e: print("Operation Finished - Restart Alchemy", e)