Source code for cgl.plugins.smart_task

from __future__ import annotations

import importlib
import inspect
import logging
import os
from pathlib import Path

from cgl.core.config.query import AlchemyConfigManager

CFG = AlchemyConfigManager()


[docs] class SmartTask(object): """ This is a template for a "task" within the cookbook. It covers common areas when dealing with digital assets specific to different tasks. """ scene = None path_object = None msd_path = "" msd_dict = None task = None task_entity_type = None task_object = None dcc = None def __init__( self, msd=None, task_object=None, path_object=None, dcc=None, task=None, scene=None, **kwargs, ): """ Args: dcc: the dcc we are working in task: the task we are working on path_object: the path object we are working on origin_task: the task this SmartTask is originating from (for example, if we are exporting a camera from a previz task. **kwargs: any additional arguments we want to pass in """ self.msd = msd self.task = task self.task_object = task_object self.path_object = path_object self.dcc = dcc self.scene = scene caller_file = inspect.stack()[1].filename for key in kwargs: setattr(self, key, kwargs[key]) if not self.scene: self.set_scene() if not self.path_object: self.set_path_object() if not self.dcc: self.dcc = get_dcc_from_task_file(caller_file) if not self.task: if not self.task_object: self.task = os.path.basename(os.path.dirname(caller_file)) else: self.task = self.task_object.task if self.task and not self.task_object: self.set_task_object() if not self.task_object: raise ValueError( "Task object not set. Please provide a task_object or set the task." ) # if self.path_object: # if not self.task_object: # self.origin_task = self.path_object.task # self.set_task_object() # else: # raise ValueError(f"Could not set path_object for DCC '{self.dcc}'")
[docs] def set_scene(self): try: logging.debug( f"[set_scene()] setting path object for dcc: {self.dcc} {self.task}" ) module_path = f"cgl.plugins.{self.dcc}.alchemy" logging.info("[set_scene()] loading: {}".format(module_path)) module = importlib.import_module(module_path) self.scene_class = getattr(module, "Scene") # <-- store it if self.dcc != "alchemy": self.scene = self.scene_class() except (ModuleNotFoundError, AttributeError) as e: raise ImportError( f"[set_scene()] Could not load Scene class for DCC '{self.dcc}': {e}" )
[docs] def set_path_object(self): self.path_object = self.scene.path_object
[docs] def get_task_object(self, task, latest=True): """Returns a PathObject with the task set to the input task Args: task: the task we want to set the PathObject to Returns: PathObject: the PathObject with the task set to the input task """ if self.path_object.task != task: entity_type = CFG.get_task_entity_type(task) return self.path_object.copy( tree="render", entity_type=entity_type, user="publish", task=task, latest=latest, set_filename=True, ) else: print("Copying path object for task.") return self.path_object.copy()
[docs] def set_task_object(self): """ Gets the Unreal Engine Task Object for this task. Returns: """ if self.task and not self.task_object: print(f"------>>>> Setting task object for task: {self.task}") self.task_object = self.get_task_object(task=self.task) self.msd_path = self.task_object.get_msd_path() self.msd_dict = self.task_object.get_msd_dict() else: raise ValueError("Task not set")
def _import(self, file_path: str | Path, reference: bool = False): """Imports the file into the scene - this function should be smart enough to handle various file types as well as. """ if os.path.exists(file_path): self.Scene().import_file(file_path, reference=reference) return file_path else: logging.info( f"No published file found for " f"{self.path_object.sequence}/{self.path_object.shot}/{self.path_object.task}" ) return None
[docs] def import_latest(self, task=None, reference=False, **kwargs): """ Imports the lastest published version of whatever files this task is responsible for. This method is typically overridden in the specific task class to handle the import logic. """ print( "\nDefault import_latest method called. Override this in your task " "class to implement specific import logic." ) print(f"MSD path: {self.msd_path}") print(f"MSD dict: {self.msd_dict}")
[docs] def get_msd_data(self): """ creates the msd dictionary for the task being called. This is used at publish time to get the data to be wrtten to the .msd file. Args: task_name: """ pass
[docs] def export_task(self): """ Basic export method for tasks This should call the customizable '_export()' method per file. This method is typically overridden in the specific task class to handle the export logic. """ self.select() render_folder = self.task_object.get_render_path(dirname=True) print( f"emplement your own export_tas function - for example Render folder: {render_folder}" )
# self._export(render_folder) def _export(self, file_path): """Exports the task This must be customized per dcc and per task. The Scene() class in the dcc plugin should have an export method that handles the export logic. """ self.Scene().export(file_path)
[docs] def render(self): """ """ pass
[docs] def select(self): """ Selects the task in the scene This must be customized per dcc """ pass
[docs] def version_up(self): """ Version up the task. Override this method in the task class if you want to add additional functionality. """ pass
[docs] def get_dcc_from_task_file(caller_file): """ Gets the dcc from the caller file path. Args: caller_file: the file path of the caller Returns: str: the dcc name """ # Extract the dcc from the caller file path if "cookbook" in caller_file: # we're using a cookbook smart task dcc = caller_file.split("cookbook")[-1] dcc = dcc.split("\\")[1] return dcc else: raise ( f"Dealing with a legacy SmartTask() from the core plugins repository - " f"move to cookbook.\n\t {caller_file}" )
[docs] def get_task_class(software, task): """ gets the class that relates to the specified task, if no task is specified the task for the current scene will be used. Args: software: the dcc in the plugins directory where we can find Task().get_msd_data() task: the task to get the class for Returns: class or None if the class does not exist """ module = "cookbook.{}.tasks.{}.{}".format(software, task, task) logging.info(f"[get_task_class] Loading task class from module: {module}") module_name = task try: loaded_module = importlib.import_module(module, module_name) class_ = getattr(loaded_module, "Task") return class_ except ModuleNotFoundError: logging.info(f"Module: {module} does not exist") return None
[docs] def get_legacy_task_class(software, task): """ Gets the legacy task class for the specified software and task. This is used for tasks that are not in the cookbook structure. Args: software: the dcc in the plugins directory where we can find Task().get_msd_data() task: the task to get the class for Returns: class or None if the class does not exist """ module = "cgl.plugins.{}.tasks.{}".format(software, task) print(f"Loading legacy task class from module: {module}") module_name = task try: loaded_module = importlib.import_module(module, module_name) class_ = getattr(loaded_module, "Task") return class_ except ModuleNotFoundError: logging.info(f"Legacy Module: {module} does not exist") return None
if __name__ == "__main__": caller_file = r"E:\Alchemy\CGL\config\cookbook\blender\tasks\srb\task.py" print(get_dcc_from_task_file(caller_file))