"""
File: ops_engine.py
Description: The central, DCC-agnostic pipeline engine for openPypeline Studio.
This module orchestrates the core data flow, detects the host application
dynamically, and delegates file operations to the appropriate application handlers.
Usage Example (Testing):
'''
import sys
sys.path.append("/path/to/openPypeline/openpypeline")
import ops_engine
import importlib
importlib.reload(ops_engine)
ops_engine.OpsEngine().ui()
'''
Original Framework: openPipeline by Kickstand
License: Common Public License 1.0 (CPL-1.0)
"""
import os
import importlib
import sys
import logging
logger = logging.getLogger("openPypeline.engine")
[docs]
class OpsEngine:
"""
The main engine class that drives openPypeline Studio.
It acts as the bridge between the UI/preferences, the core data models, and the host DCC.
"""
def __init__(self):
# Detect the environment to adapt pipeline behaviors accordingly upon initialization
self.host_app = self._detect_host_app()
logger.info(f"Initialized OpsEngine. Detected host: {self.host_app}")
# Load DCC-Specific File Handler
self.file_handler = None
if self.host_app != 'standalone':
try:
module_path = f"app.{self.host_app}.file.file"
self.file_handler = importlib.import_module(module_path)
importlib.reload(self.file_handler)
except ImportError:
logger.warning(f"Could not load file handler for host '{self.host_app}' at {module_path}.")
def _detect_host_app(self):
"""Detects which host application is currently running the engine."""
# Check loaded system modules for standard DCC libraries to deduce the active host
if 'maya' in sys.modules or 'maya.cmds' in sys.modules:
return 'maya'
elif 'nuke' in sys.modules:
return 'nuke'
elif 'hou' in sys.modules:
return 'houdini'
elif 'bpy' in sys.modules:
return 'blender'
elif 'unreal' in sys.modules:
return 'unreal'
return 'standalone'
[docs]
def ui(self):
"""
Executes a diagnostic routine that simulates a core pipeline interaction.
It loads modules, retrieves preferences, queries the inventory, and evaluates
the status (versions, master, notes) of items in the current working directory.
"""
# --- 1. Load Core Modules ---
# Reload modules to ensure the latest code is executing (useful for development)
from core.file import inventory
importlib.reload(inventory)
from core.version import Version
importlib.reload(Version)
from core.file import master
importlib.reload(master)
from openpypeline.core.file import wip
importlib.reload(wip)
from core.notes import notes
importlib.reload(notes)
# --- 2. Retrieve Preferences ---
from core.util import prefs
importlib.reload(prefs)
# Determine the project root, falling back to the host workspace if none is set
project = prefs.get_pref("ops_currProjectPath", "")
if not project:
logger.warning("'ops_currProjectPath' preference is not set. Using host workspace root as fallback.")
project = prefs.get_workspace_root()
# Fetch the active UI selection states (Tab, Level 1/2/3) from preferences
# Fallbacks are provided to allow testing outside of the actual UI
tab = prefs.get_pref("ops_currOpenTab", 2)
level1 = prefs.get_pref("ops_currOpenLevel1", "")
level2 = prefs.get_pref("ops_currOpenLevel2", "")
level3 = prefs.get_pref("ops_currOpenLevel3", "")
module = "lib" if tab == 2 else "scenes"
item_type = level1 if level1 else "characters"
asset = level2 if level2 else "woman"
component = level3 if level3 else "model"
# Construct an ordered list of path elements, ignoring empty values
elements = [e for e in [project, module, item_type, asset, component] if e]
# --- 3. Query Inventory and Evaluate Items ---
# Ask the inventory module what items actually exist at the evaluated path
inv = inventory.Inventory(elements).list()
logger.debug(f"Inventory list: {inv}")
for item in inv:
# Build the full absolute path for each discovered item
path = inventory.Inventory(elements).getPath()
fullpath = os.path.join(path, item)
logger.debug(f"Checking path: {fullpath}")
# Check what workshop versions are available
versions = Version.Version().all(fullpath)
logger.debug(f"Versions found: {versions}")
# Check if this item has been finalized as a Master
hasMaster = master.Master().query(fullpath)
if hasMaster == 1:
logger.debug(f"{item}: mastered")
else:
logger.debug(f"{item}: not mastered")
# Check if artists have left XML notes for this item
hasNotes = notes.Notes().query(fullpath, item)
if hasNotes == 1:
logger.debug(f"{item}: has notes")
else:
logger.debug(f"{item}: no notes")
# --- 4. Attempt File Operation ---
# Finally, find the latest workshop version and attempt to open it in the host DCC
getLatestWorkshopNumber = Version.Version().latest(fullpath)
if getLatestWorkshopNumber is not None:
latestWorkshop = wip.WIP().open(fullpath, item, getLatestWorkshopNumber)
if self.file_handler and hasattr(self.file_handler, 'open'):
self.file_handler.open(latestWorkshop)
else:
logger.warning(f"Would open {latestWorkshop} (no DCC file handler available)")