Merge pull request #1571 from pypeclub/feature/895-add-option-to-define-paht-to-workfile-template

This commit is contained in:
Milan Kolar 2021-06-01 20:46:15 +02:00 committed by GitHub
commit 17d8409cd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 754 additions and 105 deletions

View file

@ -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"):

View file

@ -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)
)

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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",

View file

@ -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
)

View file

@ -18,5 +18,9 @@
"secondary_pool": "",
"chunk_size": 1000000
}
},
"workfile_builder": {
"create_first_version": false,
"custom_templates": []
}
}

View file

@ -0,0 +1,6 @@
{
"workfile_builder": {
"create_first_version": false,
"custom_templates": []
}
}

View file

@ -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"
]
}
],

View file

@ -13,5 +13,9 @@
"jpg"
]
}
},
"workfile_builder": {
"create_first_version": false,
"custom_templates": []
}
}

View file

@ -32,5 +32,9 @@
}
}
},
"workfile_builder": {
"create_first_version": false,
"custom_templates": []
},
"filters": {}
}

View file

@ -78,6 +78,10 @@
"type": "schema",
"name": "schema_project_hiero"
},
{
"type": "schema",
"name": "schema_project_blender"
},
{
"type": "schema",
"name": "schema_project_aftereffects"

View file

@ -85,6 +85,14 @@
]
}
]
},
{
"type": "schema_template",
"name": "template_workfile_options",
"skip_paths": [
"workfile_builder/builder_on_start",
"workfile_builder/profiles"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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",

View file

@ -52,6 +52,14 @@
]
}
]
},
{
"type": "schema_template",
"name": "template_workfile_options",
"skip_paths": [
"workfile_builder/builder_on_start",
"workfile_builder/profiles"
]
}
]
}

View file

@ -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"

View file

@ -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
}
]
}
]

View file

@ -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": "<b>Current Context</b>",
"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": "<b>Linked Assets/Shots</b>",
"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"
}
]
}
}
]
}
}
]
}
]

View file

@ -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)

@ -1 +1 @@
Subproject commit e9882d0ffff27fed03a03459f496c29da0310cd2
Subproject commit 07756c7368e4447d28f27dc88b6a46c95692e035

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -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.)
(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)

View file

@ -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)

View file

@ -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"
],
},
],