mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #2429 from pypeclub/feature/validation_exceptions_photoshop
Photoshop: New style validations for New publisher
This commit is contained in:
commit
ef1ddb2866
10 changed files with 203 additions and 18 deletions
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Subset context</title>
|
||||
<description>
|
||||
## Invalid subset context
|
||||
|
||||
Asset name found '{found}' in subsets, expected '{expected}'.
|
||||
|
||||
### How to repair?
|
||||
|
||||
You can fix this with `Repair` button on the right. This will use '{expected}' asset name and overwrite '{found}' asset name in scene metadata.
|
||||
|
||||
After that restart `Publish` with a `Reload button`.
|
||||
|
||||
If this is unwanted, close workfile and open again, that way different asset value would be used for context information.
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
||||
This might happen if you are reuse old workfile and open it in different context.
|
||||
(Eg. you created subset "renderCompositingDefault" from asset "Robot' in "your_project_Robot_compositing.aep", now you opened this workfile in a context "Sloth" but existing subset for "Robot" asset stayed in the workfile.)
|
||||
</detail>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Invalid name</title>
|
||||
<description>
|
||||
## Invalid name of subset
|
||||
|
||||
Name of subset is created from a layer name. Some characters (whitespace, '/' etc.) are not allowed because of publishing (files couldn't be saved on some OSes).
|
||||
|
||||
### How to repair?
|
||||
|
||||
You can fix this with `Repair` button on the right. This will remove invalid characters with safe character ('_' by default) in both subset names and matching group names.
|
||||
|
||||
After that restart `Publish` with a `Reload button`.
|
||||
|
||||
Or you use `Subset Manager` to delete existing subsets, remove created groups, rename layers that are used for their creation and use `Create` option in the Openpype menu to create them again.
|
||||
|
||||
Invalid characters and 'safe character' could be configured in Settings. Ask your OpenPype admin to modify them if necessary.
|
||||
</description>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Subsets duplicated</title>
|
||||
<description>
|
||||
## Some subsets are duplicated
|
||||
|
||||
Created subsets must be unique.
|
||||
|
||||
Subsets '{duplicates_str}' are duplicated.
|
||||
|
||||
### How to repair?
|
||||
|
||||
Use `Subset Manager` to delete duplicated subset to have only unique subset names and restart `Publish` with a `Reload button`.
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
||||
Subset names are created from layer names. Layer names are filtered for characters that would break publishing process when files are created.
|
||||
This replacement process might result in duplicate names of subsets.
|
||||
</detail>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
from avalon import api
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
from avalon import photoshop
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import PublishXmlValidationError
|
||||
|
||||
|
||||
class ValidateInstanceAssetRepair(pyblish.api.Action):
|
||||
"""Repair the instance asset."""
|
||||
|
|
@ -56,4 +58,10 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin):
|
|||
f"If that's not correct value, close workfile and "
|
||||
f"reopen via Workfiles!"
|
||||
)
|
||||
assert instance_asset == current_asset, msg
|
||||
formatting_data = {
|
||||
"found": instance_asset,
|
||||
"expected": current_asset
|
||||
}
|
||||
if instance_asset != current_asset:
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import re
|
||||
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
from avalon import photoshop
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import PublishXmlValidationError
|
||||
|
||||
|
||||
class ValidateNamingRepair(pyblish.api.Action):
|
||||
"""Repair the instance asset."""
|
||||
|
|
@ -69,14 +71,18 @@ class ValidateNaming(pyblish.api.InstancePlugin):
|
|||
replace_char = ''
|
||||
|
||||
def process(self, instance):
|
||||
help_msg = ' Use Repair action (A) in Pyblish to fix it.'
|
||||
msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"],
|
||||
help_msg)
|
||||
assert not re.search(self.invalid_chars, instance.data["name"]), msg
|
||||
msg = "Name \"{}\" is not allowed.".format(instance.data["name"])
|
||||
|
||||
msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"],
|
||||
help_msg)
|
||||
assert not re.search(self.invalid_chars, instance.data["subset"]), msg
|
||||
formatting_data = {"error_msg": msg}
|
||||
if re.search(self.invalid_chars, instance.data["name"]):
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
||||
msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"])
|
||||
formatting_data = {"error_msg": msg}
|
||||
if re.search(self.invalid_chars, instance.data["subset"]):
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
||||
@classmethod
|
||||
def get_replace_chars(cls):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import collections
|
||||
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
from openpype.pipeline import PublishXmlValidationError
|
||||
|
||||
|
||||
class ValidateSubsetUniqueness(pyblish.api.ContextPlugin):
|
||||
|
|
@ -19,8 +22,18 @@ class ValidateSubsetUniqueness(pyblish.api.ContextPlugin):
|
|||
if instance.data.get('publish'):
|
||||
subset_names.append(instance.data.get('subset'))
|
||||
|
||||
msg = (
|
||||
"Instance subset names are not unique. " +
|
||||
"Remove duplicates via SubsetManager."
|
||||
)
|
||||
assert len(subset_names) == len(set(subset_names)), msg
|
||||
duplicates = [item
|
||||
for item, count in
|
||||
collections.Counter(subset_names).items()
|
||||
if count > 1]
|
||||
|
||||
if duplicates:
|
||||
duplicates_str = ",".join(duplicates)
|
||||
formatting_data = {"duplicates_str": duplicates_str}
|
||||
msg = (
|
||||
"Instance subset names {} are not unique.".format(
|
||||
duplicates_str) +
|
||||
" Remove duplicates via SubsetManager."
|
||||
)
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from .create import (
|
|||
|
||||
from .publish import (
|
||||
PublishValidationError,
|
||||
PublishXmlValidationError,
|
||||
KnownPublishError,
|
||||
OpenPypePyblishPluginMixin
|
||||
)
|
||||
|
|
@ -23,6 +24,7 @@ __all__ = (
|
|||
"CreatedInstance",
|
||||
|
||||
"PublishValidationError",
|
||||
"PublishXmlValidationError",
|
||||
"KnownPublishError",
|
||||
"OpenPypePyblishPluginMixin"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
from .publish_plugins import (
|
||||
PublishValidationError,
|
||||
PublishXmlValidationError,
|
||||
KnownPublishError,
|
||||
OpenPypePyblishPluginMixin
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
DiscoverResult,
|
||||
publish_plugins_discover
|
||||
publish_plugins_discover,
|
||||
load_help_content_from_plugin,
|
||||
load_help_content_from_filepath
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PublishValidationError",
|
||||
"PublishXmlValidationError",
|
||||
"KnownPublishError",
|
||||
"OpenPypePyblishPluginMixin",
|
||||
|
||||
"DiscoverResult",
|
||||
"publish_plugins_discover"
|
||||
"publish_plugins_discover",
|
||||
"load_help_content_from_plugin",
|
||||
"load_help_content_from_filepath"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
import types
|
||||
import inspect
|
||||
import xml.etree.ElementTree
|
||||
|
||||
import six
|
||||
import pyblish.plugin
|
||||
|
|
@ -28,6 +30,61 @@ class DiscoverResult:
|
|||
self.plugins[item] = value
|
||||
|
||||
|
||||
class HelpContent:
|
||||
def __init__(self, title, description, detail=None):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def load_help_content_from_filepath(filepath):
|
||||
"""Load help content from xml file.
|
||||
|
||||
Xml file may containt errors and warnings.
|
||||
"""
|
||||
errors = {}
|
||||
warnings = {}
|
||||
output = {
|
||||
"errors": errors,
|
||||
"warnings": warnings
|
||||
}
|
||||
if not os.path.exists(filepath):
|
||||
return output
|
||||
tree = xml.etree.ElementTree.parse(filepath)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
child_id = child.attrib.get("id")
|
||||
if child_id is None:
|
||||
continue
|
||||
|
||||
# Make sure ID is string
|
||||
child_id = str(child_id)
|
||||
|
||||
title = child.find("title").text
|
||||
description = child.find("description").text
|
||||
detail_node = child.find("detail")
|
||||
detail = None
|
||||
if detail_node is not None:
|
||||
detail = detail_node.text
|
||||
if child.tag == "error":
|
||||
errors[child_id] = HelpContent(title, description, detail)
|
||||
elif child.tag == "warning":
|
||||
warnings[child_id] = HelpContent(title, description, detail)
|
||||
return output
|
||||
|
||||
|
||||
def load_help_content_from_plugin(plugin):
|
||||
cls = plugin
|
||||
if not inspect.isclass(plugin):
|
||||
cls = plugin.__class__
|
||||
plugin_filepath = inspect.getfile(cls)
|
||||
plugin_dir = os.path.dirname(plugin_filepath)
|
||||
basename = os.path.splitext(os.path.basename(plugin_filepath))[0]
|
||||
filename = basename + ".xml"
|
||||
filepath = os.path.join(plugin_dir, "help", filename)
|
||||
return load_help_content_from_filepath(filepath)
|
||||
|
||||
|
||||
def publish_plugins_discover(paths=None):
|
||||
"""Find and return available pyblish plug-ins
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
from .lib import load_help_content_from_plugin
|
||||
|
||||
|
||||
class PublishValidationError(Exception):
|
||||
"""Validation error happened during publishing.
|
||||
|
||||
|
|
@ -12,13 +15,34 @@ class PublishValidationError(Exception):
|
|||
description(str): Detailed description of an error. It is possible
|
||||
to use Markdown syntax.
|
||||
"""
|
||||
def __init__(self, message, title=None, description=None):
|
||||
def __init__(self, message, title=None, description=None, detail=None):
|
||||
self.message = message
|
||||
self.title = title or "< Missing title >"
|
||||
self.description = description or message
|
||||
self.detail = detail
|
||||
super(PublishValidationError, self).__init__(message)
|
||||
|
||||
|
||||
class PublishXmlValidationError(PublishValidationError):
|
||||
def __init__(
|
||||
self, plugin, message, key=None, formatting_data=None
|
||||
):
|
||||
if key is None:
|
||||
key = "main"
|
||||
|
||||
if not formatting_data:
|
||||
formatting_data = {}
|
||||
result = load_help_content_from_plugin(plugin)
|
||||
content_obj = result["errors"][key]
|
||||
description = content_obj.description.format(**formatting_data)
|
||||
detail = content_obj.detail
|
||||
if detail:
|
||||
detail = detail.format(**formatting_data)
|
||||
super(PublishXmlValidationError, self).__init__(
|
||||
message, content_obj.title, description, detail
|
||||
)
|
||||
|
||||
|
||||
class KnownPublishError(Exception):
|
||||
"""Publishing crashed because of known error.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue