mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
966fa92d8e
34 changed files with 6890 additions and 816 deletions
44
.github/workflows/prerelease.yml
vendored
44
.github/workflows/prerelease.yml
vendored
|
|
@ -37,27 +37,27 @@ jobs:
|
|||
|
||||
echo ::set-output name=next_tag::$RESULT
|
||||
|
||||
- name: "✏️ Generate full changelog"
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
id: generate-full-changelog
|
||||
uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
with:
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
issues: false
|
||||
issuesWoLabels: false
|
||||
sinceTag: "3.12.0"
|
||||
maxIssues: 100
|
||||
pullRequests: true
|
||||
prWoLabels: false
|
||||
author: false
|
||||
unreleased: true
|
||||
compareLink: true
|
||||
stripGeneratorNotice: true
|
||||
verbose: true
|
||||
unreleasedLabel: ${{ steps.version.outputs.next_tag }}
|
||||
excludeTagsRegex: "CI/.+"
|
||||
releaseBranch: "main"
|
||||
# - name: "✏️ Generate full changelog"
|
||||
# if: steps.version_type.outputs.type != 'skip'
|
||||
# id: generate-full-changelog
|
||||
# uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
# with:
|
||||
# token: ${{ secrets.ADMIN_TOKEN }}
|
||||
# addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
# issues: false
|
||||
# issuesWoLabels: false
|
||||
# sinceTag: "3.12.0"
|
||||
# maxIssues: 100
|
||||
# pullRequests: true
|
||||
# prWoLabels: false
|
||||
# author: false
|
||||
# unreleased: true
|
||||
# compareLink: true
|
||||
# stripGeneratorNotice: true
|
||||
# verbose: true
|
||||
# unreleasedLabel: ${{ steps.version.outputs.next_tag }}
|
||||
# excludeTagsRegex: "CI/.+"
|
||||
# releaseBranch: "main"
|
||||
|
||||
- name: "🖨️ Print changelog to console"
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
|
|
@ -85,7 +85,7 @@ jobs:
|
|||
tags: true
|
||||
unprotect_reviews: true
|
||||
|
||||
- name: 🔨 Merge main back to develop
|
||||
- name: 🔨 Merge main back to develop
|
||||
uses: everlytic/branch-merge@1.1.0
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
with:
|
||||
|
|
|
|||
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Stable Release
|
|||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
types:
|
||||
- prereleased
|
||||
|
||||
jobs:
|
||||
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
|
|
@ -33,27 +33,27 @@ jobs:
|
|||
echo ::set-output name=last_release::$LASTRELEASE
|
||||
echo ::set-output name=release_tag::$RESULT
|
||||
|
||||
- name: "✏️ Generate full changelog"
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
id: generate-full-changelog
|
||||
uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
with:
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
issues: false
|
||||
issuesWoLabels: false
|
||||
sinceTag: "3.12.0"
|
||||
maxIssues: 100
|
||||
pullRequests: true
|
||||
prWoLabels: false
|
||||
author: false
|
||||
unreleased: true
|
||||
compareLink: true
|
||||
stripGeneratorNotice: true
|
||||
verbose: true
|
||||
futureRelease: ${{ steps.version.outputs.release_tag }}
|
||||
excludeTagsRegex: "CI/.+"
|
||||
releaseBranch: "main"
|
||||
# - name: "✏️ Generate full changelog"
|
||||
# if: steps.version.outputs.release_tag != 'skip'
|
||||
# id: generate-full-changelog
|
||||
# uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
# with:
|
||||
# token: ${{ secrets.ADMIN_TOKEN }}
|
||||
# addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
# issues: false
|
||||
# issuesWoLabels: false
|
||||
# sinceTag: "3.12.0"
|
||||
# maxIssues: 100
|
||||
# pullRequests: true
|
||||
# prWoLabels: false
|
||||
# author: false
|
||||
# unreleased: true
|
||||
# compareLink: true
|
||||
# stripGeneratorNotice: true
|
||||
# verbose: true
|
||||
# futureRelease: ${{ steps.version.outputs.release_tag }}
|
||||
# excludeTagsRegex: "CI/.+"
|
||||
# releaseBranch: "main"
|
||||
|
||||
- name: 💾 Commit and Tag
|
||||
id: git_commit
|
||||
|
|
@ -73,8 +73,8 @@ jobs:
|
|||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
branch: main
|
||||
tags: true
|
||||
unprotect_reviews: true
|
||||
|
||||
unprotect_reviews: true
|
||||
|
||||
- name: "✏️ Generate last changelog"
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
id: generate-last-changelog
|
||||
|
|
@ -114,7 +114,7 @@ jobs:
|
|||
with:
|
||||
tag: "${{ steps.version.outputs.current_version }}"
|
||||
|
||||
- name: 🔁 Merge main back to develop
|
||||
- name: 🔁 Merge main back to develop
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
uses: everlytic/branch-merge@1.1.0
|
||||
with:
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -110,3 +110,5 @@ tools/run_eventserver.*
|
|||
|
||||
# Developer tools
|
||||
tools/dev_*
|
||||
|
||||
.github_changelog_generator
|
||||
|
|
|
|||
1763
CHANGELOG.md
1763
CHANGELOG.md
File diff suppressed because it is too large
Load diff
1818
HISTORY.md
1818
HISTORY.md
File diff suppressed because it is too large
Load diff
|
|
@ -260,20 +260,20 @@ class ARenderProducts:
|
|||
|
||||
"""
|
||||
try:
|
||||
file_prefix_attr = IMAGE_PREFIXES[self.renderer]
|
||||
prefix_attr = IMAGE_PREFIXES[self.renderer]
|
||||
except KeyError:
|
||||
raise UnsupportedRendererException(
|
||||
"Unsupported renderer {}".format(self.renderer)
|
||||
)
|
||||
|
||||
file_prefix = self._get_attr(file_prefix_attr)
|
||||
prefix = self._get_attr(prefix_attr)
|
||||
|
||||
if not file_prefix:
|
||||
if not prefix:
|
||||
# Fall back to scene name by default
|
||||
log.debug("Image prefix not set, using <Scene>")
|
||||
file_prefix = "<Scene>"
|
||||
|
||||
return file_prefix
|
||||
return prefix
|
||||
|
||||
def get_render_attribute(self, attribute):
|
||||
"""Get attribute from render options.
|
||||
|
|
@ -730,13 +730,16 @@ class RenderProductsVray(ARenderProducts):
|
|||
"""Get image prefix for V-Ray.
|
||||
|
||||
This overrides :func:`ARenderProducts.get_renderer_prefix()` as
|
||||
we must add `<aov>` token manually.
|
||||
we must add `<aov>` token manually. This is done only for
|
||||
non-multipart outputs, where `<aov>` token doesn't make sense.
|
||||
|
||||
See also:
|
||||
:func:`ARenderProducts.get_renderer_prefix()`
|
||||
|
||||
"""
|
||||
prefix = super(RenderProductsVray, self).get_renderer_prefix()
|
||||
if self.multipart:
|
||||
return prefix
|
||||
aov_separator = self._get_aov_separator()
|
||||
prefix = "{}{}<aov>".format(prefix, aov_separator)
|
||||
return prefix
|
||||
|
|
@ -974,15 +977,18 @@ class RenderProductsRedshift(ARenderProducts):
|
|||
"""Get image prefix for Redshift.
|
||||
|
||||
This overrides :func:`ARenderProducts.get_renderer_prefix()` as
|
||||
we must add `<aov>` token manually.
|
||||
we must add `<aov>` token manually. This is done only for
|
||||
non-multipart outputs, where `<aov>` token doesn't make sense.
|
||||
|
||||
See also:
|
||||
:func:`ARenderProducts.get_renderer_prefix()`
|
||||
|
||||
"""
|
||||
file_prefix = super(RenderProductsRedshift, self).get_renderer_prefix()
|
||||
separator = self.extract_separator(file_prefix)
|
||||
prefix = "{}{}<aov>".format(file_prefix, separator or "_")
|
||||
prefix = super(RenderProductsRedshift, self).get_renderer_prefix()
|
||||
if self.multipart:
|
||||
return prefix
|
||||
separator = self.extract_separator(prefix)
|
||||
prefix = "{}{}<aov>".format(prefix, separator or "_")
|
||||
return prefix
|
||||
|
||||
def get_render_products(self):
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class CreateAnimation(plugin.Creator):
|
|||
family = "animation"
|
||||
icon = "male"
|
||||
write_color_sets = False
|
||||
write_face_sets = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateAnimation, self).__init__(*args, **kwargs)
|
||||
|
|
@ -24,7 +25,7 @@ class CreateAnimation(plugin.Creator):
|
|||
|
||||
# Write vertex colors with the geometry.
|
||||
self.data["writeColorSets"] = self.write_color_sets
|
||||
self.data["writeFaceSets"] = False
|
||||
self.data["writeFaceSets"] = self.write_face_sets
|
||||
|
||||
# Include only renderable visible shapes.
|
||||
# Skips locators and empty transforms
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ class CreateModel(plugin.Creator):
|
|||
family = "model"
|
||||
icon = "cube"
|
||||
defaults = ["Main", "Proxy", "_MD", "_HD", "_LD"]
|
||||
|
||||
write_color_sets = False
|
||||
write_face_sets = False
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateModel, self).__init__(*args, **kwargs)
|
||||
|
||||
# Vertex colors with the geometry
|
||||
self.data["writeColorSets"] = False
|
||||
self.data["writeFaceSets"] = False
|
||||
self.data["writeColorSets"] = self.write_color_sets
|
||||
self.data["writeFaceSets"] = self.write_face_sets
|
||||
|
||||
# Include attributes by attribute name or prefix
|
||||
self.data["attr"] = ""
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class CreatePointCache(plugin.Creator):
|
|||
family = "pointcache"
|
||||
icon = "gears"
|
||||
write_color_sets = False
|
||||
write_face_sets = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreatePointCache, self).__init__(*args, **kwargs)
|
||||
|
|
@ -21,7 +22,8 @@ class CreatePointCache(plugin.Creator):
|
|||
|
||||
# Vertex colors with the geometry.
|
||||
self.data["writeColorSets"] = self.write_color_sets
|
||||
self.data["writeFaceSets"] = False # Vertex colors with the geometry.
|
||||
# Vertex colors with the geometry.
|
||||
self.data["writeFaceSets"] = self.write_face_sets
|
||||
self.data["renderableOnly"] = False # Only renderable visible shapes
|
||||
self.data["visibleOnly"] = False # only nodes that are visible
|
||||
self.data["includeParentHierarchy"] = False # Include parent groups
|
||||
|
|
|
|||
|
|
@ -3,11 +3,33 @@ import re
|
|||
import collections
|
||||
import uuid
|
||||
import json
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
|
||||
import six
|
||||
import clique
|
||||
|
||||
# Global variable which store attribude definitions by type
|
||||
# - default types are registered on import
|
||||
_attr_defs_by_type = {}
|
||||
|
||||
|
||||
def register_attr_def_class(cls):
|
||||
"""Register attribute definition.
|
||||
|
||||
Currently are registered definitions used to deserialize data to objects.
|
||||
|
||||
Attrs:
|
||||
cls (AbtractAttrDef): Non-abstract class to be registered with unique
|
||||
'type' attribute.
|
||||
|
||||
Raises:
|
||||
KeyError: When type was already registered.
|
||||
"""
|
||||
|
||||
if cls.type in _attr_defs_by_type:
|
||||
raise KeyError("Type \"{}\" was already registered".format(cls.type))
|
||||
_attr_defs_by_type[cls.type] = cls
|
||||
|
||||
|
||||
def get_attributes_keys(attribute_definitions):
|
||||
"""Collect keys from list of attribute definitions.
|
||||
|
|
@ -90,6 +112,8 @@ class AbtractAttrDef:
|
|||
next to value input or ahead.
|
||||
"""
|
||||
|
||||
type_attributes = []
|
||||
|
||||
is_value_def = True
|
||||
|
||||
def __init__(
|
||||
|
|
@ -115,6 +139,16 @@ class AbtractAttrDef:
|
|||
return False
|
||||
return self.key == other.key
|
||||
|
||||
@abstractproperty
|
||||
def type(self):
|
||||
"""Attribute definition type also used as identifier of class.
|
||||
|
||||
Returns:
|
||||
str: Type of attribute definition.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def convert_value(self, value):
|
||||
"""Convert value to a valid one.
|
||||
|
|
@ -125,6 +159,35 @@ class AbtractAttrDef:
|
|||
|
||||
pass
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize object to data so it's possible to recreate it.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Serialized object that can be passed to
|
||||
'deserialize' method.
|
||||
"""
|
||||
|
||||
data = {
|
||||
"type": self.type,
|
||||
"key": self.key,
|
||||
"label": self.label,
|
||||
"tooltip": self.tooltip,
|
||||
"default": self.default,
|
||||
"is_label_horizontal": self.is_label_horizontal
|
||||
}
|
||||
for attr in self.type_attributes:
|
||||
data[attr] = getattr(self, attr)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, data):
|
||||
"""Recreate object from data.
|
||||
|
||||
Data can be received using 'serialize' method.
|
||||
"""
|
||||
|
||||
return cls(**data)
|
||||
|
||||
|
||||
# -----------------------------------------
|
||||
# UI attribute definitoins won't hold value
|
||||
|
|
@ -141,10 +204,12 @@ class UIDef(AbtractAttrDef):
|
|||
|
||||
|
||||
class UISeparatorDef(UIDef):
|
||||
pass
|
||||
type = "separator"
|
||||
|
||||
|
||||
class UILabelDef(UIDef):
|
||||
type = "label"
|
||||
|
||||
def __init__(self, label):
|
||||
super(UILabelDef, self).__init__(label=label)
|
||||
|
||||
|
|
@ -160,6 +225,8 @@ class UnknownDef(AbtractAttrDef):
|
|||
have known definition of type.
|
||||
"""
|
||||
|
||||
type = "unknown"
|
||||
|
||||
def __init__(self, key, default=None, **kwargs):
|
||||
kwargs["default"] = default
|
||||
super(UnknownDef, self).__init__(key, **kwargs)
|
||||
|
|
@ -181,6 +248,13 @@ class NumberDef(AbtractAttrDef):
|
|||
default(int, float): Default value for conversion.
|
||||
"""
|
||||
|
||||
type = "number"
|
||||
type_attributes = [
|
||||
"minimum",
|
||||
"maximum",
|
||||
"decimals"
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self, key, minimum=None, maximum=None, decimals=None, default=None,
|
||||
**kwargs
|
||||
|
|
@ -252,6 +326,12 @@ class TextDef(AbtractAttrDef):
|
|||
default(str, None): Default value. Empty string used when not defined.
|
||||
"""
|
||||
|
||||
type = "text"
|
||||
type_attributes = [
|
||||
"multiline",
|
||||
"placeholder",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self, key, multiline=None, regex=None, placeholder=None, default=None,
|
||||
**kwargs
|
||||
|
|
@ -290,6 +370,11 @@ class TextDef(AbtractAttrDef):
|
|||
return value
|
||||
return self.default
|
||||
|
||||
def serialize(self):
|
||||
data = super(TextDef, self).serialize()
|
||||
data["regex"] = self.regex.pattern
|
||||
return data
|
||||
|
||||
|
||||
class EnumDef(AbtractAttrDef):
|
||||
"""Enumeration of single item from items.
|
||||
|
|
@ -301,6 +386,8 @@ class EnumDef(AbtractAttrDef):
|
|||
default: Default value. Must be one key(value) from passed items.
|
||||
"""
|
||||
|
||||
type = "enum"
|
||||
|
||||
def __init__(self, key, items, default=None, **kwargs):
|
||||
if not items:
|
||||
raise ValueError((
|
||||
|
|
@ -335,6 +422,11 @@ class EnumDef(AbtractAttrDef):
|
|||
return value
|
||||
return self.default
|
||||
|
||||
def serialize(self):
|
||||
data = super(TextDef, self).serialize()
|
||||
data["items"] = list(self.items)
|
||||
return data
|
||||
|
||||
|
||||
class BoolDef(AbtractAttrDef):
|
||||
"""Boolean representation.
|
||||
|
|
@ -343,6 +435,8 @@ class BoolDef(AbtractAttrDef):
|
|||
default(bool): Default value. Set to `False` if not defined.
|
||||
"""
|
||||
|
||||
type = "bool"
|
||||
|
||||
def __init__(self, key, default=None, **kwargs):
|
||||
if default is None:
|
||||
default = False
|
||||
|
|
@ -585,6 +679,15 @@ class FileDef(AbtractAttrDef):
|
|||
default(str, List[str]): Default value.
|
||||
"""
|
||||
|
||||
type = "path"
|
||||
type_attributes = [
|
||||
"single_item",
|
||||
"folders",
|
||||
"extensions",
|
||||
"allow_sequences",
|
||||
"extensions_label",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self, key, single_item=True, folders=None, extensions=None,
|
||||
allow_sequences=True, extensions_label=None, default=None, **kwargs
|
||||
|
|
@ -675,3 +778,71 @@ class FileDef(AbtractAttrDef):
|
|||
if self.single_item:
|
||||
return FileDefItem.create_empty_item().to_dict()
|
||||
return []
|
||||
|
||||
|
||||
def serialize_attr_def(attr_def):
|
||||
"""Serialize attribute definition to data.
|
||||
|
||||
Args:
|
||||
attr_def (AbtractAttrDef): Attribute definition to serialize.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Serialized data.
|
||||
"""
|
||||
|
||||
return attr_def.serialize()
|
||||
|
||||
|
||||
def serialize_attr_defs(attr_defs):
|
||||
"""Serialize attribute definitions to data.
|
||||
|
||||
Args:
|
||||
attr_defs (List[AbtractAttrDef]): Attribute definitions to serialize.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Serialized data.
|
||||
"""
|
||||
|
||||
return [
|
||||
serialize_attr_def(attr_def)
|
||||
for attr_def in attr_defs
|
||||
]
|
||||
|
||||
|
||||
def deserialize_attr_def(attr_def_data):
|
||||
"""Deserialize attribute definition from data.
|
||||
|
||||
Args:
|
||||
attr_def (Dict[str, Any]): Attribute definition data to deserialize.
|
||||
"""
|
||||
|
||||
attr_type = attr_def_data.pop("type")
|
||||
cls = _attr_defs_by_type[attr_type]
|
||||
return cls.deserialize(attr_def_data)
|
||||
|
||||
|
||||
def deserialize_attr_defs(attr_defs_data):
|
||||
"""Deserialize attribute definitions.
|
||||
|
||||
Args:
|
||||
List[Dict[str, Any]]: List of attribute definitions.
|
||||
"""
|
||||
|
||||
return [
|
||||
deserialize_attr_def(attr_def_data)
|
||||
for attr_def_data in attr_defs_data
|
||||
]
|
||||
|
||||
|
||||
# Register attribute definitions
|
||||
for _attr_class in (
|
||||
UISeparatorDef,
|
||||
UILabelDef,
|
||||
UnknownDef,
|
||||
NumberDef,
|
||||
TextDef,
|
||||
EnumDef,
|
||||
BoolDef,
|
||||
FileDef
|
||||
):
|
||||
register_attr_def_class(_attr_class)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Events holding data about specific event."""
|
||||
import os
|
||||
import re
|
||||
import copy
|
||||
import inspect
|
||||
import logging
|
||||
import weakref
|
||||
|
|
@ -207,6 +208,12 @@ class Event(object):
|
|||
|
||||
@property
|
||||
def source(self):
|
||||
"""Event's source used for triggering callbacks.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Source string or None. Source is optional.
|
||||
"""
|
||||
|
||||
return self._source
|
||||
|
||||
@property
|
||||
|
|
@ -215,6 +222,12 @@ class Event(object):
|
|||
|
||||
@property
|
||||
def topic(self):
|
||||
"""Event's topic used for triggering callbacks.
|
||||
|
||||
Returns:
|
||||
str: Topic string.
|
||||
"""
|
||||
|
||||
return self._topic
|
||||
|
||||
def emit(self):
|
||||
|
|
@ -227,6 +240,42 @@ class Event(object):
|
|||
)
|
||||
self._event_system.emit_event(self)
|
||||
|
||||
def to_data(self):
|
||||
"""Convert Event object to data.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Event data.
|
||||
"""
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"topic": self.topic,
|
||||
"source": self.source,
|
||||
"data": copy.deepcopy(self.data)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, event_data, event_system=None):
|
||||
"""Create event from data.
|
||||
|
||||
Args:
|
||||
event_data (Dict[str, Any]): Event data with defined keys. Can be
|
||||
created using 'to_data' method.
|
||||
event_system (EventSystem): System to which the event belongs.
|
||||
|
||||
Returns:
|
||||
Event: Event with attributes from passed data.
|
||||
"""
|
||||
|
||||
obj = cls(
|
||||
event_data["topic"],
|
||||
event_data["data"],
|
||||
event_data["source"],
|
||||
event_system
|
||||
)
|
||||
obj._id = event_data["id"]
|
||||
return obj
|
||||
|
||||
|
||||
class EventSystem(object):
|
||||
"""Encapsulate event handling into an object.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ from maya import cmds
|
|||
|
||||
from openpype.pipeline import legacy_io
|
||||
|
||||
from openpype.hosts.maya.api.lib_rendersettings import RenderSettings
|
||||
from openpype.hosts.maya.api.lib import get_attr_in_layer
|
||||
|
||||
from openpype_modules.deadline import abstract_submit_deadline
|
||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||
|
||||
|
|
@ -498,9 +501,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
job_info.AssetDependency += self.scene_path
|
||||
|
||||
# Get layer prefix
|
||||
render_products = self._instance.data["renderProducts"]
|
||||
layer_metadata = render_products.layer_data
|
||||
layer_prefix = layer_metadata.filePrefix
|
||||
renderlayer = self._instance.data["setMembers"]
|
||||
renderer = self._instance.data["renderer"]
|
||||
layer_prefix_attr = RenderSettings.get_image_prefix_attr(renderer)
|
||||
layer_prefix = get_attr_in_layer(layer_prefix_attr, layer=renderlayer)
|
||||
|
||||
plugin_info = copy.deepcopy(self.plugin_info)
|
||||
plugin_info.update({
|
||||
|
|
|
|||
|
|
@ -6,8 +6,17 @@ import collections
|
|||
import numbers
|
||||
|
||||
import six
|
||||
import time
|
||||
|
||||
from openpype.settings.lib import get_anatomy_settings
|
||||
from openpype.settings.lib import (
|
||||
get_project_settings,
|
||||
get_local_settings,
|
||||
)
|
||||
from openpype.settings.constants import (
|
||||
DEFAULT_PROJECT_KEY
|
||||
)
|
||||
|
||||
from openpype.client import get_project
|
||||
from openpype.lib.path_templates import (
|
||||
TemplateUnsolved,
|
||||
TemplateResult,
|
||||
|
|
@ -39,34 +48,23 @@ class RootCombinationError(Exception):
|
|||
super(RootCombinationError, self).__init__(msg)
|
||||
|
||||
|
||||
class Anatomy:
|
||||
class BaseAnatomy(object):
|
||||
"""Anatomy module helps to keep project settings.
|
||||
|
||||
Wraps key project specifications, AnatomyTemplates and Roots.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name to look on overrides.
|
||||
"""
|
||||
|
||||
root_key_regex = re.compile(r"{(root?[^}]+)}")
|
||||
root_name_regex = re.compile(r"root\[([^]]+)\]")
|
||||
|
||||
def __init__(self, project_name=None, site_name=None):
|
||||
if not project_name:
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
|
||||
if not project_name:
|
||||
raise ProjectNotSet((
|
||||
"Implementation bug: Project name is not set. Anatomy requires"
|
||||
" to load data for specific project."
|
||||
))
|
||||
|
||||
def __init__(self, project_doc, local_settings, site_name):
|
||||
project_name = project_doc["name"]
|
||||
self.project_name = project_name
|
||||
|
||||
self._data = self._prepare_anatomy_data(
|
||||
get_anatomy_settings(project_name, site_name)
|
||||
)
|
||||
self._site_name = site_name
|
||||
|
||||
self._data = self._prepare_anatomy_data(
|
||||
project_doc, local_settings, site_name
|
||||
)
|
||||
self._templates_obj = AnatomyTemplates(self)
|
||||
self._roots_obj = Roots(self)
|
||||
|
||||
|
|
@ -87,12 +85,14 @@ class Anatomy:
|
|||
def items(self):
|
||||
return copy.deepcopy(self._data).items()
|
||||
|
||||
@staticmethod
|
||||
def _prepare_anatomy_data(anatomy_data):
|
||||
def _prepare_anatomy_data(self, project_doc, local_settings, site_name):
|
||||
"""Prepare anatomy data for further processing.
|
||||
|
||||
Method added to replace `{task}` with `{task[name]}` in templates.
|
||||
"""
|
||||
project_name = project_doc["name"]
|
||||
anatomy_data = self._project_doc_to_anatomy_data(project_doc)
|
||||
|
||||
templates_data = anatomy_data.get("templates")
|
||||
if templates_data:
|
||||
# Replace `{task}` with `{task[name]}` in templates
|
||||
|
|
@ -103,23 +103,13 @@ class Anatomy:
|
|||
if not isinstance(item, dict):
|
||||
continue
|
||||
|
||||
for key in tuple(item.keys()):
|
||||
value = item[key]
|
||||
if isinstance(value, dict):
|
||||
value_queue.append(value)
|
||||
self._apply_local_settings_on_anatomy_data(anatomy_data,
|
||||
local_settings,
|
||||
project_name,
|
||||
site_name)
|
||||
|
||||
elif isinstance(value, six.string_types):
|
||||
item[key] = value.replace("{task}", "{task[name]}")
|
||||
return anatomy_data
|
||||
|
||||
def reset(self):
|
||||
"""Reset values of cached data in templates and roots objects."""
|
||||
self._data = self._prepare_anatomy_data(
|
||||
get_anatomy_settings(self.project_name, self._site_name)
|
||||
)
|
||||
self.templates_obj.reset()
|
||||
self.roots_obj.reset()
|
||||
|
||||
@property
|
||||
def templates(self):
|
||||
"""Wrap property `templates` of Anatomy's AnatomyTemplates instance."""
|
||||
|
|
@ -338,6 +328,161 @@ class Anatomy:
|
|||
data = self.root_environmets_fill_data(template)
|
||||
return rootless_path.format(**data)
|
||||
|
||||
def _project_doc_to_anatomy_data(self, project_doc):
|
||||
"""Convert project document to anatomy data.
|
||||
|
||||
Probably should fill missing keys and values.
|
||||
"""
|
||||
|
||||
output = copy.deepcopy(project_doc["config"])
|
||||
output["attributes"] = copy.deepcopy(project_doc["data"])
|
||||
|
||||
return output
|
||||
|
||||
def _apply_local_settings_on_anatomy_data(
|
||||
self, anatomy_data, local_settings, project_name, site_name
|
||||
):
|
||||
"""Apply local settings on anatomy data.
|
||||
|
||||
ATM local settings can modify project roots. Project name is required
|
||||
as local settings have data stored data by project's name.
|
||||
|
||||
Local settings override root values in this order:
|
||||
1.) Check if local settings contain overrides for default project and
|
||||
apply it's values on roots if there are any.
|
||||
2.) If passed `project_name` is not None then check project specific
|
||||
overrides in local settings for the project and apply it's value on
|
||||
roots if there are any.
|
||||
|
||||
NOTE: Root values of default project from local settings are always
|
||||
applied if are set.
|
||||
|
||||
Args:
|
||||
anatomy_data (dict): Data for anatomy.
|
||||
local_settings (dict): Data of local settings.
|
||||
project_name (str): Name of project for which anatomy data are.
|
||||
"""
|
||||
if not local_settings:
|
||||
return
|
||||
|
||||
local_project_settings = local_settings.get("projects") or {}
|
||||
|
||||
# Check for roots existence in local settings first
|
||||
roots_project_locals = (
|
||||
local_project_settings
|
||||
.get(project_name, {})
|
||||
)
|
||||
roots_default_locals = (
|
||||
local_project_settings
|
||||
.get(DEFAULT_PROJECT_KEY, {})
|
||||
)
|
||||
|
||||
# Skip rest of processing if roots are not set
|
||||
if not roots_project_locals and not roots_default_locals:
|
||||
return
|
||||
|
||||
# Combine roots from local settings
|
||||
roots_locals = roots_default_locals.get(site_name) or {}
|
||||
roots_locals.update(roots_project_locals.get(site_name) or {})
|
||||
# Skip processing if roots for current active site are not available in
|
||||
# local settings
|
||||
if not roots_locals:
|
||||
return
|
||||
|
||||
current_platform = platform.system().lower()
|
||||
|
||||
root_data = anatomy_data["roots"]
|
||||
for root_name, path in roots_locals.items():
|
||||
if root_name not in root_data:
|
||||
continue
|
||||
anatomy_data["roots"][root_name][current_platform] = (
|
||||
path
|
||||
)
|
||||
|
||||
|
||||
class Anatomy(BaseAnatomy):
|
||||
_project_cache = {}
|
||||
_site_cache = {}
|
||||
|
||||
def __init__(self, project_name=None, site_name=None):
|
||||
if not project_name:
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
|
||||
if not project_name:
|
||||
raise ProjectNotSet((
|
||||
"Implementation bug: Project name is not set. Anatomy requires"
|
||||
" to load data for specific project."
|
||||
))
|
||||
|
||||
project_doc = self.get_project_doc_from_cache(project_name)
|
||||
local_settings = get_local_settings()
|
||||
if not site_name:
|
||||
site_name = self.get_site_name_from_cache(
|
||||
project_name, local_settings
|
||||
)
|
||||
|
||||
super(Anatomy, self).__init__(
|
||||
project_doc,
|
||||
local_settings,
|
||||
site_name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_project_doc_from_cache(cls, project_name):
|
||||
project_cache = cls._project_cache.get(project_name)
|
||||
if project_cache is not None:
|
||||
if time.time() - project_cache["start"] > 10:
|
||||
cls._project_cache.pop(project_name)
|
||||
project_cache = None
|
||||
|
||||
if project_cache is None:
|
||||
project_cache = {
|
||||
"project_doc": get_project(project_name),
|
||||
"start": time.time()
|
||||
}
|
||||
cls._project_cache[project_name] = project_cache
|
||||
|
||||
return copy.deepcopy(
|
||||
cls._project_cache[project_name]["project_doc"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_site_name_from_cache(cls, project_name, local_settings):
|
||||
site_cache = cls._site_cache.get(project_name)
|
||||
if site_cache is not None:
|
||||
if time.time() - site_cache["start"] > 10:
|
||||
cls._site_cache.pop(project_name)
|
||||
site_cache = None
|
||||
|
||||
if site_cache:
|
||||
return site_cache["site_name"]
|
||||
|
||||
local_project_settings = local_settings.get("projects")
|
||||
if not local_project_settings:
|
||||
return
|
||||
|
||||
project_locals = local_project_settings.get(project_name) or {}
|
||||
default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {}
|
||||
active_site = (
|
||||
project_locals.get("active_site")
|
||||
or default_locals.get("active_site")
|
||||
)
|
||||
if not active_site:
|
||||
project_settings = get_project_settings(project_name)
|
||||
active_site = (
|
||||
project_settings
|
||||
["global"]
|
||||
["sync_server"]
|
||||
["config"]
|
||||
["active_site"]
|
||||
)
|
||||
|
||||
cls._site_cache[project_name] = {
|
||||
"site_name": active_site,
|
||||
"start": time.time()
|
||||
}
|
||||
return active_site
|
||||
|
||||
|
||||
class AnatomyTemplateUnsolved(TemplateUnsolved):
|
||||
"""Exception for unsolved template when strict is set to True."""
|
||||
|
|
|
|||
|
|
@ -200,6 +200,16 @@ class AttributeValues:
|
|||
def changes(self):
|
||||
return self.calculate_changes(self._data, self._origin_data)
|
||||
|
||||
def apply_changes(self, changes):
|
||||
for key, item in changes.items():
|
||||
old_value, new_value = item
|
||||
if new_value is None:
|
||||
if key in self:
|
||||
self.pop(key)
|
||||
|
||||
elif self.get(key) != new_value:
|
||||
self[key] = new_value
|
||||
|
||||
|
||||
class CreatorAttributeValues(AttributeValues):
|
||||
"""Creator specific attribute values of an instance.
|
||||
|
|
@ -333,6 +343,21 @@ class PublishAttributes:
|
|||
changes[key] = (value, None)
|
||||
return changes
|
||||
|
||||
def apply_changes(self, changes):
|
||||
for key, item in changes.items():
|
||||
if isinstance(item, dict):
|
||||
self._data[key].apply_changes(item)
|
||||
continue
|
||||
|
||||
old_value, new_value = item
|
||||
if new_value is not None:
|
||||
raise ValueError(
|
||||
"Unexpected type \"{}\" expected None".format(
|
||||
str(type(new_value))
|
||||
)
|
||||
)
|
||||
self.pop(key)
|
||||
|
||||
def set_publish_plugins(self, attr_plugins):
|
||||
"""Set publish plugins attribute definitions."""
|
||||
|
||||
|
|
@ -730,6 +755,97 @@ class CreatedInstance:
|
|||
if member not in self._members:
|
||||
self._members.append(member)
|
||||
|
||||
def serialize_for_remote(self):
|
||||
return {
|
||||
"data": self.data_to_store(),
|
||||
"orig_data": copy.deepcopy(self._orig_data)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def deserialize_on_remote(cls, serialized_data, creator_items):
|
||||
"""Convert instance data to CreatedInstance.
|
||||
|
||||
This is fake instance in remote process e.g. in UI process. The creator
|
||||
is not a full creator and should not be used for calling methods when
|
||||
instance is created from this method (matters on implementation).
|
||||
|
||||
Args:
|
||||
serialized_data (Dict[str, Any]): Serialized data for remote
|
||||
recreating. Should contain 'data' and 'orig_data'.
|
||||
creator_items (Dict[str, Any]): Mapping of creator identifier and
|
||||
objects that behave like a creator for most of attribute
|
||||
access.
|
||||
"""
|
||||
|
||||
instance_data = copy.deepcopy(serialized_data["data"])
|
||||
creator_identifier = instance_data["creator_identifier"]
|
||||
creator_item = creator_items[creator_identifier]
|
||||
|
||||
family = instance_data.get("family", None)
|
||||
if family is None:
|
||||
family = creator_item.family
|
||||
subset_name = instance_data.get("subset", None)
|
||||
|
||||
obj = cls(
|
||||
family, subset_name, instance_data, creator_item, new=False
|
||||
)
|
||||
obj._orig_data = serialized_data["orig_data"]
|
||||
|
||||
return obj
|
||||
|
||||
def remote_changes(self):
|
||||
"""Prepare serializable changes on remote side.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Prepared changes that can be send to client side.
|
||||
"""
|
||||
|
||||
return {
|
||||
"changes": self.changes(),
|
||||
"asset_is_valid": self._asset_is_valid,
|
||||
"task_is_valid": self._task_is_valid,
|
||||
}
|
||||
|
||||
def update_from_remote(self, remote_changes):
|
||||
"""Apply changes from remote side on client side.
|
||||
|
||||
Args:
|
||||
remote_changes (Dict[str, Any]): Changes created on remote side.
|
||||
"""
|
||||
|
||||
self._asset_is_valid = remote_changes["asset_is_valid"]
|
||||
self._task_is_valid = remote_changes["task_is_valid"]
|
||||
|
||||
changes = remote_changes["changes"]
|
||||
creator_attributes = changes.pop("creator_attributes", None) or {}
|
||||
publish_attributes = changes.pop("publish_attributes", None) or {}
|
||||
if changes:
|
||||
self.apply_changes(changes)
|
||||
|
||||
if creator_attributes:
|
||||
self.creator_attributes.apply_changes(creator_attributes)
|
||||
|
||||
if publish_attributes:
|
||||
self.publish_attributes.apply_changes(publish_attributes)
|
||||
|
||||
def apply_changes(self, changes):
|
||||
"""Apply changes created via 'changes'.
|
||||
|
||||
Args:
|
||||
Dict[str, Tuple[Any, Any]]: Instance changes to apply. Same values
|
||||
are kept untouched.
|
||||
"""
|
||||
|
||||
for key, item in changes.items():
|
||||
old_value, new_value = item
|
||||
if new_value is None:
|
||||
if key in self:
|
||||
self.pop(key)
|
||||
else:
|
||||
current_value = self.get(key)
|
||||
if current_value != new_value:
|
||||
self[key] = new_value
|
||||
|
||||
|
||||
class CreateContext:
|
||||
"""Context of instance creation.
|
||||
|
|
@ -817,6 +933,10 @@ class CreateContext:
|
|||
def instances(self):
|
||||
return self._instances_by_id.values()
|
||||
|
||||
@property
|
||||
def instances_by_id(self):
|
||||
return self._instances_by_id
|
||||
|
||||
@property
|
||||
def publish_attributes(self):
|
||||
"""Access to global publish attributes."""
|
||||
|
|
@ -976,7 +1096,8 @@ class CreateContext:
|
|||
and creator_class.host_name != self.host_name
|
||||
):
|
||||
self.log.info((
|
||||
"Creator's host name is not supported for current host {}"
|
||||
"Creator's host name \"{}\""
|
||||
" is not supported for current host \"{}\""
|
||||
).format(creator_class.host_name, self.host_name))
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"CreateAnimation": {
|
||||
"enabled": true,
|
||||
"write_color_sets": false,
|
||||
"write_face_sets": false,
|
||||
"defaults": [
|
||||
"Main"
|
||||
]
|
||||
|
|
@ -133,6 +134,7 @@
|
|||
"CreatePointCache": {
|
||||
"enabled": true,
|
||||
"write_color_sets": false,
|
||||
"write_face_sets": false,
|
||||
"defaults": [
|
||||
"Main"
|
||||
]
|
||||
|
|
@ -187,6 +189,8 @@
|
|||
},
|
||||
"CreateModel": {
|
||||
"enabled": true,
|
||||
"write_color_sets": false,
|
||||
"write_face_sets": false,
|
||||
"defaults": [
|
||||
"Main",
|
||||
"Proxy",
|
||||
|
|
|
|||
|
|
@ -127,6 +127,41 @@
|
|||
"key": "write_color_sets",
|
||||
"label": "Write Color Sets"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "write_face_sets",
|
||||
"label": "Write Face Sets"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Default Subsets",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateModel",
|
||||
"label": "Create Model",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "write_color_sets",
|
||||
"label": "Write Color Sets"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "write_face_sets",
|
||||
"label": "Write Face Sets"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
|
|
@ -152,6 +187,11 @@
|
|||
"key": "write_color_sets",
|
||||
"label": "Write Color Sets"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "write_face_sets",
|
||||
"label": "Write Face Sets"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
|
|
@ -160,7 +200,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_create_plugin",
|
||||
|
|
@ -197,10 +237,6 @@
|
|||
"key": "CreateMayaScene",
|
||||
"label": "Create Maya Scene"
|
||||
},
|
||||
{
|
||||
"key": "CreateModel",
|
||||
"label": "Create Model"
|
||||
},
|
||||
{
|
||||
"key": "CreateRenderSetup",
|
||||
"label": "Create Render Setup"
|
||||
|
|
|
|||
|
|
@ -973,23 +973,22 @@ VariantInputsWidget QToolButton {
|
|||
background: {color:bg};
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
#PublishInfoFrame[state="-1"] {
|
||||
background: rgb(194, 226, 236);
|
||||
}
|
||||
|
||||
#PublishInfoFrame[state="0"] {
|
||||
background: {color:publisher:crash};
|
||||
background: {color:publisher:success};
|
||||
}
|
||||
|
||||
#PublishInfoFrame[state="1"] {
|
||||
background: {color:publisher:success};
|
||||
background: {color:publisher:crash};
|
||||
}
|
||||
|
||||
#PublishInfoFrame[state="2"] {
|
||||
background: {color:publisher:warning};
|
||||
}
|
||||
|
||||
#PublishInfoFrame[state="3"], #PublishInfoFrame[state="4"] {
|
||||
background: rgb(194, 226, 236);
|
||||
}
|
||||
|
||||
#PublishInfoFrame QLabel {
|
||||
color: black;
|
||||
font-style: bold;
|
||||
|
|
@ -1086,7 +1085,7 @@ ValidationArtistMessage QLabel {
|
|||
border-color: {color:publisher:error};
|
||||
}
|
||||
|
||||
#PublishProgressBar[state="0"]::chunk {
|
||||
#PublishProgressBar[state="1"]::chunk, #PublishProgressBar[state="4"]::chunk {
|
||||
background: {color:bg-buttons};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
from .app import show
|
||||
from .window import PublisherWindow
|
||||
|
||||
__all__ = (
|
||||
"show",
|
||||
"PublisherWindow"
|
||||
)
|
||||
File diff suppressed because it is too large
Load diff
405
openpype/tools/publisher/control_qt.py
Normal file
405
openpype/tools/publisher/control_qt.py
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
import collections
|
||||
from abc import abstractmethod, abstractproperty
|
||||
|
||||
from Qt import QtCore
|
||||
|
||||
from openpype.lib.events import Event
|
||||
from openpype.pipeline.create import CreatedInstance
|
||||
|
||||
from .control import (
|
||||
MainThreadItem,
|
||||
PublisherController,
|
||||
BasePublisherController,
|
||||
)
|
||||
|
||||
|
||||
class MainThreadProcess(QtCore.QObject):
|
||||
"""Qt based main thread process executor.
|
||||
|
||||
Has timer which controls each 50ms if there is new item to process.
|
||||
|
||||
This approach gives ability to update UI meanwhile plugin is in progress.
|
||||
"""
|
||||
|
||||
count_timeout = 2
|
||||
|
||||
def __init__(self):
|
||||
super(MainThreadProcess, self).__init__()
|
||||
self._items_to_process = collections.deque()
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.setInterval(0)
|
||||
|
||||
timer.timeout.connect(self._execute)
|
||||
|
||||
self._timer = timer
|
||||
self._switch_counter = self.count_timeout
|
||||
|
||||
def process(self, func, *args, **kwargs):
|
||||
item = MainThreadItem(func, *args, **kwargs)
|
||||
self.add_item(item)
|
||||
|
||||
def add_item(self, item):
|
||||
self._items_to_process.append(item)
|
||||
|
||||
def _execute(self):
|
||||
if not self._items_to_process:
|
||||
return
|
||||
|
||||
if self._switch_counter > 0:
|
||||
self._switch_counter -= 1
|
||||
return
|
||||
|
||||
self._switch_counter = self.count_timeout
|
||||
|
||||
item = self._items_to_process.popleft()
|
||||
item.process()
|
||||
|
||||
def start(self):
|
||||
if not self._timer.isActive():
|
||||
self._timer.start()
|
||||
|
||||
def stop(self):
|
||||
if self._timer.isActive():
|
||||
self._timer.stop()
|
||||
|
||||
def clear(self):
|
||||
if self._timer.isActive():
|
||||
self._timer.stop()
|
||||
self._items_to_process = collections.deque()
|
||||
|
||||
|
||||
class QtPublisherController(PublisherController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._main_thread_processor = MainThreadProcess()
|
||||
|
||||
super(QtPublisherController, self).__init__(*args, **kwargs)
|
||||
|
||||
self.event_system.add_callback(
|
||||
"publish.process.started", self._qt_on_publish_start
|
||||
)
|
||||
self.event_system.add_callback(
|
||||
"publish.process.stopped", self._qt_on_publish_stop
|
||||
)
|
||||
|
||||
def _reset_publish(self):
|
||||
super(QtPublisherController, self)._reset_publish()
|
||||
self._main_thread_processor.clear()
|
||||
|
||||
def _process_main_thread_item(self, item):
|
||||
self._main_thread_processor.add_item(item)
|
||||
|
||||
def _qt_on_publish_start(self):
|
||||
self._main_thread_processor.start()
|
||||
|
||||
def _qt_on_publish_stop(self):
|
||||
self._main_thread_processor.stop()
|
||||
|
||||
|
||||
class QtRemotePublishController(BasePublisherController):
|
||||
"""Abstract Remote controller for Qt UI.
|
||||
|
||||
This controller should be used in process where UI is running and should
|
||||
listen and ask for data on a client side.
|
||||
|
||||
All objects that are used during UI processing should be able to convert
|
||||
on client side to json serializable data and then recreated here. Keep in
|
||||
mind that all changes made here should be send back to client controller
|
||||
before critical actions.
|
||||
|
||||
ATM Was not tested and will require some changes. All code written here is
|
||||
based on theoretical idea how it could work.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._created_instances = {}
|
||||
|
||||
@abstractmethod
|
||||
def _get_serialized_instances(self):
|
||||
"""Receive serialized instances from client process.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Serialized instances.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def _on_create_instance_change(self):
|
||||
serialized_instances = self._get_serialized_instances()
|
||||
|
||||
created_instances = {}
|
||||
for serialized_data in serialized_instances:
|
||||
item = CreatedInstance.deserialize_on_remote(
|
||||
serialized_data,
|
||||
self._creator_items
|
||||
)
|
||||
created_instances[item.id] = item
|
||||
|
||||
self._created_instances = created_instances
|
||||
self._emit_event("instances.refresh.finished")
|
||||
|
||||
def remote_events_handler(self, event_data):
|
||||
event = Event.from_data(event_data)
|
||||
|
||||
# Topics that cause "replication" of controller changes
|
||||
if event.topic == "publish.max_progress.changed":
|
||||
self.publish_max_progress = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.progress.changed":
|
||||
self.publish_progress = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.has_validated.changed":
|
||||
self.publish_has_validated = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.is_running.changed":
|
||||
self.publish_is_running = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.publish_error.changed":
|
||||
self.publish_error_msg = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.has_crashed.changed":
|
||||
self.publish_has_crashed = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.has_validation_errors.changed":
|
||||
self.publish_has_validation_errors = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.finished.changed":
|
||||
self.publish_has_finished = event["value"]
|
||||
return
|
||||
|
||||
if event.topic == "publish.host_is_valid.changed":
|
||||
self.host_is_valid = event["value"]
|
||||
return
|
||||
|
||||
# Topics that can be just passed by because are not affecting
|
||||
# controller itself
|
||||
# - "show.card.message"
|
||||
# - "show.detailed.help"
|
||||
# - "publish.reset.finished"
|
||||
# - "instances.refresh.finished"
|
||||
# - "plugins.refresh.finished"
|
||||
# - "controller.reset.finished"
|
||||
# - "publish.process.started"
|
||||
# - "publish.process.stopped"
|
||||
# - "publish.process.plugin.changed"
|
||||
# - "publish.process.instance.changed"
|
||||
self.event_system.emit_event(event)
|
||||
|
||||
@abstractproperty
|
||||
def project_name(self):
|
||||
"""Current context project name from client.
|
||||
|
||||
Returns:
|
||||
str: Name of project.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def current_asset_name(self):
|
||||
"""Current context asset name from client.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Name of asset.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def current_task_name(self):
|
||||
"""Current context task name from client.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Name of task.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@property
|
||||
def instances(self):
|
||||
"""Collected/created instances.
|
||||
|
||||
Returns:
|
||||
List[CreatedInstance]: List of created instances.
|
||||
"""
|
||||
|
||||
return self._created_instances
|
||||
|
||||
def get_context_title(self):
|
||||
"""Get context title for artist shown at the top of main window.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Context title for window or None. In case of None
|
||||
a warning is displayed (not nice for artists).
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def get_asset_docs(self):
|
||||
pass
|
||||
|
||||
def get_asset_hierarchy(self):
|
||||
pass
|
||||
|
||||
def get_task_names_by_asset_names(self, asset_names):
|
||||
pass
|
||||
|
||||
def get_existing_subset_names(self, asset_name):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_subset_name(
|
||||
self,
|
||||
creator_identifier,
|
||||
variant,
|
||||
task_name,
|
||||
asset_name,
|
||||
instance_id=None
|
||||
):
|
||||
"""Get subset name based on passed data.
|
||||
|
||||
Args:
|
||||
creator_identifier (str): Identifier of creator which should be
|
||||
responsible for subset name creation.
|
||||
variant (str): Variant value from user's input.
|
||||
task_name (str): Name of task for which is instance created.
|
||||
asset_name (str): Name of asset for which is instance created.
|
||||
instance_id (Union[str, None]): Existing instance id when subset
|
||||
name is updated.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create(
|
||||
self, creator_identifier, subset_name, instance_data, options
|
||||
):
|
||||
"""Trigger creation by creator identifier.
|
||||
|
||||
Should also trigger refresh of instanes.
|
||||
|
||||
Args:
|
||||
creator_identifier (str): Identifier of Creator plugin.
|
||||
subset_name (str): Calculated subset name.
|
||||
instance_data (Dict[str, Any]): Base instance data with variant,
|
||||
asset name and task name.
|
||||
options (Dict[str, Any]): Data from pre-create attributes.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def _get_instance_changes_for_client(self):
|
||||
"""Preimplemented method to receive instance changes for client."""
|
||||
|
||||
created_instance_changes = {}
|
||||
for instance_id, instance in self._created_instances.items():
|
||||
created_instance_changes[instance_id] = (
|
||||
instance.remote_changes()
|
||||
)
|
||||
return created_instance_changes
|
||||
|
||||
@abstractmethod
|
||||
def _send_instance_changes_to_client(self):
|
||||
instance_changes = self._get_instance_changes_for_client()
|
||||
# Implement to send 'instance_changes' value to client
|
||||
|
||||
@abstractmethod
|
||||
def save_changes(self):
|
||||
"""Save changes happened during creation."""
|
||||
|
||||
self._send_instance_changes_to_client()
|
||||
|
||||
@abstractmethod
|
||||
def remove_instances(self, instance_ids):
|
||||
"""Remove list of instances from create context."""
|
||||
# TODO add Args:
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_publish_report(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_validation_errors(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
"""Reset whole controller.
|
||||
|
||||
This should reset create context, publish context and all variables
|
||||
that are related to it.
|
||||
"""
|
||||
|
||||
self._send_instance_changes_to_client()
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def publish(self):
|
||||
"""Trigger publishing without any order limitations."""
|
||||
|
||||
self._send_instance_changes_to_client()
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def validate(self):
|
||||
"""Trigger publishing which will stop after validation order."""
|
||||
|
||||
self._send_instance_changes_to_client()
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop_publish(self):
|
||||
"""Stop publishing can be also used to pause publishing.
|
||||
|
||||
Pause of publishing is possible only if all plugins successfully
|
||||
finished.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run_action(self, plugin_id, action_id):
|
||||
"""Trigger pyblish action on a plugin.
|
||||
|
||||
Args:
|
||||
plugin_id (str): Id of publish plugin.
|
||||
action_id (str): Id of publish action.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_comment(self, comment):
|
||||
"""Set comment on pyblish context.
|
||||
|
||||
Set "comment" key on current pyblish.api.Context data.
|
||||
|
||||
Args:
|
||||
comment (str): Artist's comment.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def emit_card_message(self, message):
|
||||
"""Emit a card message which can have a lifetime.
|
||||
|
||||
This is for UI purposes. Method can be extended to more arguments
|
||||
in future e.g. different message timeout or type (color).
|
||||
|
||||
Args:
|
||||
message (str): Message that will be showed.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -139,6 +139,9 @@ class PluginLoadReportWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class ZoomPlainText(QtWidgets.QPlainTextEdit):
|
||||
min_point_size = 1.0
|
||||
max_point_size = 200.0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ZoomPlainText, self).__init__(*args, **kwargs)
|
||||
|
||||
|
|
@ -148,12 +151,12 @@ class ZoomPlainText(QtWidgets.QPlainTextEdit):
|
|||
anim_timer.timeout.connect(self._scaling_callback)
|
||||
|
||||
self._anim_timer = anim_timer
|
||||
self._zoom_enabled = False
|
||||
self._scheduled_scalings = 0
|
||||
self._point_size = None
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if not self._zoom_enabled:
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
if modifiers != QtCore.Qt.ControlModifier:
|
||||
super(ZoomPlainText, self).wheelEvent(event)
|
||||
return
|
||||
|
||||
|
|
@ -172,33 +175,40 @@ class ZoomPlainText(QtWidgets.QPlainTextEdit):
|
|||
|
||||
factor = 1.0 + (self._scheduled_scalings / 300)
|
||||
font = self.font()
|
||||
|
||||
if self._point_size is None:
|
||||
self._point_size = font.pointSizeF()
|
||||
point_size = font.pointSizeF()
|
||||
else:
|
||||
point_size = self._point_size
|
||||
|
||||
self._point_size *= factor
|
||||
if self._point_size < 1:
|
||||
self._point_size = 1.0
|
||||
point_size *= factor
|
||||
min_hit = False
|
||||
max_hit = False
|
||||
if point_size < self.min_point_size:
|
||||
point_size = self.min_point_size
|
||||
min_hit = True
|
||||
elif point_size > self.max_point_size:
|
||||
point_size = self.max_point_size
|
||||
max_hit = True
|
||||
|
||||
font.setPointSizeF(self._point_size)
|
||||
self._point_size = point_size
|
||||
|
||||
font.setPointSizeF(point_size)
|
||||
# Using 'self.setFont(font)' would not be propagated when stylesheets
|
||||
# are applied on this widget
|
||||
self.setStyleSheet("font-size: {}pt".format(font.pointSize()))
|
||||
|
||||
if self._scheduled_scalings > 0:
|
||||
if (
|
||||
(max_hit and self._scheduled_scalings > 0)
|
||||
or (min_hit and self._scheduled_scalings < 0)
|
||||
):
|
||||
self._scheduled_scalings = 0
|
||||
|
||||
elif self._scheduled_scalings > 0:
|
||||
self._scheduled_scalings -= 1
|
||||
else:
|
||||
self._scheduled_scalings += 1
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Control:
|
||||
self._zoom_enabled = True
|
||||
super(ZoomPlainText, self).keyPressEvent(event)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Control:
|
||||
self._zoom_enabled = False
|
||||
super(ZoomPlainText, self).keyReleaseEvent(event)
|
||||
|
||||
|
||||
class DetailsWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import collections
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from openpype.tools.utils import (
|
||||
PlaceholderLineEdit,
|
||||
RecursiveSortFilterProxyModel,
|
||||
|
|
@ -163,6 +164,16 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel):
|
|||
return item_name in self._items_by_name
|
||||
|
||||
|
||||
class AssetDialogView(QtWidgets.QTreeView):
|
||||
double_clicked = QtCore.Signal(QtCore.QModelIndex)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
index = self.indexAt(event.pos())
|
||||
if index.isValid():
|
||||
self.double_clicked.emit(index)
|
||||
event.accept()
|
||||
|
||||
|
||||
class AssetsDialog(QtWidgets.QDialog):
|
||||
"""Dialog to select asset for a context of instance."""
|
||||
|
||||
|
|
@ -178,7 +189,7 @@ class AssetsDialog(QtWidgets.QDialog):
|
|||
filter_input = PlaceholderLineEdit(self)
|
||||
filter_input.setPlaceholderText("Filter assets..")
|
||||
|
||||
asset_view = QtWidgets.QTreeView(self)
|
||||
asset_view = AssetDialogView(self)
|
||||
asset_view.setModel(proxy_model)
|
||||
asset_view.setHeaderHidden(True)
|
||||
asset_view.setFrameShape(QtWidgets.QFrame.NoFrame)
|
||||
|
|
@ -200,6 +211,7 @@ class AssetsDialog(QtWidgets.QDialog):
|
|||
layout.addWidget(asset_view, 1)
|
||||
layout.addLayout(btns_layout, 0)
|
||||
|
||||
asset_view.double_clicked.connect(self._on_ok_clicked)
|
||||
filter_input.textChanged.connect(self._on_filter_change)
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
|
@ -274,7 +286,7 @@ class AssetsDialog(QtWidgets.QDialog):
|
|||
index = self._asset_view.currentIndex()
|
||||
asset_name = None
|
||||
if index.isValid():
|
||||
asset_name = index.data(QtCore.Qt.DisplayRole)
|
||||
asset_name = index.data(ASSET_NAME_ROLE)
|
||||
self._selected_asset = asset_name
|
||||
self.done(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -41,9 +41,26 @@ from ..constants import (
|
|||
)
|
||||
|
||||
|
||||
class SelectionType:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, SelectionType):
|
||||
other = other.name
|
||||
return self.name == other
|
||||
|
||||
|
||||
class SelectionTypes:
|
||||
clear = SelectionType("clear")
|
||||
extend = SelectionType("extend")
|
||||
extend_to = SelectionType("extend_to")
|
||||
|
||||
|
||||
class GroupWidget(QtWidgets.QWidget):
|
||||
"""Widget wrapping instances under group."""
|
||||
selected = QtCore.Signal(str, str)
|
||||
|
||||
selected = QtCore.Signal(str, str, SelectionType)
|
||||
active_changed = QtCore.Signal()
|
||||
removed_selected = QtCore.Signal()
|
||||
|
||||
|
|
@ -72,21 +89,73 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
self._group_icons = group_icons
|
||||
|
||||
self._widgets_by_id = {}
|
||||
self._ordered_instance_ids = []
|
||||
|
||||
self._label_widget = label_widget
|
||||
self._content_layout = layout
|
||||
|
||||
@property
|
||||
def group_name(self):
|
||||
"""Group which widget represent.
|
||||
|
||||
Returns:
|
||||
str: Name of group.
|
||||
"""
|
||||
|
||||
return self._group
|
||||
|
||||
def get_selected_instance_ids(self):
|
||||
"""Selected instance ids.
|
||||
|
||||
Returns:
|
||||
Set[str]: Instance ids that are selected.
|
||||
"""
|
||||
|
||||
return {
|
||||
instance_id
|
||||
for instance_id, widget in self._widgets_by_id.items()
|
||||
if widget.is_selected
|
||||
}
|
||||
|
||||
def get_selected_widgets(self):
|
||||
"""Access to widgets marked as selected.
|
||||
|
||||
Returns:
|
||||
List[InstanceCardWidget]: Instance widgets that are selected.
|
||||
"""
|
||||
|
||||
return [
|
||||
widget
|
||||
for instance_id, widget in self._widgets_by_id.items()
|
||||
if widget.is_selected
|
||||
]
|
||||
|
||||
def get_ordered_widgets(self):
|
||||
"""Get instance ids in order as are shown in ui.
|
||||
|
||||
Returns:
|
||||
List[str]: Instance ids.
|
||||
"""
|
||||
|
||||
return [
|
||||
self._widgets_by_id[instance_id]
|
||||
for instance_id in self._ordered_instance_ids
|
||||
]
|
||||
|
||||
def get_widget_by_instance_id(self, instance_id):
|
||||
"""Get instance widget by it's id."""
|
||||
|
||||
return self._widgets_by_id.get(instance_id)
|
||||
|
||||
def update_instance_values(self):
|
||||
"""Trigger update on instance widgets."""
|
||||
|
||||
for widget in self._widgets_by_id.values():
|
||||
widget.update_instance_values()
|
||||
|
||||
def confirm_remove_instance_id(self, instance_id):
|
||||
"""Delete widget by instance id."""
|
||||
|
||||
widget = self._widgets_by_id.pop(instance_id)
|
||||
widget.setVisible(False)
|
||||
self._content_layout.removeWidget(widget)
|
||||
|
|
@ -123,6 +192,7 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
|
||||
# Sort instances by subset name
|
||||
sorted_subset_names = list(sorted(instances_by_subset_name.keys()))
|
||||
|
||||
# Add new instances to widget
|
||||
widget_idx = 1
|
||||
for subset_names in sorted_subset_names:
|
||||
|
|
@ -135,17 +205,30 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
widget = InstanceCardWidget(
|
||||
instance, group_icon, self
|
||||
)
|
||||
widget.selected.connect(self.selected)
|
||||
widget.selected.connect(self._on_widget_selection)
|
||||
widget.active_changed.connect(self.active_changed)
|
||||
self._widgets_by_id[instance.id] = widget
|
||||
self._content_layout.insertWidget(widget_idx, widget)
|
||||
widget_idx += 1
|
||||
|
||||
ordered_instance_ids = []
|
||||
for idx in range(self._content_layout.count()):
|
||||
if idx > 0:
|
||||
item = self._content_layout.itemAt(idx)
|
||||
widget = item.widget()
|
||||
if widget is not None:
|
||||
ordered_instance_ids.append(widget.id)
|
||||
|
||||
self._ordered_instance_ids = ordered_instance_ids
|
||||
|
||||
def _on_widget_selection(self, instance_id, group_id, selection_type):
|
||||
self.selected.emit(instance_id, group_id, selection_type)
|
||||
|
||||
|
||||
class CardWidget(BaseClickableFrame):
|
||||
"""Clickable card used as bigger button."""
|
||||
|
||||
selected = QtCore.Signal(str, str)
|
||||
selected = QtCore.Signal(str, str, SelectionType)
|
||||
# Group identifier of card
|
||||
# - this must be set because if send when mouse is released with card id
|
||||
_group_identifier = None
|
||||
|
|
@ -157,6 +240,12 @@ class CardWidget(BaseClickableFrame):
|
|||
self._selected = False
|
||||
self._id = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Id of card."""
|
||||
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def is_selected(self):
|
||||
"""Is card selected."""
|
||||
|
|
@ -173,7 +262,16 @@ class CardWidget(BaseClickableFrame):
|
|||
|
||||
def _mouse_release_callback(self):
|
||||
"""Trigger selected signal."""
|
||||
self.selected.emit(self._id, self._group_identifier)
|
||||
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
selection_type = SelectionTypes.clear
|
||||
if bool(modifiers & QtCore.Qt.ShiftModifier):
|
||||
selection_type = SelectionTypes.extend_to
|
||||
|
||||
elif bool(modifiers & QtCore.Qt.ControlModifier):
|
||||
selection_type = SelectionTypes.extend
|
||||
|
||||
self.selected.emit(self._id, self._group_identifier, selection_type)
|
||||
|
||||
|
||||
class ContextCardWidget(CardWidget):
|
||||
|
|
@ -351,10 +449,11 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
Wrapper of all widgets in card view.
|
||||
"""
|
||||
|
||||
def __init__(self, controller, parent):
|
||||
super(InstanceCardView, self).__init__(parent)
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
|
||||
scroll_area = QtWidgets.QScrollArea(self)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
|
|
@ -381,11 +480,12 @@ class InstanceCardView(AbstractInstanceView):
|
|||
self._content_layout = content_layout
|
||||
self._content_widget = content_widget
|
||||
|
||||
self._widgets_by_group = {}
|
||||
self._context_widget = None
|
||||
self._widgets_by_group = {}
|
||||
self._ordered_groups = []
|
||||
|
||||
self._selected_group = None
|
||||
self._selected_instance_id = None
|
||||
self._explicitly_selected_instance_ids = []
|
||||
self._explicitly_selected_groups = []
|
||||
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Minimum,
|
||||
|
|
@ -405,21 +505,30 @@ class InstanceCardView(AbstractInstanceView):
|
|||
result.setWidth(width)
|
||||
return result
|
||||
|
||||
def _get_selected_widget(self):
|
||||
if self._selected_instance_id == CONTEXT_ID:
|
||||
return self._context_widget
|
||||
def _get_selected_widgets(self):
|
||||
output = []
|
||||
if (
|
||||
self._context_widget is not None
|
||||
and self._context_widget.is_selected
|
||||
):
|
||||
output.append(self._context_widget)
|
||||
|
||||
group_widget = self._widgets_by_group.get(
|
||||
self._selected_group
|
||||
)
|
||||
if group_widget is not None:
|
||||
widget = group_widget.get_widget_by_instance_id(
|
||||
self._selected_instance_id
|
||||
)
|
||||
if widget is not None:
|
||||
return widget
|
||||
for group_widget in self._widgets_by_group.values():
|
||||
for widget in group_widget.get_selected_widgets():
|
||||
output.append(widget)
|
||||
return output
|
||||
|
||||
return None
|
||||
def _get_selected_instance_ids(self):
|
||||
output = []
|
||||
if (
|
||||
self._context_widget is not None
|
||||
and self._context_widget.is_selected
|
||||
):
|
||||
output.append(CONTEXT_ID)
|
||||
|
||||
for group_widget in self._widgets_by_group.values():
|
||||
output.extend(group_widget.get_selected_instance_ids())
|
||||
return output
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh instances in view based on CreatedContext."""
|
||||
|
|
@ -435,12 +544,10 @@ class InstanceCardView(AbstractInstanceView):
|
|||
self.selection_changed.emit()
|
||||
self._content_layout.insertWidget(0, widget)
|
||||
|
||||
self.select_item(CONTEXT_ID, None)
|
||||
|
||||
# Prepare instances by group and identifiers by group
|
||||
instances_by_group = collections.defaultdict(list)
|
||||
identifiers_by_group = collections.defaultdict(set)
|
||||
for instance in self.controller.instances:
|
||||
for instance in self._controller.instances.values():
|
||||
group_name = instance.group_label
|
||||
instances_by_group[group_name].append(instance)
|
||||
identifiers_by_group[group_name].add(
|
||||
|
|
@ -452,15 +559,17 @@ class InstanceCardView(AbstractInstanceView):
|
|||
if group_name in instances_by_group:
|
||||
continue
|
||||
|
||||
if group_name == self._selected_group:
|
||||
self._on_remove_selected()
|
||||
widget = self._widgets_by_group.pop(group_name)
|
||||
widget.setVisible(False)
|
||||
self._content_layout.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
|
||||
if group_name in self._explicitly_selected_groups:
|
||||
self._explicitly_selected_groups.remove(group_name)
|
||||
|
||||
# Sort groups
|
||||
sorted_group_names = list(sorted(instances_by_group.keys()))
|
||||
|
||||
# Keep track of widget indexes
|
||||
# - we start with 1 because Context item as at the top
|
||||
widget_idx = 1
|
||||
|
|
@ -469,7 +578,7 @@ class InstanceCardView(AbstractInstanceView):
|
|||
group_widget = self._widgets_by_group[group_name]
|
||||
else:
|
||||
group_icons = {
|
||||
idenfier: self.controller.get_icon_for_family(idenfier)
|
||||
idenfier: self._controller.get_creator_icon(idenfier)
|
||||
for idenfier in identifiers_by_group[group_name]
|
||||
}
|
||||
|
||||
|
|
@ -478,9 +587,6 @@ class InstanceCardView(AbstractInstanceView):
|
|||
)
|
||||
group_widget.active_changed.connect(self._on_active_changed)
|
||||
group_widget.selected.connect(self._on_widget_selection)
|
||||
group_widget.removed_selected.connect(
|
||||
self._on_remove_selected
|
||||
)
|
||||
self._content_layout.insertWidget(widget_idx, group_widget)
|
||||
self._widgets_by_group[group_name] = group_widget
|
||||
|
||||
|
|
@ -489,6 +595,16 @@ class InstanceCardView(AbstractInstanceView):
|
|||
instances_by_group[group_name]
|
||||
)
|
||||
|
||||
ordered_group_names = [""]
|
||||
for idx in range(self._content_layout.count()):
|
||||
if idx > 0:
|
||||
item = self._content_layout.itemAt(idx)
|
||||
group_widget = item.widget()
|
||||
if group_widget is not None:
|
||||
ordered_group_names.append(group_widget.group_name)
|
||||
|
||||
self._ordered_groups = ordered_group_names
|
||||
|
||||
def refresh_instance_states(self):
|
||||
"""Trigger update of instances on group widgets."""
|
||||
for widget in self._widgets_by_group.values():
|
||||
|
|
@ -497,10 +613,7 @@ class InstanceCardView(AbstractInstanceView):
|
|||
def _on_active_changed(self):
|
||||
self.active_changed.emit()
|
||||
|
||||
def _on_widget_selection(self, instance_id, group_name):
|
||||
self.select_item(instance_id, group_name)
|
||||
|
||||
def select_item(self, instance_id, group_name):
|
||||
def _on_widget_selection(self, instance_id, group_name, selection_type):
|
||||
"""Select specific item by instance id.
|
||||
|
||||
Pass `CONTEXT_ID` as instance id and empty string as group to select
|
||||
|
|
@ -512,34 +625,318 @@ class InstanceCardView(AbstractInstanceView):
|
|||
group_widget = self._widgets_by_group[group_name]
|
||||
new_widget = group_widget.get_widget_by_instance_id(instance_id)
|
||||
|
||||
selected_widget = self._get_selected_widget()
|
||||
if new_widget is selected_widget:
|
||||
return
|
||||
|
||||
if selected_widget is not None:
|
||||
selected_widget.set_selected(False)
|
||||
|
||||
self._selected_instance_id = instance_id
|
||||
self._selected_group = group_name
|
||||
if new_widget is not None:
|
||||
new_widget.set_selected(True)
|
||||
if selection_type is SelectionTypes.clear:
|
||||
self._select_item_clear(instance_id, group_name, new_widget)
|
||||
elif selection_type is SelectionTypes.extend:
|
||||
self._select_item_extend(instance_id, group_name, new_widget)
|
||||
elif selection_type is SelectionTypes.extend_to:
|
||||
self._select_item_extend_to(instance_id, group_name, new_widget)
|
||||
|
||||
self.selection_changed.emit()
|
||||
|
||||
def _on_remove_selected(self):
|
||||
selected_widget = self._get_selected_widget()
|
||||
if selected_widget is None:
|
||||
self._on_widget_selection(CONTEXT_ID, None)
|
||||
def _select_item_clear(self, instance_id, group_name, new_widget):
|
||||
"""Select specific item by instance id and clear previous selection.
|
||||
|
||||
Pass `CONTEXT_ID` as instance id and empty string as group to select
|
||||
global context item.
|
||||
"""
|
||||
|
||||
selected_widgets = self._get_selected_widgets()
|
||||
for widget in selected_widgets:
|
||||
if widget.id != instance_id:
|
||||
widget.set_selected(False)
|
||||
|
||||
self._explicitly_selected_groups = [group_name]
|
||||
self._explicitly_selected_instance_ids = [instance_id]
|
||||
|
||||
if new_widget is not None:
|
||||
new_widget.set_selected(True)
|
||||
|
||||
def _select_item_extend(self, instance_id, group_name, new_widget):
|
||||
"""Add/Remove single item to/from current selection.
|
||||
|
||||
If item is already selected the selection is removed.
|
||||
"""
|
||||
|
||||
self._explicitly_selected_instance_ids = (
|
||||
self._get_selected_instance_ids()
|
||||
)
|
||||
if new_widget.is_selected:
|
||||
self._explicitly_selected_instance_ids.remove(instance_id)
|
||||
new_widget.set_selected(False)
|
||||
remove_group = False
|
||||
if instance_id == CONTEXT_ID:
|
||||
remove_group = True
|
||||
else:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
if not group_widget.get_selected_widgets():
|
||||
remove_group = True
|
||||
|
||||
if remove_group:
|
||||
self._explicitly_selected_groups.remove(group_name)
|
||||
return
|
||||
|
||||
self._explicitly_selected_instance_ids.append(instance_id)
|
||||
if group_name in self._explicitly_selected_groups:
|
||||
self._explicitly_selected_groups.remove(group_name)
|
||||
self._explicitly_selected_groups.append(group_name)
|
||||
new_widget.set_selected(True)
|
||||
|
||||
def _select_item_extend_to(self, instance_id, group_name, new_widget):
|
||||
"""Extend selected items to specific instance id.
|
||||
|
||||
This method is handling Shift+click selection of widgets. Selection
|
||||
is not stored to explicit selection items. That's because user can
|
||||
shift select again and it should use last explicit selected item as
|
||||
source item for selection.
|
||||
|
||||
Items selected via this function can get to explicit selection only if
|
||||
selection is extended by one specific item ('_select_item_extend').
|
||||
From that moment the selection is locked to new last explicit selected
|
||||
item.
|
||||
|
||||
It's required to traverse through group widgets in their UI order and
|
||||
through their instances in UI order. All explicitly selected items
|
||||
must not change their selection state during this function. Passed
|
||||
instance id can be above or under last selected item so a start item
|
||||
and end item must be found to be able know which direction is selection
|
||||
happening.
|
||||
"""
|
||||
|
||||
# Start group name (in '_ordered_groups')
|
||||
start_group = None
|
||||
# End group name (in '_ordered_groups')
|
||||
end_group = None
|
||||
# Instance id of first selected item
|
||||
start_instance_id = None
|
||||
# Instance id of last selected item
|
||||
end_instance_id = None
|
||||
|
||||
# Get previously selected group by explicit selected groups
|
||||
previous_group = None
|
||||
if self._explicitly_selected_groups:
|
||||
previous_group = self._explicitly_selected_groups[-1]
|
||||
|
||||
# Find last explicitly selected instance id
|
||||
previous_last_selected_id = None
|
||||
if self._explicitly_selected_instance_ids:
|
||||
previous_last_selected_id = (
|
||||
self._explicitly_selected_instance_ids[-1]
|
||||
)
|
||||
|
||||
# If last instance id was not found or available then last selected
|
||||
# group is also invalid.
|
||||
# NOTE: This probably never happen?
|
||||
if previous_last_selected_id is None:
|
||||
previous_group = None
|
||||
|
||||
# Check if previously selected group is available and find out if
|
||||
# new instance group is above or under previous selection
|
||||
# - based on these information are start/end group/instance filled
|
||||
if previous_group in self._ordered_groups:
|
||||
new_idx = self._ordered_groups.index(group_name)
|
||||
prev_idx = self._ordered_groups.index(previous_group)
|
||||
if new_idx < prev_idx:
|
||||
start_group = group_name
|
||||
end_group = previous_group
|
||||
start_instance_id = instance_id
|
||||
end_instance_id = previous_last_selected_id
|
||||
else:
|
||||
start_group = previous_group
|
||||
end_group = group_name
|
||||
start_instance_id = previous_last_selected_id
|
||||
end_instance_id = instance_id
|
||||
|
||||
# If start group is not set then use context item group name
|
||||
if start_group is None:
|
||||
start_group = ""
|
||||
|
||||
# If start instance id is not filled then use context id (similar to
|
||||
# group)
|
||||
if start_instance_id is None:
|
||||
start_instance_id = CONTEXT_ID
|
||||
|
||||
# If end group is not defined then use passed group name
|
||||
# - this can be happen when previous group was not selected
|
||||
# - when this happens the selection will probably happen from context
|
||||
# item to item selected by user
|
||||
if end_group is None:
|
||||
end_group = group_name
|
||||
|
||||
# If end instance is not filled then use instance selected by user
|
||||
if end_instance_id is None:
|
||||
end_instance_id = instance_id
|
||||
|
||||
# Start and end group are the same
|
||||
# - a different logic is needed in that case
|
||||
same_group = start_group == end_group
|
||||
|
||||
# Process known information and change selection of items
|
||||
passed_start_group = False
|
||||
passed_end_group = False
|
||||
# Go through ordered groups (from top to bottom) and change selection
|
||||
for name in self._ordered_groups:
|
||||
# Prepare sorted instance widgets
|
||||
if name == "":
|
||||
sorted_widgets = [self._context_widget]
|
||||
else:
|
||||
group_widget = self._widgets_by_group[name]
|
||||
sorted_widgets = group_widget.get_ordered_widgets()
|
||||
|
||||
# Change selection based on explicit selection if start group
|
||||
# was not passed yet
|
||||
if not passed_start_group:
|
||||
if name != start_group:
|
||||
for widget in sorted_widgets:
|
||||
widget.set_selected(
|
||||
widget.id in self._explicitly_selected_instance_ids
|
||||
)
|
||||
continue
|
||||
|
||||
# Change selection based on explicit selection if end group
|
||||
# already passed
|
||||
if passed_end_group:
|
||||
for widget in sorted_widgets:
|
||||
widget.set_selected(
|
||||
widget.id in self._explicitly_selected_instance_ids
|
||||
)
|
||||
continue
|
||||
|
||||
# Start group is already passed and end group was not yet hit
|
||||
if same_group:
|
||||
passed_start_group = True
|
||||
passed_end_group = True
|
||||
passed_start_instance = False
|
||||
passed_end_instance = False
|
||||
for widget in sorted_widgets:
|
||||
if not passed_start_instance:
|
||||
if widget.id in (start_instance_id, end_instance_id):
|
||||
if widget.id != start_instance_id:
|
||||
# Swap start/end instance if start instance is
|
||||
# after end
|
||||
# - fix 'passed_end_instance' check
|
||||
start_instance_id, end_instance_id = (
|
||||
end_instance_id, start_instance_id
|
||||
)
|
||||
passed_start_instance = True
|
||||
|
||||
# Find out if widget should be selected
|
||||
select = False
|
||||
if passed_end_instance:
|
||||
select = False
|
||||
|
||||
elif passed_start_instance:
|
||||
select = True
|
||||
|
||||
# Check if instance is in explicitly selected items if
|
||||
# should ont be selected
|
||||
if (
|
||||
not select
|
||||
and widget.id in self._explicitly_selected_instance_ids
|
||||
):
|
||||
select = True
|
||||
|
||||
widget.set_selected(select)
|
||||
|
||||
if (
|
||||
not passed_end_instance
|
||||
and widget.id == end_instance_id
|
||||
):
|
||||
passed_end_instance = True
|
||||
|
||||
elif name == start_group:
|
||||
# First group from which selection should start
|
||||
# - look for start instance first from which the selection
|
||||
# should happen
|
||||
passed_start_group = True
|
||||
passed_start_instance = False
|
||||
for widget in sorted_widgets:
|
||||
if widget.id == start_instance_id:
|
||||
passed_start_instance = True
|
||||
|
||||
select = False
|
||||
# Check if passed start instance or instance is
|
||||
# in explicitly selected items to be selected
|
||||
if (
|
||||
passed_start_instance
|
||||
or widget.id in self._explicitly_selected_instance_ids
|
||||
):
|
||||
select = True
|
||||
widget.set_selected(select)
|
||||
|
||||
elif name == end_group:
|
||||
# Last group where selection should happen
|
||||
# - look for end instance first after which the selection
|
||||
# should stop
|
||||
passed_end_group = True
|
||||
passed_end_instance = False
|
||||
for widget in sorted_widgets:
|
||||
select = False
|
||||
# Check if not yet passed end instance or if instance is
|
||||
# in explicitly selected items to be selected
|
||||
if (
|
||||
not passed_end_instance
|
||||
or widget.id in self._explicitly_selected_instance_ids
|
||||
):
|
||||
select = True
|
||||
|
||||
widget.set_selected(select)
|
||||
|
||||
if widget.id == end_instance_id:
|
||||
passed_end_instance = True
|
||||
|
||||
else:
|
||||
# Just select everything between start and end group
|
||||
for widget in sorted_widgets:
|
||||
widget.set_selected(True)
|
||||
|
||||
def get_selected_items(self):
|
||||
"""Get selected instance ids and context."""
|
||||
instances = []
|
||||
context_selected = False
|
||||
selected_widget = self._get_selected_widget()
|
||||
if selected_widget is self._context_widget:
|
||||
context_selected = True
|
||||
selected_widgets = self._get_selected_widgets()
|
||||
|
||||
elif selected_widget is not None:
|
||||
instances.append(selected_widget.instance)
|
||||
context_selected = False
|
||||
for widget in selected_widgets:
|
||||
if widget is self._context_widget:
|
||||
context_selected = True
|
||||
else:
|
||||
instances.append(widget.id)
|
||||
|
||||
return instances, context_selected
|
||||
|
||||
def set_selected_items(self, instance_ids, context_selected):
|
||||
s_instance_ids = set(instance_ids)
|
||||
cur_ids, cur_context = self.get_selected_items()
|
||||
if (
|
||||
set(cur_ids) == s_instance_ids
|
||||
and cur_context == context_selected
|
||||
):
|
||||
return
|
||||
|
||||
selected_groups = []
|
||||
selected_instances = []
|
||||
if context_selected:
|
||||
selected_groups.append("")
|
||||
selected_instances.append(CONTEXT_ID)
|
||||
|
||||
self._context_widget.set_selected(context_selected)
|
||||
|
||||
for group_name in self._ordered_groups:
|
||||
if group_name == "":
|
||||
continue
|
||||
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
group_selected = False
|
||||
for widget in group_widget.get_ordered_widgets():
|
||||
select = False
|
||||
if widget.id in s_instance_ids:
|
||||
selected_instances.append(widget.id)
|
||||
group_selected = True
|
||||
select = True
|
||||
widget.set_selected(select)
|
||||
|
||||
if group_selected:
|
||||
selected_groups.append(group_name)
|
||||
|
||||
self._explicitly_selected_groups = selected_groups
|
||||
self._explicitly_selected_instance_ids = selected_instances
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import sys
|
||||
import re
|
||||
import traceback
|
||||
import copy
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from openpype.client import get_asset_by_name, get_subsets
|
||||
from openpype.pipeline.create import (
|
||||
CreatorError,
|
||||
SUBSET_NAME_ALLOWED_SYMBOLS,
|
||||
|
|
@ -150,18 +148,18 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
|
|||
self._family_label = family_label
|
||||
self._description_label = description_label
|
||||
|
||||
def set_plugin(self, plugin=None):
|
||||
if not plugin:
|
||||
def set_creator_item(self, creator_item=None):
|
||||
if not creator_item:
|
||||
self._icon_widget.set_icon_def(None)
|
||||
self._family_label.setText("")
|
||||
self._description_label.setText("")
|
||||
return
|
||||
|
||||
plugin_icon = plugin.get_icon()
|
||||
description = plugin.get_description() or ""
|
||||
plugin_icon = creator_item.icon
|
||||
description = creator_item.description or ""
|
||||
|
||||
self._icon_widget.set_icon_def(plugin_icon)
|
||||
self._family_label.setText("<b>{}</b>".format(plugin.family))
|
||||
self._family_label.setText("<b>{}</b>".format(creator_item.family))
|
||||
self._family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
|
||||
self._description_label.setText(description)
|
||||
|
||||
|
|
@ -174,7 +172,7 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
|
||||
self._controller = controller
|
||||
|
||||
self._asset_doc = None
|
||||
self._asset_name = None
|
||||
self._subset_names = None
|
||||
self._selected_creator = None
|
||||
|
||||
|
|
@ -380,7 +378,7 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
|
||||
if asset_name is None:
|
||||
asset_name = self.current_asset_name
|
||||
return asset_name
|
||||
return asset_name or None
|
||||
|
||||
def _get_task_name(self):
|
||||
task_name = None
|
||||
|
|
@ -444,7 +442,7 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
prereq_available = False
|
||||
creator_btn_tooltips.append("Creator is not selected")
|
||||
|
||||
if self._context_change_is_enabled() and self._asset_doc is None:
|
||||
if self._context_change_is_enabled() and self._asset_name is None:
|
||||
# QUESTION how to handle invalid asset?
|
||||
prereq_available = False
|
||||
creator_btn_tooltips.append("Context is not selected")
|
||||
|
|
@ -468,30 +466,19 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
asset_name = self._get_asset_name()
|
||||
|
||||
# Skip if asset did not change
|
||||
if self._asset_doc and self._asset_doc["name"] == asset_name:
|
||||
if self._asset_name and self._asset_name == asset_name:
|
||||
return
|
||||
|
||||
# Make sure `_asset_doc` and `_subset_names` variables are reset
|
||||
self._asset_doc = None
|
||||
# Make sure `_asset_name` and `_subset_names` variables are reset
|
||||
self._asset_name = asset_name
|
||||
self._subset_names = None
|
||||
if asset_name is None:
|
||||
return
|
||||
|
||||
project_name = self._controller.project_name
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
self._asset_doc = asset_doc
|
||||
subset_names = self._controller.get_existing_subset_names(asset_name)
|
||||
|
||||
if asset_doc:
|
||||
asset_id = asset_doc["_id"]
|
||||
subset_docs = get_subsets(
|
||||
project_name, asset_ids=[asset_id], fields=["name"]
|
||||
)
|
||||
self._subset_names = {
|
||||
subset_doc["name"]
|
||||
for subset_doc in subset_docs
|
||||
}
|
||||
|
||||
if not asset_doc:
|
||||
self._subset_names = subset_names
|
||||
if subset_names is None:
|
||||
self.subset_name_input.setText("< Asset is not set >")
|
||||
|
||||
def _refresh_creators(self):
|
||||
|
|
@ -506,7 +493,10 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
|
||||
# Add new families
|
||||
new_creators = set()
|
||||
for identifier, creator in self._controller.manual_creators.items():
|
||||
for identifier, creator_item in self._controller.creator_items.items():
|
||||
if creator_item.creator_type != "artist":
|
||||
continue
|
||||
|
||||
# TODO add details about creator
|
||||
new_creators.add(identifier)
|
||||
if identifier in existing_items:
|
||||
|
|
@ -518,10 +508,9 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
)
|
||||
self._creators_model.appendRow(item)
|
||||
|
||||
label = creator.label or identifier
|
||||
item.setData(label, QtCore.Qt.DisplayRole)
|
||||
item.setData(creator_item.label, QtCore.Qt.DisplayRole)
|
||||
item.setData(identifier, CREATOR_IDENTIFIER_ROLE)
|
||||
item.setData(creator.family, FAMILY_ROLE)
|
||||
item.setData(creator_item.family, FAMILY_ROLE)
|
||||
|
||||
# Remove families that are no more available
|
||||
for identifier in (old_creators - new_creators):
|
||||
|
|
@ -572,11 +561,11 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
identifier = new_index.data(CREATOR_IDENTIFIER_ROLE)
|
||||
self._set_creator_by_identifier(identifier)
|
||||
|
||||
def _set_creator_detailed_text(self, creator):
|
||||
def _set_creator_detailed_text(self, creator_item):
|
||||
# TODO implement
|
||||
description = ""
|
||||
if creator is not None:
|
||||
description = creator.get_detail_description() or description
|
||||
if creator_item is not None:
|
||||
description = creator_item.detailed_description or description
|
||||
self._controller.event_system.emit(
|
||||
"show.detailed.help",
|
||||
{
|
||||
|
|
@ -586,32 +575,39 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
)
|
||||
|
||||
def _set_creator_by_identifier(self, identifier):
|
||||
creator = self._controller.manual_creators.get(identifier)
|
||||
self._set_creator(creator)
|
||||
creator_item = self._controller.creator_items.get(identifier)
|
||||
self._set_creator(creator_item)
|
||||
|
||||
def _set_creator(self, creator):
|
||||
self._creator_short_desc_widget.set_plugin(creator)
|
||||
self._set_creator_detailed_text(creator)
|
||||
self._pre_create_widget.set_plugin(creator)
|
||||
def _set_creator(self, creator_item):
|
||||
"""Set current creator item.
|
||||
|
||||
self._selected_creator = creator
|
||||
Args:
|
||||
creator_item (CreatorItem): Item representing creator that can be
|
||||
triggered by artist.
|
||||
"""
|
||||
|
||||
if not creator:
|
||||
self._creator_short_desc_widget.set_creator_item(creator_item)
|
||||
self._set_creator_detailed_text(creator_item)
|
||||
self._pre_create_widget.set_creator_item(creator_item)
|
||||
|
||||
self._selected_creator = creator_item
|
||||
|
||||
if not creator_item:
|
||||
self._set_context_enabled(False)
|
||||
return
|
||||
|
||||
if (
|
||||
creator.create_allow_context_change
|
||||
creator_item.create_allow_context_change
|
||||
!= self._context_change_is_enabled()
|
||||
):
|
||||
self._set_context_enabled(creator.create_allow_context_change)
|
||||
self._set_context_enabled(creator_item.create_allow_context_change)
|
||||
self._refresh_asset()
|
||||
|
||||
default_variants = creator.get_default_variants()
|
||||
default_variants = creator_item.default_variants
|
||||
if not default_variants:
|
||||
default_variants = ["Main"]
|
||||
|
||||
default_variant = creator.get_default_variant()
|
||||
default_variant = creator_item.default_variant
|
||||
if not default_variant:
|
||||
default_variant = default_variants[0]
|
||||
|
||||
|
|
@ -670,14 +666,13 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
self.subset_name_input.setText("< Valid variant >")
|
||||
return
|
||||
|
||||
project_name = self._controller.project_name
|
||||
asset_name = self._get_asset_name()
|
||||
task_name = self._get_task_name()
|
||||
|
||||
asset_doc = copy.deepcopy(self._asset_doc)
|
||||
creator_idenfier = self._selected_creator.identifier
|
||||
# Calculate subset name with Creator plugin
|
||||
try:
|
||||
subset_name = self._selected_creator.get_subset_name(
|
||||
variant_value, task_name, asset_doc, project_name
|
||||
subset_name = self._controller.get_subset_name(
|
||||
creator_idenfier, variant_value, task_name, asset_name
|
||||
)
|
||||
except TaskNotSetError:
|
||||
self._create_btn.setEnabled(False)
|
||||
|
|
|
|||
|
|
@ -44,8 +44,10 @@ class HelpWidget(QtWidgets.QWidget):
|
|||
if commonmark:
|
||||
html = commonmark.commonmark(text)
|
||||
self._detail_description_input.setHtml(html)
|
||||
else:
|
||||
elif hasattr(self._detail_description_input, "setMarkdown"):
|
||||
self._detail_description_input.setMarkdown(text)
|
||||
else:
|
||||
self._detail_description_input.setText(text)
|
||||
|
||||
|
||||
class HelpDialog(QtWidgets.QDialog):
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
def __init__(self, controller, parent):
|
||||
super(InstanceListView, self).__init__(parent)
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
|
||||
instance_view = InstanceTreeView(self)
|
||||
instance_delegate = ListItemDelegate(instance_view)
|
||||
|
|
@ -520,7 +520,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
# Prepare instances by their groups
|
||||
instances_by_group_name = collections.defaultdict(list)
|
||||
group_names = set()
|
||||
for instance in self.controller.instances:
|
||||
for instance in self._controller.instances.values():
|
||||
group_label = instance.group_label
|
||||
group_names.add(group_label)
|
||||
instances_by_group_name[group_label].append(instance)
|
||||
|
|
@ -723,13 +723,13 @@ class InstanceListView(AbstractInstanceView):
|
|||
widget.update_instance_values()
|
||||
|
||||
def _on_active_changed(self, changed_instance_id, new_value):
|
||||
selected_instances, _ = self.get_selected_items()
|
||||
selected_instance_ids, _ = self.get_selected_items()
|
||||
|
||||
selected_ids = set()
|
||||
found = False
|
||||
for instance in selected_instances:
|
||||
selected_ids.add(instance.id)
|
||||
if not found and instance.id == changed_instance_id:
|
||||
for instance_id in selected_instance_ids:
|
||||
selected_ids.add(instance_id)
|
||||
if not found and instance_id == changed_instance_id:
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
|
|
@ -760,32 +760,6 @@ class InstanceListView(AbstractInstanceView):
|
|||
if changed_ids:
|
||||
self.active_changed.emit()
|
||||
|
||||
def get_selected_items(self):
|
||||
"""Get selected instance ids and context selection.
|
||||
|
||||
Returns:
|
||||
tuple<list, bool>: Selected instance ids and boolean if context
|
||||
is selected.
|
||||
"""
|
||||
instances = []
|
||||
context_selected = False
|
||||
instances_by_id = {
|
||||
instance.id: instance
|
||||
for instance in self.controller.instances
|
||||
}
|
||||
|
||||
for index in self._instance_view.selectionModel().selectedIndexes():
|
||||
instance_id = index.data(INSTANCE_ID_ROLE)
|
||||
if not context_selected and instance_id == CONTEXT_ID:
|
||||
context_selected = True
|
||||
|
||||
elif instance_id is not None:
|
||||
instance = instances_by_id.get(instance_id)
|
||||
if instance:
|
||||
instances.append(instance)
|
||||
|
||||
return instances, context_selected
|
||||
|
||||
def _on_selection_change(self, *_args):
|
||||
self.selection_changed.emit()
|
||||
|
||||
|
|
@ -825,3 +799,102 @@ class InstanceListView(AbstractInstanceView):
|
|||
proxy_index = self._proxy_model.mapFromSource(group_item.index())
|
||||
if not self._instance_view.isExpanded(proxy_index):
|
||||
self._instance_view.expand(proxy_index)
|
||||
|
||||
def get_selected_items(self):
|
||||
"""Get selected instance ids and context selection.
|
||||
|
||||
Returns:
|
||||
tuple<list, bool>: Selected instance ids and boolean if context
|
||||
is selected.
|
||||
"""
|
||||
instance_ids = []
|
||||
context_selected = False
|
||||
|
||||
for index in self._instance_view.selectionModel().selectedIndexes():
|
||||
instance_id = index.data(INSTANCE_ID_ROLE)
|
||||
if not context_selected and instance_id == CONTEXT_ID:
|
||||
context_selected = True
|
||||
|
||||
elif instance_id is not None:
|
||||
instance_ids.append(instance_id)
|
||||
|
||||
return instance_ids, context_selected
|
||||
|
||||
def set_selected_items(self, instance_ids, context_selected):
|
||||
s_instance_ids = set(instance_ids)
|
||||
cur_ids, cur_context = self.get_selected_items()
|
||||
if (
|
||||
set(cur_ids) == s_instance_ids
|
||||
and cur_context == context_selected
|
||||
):
|
||||
return
|
||||
|
||||
view = self._instance_view
|
||||
src_model = self._instance_model
|
||||
proxy_model = self._proxy_model
|
||||
|
||||
select_indexes = []
|
||||
|
||||
select_queue = collections.deque()
|
||||
select_queue.append(
|
||||
(src_model.invisibleRootItem(), [])
|
||||
)
|
||||
while select_queue:
|
||||
queue_item = select_queue.popleft()
|
||||
item, parent_items = queue_item
|
||||
|
||||
if item.hasChildren():
|
||||
new_parent_items = list(parent_items)
|
||||
new_parent_items.append(item)
|
||||
for row in range(item.rowCount()):
|
||||
select_queue.append(
|
||||
(item.child(row), list(new_parent_items))
|
||||
)
|
||||
|
||||
instance_id = item.data(INSTANCE_ID_ROLE)
|
||||
if not instance_id:
|
||||
continue
|
||||
|
||||
if instance_id in s_instance_ids:
|
||||
select_indexes.append(item.index())
|
||||
for parent_item in parent_items:
|
||||
index = parent_item.index()
|
||||
proxy_index = proxy_model.mapFromSource(index)
|
||||
if not view.isExpanded(proxy_index):
|
||||
view.expand(proxy_index)
|
||||
|
||||
elif context_selected and instance_id == CONTEXT_ID:
|
||||
select_indexes.append(item.index())
|
||||
|
||||
selection_model = view.selectionModel()
|
||||
if not select_indexes:
|
||||
selection_model.clear()
|
||||
return
|
||||
|
||||
if len(select_indexes) == 1:
|
||||
proxy_index = proxy_model.mapFromSource(select_indexes[0])
|
||||
selection_model.setCurrentIndex(
|
||||
proxy_index,
|
||||
selection_model.ClearAndSelect | selection_model.Rows
|
||||
)
|
||||
return
|
||||
|
||||
first_index = proxy_model.mapFromSource(select_indexes.pop(0))
|
||||
last_index = proxy_model.mapFromSource(select_indexes.pop(-1))
|
||||
|
||||
selection_model.setCurrentIndex(
|
||||
first_index,
|
||||
selection_model.ClearAndSelect | selection_model.Rows
|
||||
)
|
||||
|
||||
for index in select_indexes:
|
||||
proxy_index = proxy_model.mapFromSource(index)
|
||||
selection_model.select(
|
||||
proxy_index,
|
||||
selection_model.Select | selection_model.Rows
|
||||
)
|
||||
|
||||
selection_model.setCurrentIndex(
|
||||
last_index,
|
||||
selection_model.Select | selection_model.Rows
|
||||
)
|
||||
|
|
|
|||
|
|
@ -201,16 +201,16 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
self.create_requested.emit()
|
||||
|
||||
def _on_delete_clicked(self):
|
||||
instances, _ = self.get_selected_items()
|
||||
instance_ids, _ = self.get_selected_items()
|
||||
|
||||
# Ask user if he really wants to remove instances
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
dialog.setIcon(QtWidgets.QMessageBox.Question)
|
||||
dialog.setWindowTitle("Are you sure?")
|
||||
if len(instances) > 1:
|
||||
if len(instance_ids) > 1:
|
||||
msg = (
|
||||
"Do you really want to remove {} instances?"
|
||||
).format(len(instances))
|
||||
).format(len(instance_ids))
|
||||
else:
|
||||
msg = (
|
||||
"Do you really want to remove the instance?"
|
||||
|
|
@ -224,7 +224,8 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
dialog.exec_()
|
||||
# Skip if OK was not clicked
|
||||
if dialog.result() == QtWidgets.QMessageBox.Ok:
|
||||
self._controller.remove_instances(instances)
|
||||
instance_ids = set(instance_ids)
|
||||
self._controller.remove_instances(instance_ids)
|
||||
|
||||
def _on_change_view_clicked(self):
|
||||
self._change_view_type()
|
||||
|
|
@ -234,11 +235,16 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
if self._refreshing_instances:
|
||||
return
|
||||
|
||||
instances, context_selected = self.get_selected_items()
|
||||
instance_ids, context_selected = self.get_selected_items()
|
||||
|
||||
# Disable delete button if nothing is selected
|
||||
self._delete_btn.setEnabled(len(instances) > 0)
|
||||
self._delete_btn.setEnabled(len(instance_ids) > 0)
|
||||
|
||||
instances_by_id = self._controller.instances
|
||||
instances = [
|
||||
instances_by_id[instance_id]
|
||||
for instance_id in instance_ids
|
||||
]
|
||||
self._subset_attributes_widget.set_current_instances(
|
||||
instances, context_selected
|
||||
)
|
||||
|
|
@ -315,15 +321,21 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
def _change_view_type(self):
|
||||
idx = self._subset_views_layout.currentIndex()
|
||||
new_idx = (idx + 1) % self._subset_views_layout.count()
|
||||
self._subset_views_layout.setCurrentIndex(new_idx)
|
||||
|
||||
new_view = self._subset_views_layout.currentWidget()
|
||||
old_view = self._subset_views_layout.currentWidget()
|
||||
new_view = self._subset_views_layout.widget(new_idx)
|
||||
|
||||
if not new_view.refreshed:
|
||||
new_view.refresh()
|
||||
new_view.set_refreshed(True)
|
||||
else:
|
||||
new_view.refresh_instance_states()
|
||||
|
||||
instance_ids, context_selected = old_view.get_selected_items()
|
||||
new_view.set_selected_items(instance_ids, context_selected)
|
||||
|
||||
self._subset_views_layout.setCurrentIndex(new_idx)
|
||||
|
||||
self._on_subset_change()
|
||||
|
||||
def _refresh_instances(self):
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ class PreCreateWidget(QtWidgets.QWidget):
|
|||
def current_value(self):
|
||||
return self._attributes_widget.current_value()
|
||||
|
||||
def set_plugin(self, creator):
|
||||
def set_creator_item(self, creator_item):
|
||||
attr_defs = []
|
||||
creator_selected = False
|
||||
if creator is not None:
|
||||
if creator_item is not None:
|
||||
creator_selected = True
|
||||
attr_defs = creator.get_pre_create_attr_defs()
|
||||
attr_defs = creator_item.pre_create_attributes_defs
|
||||
|
||||
self._attributes_widget.set_attr_defs(attr_defs)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import time
|
|||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
from openpype.pipeline import KnownPublishError
|
||||
|
||||
from .widgets import (
|
||||
StopBtn,
|
||||
ResetBtn,
|
||||
|
|
@ -170,7 +168,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
"publish.process.started", self._on_publish_start
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.validated", self._on_publish_validated
|
||||
"publish.has_validated.changed", self._on_publish_validated_change
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.stopped", self._on_publish_stop
|
||||
|
|
@ -185,7 +183,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
|
||||
self._shrunk_anim = shrunk_anim
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
|
||||
self._content_frame = content_frame
|
||||
self._content_layout = content_layout
|
||||
|
|
@ -320,8 +318,8 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
self._validate_btn.setEnabled(True)
|
||||
self._publish_btn.setEnabled(True)
|
||||
|
||||
self._progress_bar.setValue(self.controller.publish_progress)
|
||||
self._progress_bar.setMaximum(self.controller.publish_max_progress)
|
||||
self._progress_bar.setValue(self._controller.publish_progress)
|
||||
self._progress_bar.setMaximum(self._controller.publish_max_progress)
|
||||
|
||||
def _on_publish_start(self):
|
||||
if self._last_plugin_label:
|
||||
|
|
@ -330,7 +328,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
if self._last_instance_label:
|
||||
self._instance_label.setText(self._last_instance_label)
|
||||
|
||||
self._set_success_property(-1)
|
||||
self._set_success_property(3)
|
||||
self._set_progress_visibility(True)
|
||||
self._set_main_label("Publishing...")
|
||||
|
||||
|
|
@ -341,8 +339,9 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
|
||||
self.set_shrunk_state(False)
|
||||
|
||||
def _on_publish_validated(self):
|
||||
self._validate_btn.setEnabled(False)
|
||||
def _on_publish_validated_change(self, event):
|
||||
if event["value"]:
|
||||
self._validate_btn.setEnabled(False)
|
||||
|
||||
def _on_instance_change(self, event):
|
||||
"""Change instance label when instance is going to be processed."""
|
||||
|
|
@ -355,12 +354,12 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
"""Change plugin label when instance is going to be processed."""
|
||||
|
||||
self._last_plugin_label = event["plugin_label"]
|
||||
self._progress_bar.setValue(self.controller.publish_progress)
|
||||
self._progress_bar.setValue(self._controller.publish_progress)
|
||||
self._plugin_label.setText(event["plugin_label"])
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
def _on_publish_stop(self):
|
||||
self._progress_bar.setValue(self.controller.publish_progress)
|
||||
self._progress_bar.setValue(self._controller.publish_progress)
|
||||
|
||||
self._reset_btn.setEnabled(True)
|
||||
self._stop_btn.setEnabled(False)
|
||||
|
|
@ -368,33 +367,31 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
self._instance_label.setText("")
|
||||
self._plugin_label.setText("")
|
||||
|
||||
validate_enabled = not self.controller.publish_has_crashed
|
||||
publish_enabled = not self.controller.publish_has_crashed
|
||||
validate_enabled = not self._controller.publish_has_crashed
|
||||
publish_enabled = not self._controller.publish_has_crashed
|
||||
if validate_enabled:
|
||||
validate_enabled = not self.controller.publish_has_validated
|
||||
validate_enabled = not self._controller.publish_has_validated
|
||||
if publish_enabled:
|
||||
if (
|
||||
self.controller.publish_has_validated
|
||||
and self.controller.publish_has_validation_errors
|
||||
self._controller.publish_has_validated
|
||||
and self._controller.publish_has_validation_errors
|
||||
):
|
||||
publish_enabled = False
|
||||
|
||||
else:
|
||||
publish_enabled = not self.controller.publish_has_finished
|
||||
publish_enabled = not self._controller.publish_has_finished
|
||||
|
||||
self._validate_btn.setEnabled(validate_enabled)
|
||||
self._publish_btn.setEnabled(publish_enabled)
|
||||
|
||||
error = self.controller.get_publish_crash_error()
|
||||
validation_errors = self.controller.get_validation_errors()
|
||||
if error:
|
||||
self._set_error(error)
|
||||
if self._controller.publish_has_crashed:
|
||||
self._set_error_msg()
|
||||
|
||||
elif validation_errors:
|
||||
elif self._controller.publish_has_validation_errors:
|
||||
self._set_progress_visibility(False)
|
||||
self._set_validation_errors()
|
||||
|
||||
elif self.controller.publish_has_finished:
|
||||
elif self._controller.publish_has_finished:
|
||||
self._set_finished()
|
||||
|
||||
else:
|
||||
|
|
@ -402,7 +399,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
|
||||
def _set_stopped(self):
|
||||
main_label = "Publish paused"
|
||||
if self.controller.publish_has_validated:
|
||||
if self._controller.publish_has_validated:
|
||||
main_label += " - Validation passed"
|
||||
|
||||
self._set_main_label(main_label)
|
||||
|
|
@ -410,20 +407,16 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
"Hit publish (play button) to continue."
|
||||
)
|
||||
|
||||
self._set_success_property(-1)
|
||||
self._set_success_property(4)
|
||||
|
||||
def _set_error_msg(self):
|
||||
"""Show error message to artist on publish crash."""
|
||||
|
||||
def _set_error(self, error):
|
||||
self._set_main_label("Error happened")
|
||||
if isinstance(error, KnownPublishError):
|
||||
msg = str(error)
|
||||
else:
|
||||
msg = (
|
||||
"Something went wrong. Send report"
|
||||
" to your supervisor or OpenPype."
|
||||
)
|
||||
self._message_label_top.setText(msg)
|
||||
|
||||
self._set_success_property(0)
|
||||
self._message_label_top.setText(self._controller.publish_error_msg)
|
||||
|
||||
self._set_success_property(1)
|
||||
|
||||
def _set_validation_errors(self):
|
||||
self._set_main_label("Your publish didn't pass studio validations")
|
||||
|
|
@ -433,7 +426,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
def _set_finished(self):
|
||||
self._set_main_label("Finished")
|
||||
self._message_label_top.setText("")
|
||||
self._set_success_property(1)
|
||||
self._set_success_property(0)
|
||||
|
||||
def _set_progress_visibility(self, visible):
|
||||
window_height = self.height()
|
||||
|
|
@ -454,6 +447,17 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
self.move(window_pos.x(), window_pos_y)
|
||||
|
||||
def _set_success_property(self, state=None):
|
||||
"""Apply styles by state.
|
||||
|
||||
State enum:
|
||||
- None - Default state after restart
|
||||
- 0 - Success finish
|
||||
- 1 - Error happened
|
||||
- 2 - Validation error
|
||||
- 3 - In progress
|
||||
- 4 - Stopped/Paused
|
||||
"""
|
||||
|
||||
if state is None:
|
||||
state = ""
|
||||
else:
|
||||
|
|
@ -465,7 +469,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
widget.style().polish(widget)
|
||||
|
||||
def _copy_report(self):
|
||||
logs = self.controller.get_publish_report()
|
||||
logs = self._controller.get_publish_report()
|
||||
logs_string = json.dumps(logs, indent=4)
|
||||
|
||||
mime_data = QtCore.QMimeData()
|
||||
|
|
@ -488,7 +492,7 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
if not ext or not new_filepath:
|
||||
return
|
||||
|
||||
logs = self.controller.get_publish_report()
|
||||
logs = self._controller.get_publish_report()
|
||||
full_path = new_filepath + ext
|
||||
dir_path = os.path.dirname(full_path)
|
||||
if not os.path.exists(dir_path):
|
||||
|
|
@ -508,13 +512,13 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
self.details_page_requested.emit()
|
||||
|
||||
def _on_reset_clicked(self):
|
||||
self.controller.reset()
|
||||
self._controller.reset()
|
||||
|
||||
def _on_stop_clicked(self):
|
||||
self.controller.stop_publish()
|
||||
self._controller.stop_publish()
|
||||
|
||||
def _on_validate_clicked(self):
|
||||
self.controller.validate()
|
||||
self._controller.validate()
|
||||
|
||||
def _on_publish_clicked(self):
|
||||
self.controller.publish()
|
||||
self._controller.publish()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
Has toggle button to show/hide instances on which validation error happened
|
||||
if there is a list (Valdation error may happen on context).
|
||||
"""
|
||||
|
||||
selected = QtCore.Signal(int)
|
||||
instance_changed = QtCore.Signal(int)
|
||||
|
||||
|
|
@ -75,34 +76,31 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
title_frame_layout.addWidget(toggle_instance_btn, 0)
|
||||
|
||||
instances_model = QtGui.QStandardItemModel()
|
||||
error_info = error_info["error_info"]
|
||||
|
||||
help_text_by_instance_id = {}
|
||||
context_validation = False
|
||||
items = []
|
||||
if (
|
||||
not error_info
|
||||
or (len(error_info) == 1 and error_info[0][0] is None)
|
||||
):
|
||||
context_validation = True
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow)
|
||||
description = self._prepare_description(error_info[0][1])
|
||||
help_text_by_instance_id[None] = description
|
||||
# Add fake item to have minimum size hint of view widget
|
||||
items.append(QtGui.QStandardItem("Context"))
|
||||
|
||||
else:
|
||||
for instance, exception in error_info:
|
||||
label = instance.data.get("label") or instance.data.get("name")
|
||||
item = QtGui.QStandardItem(label)
|
||||
item.setFlags(
|
||||
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
)
|
||||
item.setData(label, QtCore.Qt.ToolTipRole)
|
||||
item.setData(instance.id, INSTANCE_ID_ROLE)
|
||||
items.append(item)
|
||||
description = self._prepare_description(exception)
|
||||
help_text_by_instance_id[instance.id] = description
|
||||
items = []
|
||||
context_validation = False
|
||||
for error_item in error_info["error_items"]:
|
||||
context_validation = error_item.context_validation
|
||||
if context_validation:
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow)
|
||||
description = self._prepare_description(error_item)
|
||||
help_text_by_instance_id[None] = description
|
||||
# Add fake item to have minimum size hint of view widget
|
||||
items.append(QtGui.QStandardItem("Context"))
|
||||
continue
|
||||
|
||||
label = error_item.instance_label
|
||||
item = QtGui.QStandardItem(label)
|
||||
item.setFlags(
|
||||
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
)
|
||||
item.setData(label, QtCore.Qt.ToolTipRole)
|
||||
item.setData(error_item.instance_id, INSTANCE_ID_ROLE)
|
||||
items.append(item)
|
||||
description = self._prepare_description(error_item)
|
||||
help_text_by_instance_id[error_item.instance_id] = description
|
||||
|
||||
if items:
|
||||
root_item = instances_model.invisibleRootItem()
|
||||
|
|
@ -167,9 +165,19 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
def minimumSizeHint(self):
|
||||
return self.sizeHint()
|
||||
|
||||
def _prepare_description(self, exception):
|
||||
dsc = exception.description
|
||||
detail = exception.detail
|
||||
def _prepare_description(self, error_item):
|
||||
"""Prepare description text for detail intput.
|
||||
|
||||
Args:
|
||||
error_item (ValidationErrorItem): Item which hold information about
|
||||
validation error.
|
||||
|
||||
Returns:
|
||||
str: Prepared detailed description.
|
||||
"""
|
||||
|
||||
dsc = error_item.description
|
||||
detail = error_item.detail
|
||||
if detail:
|
||||
dsc += "<br/><br/>{}".format(detail)
|
||||
|
||||
|
|
@ -196,32 +204,51 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
|
||||
@property
|
||||
def is_selected(self):
|
||||
"""Is widget marked a selected"""
|
||||
"""Is widget marked a selected.
|
||||
|
||||
Returns:
|
||||
bool: Item is selected or not.
|
||||
"""
|
||||
|
||||
return self._selected
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
"""Widget's index set by parent."""
|
||||
"""Widget's index set by parent.
|
||||
|
||||
Returns:
|
||||
int: Index of widget.
|
||||
"""
|
||||
|
||||
return self._index
|
||||
|
||||
def set_index(self, index):
|
||||
"""Set index of widget (called by parent)."""
|
||||
"""Set index of widget (called by parent).
|
||||
|
||||
Args:
|
||||
int: New index of widget.
|
||||
"""
|
||||
|
||||
self._index = index
|
||||
|
||||
def _change_style_property(self, selected):
|
||||
"""Change style of widget based on selection."""
|
||||
|
||||
value = "1" if selected else ""
|
||||
self._title_frame.setProperty("selected", value)
|
||||
self._title_frame.style().polish(self._title_frame)
|
||||
|
||||
def set_selected(self, selected=None):
|
||||
"""Change selected state of widget."""
|
||||
|
||||
if selected is None:
|
||||
selected = not self._selected
|
||||
|
||||
# Clear instance view selection on deselect
|
||||
if not selected:
|
||||
self._instances_view.clearSelection()
|
||||
|
||||
# Skip if has same value
|
||||
if selected == self._selected:
|
||||
return
|
||||
|
||||
|
|
@ -263,18 +290,23 @@ class ActionButton(BaseClickableFrame):
|
|||
"""Plugin's action callback button.
|
||||
|
||||
Action may have label or icon or both.
|
||||
"""
|
||||
action_clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, action, parent):
|
||||
Args:
|
||||
plugin_action_item (PublishPluginActionItem): Action item that can be
|
||||
triggered by it's id.
|
||||
"""
|
||||
|
||||
action_clicked = QtCore.Signal(str, str)
|
||||
|
||||
def __init__(self, plugin_action_item, parent):
|
||||
super(ActionButton, self).__init__(parent)
|
||||
|
||||
self.setObjectName("ValidationActionButton")
|
||||
|
||||
self.action = action
|
||||
self.plugin_action_item = plugin_action_item
|
||||
|
||||
action_label = action.label or action.__name__
|
||||
action_icon = getattr(action, "icon", None)
|
||||
action_label = plugin_action_item.label
|
||||
action_icon = plugin_action_item.icon
|
||||
label_widget = QtWidgets.QLabel(action_label, self)
|
||||
icon_label = None
|
||||
if action_icon:
|
||||
|
|
@ -292,7 +324,10 @@ class ActionButton(BaseClickableFrame):
|
|||
)
|
||||
|
||||
def _mouse_release_callback(self):
|
||||
self.action_clicked.emit(self.action.id)
|
||||
self.action_clicked.emit(
|
||||
self.plugin_action_item.plugin_id,
|
||||
self.plugin_action_item.action_id
|
||||
)
|
||||
|
||||
|
||||
class ValidateActionsWidget(QtWidgets.QFrame):
|
||||
|
|
@ -300,6 +335,7 @@ class ValidateActionsWidget(QtWidgets.QFrame):
|
|||
|
||||
Change actions based on selected validation error.
|
||||
"""
|
||||
|
||||
def __init__(self, controller, parent):
|
||||
super(ValidateActionsWidget, self).__init__(parent)
|
||||
|
||||
|
|
@ -312,10 +348,9 @@ class ValidateActionsWidget(QtWidgets.QFrame):
|
|||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(content_widget)
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
self._content_widget = content_widget
|
||||
self._content_layout = content_layout
|
||||
self._plugin = None
|
||||
self._actions_mapping = {}
|
||||
|
||||
def clear(self):
|
||||
|
|
@ -328,28 +363,34 @@ class ValidateActionsWidget(QtWidgets.QFrame):
|
|||
widget.deleteLater()
|
||||
self._actions_mapping = {}
|
||||
|
||||
def set_plugin(self, plugin):
|
||||
def set_error_item(self, error_item):
|
||||
"""Set selected plugin and show it's actions.
|
||||
|
||||
Clears current actions from widget and recreate them from the plugin.
|
||||
|
||||
Args:
|
||||
Dict[str, Any]: Object holding error items, title and possible
|
||||
actions to run.
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
self._plugin = plugin
|
||||
if not plugin:
|
||||
|
||||
if not error_item:
|
||||
self.setVisible(False)
|
||||
return
|
||||
|
||||
actions = getattr(plugin, "actions", [])
|
||||
for action in actions:
|
||||
if not action.active:
|
||||
plugin_action_items = error_item["plugin_action_items"]
|
||||
for plugin_action_item in plugin_action_items:
|
||||
if not plugin_action_item.active:
|
||||
continue
|
||||
|
||||
if action.on not in ("failed", "all"):
|
||||
if plugin_action_item.on_filter not in ("failed", "all"):
|
||||
continue
|
||||
|
||||
self._actions_mapping[action.id] = action
|
||||
action_id = plugin_action_item.action_id
|
||||
self._actions_mapping[action_id] = plugin_action_item
|
||||
|
||||
action_btn = ActionButton(action, self._content_widget)
|
||||
action_btn = ActionButton(plugin_action_item, self._content_widget)
|
||||
action_btn.action_clicked.connect(self._on_action_click)
|
||||
self._content_layout.addWidget(action_btn)
|
||||
|
||||
|
|
@ -359,9 +400,8 @@ class ValidateActionsWidget(QtWidgets.QFrame):
|
|||
else:
|
||||
self.setVisible(False)
|
||||
|
||||
def _on_action_click(self, action_id):
|
||||
action = self._actions_mapping[action_id]
|
||||
self.controller.run_action(self._plugin, action)
|
||||
def _on_action_click(self, plugin_id, action_id):
|
||||
self._controller.run_action(plugin_id, action_id)
|
||||
|
||||
|
||||
class VerticallScrollArea(QtWidgets.QScrollArea):
|
||||
|
|
@ -373,6 +413,7 @@ class VerticallScrollArea(QtWidgets.QScrollArea):
|
|||
Resize if deferred by 100ms because at the moment of resize are not yet
|
||||
propagated sizes and visibility of scroll bars.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VerticallScrollArea, self).__init__(*args, **kwargs)
|
||||
|
||||
|
|
@ -584,45 +625,31 @@ class ValidationsWidget(QtWidgets.QFrame):
|
|||
self._errors_widget.setVisible(False)
|
||||
self._actions_widget.setVisible(False)
|
||||
|
||||
def set_errors(self, errors):
|
||||
"""Set errors into context and created titles."""
|
||||
def _set_errors(self, validation_error_report):
|
||||
"""Set errors into context and created titles.
|
||||
|
||||
Args:
|
||||
validation_error_report (PublishValidationErrorsReport): Report
|
||||
with information about validation errors and publish plugin
|
||||
actions.
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
if not errors:
|
||||
if not validation_error_report:
|
||||
return
|
||||
|
||||
self._top_label.setVisible(True)
|
||||
self._error_details_frame.setVisible(True)
|
||||
self._errors_widget.setVisible(True)
|
||||
|
||||
errors_by_title = []
|
||||
for plugin_info in errors:
|
||||
titles = []
|
||||
error_info_by_title = {}
|
||||
|
||||
for error_info in plugin_info["errors"]:
|
||||
exception = error_info["exception"]
|
||||
title = exception.title
|
||||
if title not in titles:
|
||||
titles.append(title)
|
||||
error_info_by_title[title] = []
|
||||
error_info_by_title[title].append(
|
||||
(error_info["instance"], exception)
|
||||
)
|
||||
|
||||
for title in titles:
|
||||
errors_by_title.append({
|
||||
"plugin": plugin_info["plugin"],
|
||||
"error_info": error_info_by_title[title],
|
||||
"title": title
|
||||
})
|
||||
|
||||
for idx, item in enumerate(errors_by_title):
|
||||
widget = ValidationErrorTitleWidget(idx, item, self)
|
||||
grouped_error_items = validation_error_report.group_items_by_title()
|
||||
for idx, error_info in enumerate(grouped_error_items):
|
||||
widget = ValidationErrorTitleWidget(idx, error_info, self)
|
||||
widget.selected.connect(self._on_select)
|
||||
widget.instance_changed.connect(self._on_instance_change)
|
||||
self._errors_layout.addWidget(widget)
|
||||
self._title_widgets[idx] = widget
|
||||
self._error_info[idx] = item
|
||||
self._error_info[idx] = error_info
|
||||
|
||||
self._errors_layout.addStretch(1)
|
||||
|
||||
|
|
@ -648,7 +675,7 @@ class ValidationsWidget(QtWidgets.QFrame):
|
|||
if self._controller.publish_has_validation_errors:
|
||||
validation_errors = self._controller.get_validation_errors()
|
||||
self._set_current_widget(self._validations_widget)
|
||||
self.set_errors(validation_errors)
|
||||
self._set_errors(validation_errors)
|
||||
return
|
||||
|
||||
if self._controller.publish_has_finished:
|
||||
|
|
@ -667,7 +694,7 @@ class ValidationsWidget(QtWidgets.QFrame):
|
|||
|
||||
error_item = self._error_info[index]
|
||||
|
||||
self._actions_widget.set_plugin(error_item["plugin"])
|
||||
self._actions_widget.set_error_item(error_item)
|
||||
|
||||
self._update_description()
|
||||
|
||||
|
|
@ -682,5 +709,7 @@ class ValidationsWidget(QtWidgets.QFrame):
|
|||
if commonmark:
|
||||
html = commonmark.commonmark(description)
|
||||
self._error_details_input.setHtml(html)
|
||||
else:
|
||||
elif hasattr(self._error_details_input, "setMarkdown"):
|
||||
self._error_details_input.setMarkdown(description)
|
||||
else:
|
||||
self._error_details_input.setText(description)
|
||||
|
|
|
|||
|
|
@ -306,10 +306,25 @@ class AbstractInstanceView(QtWidgets.QWidget):
|
|||
|
||||
Example: When delete button is clicked to know what should be deleted.
|
||||
"""
|
||||
|
||||
raise NotImplementedError((
|
||||
"{} Method 'get_selected_items' is not implemented."
|
||||
).format(self.__class__.__name__))
|
||||
|
||||
def set_selected_items(self, instance_ids, context_selected):
|
||||
"""Change selection for instances and context.
|
||||
|
||||
Used to applying selection from one view to other.
|
||||
|
||||
Args:
|
||||
instance_ids (List[str]): Selected instance ids.
|
||||
context_selected (bool): Context is selected.
|
||||
"""
|
||||
|
||||
raise NotImplementedError((
|
||||
"{} Method 'set_selected_items' is not implemented."
|
||||
).format(self.__class__.__name__))
|
||||
|
||||
|
||||
class ClickableLineEdit(QtWidgets.QLineEdit):
|
||||
"""QLineEdit capturing left mouse click.
|
||||
|
|
@ -994,7 +1009,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
def __init__(self, controller, parent):
|
||||
super(GlobalAttrsWidget, self).__init__(parent)
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
self._current_instances = []
|
||||
|
||||
variant_input = VariantInputWidget(self)
|
||||
|
|
@ -1060,24 +1075,6 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
if self.task_value_widget.has_value_changed():
|
||||
task_name = self.task_value_widget.get_selected_items()[0]
|
||||
|
||||
asset_docs_by_name = {}
|
||||
asset_names = set()
|
||||
if asset_name is None:
|
||||
for instance in self._current_instances:
|
||||
asset_names.add(instance.get("asset"))
|
||||
else:
|
||||
asset_names.add(asset_name)
|
||||
|
||||
for asset_doc in self.controller.get_asset_docs():
|
||||
_asset_name = asset_doc["name"]
|
||||
if _asset_name in asset_names:
|
||||
asset_names.remove(_asset_name)
|
||||
asset_docs_by_name[_asset_name] = asset_doc
|
||||
|
||||
if not asset_names:
|
||||
break
|
||||
|
||||
project_name = self.controller.project_name
|
||||
subset_names = set()
|
||||
invalid_tasks = False
|
||||
for instance in self._current_instances:
|
||||
|
|
@ -1093,16 +1090,15 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
if task_name is not None:
|
||||
new_task_name = task_name
|
||||
|
||||
asset_doc = asset_docs_by_name[new_asset_name]
|
||||
|
||||
try:
|
||||
new_subset_name = instance.creator.get_subset_name(
|
||||
new_subset_name = self._controller.get_subset_name(
|
||||
instance.creator_identifier,
|
||||
new_variant_value,
|
||||
new_task_name,
|
||||
asset_doc,
|
||||
project_name,
|
||||
instance=instance
|
||||
new_asset_name,
|
||||
instance.id,
|
||||
)
|
||||
|
||||
except TaskNotSetError:
|
||||
invalid_tasks = True
|
||||
instance.set_task_invalid(True)
|
||||
|
|
@ -1249,7 +1245,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
|
||||
self._main_layout = main_layout
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
self._scroll_area = scroll_area
|
||||
|
||||
self._attr_def_id_to_instances = {}
|
||||
|
|
@ -1278,7 +1274,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
self._attr_def_id_to_instances = {}
|
||||
self._attr_def_id_to_attr_def = {}
|
||||
|
||||
result = self.controller.get_creator_attribute_definitions(
|
||||
result = self._controller.get_creator_attribute_definitions(
|
||||
instances
|
||||
)
|
||||
|
||||
|
|
@ -1370,7 +1366,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
|
||||
self._main_layout = main_layout
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
self._scroll_area = scroll_area
|
||||
|
||||
self._attr_def_id_to_instances = {}
|
||||
|
|
@ -1402,7 +1398,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
self._attr_def_id_to_attr_def = {}
|
||||
self._attr_def_id_to_plugin_name = {}
|
||||
|
||||
result = self.controller.get_publish_attribute_definitions(
|
||||
result = self._controller.get_publish_attribute_definitions(
|
||||
instances, context_selected
|
||||
)
|
||||
|
||||
|
|
@ -1517,7 +1513,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
self._on_instance_context_changed
|
||||
)
|
||||
|
||||
self.controller = controller
|
||||
self._controller = controller
|
||||
|
||||
self.global_attrs_widget = global_attrs_widget
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from openpype.tools.utils import (
|
|||
)
|
||||
|
||||
from .publish_report_viewer import PublishReportViewerWidget
|
||||
from .control import PublisherController
|
||||
from .control_qt import QtPublisherController
|
||||
from .widgets import (
|
||||
OverviewWidget,
|
||||
ValidationsWidget,
|
||||
|
|
@ -36,7 +36,7 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
footer_border = 8
|
||||
publish_footer_spacer = 2
|
||||
|
||||
def __init__(self, parent=None, reset_on_show=None):
|
||||
def __init__(self, parent=None, controller=None, reset_on_show=None):
|
||||
super(PublisherWindow, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("OpenPype publisher")
|
||||
|
|
@ -61,7 +61,8 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
| on_top_flag
|
||||
)
|
||||
|
||||
controller = PublisherController()
|
||||
if controller is None:
|
||||
controller = QtPublisherController()
|
||||
|
||||
help_dialog = HelpDialog(controller, self)
|
||||
|
||||
|
|
@ -250,7 +251,7 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
"publish.process.started", self._on_publish_start
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.validated", self._on_publish_validated
|
||||
"publish.has_validated.changed", self._on_publish_validated_change
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.stopped", self._on_publish_stop
|
||||
|
|
@ -441,11 +442,7 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
self._controller.stop_publish()
|
||||
|
||||
def _set_publish_comment(self):
|
||||
if self._controller.publish_comment_is_set:
|
||||
return
|
||||
|
||||
comment = self._comment_input.text()
|
||||
self._controller.set_comment(comment)
|
||||
self._controller.set_comment(self._comment_input.text())
|
||||
|
||||
def _on_validate_clicked(self):
|
||||
self._set_publish_comment()
|
||||
|
|
@ -473,6 +470,11 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
self._set_publish_visibility(False)
|
||||
self._set_footer_enabled(False)
|
||||
self._update_publish_details_widget()
|
||||
if (
|
||||
not self._tabs_widget.is_current_tab("create")
|
||||
or not self._tabs_widget.is_current_tab("publish")
|
||||
):
|
||||
self._tabs_widget.set_current_tab("publish")
|
||||
|
||||
def _on_publish_start(self):
|
||||
self._create_tab.setEnabled(False)
|
||||
|
|
@ -491,15 +493,20 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
if self._tabs_widget.is_current_tab(self._create_tab):
|
||||
self._tabs_widget.set_current_tab("publish")
|
||||
|
||||
def _on_publish_validated(self):
|
||||
self._validate_btn.setEnabled(False)
|
||||
def _on_publish_validated_change(self, event):
|
||||
if event["value"]:
|
||||
self._validate_btn.setEnabled(False)
|
||||
|
||||
def _on_publish_stop(self):
|
||||
self._set_publish_overlay_visibility(False)
|
||||
self._reset_btn.setEnabled(True)
|
||||
self._stop_btn.setEnabled(False)
|
||||
validate_enabled = not self._controller.publish_has_crashed
|
||||
publish_enabled = not self._controller.publish_has_crashed
|
||||
publish_has_crashed = self._controller.publish_has_crashed
|
||||
validate_enabled = not publish_has_crashed
|
||||
publish_enabled = not publish_has_crashed
|
||||
if self._tabs_widget.is_current_tab("publish"):
|
||||
self._go_to_report_tab()
|
||||
|
||||
if validate_enabled:
|
||||
validate_enabled = not self._controller.publish_has_validated
|
||||
if publish_enabled:
|
||||
|
|
@ -508,8 +515,6 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
and self._controller.publish_has_validation_errors
|
||||
):
|
||||
publish_enabled = False
|
||||
if self._tabs_widget.is_current_tab("publish"):
|
||||
self._go_to_report_tab()
|
||||
|
||||
else:
|
||||
publish_enabled = not self._controller.publish_has_finished
|
||||
|
|
@ -525,7 +530,7 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
return
|
||||
|
||||
all_valid = None
|
||||
for instance in self._controller.instances:
|
||||
for instance in self._controller.instances.values():
|
||||
if not instance["active"]:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import appdirs
|
|||
from openpype.lib import JSONSettingRegistry
|
||||
from openpype.pipeline import install_host
|
||||
from openpype.hosts.traypublisher.api import TrayPublisherHost
|
||||
from openpype.tools.publisher.control_qt import QtPublisherController
|
||||
from openpype.tools.publisher.window import PublisherWindow
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.tools.utils.constants import PROJECT_NAME_ROLE
|
||||
|
|
@ -24,6 +25,15 @@ from openpype.tools.utils.models import (
|
|||
)
|
||||
|
||||
|
||||
class TrayPublisherController(QtPublisherController):
|
||||
@property
|
||||
def host(self):
|
||||
return self._host
|
||||
|
||||
def reset_project_data_cache(self):
|
||||
self._asset_docs_cache.reset()
|
||||
|
||||
|
||||
class TrayPublisherRegistry(JSONSettingRegistry):
|
||||
"""Class handling OpenPype general settings registry.
|
||||
|
||||
|
|
@ -179,7 +189,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
|
|||
|
||||
class TrayPublishWindow(PublisherWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TrayPublishWindow, self).__init__(reset_on_show=False)
|
||||
controller = TrayPublisherController()
|
||||
super(TrayPublishWindow, self).__init__(
|
||||
controller=controller, reset_on_show=False
|
||||
)
|
||||
|
||||
flags = self.windowFlags()
|
||||
# Disable always on top hint
|
||||
|
|
|
|||
|
|
@ -269,25 +269,25 @@ class HostToolsHelper:
|
|||
dialog.activateWindow()
|
||||
dialog.showNormal()
|
||||
|
||||
def get_publisher_tool(self, parent):
|
||||
def get_publisher_tool(self, parent=None, controller=None):
|
||||
"""Create, cache and return publisher window."""
|
||||
|
||||
if self._publisher_tool is None:
|
||||
from openpype.tools.publisher import PublisherWindow
|
||||
from openpype.tools.publisher.window import PublisherWindow
|
||||
|
||||
host = registered_host()
|
||||
ILoadHost.validate_load_methods(host)
|
||||
|
||||
publisher_window = PublisherWindow(
|
||||
parent=parent or self._parent
|
||||
controller=controller, parent=parent or self._parent
|
||||
)
|
||||
self._publisher_tool = publisher_window
|
||||
|
||||
return self._publisher_tool
|
||||
|
||||
def show_publisher_tool(self, parent=None):
|
||||
def show_publisher_tool(self, parent=None, controller=None):
|
||||
with qt_app_context():
|
||||
dialog = self.get_publisher_tool(parent)
|
||||
dialog = self.get_publisher_tool(parent, controller)
|
||||
|
||||
dialog.show()
|
||||
dialog.raise_()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue