Skip to content

Developer Guide

tobi edited this page Aug 4, 2026 · 9 revisions

Developer Guide

This page is for TD (Technical Directors) or Developers looking to maintain or extend the Ramses-Fusion plugin.

Project Structure

  • Ramses-Fusion.py: The entry point. Contains the RamsesFusionApp class (UI and high-level logic).
  • lib/fusion_host.py: The bridge. Implements the FusionHost class, which inherits from ramses.RamHost.
  • lib/fusion_config.py: The Lua parser. Contains FusionConfig for parsing and applying Saver node settings.
  • lib/asset_browser.py: The Import Published dialog. Lists an upstream step's published deliverables one row at a time (image sequences collapsed to a single entry) and creates the Loaders.
  • lib/ramses_patches.py: Runtime fixes for the vendored SDK, plus the DisableMakedirs guard.
  • lib/ramses/: The vendored Ramses Python API. Do not modify these files — they are kept replaceable from upstream. Defects go upstream or into ramses_patches.py; see UPSTREAM_SDK_FINDINGS.md in the repository.
  • lib/yaml, lib/ramses_ui_pyside: Vendored third-party dependencies.
  • tests/: A comprehensive test suite using unittest and unittest.mock.

Module Exports

Both main modules define __all__ for clear public API boundaries:

# fusion_host.py
__all__ = ["FusionHost"]

# fusion_config.py
__all__ = ["FusionConfig"]

Render Format Constants

fusion_host.py exports constants for standardized render formats:

FORMAT_QUICKTIME = "QuickTimeMovies"
CODEC_PRORES_422 = "Apple ProRes 422_apcn"      # Preview default
CODEC_PRORES_422_HQ = "Apple ProRes 422 HQ_apch"  # Final default

Use these constants instead of hardcoding strings when extending render functionality.

Running Tests

The project uses a mock Fusion environment to run tests without needing Fusion open.

Requirements: Python 3.9+ (tested with 3.10, 3.11, 3.14)

To run the full suite:

  1. Open a terminal in the project root.
  2. Run the batch file: run_tests.bat (Windows). It sets PYTHONPATH to Ramses-Fusion and Ramses-Fusion/lib, then runs unittest discovery.
  3. Or use Python directly, with that same PYTHONPATH: python -m unittest discover tests -v

All tests must pass (244, one skipped, at the time of writing). If any fail, check for missing dependencies or Python version issues.

Important

Run the suite three ways before trusting it. A test that passes one way can fail another, and each of these has caught a real defect:

  1. python -m unittest discover tests — the runner run_tests.bat actually uses.
  2. python -m pytest tests — the only runner that applies tests/conftest.py, whose autouse fixtures catch directories created at a drive root and a leaked FusionHost._pinned_comp.
  3. Each test module on its own. Ordering hides things in both directions: a module once passed only because an alphabetically earlier one had already fixed up sys.path.

The two runners order test classes differently — pytest keeps file order, unittest sorts alphabetically. A test that replaced a module attribute without restoring it therefore broke a later test under unittest only, and looked perfectly green under pytest for as long as nobody ran the batch file. When you stub a module attribute or anything on the RAMSES singleton, use patch.object with addCleanup, never a bare assignment.

Mocking the Fusion API

If you are writing new tests, refer to tests/mocks.py. It contains a MockFusion class that simulates:

  • UIManager and UIDispatcher.
  • Composition attributes and preferences.
  • Tool (Node) creation and attribute management.

UI Event Loop

The plugin uses Fusion's bmd.UIDispatcher. Note that disp.RunLoop() is blocking.

The @requires_connection Decorator

To ensure robustness, almost all UI event handlers are wrapped with the @requires_connection decorator.

  • Function: It checks if the Ramses Daemon is online before executing the wrapped function.
  • Behavior: If the connection is lost, it automatically attempts a silent reconnect. If that fails, it shows a "Connection Lost" error and prevents the action from proceeding.
  • Benefits: This eliminated over 200 lines of repetitive check-and-return logic throughout Ramses-Fusion.py.

Architectural Decisions

Monkey-Patching Strategy

Located at the top of lib/fusion_host.py, the monkey-patching block is used to fix upstream bugs in the ramses library. This is preferred over shipping a modified version of the library as it makes updates easier to track.

  • Targeted fixes: Robust identity matching (Windows), synchronous file copies, and state propagation during publish. lib/ramses_patches.py adds the DisableMakedirs guard that keeps read-only path lookups from creating folders.
  • Implementation: Replaces static methods and private class attributes using standard Python attribute assignment, behind a _fusion_patched guard — the entry script reloads the host module on every launch, so the block runs again in the same interpreter. Any patch must be idempotent, or one wrapper layer stacks per launch.
  • Remove a patch once upstream fixes it. Patches for the metadata manager and the daemon interface were dropped when the SDK moved to Ramses-Py d19ce44. A patch that replaces a method wholesale keeps overriding upstream forever and masks any later improvement to it. When you drop one, keep the test — retarget it at the SDK so the behaviour stays asserted.

Synchronous File Operations

While the Ramses API supports background threading for file copies, Ramses-Fusion disables this. In a high-stakes publish environment, it is critical that the plugin waits for the file to be fully written and verified before updating the database status. Synchronous operations prevent "Ghost Publishes" where the status changes but the file is still being moved.

Threading and Latency

High-latency network calls (like fetching statuses for 100+ shots) are wrapped in concurrent.futures.ThreadPoolExecutor.

  • The UI uses a "Header Refresh" logic that gates database queries based on the currentFilePath.

Path Normalization

CRITICAL: Always use self.ramses.host.normalizePath(path) when dealing with file paths. This ensures all paths use forward slashes (/), which prevents character escaping issues in Fusion's Lua-based string handling.

Testing a Change in Fusion

Fusion keeps one Python interpreter alive for the whole session. Reopening the panel re-runs Ramses-Fusion.py, but modules already imported from lib/ are not reloaded. After editing anything under lib/, restart Fusion — otherwise you are testing the old code and will draw the wrong conclusion from it.

Some questions cannot be answered by the mocked test suite at all, because they depend on how the host actually behaves — whether reading a value dirties the comp, whether an attribute survives a round trip. For those, write a small probe script, run it inside Fusion, and read the result. Measuring took minutes in cases where reasoning from the API docs had already produced several confident wrong answers.

Clone this wiki locally