mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge pull request #1662 from pypeclub/bugfix/tvpaint_custom_subset_name
TVPaint custom subset template
This commit is contained in:
commit
ee883ece89
7 changed files with 269 additions and 80 deletions
|
|
@ -1,9 +1,11 @@
|
|||
from avalon.api import CreatorError
|
||||
from avalon.tvpaint import (
|
||||
pipeline,
|
||||
lib,
|
||||
CommunicationWrapper
|
||||
)
|
||||
from openpype.hosts.tvpaint.api import plugin
|
||||
from openpype.lib import prepare_template_data
|
||||
|
||||
|
||||
class CreateRenderlayer(plugin.Creator):
|
||||
|
|
@ -15,13 +17,31 @@ class CreateRenderlayer(plugin.Creator):
|
|||
defaults = ["Main"]
|
||||
|
||||
rename_group = True
|
||||
render_pass = "beauty"
|
||||
|
||||
subset_template = "{family}_{name}"
|
||||
rename_script_template = (
|
||||
"tv_layercolor \"setcolor\""
|
||||
" {clip_id} {group_id} {r} {g} {b} \"{name}\""
|
||||
)
|
||||
|
||||
dynamic_subset_keys = ["render_pass", "render_layer", "group"]
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_data(
|
||||
cls, variant, task_name, asset_id, project_name, host_name
|
||||
):
|
||||
dynamic_data = super(CreateRenderlayer, cls).get_dynamic_data(
|
||||
variant, task_name, asset_id, project_name, host_name
|
||||
)
|
||||
# Use render pass name from creator's plugin
|
||||
dynamic_data["render_pass"] = cls.render_pass
|
||||
# Add variant to render layer
|
||||
dynamic_data["render_layer"] = variant
|
||||
# Change family for subset name fill
|
||||
dynamic_data["family"] = "render"
|
||||
|
||||
return dynamic_data
|
||||
|
||||
@classmethod
|
||||
def get_default_variant(cls):
|
||||
"""Default value for variant in Creator tool.
|
||||
|
|
@ -70,34 +90,44 @@ class CreateRenderlayer(plugin.Creator):
|
|||
|
||||
# Raise if there is no selection
|
||||
if not group_ids:
|
||||
raise AssertionError("Nothing is selected.")
|
||||
raise CreatorError("Nothing is selected.")
|
||||
|
||||
# This creator should run only on one group
|
||||
if len(group_ids) > 1:
|
||||
raise AssertionError("More than one group is in selection.")
|
||||
raise CreatorError("More than one group is in selection.")
|
||||
|
||||
group_id = tuple(group_ids)[0]
|
||||
# If group id is `0` it is `default` group which is invalid
|
||||
if group_id == 0:
|
||||
raise AssertionError(
|
||||
raise CreatorError(
|
||||
"Selection is not in group. Can't mark selection as Beauty."
|
||||
)
|
||||
|
||||
self.log.debug(f"Selected group id is \"{group_id}\".")
|
||||
self.data["group_id"] = group_id
|
||||
|
||||
family = self.data["family"]
|
||||
# Extract entered name
|
||||
name = self.data["subset"][len(family):]
|
||||
self.log.info(f"Extracted name from subset name \"{name}\".")
|
||||
self.data["name"] = name
|
||||
group_data = lib.groups_data()
|
||||
group_name = None
|
||||
for group in group_data:
|
||||
if group["group_id"] == group_id:
|
||||
group_name = group["name"]
|
||||
break
|
||||
|
||||
# Change subset name by template
|
||||
subset_name = self.subset_template.format(**{
|
||||
"family": self.family,
|
||||
"name": name
|
||||
})
|
||||
self.log.info(f"New subset name \"{subset_name}\".")
|
||||
if group_name is None:
|
||||
raise AssertionError(
|
||||
"Couldn't find group by id \"{}\"".format(group_id)
|
||||
)
|
||||
|
||||
subset_name_fill_data = {
|
||||
"group": group_name
|
||||
}
|
||||
|
||||
family = self.family = self.data["family"]
|
||||
|
||||
# Fill dynamic key 'group'
|
||||
subset_name = self.data["subset"].format(
|
||||
**prepare_template_data(subset_name_fill_data)
|
||||
)
|
||||
self.data["subset"] = subset_name
|
||||
|
||||
# Check for instances of same group
|
||||
|
|
@ -153,7 +183,7 @@ class CreateRenderlayer(plugin.Creator):
|
|||
|
||||
# Rename TVPaint group (keep color same)
|
||||
# - groups can't contain spaces
|
||||
new_group_name = name.replace(" ", "_")
|
||||
new_group_name = self.data["variant"].replace(" ", "_")
|
||||
rename_script = self.rename_script_template.format(
|
||||
clip_id=selected_group["clip_id"],
|
||||
group_id=selected_group["group_id"],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from avalon.api import CreatorError
|
||||
from avalon.tvpaint import (
|
||||
pipeline,
|
||||
lib,
|
||||
CommunicationWrapper
|
||||
)
|
||||
from openpype.hosts.tvpaint.api import plugin
|
||||
from openpype.lib import prepare_template_data
|
||||
|
||||
|
||||
class CreateRenderPass(plugin.Creator):
|
||||
|
|
@ -18,7 +20,19 @@ class CreateRenderPass(plugin.Creator):
|
|||
icon = "cube"
|
||||
defaults = ["Main"]
|
||||
|
||||
subset_template = "{family}_{render_layer}_{pass}"
|
||||
dynamic_subset_keys = ["render_pass", "render_layer"]
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_data(
|
||||
cls, variant, task_name, asset_id, project_name, host_name
|
||||
):
|
||||
dynamic_data = super(CreateRenderPass, cls).get_dynamic_data(
|
||||
variant, task_name, asset_id, project_name, host_name
|
||||
)
|
||||
dynamic_data["render_pass"] = variant
|
||||
dynamic_data["family"] = "render"
|
||||
|
||||
return dynamic_data
|
||||
|
||||
@classmethod
|
||||
def get_default_variant(cls):
|
||||
|
|
@ -66,11 +80,11 @@ class CreateRenderPass(plugin.Creator):
|
|||
|
||||
# Raise if nothing is selected
|
||||
if not selected_layers:
|
||||
raise AssertionError("Nothing is selected.")
|
||||
raise CreatorError("Nothing is selected.")
|
||||
|
||||
# Raise if layers from multiple groups are selected
|
||||
if len(group_ids) != 1:
|
||||
raise AssertionError("More than one group is in selection.")
|
||||
raise CreatorError("More than one group is in selection.")
|
||||
|
||||
group_id = tuple(group_ids)[0]
|
||||
self.log.debug(f"Selected group id is \"{group_id}\".")
|
||||
|
|
@ -87,34 +101,40 @@ class CreateRenderPass(plugin.Creator):
|
|||
|
||||
# Beauty is required for this creator so raise if was not found
|
||||
if beauty_instance is None:
|
||||
raise AssertionError("Beauty pass does not exist yet.")
|
||||
raise CreatorError("Beauty pass does not exist yet.")
|
||||
|
||||
render_layer = beauty_instance["name"]
|
||||
subset_name = self.data["subset"]
|
||||
|
||||
subset_name_fill_data = {}
|
||||
|
||||
# Backwards compatibility
|
||||
# - beauty may be created with older creator where variant was not
|
||||
# stored
|
||||
if "variant" not in beauty_instance:
|
||||
render_layer = beauty_instance["name"]
|
||||
else:
|
||||
render_layer = beauty_instance["variant"]
|
||||
|
||||
subset_name_fill_data["render_layer"] = render_layer
|
||||
|
||||
# Format dynamic keys in subset name
|
||||
new_subset_name = subset_name.format(
|
||||
**prepare_template_data(subset_name_fill_data)
|
||||
)
|
||||
self.data["subset"] = new_subset_name
|
||||
self.log.info(f"New subset name is \"{new_subset_name}\".")
|
||||
|
||||
# Extract entered name
|
||||
family = self.data["family"]
|
||||
name = self.data["subset"]
|
||||
# Is this right way how to get name?
|
||||
name = name[len(family):]
|
||||
self.log.info(f"Extracted name from subset name \"{name}\".")
|
||||
variant = self.data["variant"]
|
||||
|
||||
self.data["group_id"] = group_id
|
||||
self.data["pass"] = name
|
||||
self.data["pass"] = variant
|
||||
self.data["render_layer"] = render_layer
|
||||
|
||||
# Collect selected layer ids to be stored into instance
|
||||
layer_names = [layer["name"] for layer in selected_layers]
|
||||
self.data["layer_names"] = layer_names
|
||||
|
||||
# Replace `beauty` in beauty's subset name with entered name
|
||||
subset_name = self.subset_template.format(**{
|
||||
"family": family,
|
||||
"render_layer": render_layer,
|
||||
"pass": name
|
||||
})
|
||||
self.data["subset"] = subset_name
|
||||
self.log.info(f"New subset name is \"{subset_name}\".")
|
||||
|
||||
# Check if same instance already exists
|
||||
existing_instance = None
|
||||
existing_instance_idx = None
|
||||
|
|
@ -122,7 +142,7 @@ class CreateRenderPass(plugin.Creator):
|
|||
if (
|
||||
instance["family"] == family
|
||||
and instance["group_id"] == group_id
|
||||
and instance["pass"] == name
|
||||
and instance["pass"] == variant
|
||||
):
|
||||
existing_instance = instance
|
||||
existing_instance_idx = idx
|
||||
|
|
@ -131,7 +151,7 @@ class CreateRenderPass(plugin.Creator):
|
|||
if existing_instance is not None:
|
||||
self.log.info(
|
||||
f"Render pass instance for group id {group_id}"
|
||||
f" and name \"{name}\" already exists, overriding."
|
||||
f" and name \"{variant}\" already exists, overriding."
|
||||
)
|
||||
instances[existing_instance_idx] = self.data
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import copy
|
|||
import pyblish.api
|
||||
from avalon import io
|
||||
|
||||
from openpype.lib import get_subset_name
|
||||
|
||||
|
||||
class CollectInstances(pyblish.api.ContextPlugin):
|
||||
label = "Collect Instances"
|
||||
|
|
@ -62,9 +64,38 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
# Different instance creation based on family
|
||||
instance = None
|
||||
if family == "review":
|
||||
# Change subset name
|
||||
# Change subset name of review instance
|
||||
|
||||
# Collect asset doc to get asset id
|
||||
# - not sure if it's good idea to require asset id in
|
||||
# get_subset_name?
|
||||
asset_name = context.data["workfile_context"]["asset"]
|
||||
asset_doc = io.find_one(
|
||||
{
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
},
|
||||
{"_id": 1}
|
||||
)
|
||||
asset_id = None
|
||||
if asset_doc:
|
||||
asset_id = asset_doc["_id"]
|
||||
|
||||
# Project name from workfile context
|
||||
project_name = context.data["workfile_context"]["project"]
|
||||
# Host name from environemnt variable
|
||||
host_name = os.environ["AVALON_APP"]
|
||||
# Use empty variant value
|
||||
variant = ""
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
new_subset_name = "{}{}".format(family, task_name.capitalize())
|
||||
new_subset_name = get_subset_name(
|
||||
family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_id,
|
||||
project_name,
|
||||
host_name
|
||||
)
|
||||
instance_data["subset"] = new_subset_name
|
||||
|
||||
instance = context.create_instance(**instance_data)
|
||||
|
|
@ -119,19 +150,23 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
name = instance_data["name"]
|
||||
# Change label
|
||||
subset_name = instance_data["subset"]
|
||||
instance_data["label"] = "{}_Beauty".format(name)
|
||||
|
||||
# Change subset name
|
||||
# Final family of an instance will be `render`
|
||||
new_family = "render"
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
new_subset_name = "{}{}_{}_Beauty".format(
|
||||
new_family, task_name.capitalize(), name
|
||||
)
|
||||
instance_data["subset"] = new_subset_name
|
||||
self.log.debug("Changed subset name \"{}\"->\"{}\"".format(
|
||||
subset_name, new_subset_name
|
||||
))
|
||||
# Backwards compatibility
|
||||
# - subset names were not stored as final subset names during creation
|
||||
if "variant" not in instance_data:
|
||||
instance_data["label"] = "{}_Beauty".format(name)
|
||||
|
||||
# Change subset name
|
||||
# Final family of an instance will be `render`
|
||||
new_family = "render"
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
new_subset_name = "{}{}_{}_Beauty".format(
|
||||
new_family, task_name.capitalize(), name
|
||||
)
|
||||
instance_data["subset"] = new_subset_name
|
||||
self.log.debug("Changed subset name \"{}\"->\"{}\"".format(
|
||||
subset_name, new_subset_name
|
||||
))
|
||||
|
||||
# Get all layers for the layer
|
||||
layers_data = context.data["layersData"]
|
||||
|
|
@ -163,20 +198,23 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
)
|
||||
# Change label
|
||||
render_layer = instance_data["render_layer"]
|
||||
instance_data["label"] = "{}_{}".format(render_layer, pass_name)
|
||||
|
||||
# Change subset name
|
||||
# Final family of an instance will be `render`
|
||||
new_family = "render"
|
||||
old_subset_name = instance_data["subset"]
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
new_subset_name = "{}{}_{}_{}".format(
|
||||
new_family, task_name.capitalize(), render_layer, pass_name
|
||||
)
|
||||
instance_data["subset"] = new_subset_name
|
||||
self.log.debug("Changed subset name \"{}\"->\"{}\"".format(
|
||||
old_subset_name, new_subset_name
|
||||
))
|
||||
# Backwards compatibility
|
||||
# - subset names were not stored as final subset names during creation
|
||||
if "variant" not in instance_data:
|
||||
instance_data["label"] = "{}_{}".format(render_layer, pass_name)
|
||||
# Change subset name
|
||||
# Final family of an instance will be `render`
|
||||
new_family = "render"
|
||||
old_subset_name = instance_data["subset"]
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
new_subset_name = "{}{}_{}_{}".format(
|
||||
new_family, task_name.capitalize(), render_layer, pass_name
|
||||
)
|
||||
instance_data["subset"] = new_subset_name
|
||||
self.log.debug("Changed subset name \"{}\"->\"{}\"".format(
|
||||
old_subset_name, new_subset_name
|
||||
))
|
||||
|
||||
layers_data = context.data["layersData"]
|
||||
layers_by_name = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import json
|
|||
import pyblish.api
|
||||
from avalon import io
|
||||
|
||||
from openpype.lib import get_subset_name
|
||||
|
||||
|
||||
class CollectWorkfile(pyblish.api.ContextPlugin):
|
||||
label = "Collect Workfile"
|
||||
|
|
@ -20,8 +22,38 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
basename, ext = os.path.splitext(filename)
|
||||
instance = context.create_instance(name=basename)
|
||||
|
||||
# Get subset name of workfile instance
|
||||
# Collect asset doc to get asset id
|
||||
# - not sure if it's good idea to require asset id in
|
||||
# get_subset_name?
|
||||
family = "workfile"
|
||||
asset_name = context.data["workfile_context"]["asset"]
|
||||
asset_doc = io.find_one(
|
||||
{
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
},
|
||||
{"_id": 1}
|
||||
)
|
||||
asset_id = None
|
||||
if asset_doc:
|
||||
asset_id = asset_doc["_id"]
|
||||
|
||||
# Project name from workfile context
|
||||
project_name = context.data["workfile_context"]["project"]
|
||||
# Host name from environemnt variable
|
||||
host_name = os.environ["AVALON_APP"]
|
||||
# Use empty variant value
|
||||
variant = ""
|
||||
task_name = io.Session["AVALON_TASK"]
|
||||
subset_name = "workfile" + task_name.capitalize()
|
||||
subset_name = get_subset_name(
|
||||
family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_id,
|
||||
project_name,
|
||||
host_name
|
||||
)
|
||||
|
||||
# Create Workfile instance
|
||||
instance.data.update({
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ def get_subset_name(
|
|||
asset_id,
|
||||
project_name=None,
|
||||
host_name=None,
|
||||
default_template=None
|
||||
default_template=None,
|
||||
dynamic_data=None
|
||||
):
|
||||
if not family:
|
||||
return ""
|
||||
|
|
@ -68,11 +69,16 @@ def get_subset_name(
|
|||
if not task_name and "{task" in template.lower():
|
||||
raise TaskNotSetError()
|
||||
|
||||
fill_pairs = (
|
||||
("variant", variant),
|
||||
("family", family),
|
||||
("task", task_name)
|
||||
)
|
||||
fill_pairs = {
|
||||
"variant": variant,
|
||||
"family": family,
|
||||
"task": task_name
|
||||
}
|
||||
if dynamic_data:
|
||||
# Dynamic data may override default values
|
||||
for key, value in dynamic_data.items():
|
||||
fill_pairs[key] = value
|
||||
|
||||
return template.format(**prepare_template_data(fill_pairs))
|
||||
|
||||
|
||||
|
|
@ -91,7 +97,8 @@ def prepare_template_data(fill_pairs):
|
|||
|
||||
"""
|
||||
fill_data = {}
|
||||
for key, value in fill_pairs:
|
||||
regex = re.compile(r"[a-zA-Z0-9]")
|
||||
for key, value in dict(fill_pairs).items():
|
||||
# Handle cases when value is `None` (standalone publisher)
|
||||
if value is None:
|
||||
continue
|
||||
|
|
@ -102,13 +109,18 @@ def prepare_template_data(fill_pairs):
|
|||
|
||||
# Capitalize only first char of value
|
||||
# - conditions are because of possible index errors
|
||||
# - regex is to skip symbols that are not chars or numbers
|
||||
# - e.g. "{key}" which starts with curly bracket
|
||||
capitalized = ""
|
||||
if value:
|
||||
# Upper first character
|
||||
capitalized += value[0].upper()
|
||||
# Append rest of string if there is any
|
||||
if len(value) > 1:
|
||||
capitalized += value[1:]
|
||||
for idx in range(len(value or "")):
|
||||
char = value[idx]
|
||||
if not regex.match(char):
|
||||
capitalized += char
|
||||
else:
|
||||
capitalized += char.upper()
|
||||
capitalized += value[idx + 1:]
|
||||
break
|
||||
|
||||
fill_data[key.capitalize()] = capitalized
|
||||
|
||||
return fill_data
|
||||
|
|
|
|||
|
|
@ -16,13 +16,59 @@ class PypeCreatorMixin:
|
|||
|
||||
Mixin class must be used as first in inheritance order to override methods.
|
||||
"""
|
||||
dynamic_subset_keys = []
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_data(
|
||||
cls, variant, task_name, asset_id, project_name, host_name
|
||||
):
|
||||
"""Return dynamic data for current Creator plugin.
|
||||
|
||||
By default return keys from `dynamic_subset_keys` attribute as mapping
|
||||
to keep formatted template unchanged.
|
||||
|
||||
```
|
||||
dynamic_subset_keys = ["my_key"]
|
||||
---
|
||||
output = {
|
||||
"my_key": "{my_key}"
|
||||
}
|
||||
```
|
||||
|
||||
Dynamic keys may override default Creator keys (family, task, asset,
|
||||
...) but do it wisely if you need.
|
||||
|
||||
All of keys will be converted into 3 variants unchanged, capitalized
|
||||
and all upper letters. Because of that are all keys lowered.
|
||||
|
||||
This method can be modified to prefill some values just keep in mind it
|
||||
is class method.
|
||||
|
||||
Returns:
|
||||
dict: Fill data for subset name template.
|
||||
"""
|
||||
dynamic_data = {}
|
||||
for key in cls.dynamic_subset_keys:
|
||||
key = key.lower()
|
||||
dynamic_data[key] = "{" + key + "}"
|
||||
return dynamic_data
|
||||
|
||||
@classmethod
|
||||
def get_subset_name(
|
||||
cls, variant, task_name, asset_id, project_name, host_name=None
|
||||
):
|
||||
dynamic_data = cls.get_dynamic_data(
|
||||
variant, task_name, asset_id, project_name, host_name
|
||||
)
|
||||
|
||||
return get_subset_name(
|
||||
cls.family, variant, task_name, asset_id, project_name, host_name
|
||||
cls.family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_id,
|
||||
project_name,
|
||||
host_name,
|
||||
dynamic_data=dynamic_data
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,17 @@
|
|||
"hosts": [],
|
||||
"tasks": [],
|
||||
"template": "{family}{Task}{Variant}"
|
||||
},
|
||||
{
|
||||
"families": [
|
||||
"renderLayer",
|
||||
"renderPass"
|
||||
],
|
||||
"hosts": [
|
||||
"tvpaint"
|
||||
],
|
||||
"tasks": [],
|
||||
"template": "{family}{Task}_{Render_layer}_{Render_pass}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue