Architecture¶
Package Structure¶
src/REvoDesign/
├── __init__.py # Import stack: SingletonAbstract → bootstrap → ConfigBus → logger
├── REvoDesign.py # Main PyMOL plugin entry point (REvoDesignPlugin)
├── application/
│ ├── cluster_tab.py # ClusterTabController
│ ├── icon.py # IconSetter
│ ├── menu.py # MENU_LINKS, TOOLS_MENU_LINKS, CONFIG_EDIT_LINKS
│ ├── font/
│ │ └── font_manager.py # Font manager for UI styling
│ └── i18n/
│ └── language_settings.py # LanguageSwitch, LanguageItem, LanguageNameRegistry
├── basic/
│ ├── abc_singleton.py # SingletonAbstract (Borg-like singleton pattern)
│ ├── abc_third_party_module.py# ThirdPartyModuleAbstract (name, installed, __bibtex__)
│ ├── designer.py # ExternalDesignerAbstract (scorer/designer plugin base)
│ ├── mutate_runner.py # MutateRunnerAbstract (sidechain solver base)
│ ├── plugin_registry.py # PluginRegistry (package-scoped auto-discovery)
│ ├── data_structure.py # IterableLoop, generic data structures
│ ├── group_registries.py # GroupRegistryItem for dynamic UI population
│ ├── param_toggle.py # ParamChangeRegistryItem, ParamChangeRegister
│ ├── server_monitor.py # ServerControlAbstract for service lifecycle
│ ├── menu_item.py # MenuCollection, MenuItem
│ └── extensions.py # FileExtension types
├── bootstrap/
│ └── __init__.py # Config bootstrap: verify, copy, load, cache dirs
├── driver/
│ ├── ui_driver.py # ConfigBus (UI ↔ OmegaConf bridge singleton)
│ ├── widget_link.py # Config2WidgetIds, PushButtons widget ↔ config maps
│ ├── group_register.py # Widget group registries (solver lists, etc.)
│ ├── param_toggle_register.py # Param-change toggle wiring
│ ├── environ_register.py # Environment variable registration from config
│ └── file_dialog.py # FileDialog, IO_MODE, flattened compression
├── common/
│ ├── mutant.py # Mutant data model
│ ├── mutant_tree.py # MutantTree container
│ ├── multi_mutant_designer.py # MultiMutantDesigner
│ ├── mutant_visualise.py # MutantVisualizer for PyMOL
│ ├── profile_parsers.py # ProfileParserAbstract + parsers (PSSM, CSV, TSV)
│ └── file_extensions.py # File extension registry (imported at bootstrap)
├── Qt/
│ ├── __init__.py # Qt package init
│ ├── qt_wrapper.py # Qt compat layer (QT_BACKEND, QT_MAJOR, QtCompat)
│ └── ui_runtime_loader.py # RuntimeUiProxy — load .ui without codegen
├── magician/
│ ├── __init__.py # Magician singleton, DESIGNER_REGISTRY, MagicianAssistant
│ ├── designers/
│ │ └── openkinetics/ # OpenKinetics API scorer (canonical plugin example)
│ │ ├── __init__.py
│ │ ├── _scorers.py # OpenKineticsScorerAbstract + dynamic subclasses
│ │ ├── _client.py # REST API client
│ │ ├── _models.py # Dataclasses, exceptions, constants
│ │ └── _pdb.py # PDB/ligand helpers
│ └── README.md # Magician's Gimmick Orchestration Protocol
├── sidechain/
│ ├── sidechain_solver.py # SidechainSolver singleton, RUNNER_REGISTRY
│ └── mutate_runner/ # Sidechain solver implementations
│ ├── DLPacker.py
│ ├── DLPackerPytorch.py
│ ├── DiffPack.py
│ ├── DunbrackRotamerLib.py
│ ├── PIPPack.py
│ └── RosettaMutateRelax.py
├── structure/
│ ├── SurfaceFinder.py # Solvent-accessible surface detection
│ └── PocketSearcher.py # Substrate-binding pocket detection
├── phylogenetics/
│ ├── gremlin_tools.py # GREMLIN MRF analysis
│ ├── gremlin_pytorch.py # PyTorch GREMLIN implementation
│ ├── evo_mutator.py # Co-evolution mutation logic (GremlinAnalyser)
│ └── revo_designer.py # REvoDesigner iterative design engine
├── clusters/
│ ├── cluster_sequence.py # Sequence clustering for mutant reduction
│ ├── combine_positions.py # Position combination utilities
│ ├── score_clusters.py # Rosetta-based cluster scoring
│ └── methods/ # Cluster algorithm implementations
├── evaluate/
│ ├── __init__.py # Package init
│ └── evaluator.py # Mutant evaluation and decision-making
├── editor/ # Monaco editor integration
│ ├── __init__.py
│ ├── README.md
│ └── monaco/
│ ├── monaco.py # MonacoEditorManager (download, install)
│ ├── server.py # FastAPI server (file read/write, auth)
│ └── config.py # ConfigStore for editor backend
├── issues/ # Exception and warning hierarchy
│ ├── exceptions.py # REvoDesignException + subclasses
│ └── warnings.py # REvoDesignWarning + subclasses
├── shortcuts/ # PyMOL cmd.extend command infrastructure
│ ├── __init__.py
│ ├── utils.py # DialogWrapperRegistry, input resolution
│ ├── wrappers.py # Shortcut wrapper configs
│ └── registry/ # YAML shortcut definitions
├── config/ # YAML config hierarchy (OmegaConf/Hydra)
│ ├── main.yaml # Primary UI and workflow configuration
│ ├── environ.yaml # Environment variables and secrets
│ ├── logger.yaml # Logging configuration
│ ├── runtime.yaml # Runtime-specific settings
│ ├── appearence.yaml # Font and button matrix appearance
│ ├── editor.yaml # Monaco editor backend configuration
│ ├── openmm.yaml # OpenMM setup server config
│ ├── rfdiffusion/ # RFdiffusion model configs
│ ├── rosetta-node/ # Rosetta compute node definitions
│ ├── third_party/
│ │ └── scorers/
│ │ └── openkinetics_api.yaml # OpenKinetics API settings
│ └── sidechain-solver/ # Per-solver config YAML files
├── UI/
│ ├── __init__.py
│ ├── REvoDesign.ui # Qt Designer main window layout
│ ├── REvoDesign-PyMOL-entry.ui# Package manager installer UI
│ ├── types.py # Auto-generated REvoDesignUiProtocol
│ ├── preference.ui # UI preference layout
│ ├── socket.ui # Socket tab UI layout
│ └── language/ # Qt Linguist .ts/.qm translation files
├── logger/
│ ├── __init__.py
│ └── logger.py # Root logger setup (initialized during import)
├── citations/
│ └── citation_manager.py # CitableModuleAbstract, CitationManager
├── data/
│ ├── __init__.py
│ └── protein_code.py # Static data (protein codes, etc.)
├── presets/
│ └── styles.py # Style presets
└── tools/
├── customized_widgets.py # QButtonMatrix, dialogs, ParallelExecutor
├── mutant_tools.py # Mutant serialization helpers
├── pymol_utils.py # PyMOL session helper functions
├── download_registry.py # File download with mirror fallback
├── cgo_utils.py # CGO 3D graphics primitives
├── rosetta_utils.py # Rosetta environment detection helpers
├── measure_utils.py # PyMOL measurement object parsing
├── system_tools.py # System info and environment detection
├── SessionMerger.py # Safe PyMOL session merging
├── package_manager.py # Package Manager installer internals
├── ssl_certificates.py # SSL certificate management
└── REvoDesign-manager/ # Package manager sub-tools
Plugin Lifecycle¶
The bootstrap sequence is defined in src/REvoDesign/__init__.py and proceeds
through a strict ordering of imports:
- Garbage collector --
gcis imported and enabled first. - SingletonAbstract -- The base singleton class from
basic.abc_singletonis imported so all subsequent singletons work correctly. - Bootstrap --
bootstrap/__init__.pyis loaded. It verifies the config directory exists, copies default YAML files from the package tree to the user config directory, and exposes functions likereload_config_file(),save_configuration(), andset_cache_dir(). - File extensions --
common/file_extensions.pyregisters known file extension types (.pdb,.pse,.yaml, etc.). - ConfigBus -- The central UI-to-config bridge singleton is imported
(headless mode by default; GUI mode is activated when
.uiis set later). - Logger -- Root logger is set up from the logger YAML config and is available to all subsequent modules.
- Version --
__version__is read from the module.
At PyMOL start time, REvoDesignPlugin.make_window() runs:
load_runtime_ui(ui_path) # Load REvoDesign.ui → (main_window, RuntimeUiProxy)
_install_language_change_filter # Intercept Qt language change events
IconSetter(main_window) # Set window and taskbar icon
reload_configurations() # Initialize ConfigBus (headless → GUI mode),
# register env vars, widget groups, and signals
ClusterTabController(ui, bus) # Manage clustering tab state
FontSetter(main_window) # Apply application font
LanguageSwitch(window) # i18n translator setup
MenuCollection(...) # Wire static menu items (working dir, reconfigure, etc.)
QtCore.QTimer → _bind_menu_links # Deferred: scan config files for edit/recent-experiment links
StoresWidget() # Server switch monitors (Editor, OpenMM)
# Wire tab-specific button signals, combo-box connections, and WebSocket setup
Singleton Pattern¶
SingletonAbstract (in basic/abc_singleton.py) is a Borg-like singleton:
__new__returns the cached_instanceif one exists, otherwise creates it.__init__callssingleton_init()only once, guarded by aself.initializedflag.- Subclasses implement the abstract
singleton_init()method for custom initialization logic. initialize()class method creates the instance on first call, or updates existing instance attributes on subsequent calls.derive(name)dynamically creates a new subclass with its own independent_instance(used for per-class singleton isolation).reset_instance()clears_instancefor testing cleanup.
Key singleton subclasses:
| Class | Purpose |
|---|---|
ConfigBus |
Central UI-to-Config bridge |
Magician |
External designer/scorer gimmick manager |
SidechainSolver |
Manages the active mutate runner |
CitationManager |
Collects BibTeX citations from third-party tools |
StoresWidget |
Holds server-switch references |
Qt Compatibility Layer¶
All Qt imports go through REvoDesign.Qt -- never import PyQt5 or PyQt6
directly. The check-qt-binding-imports pre-commit hook (in check_qt_binding_imports.py) enforces this.
- Backend detection:
qt_wrapper.pyimportspymol.Qtat runtime and inspectspymol.Qt.PYQT_NAME. This yieldsQT_BACKEND(e.g."PyQt5","PyQt6") andQT_MAJOR(5 or 6). - Optional modules:
QtNetwork,QtWebSockets,QtSvg,QtUiToolsare imported lazily via_import_optional_qt_module(). Availability is checked withhas_qt_module(name). - Enum compatibility:
install_qt6_aliases()installs scoped enum containers on Qt5 backends (e.g.Qt.WidgetAttribute.WA_DeleteOnClose) and flat aliases on Qt6 (e.g.Qt.WA_DeleteOnClose) so code works on both. QtCompat: A_QtCompatNamespaceinstance with commonly used enum values (message box buttons, alignment flags, check states).qexec(obj): Wrapsobj.exec()/obj.exec_()for Qt5/Qt6 compatibility.
Runtime UI loading: load_runtime_ui() in ui_runtime_loader.py loads
.ui files at runtime via PyQt5.uic.loadUiType or QtUiTools.QUiLoader,
bypassing the need for pre-generated Python UI code. The returned
RuntimeUiProxy exposes named Qt objects as attributes and provides
retranslateUi().
Plugin Registry System¶
PluginRegistry (in basic/plugin_registry.py) is a frozen dataclass that
performs package-scoped auto-discovery of plugin classes:
- On initialization, it imports all modules under a given
packagepath (usingpkgutil.iter_modules). - It collects non-abstract subclasses of
base_classand indexes them by theirnameattribute. - Duplicate
namevalues raise aValueError. build_plugin_registry()is a convenience factory function.
Two registries are used in practice:
# src/REvoDesign/magician/__init__.py
DESIGNER_REGISTRY = build_plugin_registry(
base_class=ExternalDesignerAbstract,
package="REvoDesign.magician.designers",
)
# src/REvoDesign/sidechain/sidechain_solver.py
RUNNER_REGISTRY = build_plugin_registry(
base_class=MutateRunnerAbstract,
package="REvoDesign.sidechain.mutate_runner",
)
Each registry exposes .all_classes, .implemented_map (name to class),
.installed_names (filtered by the installed class attribute), and
.get(name, **kwargs) for instantiation.
Config System¶
Configuration is managed by OmegaConf/Hydra with a YAML hierarchy stored
under src/REvoDesign/config/. The user config directory is determined by
platformdirs and defaults are copied there at first launch.
Configdataclass (indriver/ui_driver.py): wraps a name, file path, andDictConfig. Providesfrom_name(),from_file(),save(),reload(),save_to_experiment().ConfigBussingleton: Loads all YAML config files from the config directory intoself.cfg_group(adict[str, Config]). The"main"group holds the primary configuration.Widget2ConfigMapper: Maps between config item keys, widget IDs, and Qt widget objects usingConfig2WidgetIdsandPushButtonsregistries.- Headless mode: When no
uiis provided at init,ConfigBus.headless = True. Methods that need widgets use@require_non_headlessdecorator and raise if called in headless mode.
Values are read with get_value(cfg_item, converter, ...) and written with
set_value(cfg_item, value). Widget-to-config synchronization is wired via
register_widget_changes_to_cfg() which connects each widget's signal to an
updater that writes the new value into the OmegaConf tree.
Extension Points Summary¶
Two abstract base classes define the plugin extension points:
ExternalDesignerAbstract (designer.py)¶
Base class for external scorers/designers (the "Magician's Gimmick").
| Attribute/Method | Purpose |
|---|---|
name |
Unique plugin name (used for registry lookup) |
installed |
Whether the dependency is available |
scorer_only |
If True, only scorer() is implemented (no designer()) |
prefer_lower |
Whether lower scores are better |
initialize() |
One-time setup (load models, validate config) |
scorer(mutant) |
Score a single mutant, return float |
designer(**kwargs) |
Generate new designs (optional) |
parallel_scorer(mutants, nproc) |
Default parallelizes via joblib.Parallel |
Auto-discovered by DESIGNER_REGISTRY under magician.designers.
MutateRunnerAbstract (mutate_runner.py)¶
Base class for sidechain solvers (mutation/packing tools).
| Attribute/Method | Purpose |
|---|---|
name |
Unique solver name |
installed |
Whether the dependency is available |
weights_preset |
Model/backend options for the UI dropdown |
default_weight_preset |
Default option in the dropdown |
run_mutate(mutant) |
Run single mutation, return PDB path |
run_mutate_parallel(mutants, nproc) |
Run mutations in parallel |
reconstruct() |
Optional reconstruction support |
Auto-discovered by RUNNER_REGISTRY under sidechain.mutate_runner.