diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 377026da4a..5ca2a42510 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -8,8 +8,19 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): This is not possible to do for all applications the same way. """ - order = 0 - app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio"] + # Execute after workfile template copy + order = 10 + app_groups = [ + "maya", + "nuke", + "nukex", + "hiero", + "nukestudio", + "blender", + "photoshop", + "tvpaint", + "afftereffects" + ] def execute(self): if not self.data.get("start_last_workfile"): diff --git a/openpype/hooks/pre_copy_template_workfile.py b/openpype/hooks/pre_copy_template_workfile.py new file mode 100644 index 0000000000..29a522f933 --- /dev/null +++ b/openpype/hooks/pre_copy_template_workfile.py @@ -0,0 +1,127 @@ +import os +import shutil +from openpype.lib import ( + PreLaunchHook, + get_custom_workfile_template_by_context, + get_custom_workfile_template_by_string_context +) +from openpype.settings import get_project_settings + + +class CopyTemplateWorkfile(PreLaunchHook): + """Copy workfile template. + + This is not possible to do for all applications the same way. + + Prelaunch hook works only if last workfile leads to not existing file. + - That is possible only if it's first version. + """ + + # Before `AddLastWorkfileToLaunchArgs` + order = 0 + app_groups = ["blender", "photoshop", "tvpaint", "afftereffects"] + + def execute(self): + """Check if can copy template for context and do it if possible. + + First check if host for current project should create first workfile. + Second check is if template is reachable and can be copied. + + Args: + last_workfile(str): Path where template will be copied. + + Returns: + None: This is a void method. + """ + + last_workfile = self.data.get("last_workfile_path") + if not last_workfile: + self.log.warning(( + "Last workfile was not collected." + " Can't add it to launch arguments or determine if should" + " copy template." + )) + return + + if os.path.exists(last_workfile): + self.log.debug("Last workfile exits. Skipping {} process.".format( + self.__class__.__name__ + )) + return + + self.log.info("Last workfile does not exits.") + + project_name = self.data["project_name"] + asset_name = self.data["asset_name"] + task_name = self.data["task_name"] + + project_settings = get_project_settings(project_name) + host_settings = project_settings[self.application.host_name] + + workfile_builder_settings = host_settings.get("workfile_builder") + if not workfile_builder_settings: + # TODO remove warning when deprecated + self.log.warning(( + "Seems like old version of settings is used." + " Can't access custom templates in host \"{}\"." + ).format(self.application.full_label)) + return + + if not workfile_builder_settings["create_first_version"]: + self.log.info(( + "Project \"{}\" has turned off to create first workfile for" + " application \"{}\"" + ).format(project_name, self.application.full_label)) + return + + # Backwards compatibility + template_profiles = workfile_builder_settings.get("custom_templates") + if not template_profiles: + self.log.info( + "Custom templates are not filled. Skipping template copy." + ) + return + + project_doc = self.data.get("project_doc") + asset_doc = self.data.get("asset_doc") + anatomy = self.data.get("anatomy") + if project_doc and asset_doc: + self.log.debug("Started filtering of custom template paths.") + template_path = get_custom_workfile_template_by_context( + template_profiles, project_doc, asset_doc, task_name, anatomy + ) + + else: + self.log.warning(( + "Global data collection probably did not execute." + " Using backup solution." + )) + dbcon = self.data.get("dbcon") + template_path = get_custom_workfile_template_by_string_context( + template_profiles, project_name, asset_name, task_name, + dbcon, anatomy + ) + + if not template_path: + self.log.info( + "Registered custom templates didn't match current context." + ) + return + + if not os.path.exists(template_path): + self.log.warning( + "Couldn't find workfile template file \"{}\"".format( + template_path + ) + ) + return + + self.log.info( + f"Creating workfile from template: \"{template_path}\"" + ) + + # Copy template workfile to new destinantion + shutil.copy2( + os.path.normpath(template_path), + os.path.normpath(last_workfile) + ) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index bd7a95f916..e6dab5cfc9 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -80,7 +80,7 @@ def install(): # Set context settings. nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root") nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") - nuke.addOnCreate(lib.open_last_workfile, nodeClass="Root") + nuke.addOnCreate(lib.process_workfile_builder, nodeClass="Root") nuke.addOnCreate(lib.launch_workfiles_app, nodeClass="Root") menu.install() diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 63cac0fd8b..3c41574dbf 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -16,6 +16,7 @@ from avalon.nuke import ( from openpype.api import ( Logger, Anatomy, + BuildWorkfile, get_version_from_path, get_anatomy_settings, get_hierarchy, @@ -1641,23 +1642,69 @@ def launch_workfiles_app(): workfiles.show(os.environ["AVALON_WORKDIR"]) -def open_last_workfile(): - # get state from settings - open_last_version = get_current_project_settings()["nuke"].get( - "general", {}).get("create_initial_workfile") +def process_workfile_builder(): + from openpype.lib import ( + env_value_to_bool, + get_custom_workfile_template + ) + + # get state from settings + workfile_builder = get_current_project_settings()["nuke"].get( + "workfile_builder", {}) + + # get all imortant settings + openlv_on = env_value_to_bool( + env_key="AVALON_OPEN_LAST_WORKFILE", + default=None) + + # get settings + createfv_on = workfile_builder.get("create_first_version") or None + custom_templates = workfile_builder.get("custom_templates") or None + builder_on = workfile_builder.get("builder_on_start") or None - log.info("Opening last workfile...") last_workfile_path = os.environ.get("AVALON_LAST_WORKFILE") - if not os.path.exists(last_workfile_path): - # return if none is defined - if not open_last_version: - return + # generate first version in file not existing and feature is enabled + if createfv_on and not os.path.exists(last_workfile_path): + # get custom template path if any + custom_template_path = get_custom_workfile_template( + custom_templates + ) + # if custom template is defined + if custom_template_path: + log.info("Adding nodes from `{}`...".format( + custom_template_path + )) + try: + # import nodes into current script + nuke.nodePaste(custom_template_path) + except RuntimeError: + raise RuntimeError(( + "Template defined for project: {} is not working. " + "Talk to your manager for an advise").format( + custom_template_path)) + + # if builder at start is defined + if builder_on: + log.info("Building nodes from presets...") + # build nodes by defined presets + BuildWorkfile().process() + + log.info("Saving script as version `{}`...".format( + last_workfile_path + )) + # safe file as version save_file(last_workfile_path) - else: - # to avoid looping of the callback, remove it! - nuke.removeOnCreate(open_last_workfile, nodeClass="Root") + return - # open workfile - open_file(last_workfile_path) + # skip opening of last version if it is not enabled + if not openlv_on or not os.path.exists(last_workfile_path): + return + + # to avoid looping of the callback, remove it! + nuke.removeOnCreate(process_workfile_builder, nodeClass="Root") + + log.info("Opening last workfile...") + # open workfile + open_file(last_workfile_path) diff --git a/openpype/hosts/tvpaint/hooks/pre_launch_args.py b/openpype/hosts/tvpaint/hooks/pre_launch_args.py index 04cca5d789..b0b13529ca 100644 --- a/openpype/hosts/tvpaint/hooks/pre_launch_args.py +++ b/openpype/hosts/tvpaint/hooks/pre_launch_args.py @@ -34,20 +34,6 @@ class TvpaintPrelaunchHook(PreLaunchHook): "run", self.launch_script_path(), executable_path ) - # Add workfile to launch arguments - workfile_path = self.workfile_path() - if workfile_path: - new_launch_args.append(workfile_path) - - # How to create new command line - # if platform.system().lower() == "windows": - # new_launch_args = [ - # "cmd.exe", - # "/c", - # "Call cmd.exe /k", - # *new_launch_args - # ] - # Append as whole list as these areguments should not be separated self.launch_context.launch_args.append(new_launch_args) @@ -64,38 +50,4 @@ class TvpaintPrelaunchHook(PreLaunchHook): "tvpaint", "launch_script.py" ) - return script_path - - def workfile_path(self): - workfile_path = self.data["last_workfile_path"] - - # copy workfile from template if doesnt exist any on path - if not os.path.exists(workfile_path): - # TODO add ability to set different template workfile path via - # settings - pype_dir = os.path.dirname(os.path.abspath(tvpaint.__file__)) - template_path = os.path.join( - pype_dir, "resources", "template.tvpp" - ) - - if not os.path.exists(template_path): - self.log.warning( - "Couldn't find workfile template file in {}".format( - template_path - ) - ) - return - - self.log.info( - f"Creating workfile from template: \"{template_path}\"" - ) - - # Copy template workfile to new destinantion - shutil.copy2( - os.path.normpath(template_path), - os.path.normpath(workfile_path) - ) - - self.log.info(f"Workfile to open: \"{workfile_path}\"") - - return workfile_path + return script_path \ No newline at end of file diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index c97545fdf4..02ae9635c1 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -81,7 +81,13 @@ from .avalon_context import ( get_creator_by_name, - change_timer_to_current_context + get_custom_workfile_template, + + change_timer_to_current_context, + + get_custom_workfile_template_by_context, + get_custom_workfile_template_by_string_context, + get_custom_workfile_template ) from .local_settings import ( @@ -192,6 +198,10 @@ __all__ = [ "change_timer_to_current_context", + "get_custom_workfile_template_by_context", + "get_custom_workfile_template_by_string_context", + "get_custom_workfile_template", + "IniSettingRegistry", "JSONSettingRegistry", "OpenPypeSecureRegistry", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 2a7c58c4ee..c4217cc6d5 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -3,6 +3,7 @@ import os import json import re import copy +import platform import logging import collections import functools @@ -755,18 +756,22 @@ class BuildWorkfile: """ host_name = avalon.api.registered_host().__name__.rsplit(".", 1)[-1] presets = get_project_settings(avalon.io.Session["AVALON_PROJECT"]) + # Get presets for host - build_presets = ( - presets.get(host_name, {}) - .get("workfile_build") - .get("profiles") - ) - if not build_presets: + wb_settings = presets.get(host_name, {}).get("workfile_builder") + + if not wb_settings: + # backward compatibility + wb_settings = presets.get(host_name, {}).get("workfile_build") + + builder_presets = wb_settings.get("profiles") + + if not builder_presets: return task_name_low = task_name.lower() per_task_preset = None - for preset in build_presets: + for preset in builder_presets: preset_tasks = preset.get("tasks") or [] preset_tasks_low = [task.lower() for task in preset_tasks] if task_name_low in preset_tasks_low: @@ -1266,3 +1271,201 @@ def change_timer_to_current_context(): } requests.post(rest_api_url, json=data) + + +def _get_task_context_data_for_anatomy( + project_doc, asset_doc, task_name, anatomy=None +): + """Prepare Task context for anatomy data. + + WARNING: this data structure is currently used only in workfile templates. + Key "task" is currently in rest of pipeline used as string with task + name. + + Args: + project_doc (dict): Project document with available "name" and + "data.code" keys. + asset_doc (dict): Asset document from MongoDB. + task_name (str): Name of context task. + anatomy (Anatomy): Optionally Anatomy for passed project name can be + passed as Anatomy creation may be slow. + + Returns: + dict: With Anatomy context data. + """ + + if anatomy is None: + anatomy = Anatomy(project_doc["name"]) + + asset_name = asset_doc["name"] + project_task_types = anatomy["tasks"] + + # get relevant task type from asset doc + assert task_name in asset_doc["data"]["tasks"], ( + "Task name \"{}\" not found on asset \"{}\"".format( + task_name, asset_name + ) + ) + + task_type = asset_doc["data"]["tasks"][task_name].get("type") + + assert task_type, ( + "Task name \"{}\" on asset \"{}\" does not have specified task type." + ).format(asset_name, task_name) + + # get short name for task type defined in default anatomy settings + project_task_type_data = project_task_types.get(task_type) + assert project_task_type_data, ( + "Something went wrong. Default anatomy tasks are not holding" + "requested task type: `{}`".format(task_type) + ) + + return { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code") + }, + "asset": asset_name, + "task": { + "name": task_name, + "type": task_type, + "short_name": project_task_type_data["short_name"] + } + } + + +def get_custom_workfile_template_by_context( + template_profiles, project_doc, asset_doc, task_name, anatomy=None +): + """Filter and fill workfile template profiles by passed context. + + It is expected that passed argument are already queried documents of + project and asset as parents of processing task name. + + Existence of formatted path is not validated. + + Args: + template_profiles(list): Template profiles from settings. + project_doc(dict): Project document from MongoDB. + asset_doc(dict): Asset document from MongoDB. + task_name(str): Name of task for which templates are filtered. + anatomy(Anatomy): Optionally passed anatomy object for passed project + name. + + Returns: + str: Path to template or None if none of profiles match current + context. (Existence of formatted path is not validated.) + """ + + from openpype.lib import filter_profiles + + if anatomy is None: + anatomy = Anatomy(project_doc["name"]) + + # get project, asset, task anatomy context data + anatomy_context_data = _get_task_context_data_for_anatomy( + project_doc, asset_doc, task_name, anatomy + ) + # add root dict + anatomy_context_data["root"] = anatomy.roots + + # get task type for the task in context + current_task_type = anatomy_context_data["task"]["type"] + + # get path from matching profile + matching_item = filter_profiles( + template_profiles, + {"task_type": current_task_type} + ) + # when path is available try to format it in case + # there are some anatomy template strings + if matching_item: + template = matching_item["path"][platform.system().lower()] + return template.format(**anatomy_context_data) + + return None + + +def get_custom_workfile_template_by_string_context( + template_profiles, project_name, asset_name, task_name, + dbcon=None, anatomy=None +): + """Filter and fill workfile template profiles by passed context. + + Passed context are string representations of project, asset and task. + Function will query documents of project and asset to be able use + `get_custom_workfile_template_by_context` for rest of logic. + + Args: + template_profiles(list): Loaded workfile template profiles. + project_name(str): Project name. + asset_name(str): Asset name. + task_name(str): Task name. + dbcon(AvalonMongoDB): Optional avalon implementation of mongo + connection with context Session. + anatomy(Anatomy): Optionally prepared anatomy object for passed + project. + + Returns: + str: Path to template or None if none of profiles match current + context. (Existence of formatted path is not validated.) + """ + + if dbcon is None: + from avalon.api import AvalonMongoDB + + dbcon = AvalonMongoDB() + + dbcon.install() + + if dbcon.Session["AVALON_PROJECT"] != project_name: + dbcon.Session["AVALON_PROJECT"] = project_name + + project_doc = dbcon.find_one( + {"type": "project"}, + # All we need is "name" and "data.code" keys + { + "name": 1, + "data.code": 1 + } + ) + asset_doc = dbcon.find_one( + { + "type": "asset", + "name": asset_name + }, + # All we need is "name" and "data.tasks" keys + { + "name": 1, + "data.tasks": 1 + } + ) + + return get_custom_workfile_template_by_context( + template_profiles, project_doc, asset_doc, task_name, anatomy + ) + + +def get_custom_workfile_template(template_profiles): + """Filter and fill workfile template profiles by current context. + + Current context is defined by `avalon.api.Session`. That's why this + function should be used only inside host where context is set and stable. + + Args: + template_profiles(list): Template profiles from settings. + + Returns: + str: Path to template or None if none of profiles match current + context. (Existence of formatted path is not validated.) + """ + # Use `avalon.io` as Mongo connection + from avalon import io + + return get_custom_workfile_template_by_string_context( + template_profiles, + io.Session["AVALON_PROJECT"], + io.Session["AVALON_ASSET"], + io.Session["AVALON_TASK"], + io + ) diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index b272c60d98..698b3b35a9 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -18,5 +18,9 @@ "secondary_pool": "", "chunk_size": 1000000 } + }, + "workfile_builder": { + "create_first_version": false, + "custom_templates": [] } } \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json new file mode 100644 index 0000000000..a7262dcb5d --- /dev/null +++ b/openpype/settings/defaults/project_settings/blender.json @@ -0,0 +1,6 @@ +{ + "workfile_builder": { + "create_first_version": false, + "custom_templates": [] + } +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index bb5232cea7..13e1924b36 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -6,9 +6,7 @@ "load": "ctrl+alt+l", "manage": "ctrl+alt+m", "build_workfile": "ctrl+alt+b" - }, - "open_workfile_at_start": false, - "create_initial_workfile": true + } }, "create": { "CreateWriteRender": { @@ -147,12 +145,13 @@ "node_name_template": "{class_name}_{ext}" } }, - "workfile_build": { + "workfile_builder": { + "create_first_version": false, + "custom_templates": [], + "builder_on_start": false, "profiles": [ { - "tasks": [ - "compositing" - ], + "tasks": [], "current_context": [ { "subset_name_filters": [], @@ -162,10 +161,12 @@ ], "repre_names": [ "exr", - "dpx" + "dpx", + "mov" ], "loaders": [ - "LoadSequence" + "LoadSequence", + "LoadMov" ] } ], diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 0db6e8248d..b306a757a6 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -13,5 +13,9 @@ "jpg" ] } + }, + "workfile_builder": { + "create_first_version": false, + "custom_templates": [] } } \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 9d5b922b8e..b4f3b315ec 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -32,5 +32,9 @@ } } }, + "workfile_builder": { + "create_first_version": false, + "custom_templates": [] + }, "filters": {} } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index e77f13d351..64c5a7f366 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -78,6 +78,10 @@ "type": "schema", "name": "schema_project_hiero" }, + { + "type": "schema", + "name": "schema_project_blender" + }, { "type": "schema", "name": "schema_project_aftereffects" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 63bf9274a3..8024de6d45 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -85,6 +85,14 @@ ] } ] + }, + { + "type": "schema_template", + "name": "template_workfile_options", + "skip_paths": [ + "workfile_builder/builder_on_start", + "workfile_builder/profiles" + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json new file mode 100644 index 0000000000..af09329a03 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -0,0 +1,17 @@ +{ + "type": "dict", + "collapsible": true, + "key": "blender", + "label": "Blender", + "is_file": true, + "children": [ + { + "type": "schema_template", + "name": "template_workfile_options", + "skip_paths": [ + "workfile_builder/builder_on_start", + "workfile_builder/profiles" + ] + } + ] +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 5022b75719..f709e84651 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -43,16 +43,6 @@ "label": "Build Workfile" } ] - }, - { - "type": "boolean", - "key": "open_workfile_at_start", - "label": "Open Workfile window at start of a Nuke session" - }, - { - "type": "boolean", - "key": "create_initial_workfile", - "label": "Create initial workfile version if none available" } ] }, @@ -103,8 +93,8 @@ "template_data": [] }, { - "type": "schema", - "name": "schema_workfile_build" + "type": "schema_template", + "name": "template_workfile_options" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 3a20b4e79c..4eb6c26dbb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -52,6 +52,14 @@ ] } ] + }, + { + "type": "schema_template", + "name": "template_workfile_options", + "skip_paths": [ + "workfile_builder/builder_on_start", + "workfile_builder/profiles" + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 00080f8247..6f90bb4263 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -112,6 +112,14 @@ } ] }, + { + "type": "schema_template", + "name": "template_workfile_options", + "skip_paths": [ + "workfile_builder/builder_on_start", + "workfile_builder/profiles" + ] + }, { "type": "schema", "name": "schema_publish_gui_filter" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json index 0cb36c2f92..078bb81bba 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json @@ -94,4 +94,4 @@ } } ] -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_builder_simple.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_builder_simple.json new file mode 100644 index 0000000000..49cf9ca94a --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_builder_simple.json @@ -0,0 +1,23 @@ +[ + { + "type": "dict", + "collapsible": true, + "key": "workfile_builder", + "label": "Workfile Builder", + "children": [ + { + "type": "boolean", + "key": "create_first_version", + "label": "Create first workfile", + "default": false + }, + { + "type": "path", + "key": "template_path", + "label": "First workfile template", + "multiplatform": true, + "multipath": false + } + ] + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_options.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_options.json new file mode 100644 index 0000000000..815df85879 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_workfile_options.json @@ -0,0 +1,145 @@ +[{ + "type": "dict", + "collapsible": true, + "key": "workfile_builder", + "label": "Workfile Builder", + "children": [ + { + "type": "boolean", + "key": "create_first_version", + "label": "Create first workfile", + "default": false + }, + { + "type": "list", + "key": "custom_templates", + "label": "Custom templates", + "is_group": true, + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "task-types-enum", + "key": "task_types", + "label": "Task types" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Absolute path to workfile template or OpenPype Anatomy text is accepted." + }, + { + "type": "path", + "key": "path", + "label": "Path", + "multiplatform": true, + "multipath": false + } + ] + } + }, + { + "type": "boolean", + "key": "builder_on_start", + "label": "Run Builder Profiles on first launch", + "default": false + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "tasks", + "label": "Tasks", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "current_context", + "label": "Current Context", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + }, + { + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + } + ] + } + }, + { + "type": "separator" + }, + { + "key": "linked_assets", + "label": "Linked Assets/Shots", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + }, + { + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + } + } + ] +} +] diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 11ccb60ae4..b23372e9ac 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -508,6 +508,8 @@ class PathWidget(BaseWidget): self.content_layout = QtWidgets.QGridLayout(self) self.content_layout.setContentsMargins(0, 0, 0, 0) self.content_layout.setSpacing(5) + # Add stretch to second column + self.content_layout.setColumnStretch(1, 1) self.body_widget = None self.setAttribute(QtCore.Qt.WA_TranslucentBackground) diff --git a/repos/avalon-core b/repos/avalon-core index e9882d0fff..07756c7368 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit e9882d0ffff27fed03a03459f496c29da0310cd2 +Subproject commit 07756c7368e4447d28f27dc88b6a46c95692e035 diff --git a/website/docs/project_settings/assets/global_tools_workfile_open_last_version.png b/website/docs/project_settings/assets/global_tools_workfile_open_last_version.png new file mode 100644 index 0000000000..dfcac072c5 Binary files /dev/null and b/website/docs/project_settings/assets/global_tools_workfile_open_last_version.png differ diff --git a/website/docs/project_settings/assets/nuke_workfile_builder_create_first_workfile.png b/website/docs/project_settings/assets/nuke_workfile_builder_create_first_workfile.png new file mode 100644 index 0000000000..f138709a7f Binary files /dev/null and b/website/docs/project_settings/assets/nuke_workfile_builder_create_first_workfile.png differ diff --git a/website/docs/project_settings/assets/nuke_workfile_builder_location.png b/website/docs/project_settings/assets/nuke_workfile_builder_location.png new file mode 100644 index 0000000000..916b79755d Binary files /dev/null and b/website/docs/project_settings/assets/nuke_workfile_builder_location.png differ diff --git a/website/docs/project_settings/assets/nuke_workfile_builder_profiles.png b/website/docs/project_settings/assets/nuke_workfile_builder_profiles.png new file mode 100644 index 0000000000..e4105767ef Binary files /dev/null and b/website/docs/project_settings/assets/nuke_workfile_builder_profiles.png differ diff --git a/website/docs/project_settings/assets/nuke_workfile_builder_template_anatomy.png b/website/docs/project_settings/assets/nuke_workfile_builder_template_anatomy.png new file mode 100644 index 0000000000..195e884bfb Binary files /dev/null and b/website/docs/project_settings/assets/nuke_workfile_builder_template_anatomy.png differ diff --git a/website/docs/project_settings/assets/nuke_workfile_builder_template_task_type.png b/website/docs/project_settings/assets/nuke_workfile_builder_template_task_type.png new file mode 100644 index 0000000000..4c84f37c0e Binary files /dev/null and b/website/docs/project_settings/assets/nuke_workfile_builder_template_task_type.png differ diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 6a08c79582..5d23dd75e6 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -17,10 +17,10 @@ Projects always use default project values unless they have [project override](. Many of the settings are using a concept of **Profile filters** -You can define multiple profiles to choose from for different contexts. Each filter is evaluated and a -profile with filters matching the current context the most, is used. +You can define multiple profiles to choose from for different contexts. Each filter is evaluated and a +profile with filters matching the current context the most, is used. -You can define profile without any filters and use it as **default**. +You can define profile without any filters and use it as **default**. Only **one or none** profile will be returned per context. @@ -129,7 +129,7 @@ Profile may generate multiple outputs from a single input. Each output must defi Saves information for all published subsets into DB, published assets are available for other hosts, tools and tasks after. #### Template name profiles -Allows to select [anatomy template](admin_settings_project_anatomy.md#templates) based on context of subset being published. +Allows to select [anatomy template](admin_settings_project_anatomy.md#templates) based on context of subset being published. For example for `render` profile you might want to publish and store assets in different location (based on anatomy setting) then for `publish` profile. [Profile filtering](#profile-filters) is used to select between appropriate template for each context of published subsets. @@ -139,7 +139,7 @@ Applicable context filters: - **`tasks`** - Current task. `["modeling", "animation"]` ![global_integrate_new_template_name_profile](assets/global_integrate_new_template_name_profile.png) - + (This image shows use case where `render` anatomy template is used for subsets of families ['review, 'render', 'prerender'], `publish` template is chosen for all other.) #### Subset grouping profiles @@ -154,5 +154,16 @@ Applicable context filters: - **`tasks`** - Current task. `["modeling", "animation"]` ![global_integrate_new_template_name_profile](assets/global_integrate_new_subset_group.png) - -(This image shows use case where only assets published from 'photoshop', for all families for all tasks should be marked as grouped with a capitalized name of Task where they are published from.) \ No newline at end of file + +(This image shows use case where only assets published from 'photoshop', for all families for all tasks should be marked as grouped with a capitalized name of Task where they are published from.) + +## Tools +Settings for OpenPype tools. + +## Workfiles +All settings related to Workfile tool. + +### Open last workfile at launch +This feature allows you to define a rule for each task/host or toggle the feature globally to all tasks as they are visible in the picture. + +![global_tools_workfile_open_last_version](assets/global_tools_workfile_open_last_version.png) \ No newline at end of file diff --git a/website/docs/project_settings/settings_project_nuke.md b/website/docs/project_settings/settings_project_nuke.md new file mode 100644 index 0000000000..561311317f --- /dev/null +++ b/website/docs/project_settings/settings_project_nuke.md @@ -0,0 +1,63 @@ +--- +id: settings_project_nuke +title: Project Nuke Setting +sidebar_label: Nuke +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Project settings can have project specific values. Each new project is using studio values defined in **default** project but these values can be modified or overriden per project. + +:::warning Default studio values +Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orage colour). Any changes in default project may affect all existing projects. +::: + +## Workfile Builder + +All Workfile Builder related settings can be found here. This is a list of available features: +- Create first workfile +- Custom Template path (task type filtered) +- Run Builder profiles at first run +- Define Builder Profiles With Filters + +![nuke_workfile_options_location](assets/nuke_workfile_builder_location.png) + +:::important Auto Load Last Version +In case you want to set the auto load of the latest available version of workfiles, you can do it from [here](settings_project_global#open-last-workfile-at-launch). +::: + +### Create first workfile + +By switchintg this feature on, OpenPype will generate initial workfile version. Following attributes are possible to configure: + +![nuke_workfile_options_create_first_version](assets/nuke_workfile_builder_create_first_workfile.png) + +#### Custom templates +Custom templates are added into nuke's node graph as nodes. List of task types can be defined for templates filtering. + +- Task types are sourced from project related Anatomy/Task Types + +![nuke_workfile_builder_template_task_type](assets/nuke_workfile_builder_template_task_type.png) + + - multi platform path can be used in a variety of ways. Along the absolut path to a template file also an python formating could be used. At the moment these keys are supported (image example bellow): + - `root[key]`: definitions from anatomy roots + - `project[name, code]`: project in context + - `asset`: name of asset/shot in context + - `task[type, name, short_name]`: as they are defined on asset/shot and in **Anatomy/Task Type** on project context + +![nuke_workfile_builder_template_anatomy](assets/nuke_workfile_builder_template_anatomy.png) + +#### Run Builder profiles on first launch +Enabling this feature will look into available Builder's Prorfiles (look bellow for more informations about this feature) and load available versions into node graph. + +### Profiles (Builder) +Builder profiles are set of rules allowing artist Load any available versions for the context of the asset, which it is run from. Preset is having following attributes: + +- **Filter**: Each profile could be defined with task filter. In case no filter is defined, a profile will be working for all. + +- **Context section**: filtres for subset name (regex accepted), families, representation names and available Loader plugin. + +- **Linked Assets/Shots**: filters for asset builds to be added + +![nuke_workfile_builder_profiles](assets/nuke_workfile_builder_profiles.png) diff --git a/website/sidebars.js b/website/sidebars.js index 7a5379c36b..0b831bccb3 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -64,7 +64,8 @@ module.exports = { type: "category", label: "Project Settings", items: [ - "project_settings/settings_project_global" + "project_settings/settings_project_global", + "project_settings/settings_project_nuke" ], }, ],