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"]`

-
+
(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"]`

-
-(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.
+
+
\ 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
+
+
+
+:::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:
+
+
+
+#### 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
+
+
+
+ - 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
+
+
+
+#### 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
+
+
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"
],
},
],