mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into release/3.15.x
This commit is contained in:
commit
6807c05c27
14 changed files with 1087 additions and 238 deletions
28
.github/workflows/milestone_assign.yml
vendored
Normal file
28
.github/workflows/milestone_assign.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: Milestone - assign to PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, edited]
|
||||
|
||||
jobs:
|
||||
run_if_release:
|
||||
if: startsWith(github.base_ref, 'release/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Assign Milestone [next-minor]'
|
||||
if: github.event.pull_request.milestone == null
|
||||
uses: zoispag/action-assign-milestone@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
milestone: 'next-minor'
|
||||
|
||||
run_if_develop:
|
||||
if: ${{ github.base_ref == 'develop' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Assign Milestone [next-patch]'
|
||||
if: github.event.pull_request.milestone == null
|
||||
uses: zoispag/action-assign-milestone@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
milestone: 'next-patch'
|
||||
62
.github/workflows/milestone_create.yml
vendored
Normal file
62
.github/workflows/milestone_create.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: Milestone - create default
|
||||
|
||||
on:
|
||||
milestone:
|
||||
types: [closed, edited]
|
||||
|
||||
jobs:
|
||||
generate-next-patch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Get Milestones'
|
||||
uses: "WyriHaximus/github-action-get-milestones@master"
|
||||
id: milestones
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number')
|
||||
id: querymilestone
|
||||
env:
|
||||
MILESTONES: ${{ steps.milestones.outputs.milestones }}
|
||||
MILESTONE: "next-patch"
|
||||
|
||||
- name: Read output
|
||||
run: |
|
||||
echo "${{ steps.querymilestone.outputs.number }}"
|
||||
|
||||
- name: 'Create `next-patch` milestone'
|
||||
if: steps.querymilestone.outputs.number == ''
|
||||
id: createmilestone
|
||||
uses: "WyriHaximus/github-action-create-milestone@v1"
|
||||
with:
|
||||
title: 'next-patch'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
generate-next-minor:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Get Milestones'
|
||||
uses: "WyriHaximus/github-action-get-milestones@master"
|
||||
id: milestones
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number')
|
||||
id: querymilestone
|
||||
env:
|
||||
MILESTONES: ${{ steps.milestones.outputs.milestones }}
|
||||
MILESTONE: "next-minor"
|
||||
|
||||
- name: Read output
|
||||
run: |
|
||||
echo "${{ steps.querymilestone.outputs.number }}"
|
||||
|
||||
- name: 'Create `next-minor` milestone'
|
||||
if: steps.querymilestone.outputs.number == ''
|
||||
id: createmilestone
|
||||
uses: "WyriHaximus/github-action-create-milestone@v1"
|
||||
with:
|
||||
title: 'next-minor'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
|
@ -24,6 +24,7 @@ from .creator_plugins import (
|
|||
Creator,
|
||||
AutoCreator,
|
||||
discover_creator_plugins,
|
||||
discover_convertor_plugins,
|
||||
CreatorError,
|
||||
)
|
||||
|
||||
|
|
@ -70,6 +71,41 @@ class HostMissRequiredMethod(Exception):
|
|||
super(HostMissRequiredMethod, self).__init__(msg)
|
||||
|
||||
|
||||
class ConvertorsOperationFailed(Exception):
|
||||
def __init__(self, msg, failed_info):
|
||||
super(ConvertorsOperationFailed, self).__init__(msg)
|
||||
self.failed_info = failed_info
|
||||
|
||||
|
||||
class ConvertorsFindFailed(ConvertorsOperationFailed):
|
||||
def __init__(self, failed_info):
|
||||
msg = "Failed to find incompatible subsets"
|
||||
super(ConvertorsFindFailed, self).__init__(
|
||||
msg, failed_info
|
||||
)
|
||||
|
||||
|
||||
class ConvertorsConversionFailed(ConvertorsOperationFailed):
|
||||
def __init__(self, failed_info):
|
||||
msg = "Failed to convert incompatible subsets"
|
||||
super(ConvertorsConversionFailed, self).__init__(
|
||||
msg, failed_info
|
||||
)
|
||||
|
||||
|
||||
def prepare_failed_convertor_operation_info(identifier, exc_info):
|
||||
exc_type, exc_value, exc_traceback = exc_info
|
||||
formatted_traceback = "".join(traceback.format_exception(
|
||||
exc_type, exc_value, exc_traceback
|
||||
))
|
||||
|
||||
return {
|
||||
"convertor_identifier": identifier,
|
||||
"message": str(exc_value),
|
||||
"traceback": formatted_traceback
|
||||
}
|
||||
|
||||
|
||||
class CreatorsOperationFailed(Exception):
|
||||
"""Raised when a creator process crashes in 'CreateContext'.
|
||||
|
||||
|
|
@ -926,6 +962,37 @@ class CreatedInstance:
|
|||
self[key] = new_value
|
||||
|
||||
|
||||
class ConvertorItem(object):
|
||||
"""Item representing convertor plugin.
|
||||
|
||||
Args:
|
||||
identifier (str): Identifier of convertor.
|
||||
label (str): Label which will be shown in UI.
|
||||
"""
|
||||
|
||||
def __init__(self, identifier, label):
|
||||
self._id = str(uuid4())
|
||||
self.identifier = identifier
|
||||
self.label = label
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
def to_data(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"identifier": self.identifier,
|
||||
"label": self.label
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
obj = cls(data["identifier"], data["label"])
|
||||
obj._id = data["id"]
|
||||
return obj
|
||||
|
||||
|
||||
class CreateContext:
|
||||
"""Context of instance creation.
|
||||
|
||||
|
|
@ -991,6 +1058,9 @@ class CreateContext:
|
|||
# Manual creators
|
||||
self.manual_creators = {}
|
||||
|
||||
self.convertors_plugins = {}
|
||||
self.convertor_items_by_id = {}
|
||||
|
||||
self.publish_discover_result = None
|
||||
self.publish_plugins_mismatch_targets = []
|
||||
self.publish_plugins = []
|
||||
|
|
@ -1071,6 +1141,7 @@ class CreateContext:
|
|||
|
||||
with self.bulk_instances_collection():
|
||||
self.reset_instances()
|
||||
self.find_convertor_items()
|
||||
self.execute_autocreators()
|
||||
|
||||
self.reset_finalization()
|
||||
|
|
@ -1125,6 +1196,12 @@ class CreateContext:
|
|||
Reloads creators from preregistered paths and can load publish plugins
|
||||
if it's enabled on context.
|
||||
"""
|
||||
|
||||
self._reset_publish_plugins(discover_publish_plugins)
|
||||
self._reset_creator_plugins()
|
||||
self._reset_convertor_plugins()
|
||||
|
||||
def _reset_publish_plugins(self, discover_publish_plugins):
|
||||
import pyblish.logic
|
||||
|
||||
from openpype.pipeline import OpenPypePyblishPluginMixin
|
||||
|
|
@ -1166,6 +1243,7 @@ class CreateContext:
|
|||
self.publish_plugins = plugins_by_targets
|
||||
self.plugins_with_defs = plugins_with_defs
|
||||
|
||||
def _reset_creator_plugins(self):
|
||||
# Prepare settings
|
||||
system_settings = get_system_settings()
|
||||
project_settings = get_project_settings(self.project_name)
|
||||
|
|
@ -1217,6 +1295,27 @@ class CreateContext:
|
|||
|
||||
self.creators = creators
|
||||
|
||||
def _reset_convertor_plugins(self):
|
||||
convertors_plugins = {}
|
||||
for convertor_class in discover_convertor_plugins():
|
||||
if inspect.isabstract(convertor_class):
|
||||
self.log.info(
|
||||
"Skipping abstract Creator {}".format(str(convertor_class))
|
||||
)
|
||||
continue
|
||||
|
||||
convertor_identifier = convertor_class.identifier
|
||||
if convertor_identifier in convertors_plugins:
|
||||
self.log.warning((
|
||||
"Duplicated Converter identifier. "
|
||||
"Using first and skipping following"
|
||||
))
|
||||
continue
|
||||
|
||||
convertors_plugins[convertor_identifier] = convertor_class(self)
|
||||
|
||||
self.convertors_plugins = convertors_plugins
|
||||
|
||||
def reset_context_data(self):
|
||||
"""Reload context data using host implementation.
|
||||
|
||||
|
|
@ -1346,6 +1445,14 @@ class CreateContext:
|
|||
|
||||
self._instances_by_id.pop(instance.id, None)
|
||||
|
||||
def add_convertor_item(self, convertor_identifier, label):
|
||||
self.convertor_items_by_id[convertor_identifier] = ConvertorItem(
|
||||
convertor_identifier, label
|
||||
)
|
||||
|
||||
def remove_convertor_item(self, convertor_identifier):
|
||||
self.convertor_items_by_id.pop(convertor_identifier, None)
|
||||
|
||||
@contextmanager
|
||||
def bulk_instances_collection(self):
|
||||
"""Validate context of instances in bulk.
|
||||
|
|
@ -1413,6 +1520,37 @@ class CreateContext:
|
|||
if failed_info:
|
||||
raise CreatorsCollectionFailed(failed_info)
|
||||
|
||||
def find_convertor_items(self):
|
||||
"""Go through convertor plugins to look for items to convert.
|
||||
|
||||
Raises:
|
||||
ConvertorsFindFailed: When one or more convertors fails during
|
||||
finding.
|
||||
"""
|
||||
|
||||
self.convertor_items_by_id = {}
|
||||
|
||||
failed_info = []
|
||||
for convertor in self.convertors_plugins.values():
|
||||
try:
|
||||
convertor.find_instances()
|
||||
|
||||
except:
|
||||
failed_info.append(
|
||||
prepare_failed_convertor_operation_info(
|
||||
convertor.identifier, sys.exc_info()
|
||||
)
|
||||
)
|
||||
self.log.warning(
|
||||
"Failed to find instances of convertor \"{}\"".format(
|
||||
convertor.identifier
|
||||
),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
if failed_info:
|
||||
raise ConvertorsFindFailed(failed_info)
|
||||
|
||||
def execute_autocreators(self):
|
||||
"""Execute discovered AutoCreator plugins.
|
||||
|
||||
|
|
@ -1668,3 +1806,51 @@ class CreateContext:
|
|||
"Accessed Collection shared data out of collection phase"
|
||||
)
|
||||
return self._collection_shared_data
|
||||
|
||||
def run_convertor(self, convertor_identifier):
|
||||
"""Run convertor plugin by it's idenfitifier.
|
||||
|
||||
Conversion is skipped if convertor is not available.
|
||||
|
||||
Args:
|
||||
convertor_identifier (str): Identifier of convertor.
|
||||
"""
|
||||
|
||||
convertor = self.convertors_plugins.get(convertor_identifier)
|
||||
if convertor is not None:
|
||||
convertor.convert()
|
||||
|
||||
def run_convertors(self, convertor_identifiers):
|
||||
"""Run convertor plugins by idenfitifiers.
|
||||
|
||||
Conversion is skipped if convertor is not available. It is recommended
|
||||
to trigger reset after conversion to reload instances.
|
||||
|
||||
Args:
|
||||
convertor_identifiers (Iterator[str]): Identifiers of convertors
|
||||
to run.
|
||||
|
||||
Raises:
|
||||
ConvertorsConversionFailed: When one or more convertors fails.
|
||||
"""
|
||||
|
||||
failed_info = []
|
||||
for convertor_identifier in convertor_identifiers:
|
||||
try:
|
||||
self.run_convertor(convertor_identifier)
|
||||
|
||||
except:
|
||||
failed_info.append(
|
||||
prepare_failed_convertor_operation_info(
|
||||
convertor_identifier, sys.exc_info()
|
||||
)
|
||||
)
|
||||
self.log.warning(
|
||||
"Failed to convert instances of convertor \"{}\"".format(
|
||||
convertor_identifier
|
||||
),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
if failed_info:
|
||||
raise ConvertorsConversionFailed(failed_info)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,111 @@ class CreatorError(Exception):
|
|||
super(CreatorError, self).__init__(message)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class SubsetConvertorPlugin(object):
|
||||
"""Helper for conversion of instances created using legacy creators.
|
||||
|
||||
Conversion from legacy creators would mean to loose legacy instances,
|
||||
convert them automatically or write a script which must user run. All of
|
||||
these solutions are workign but will happen without asking or user must
|
||||
know about them. This plugin can be used to show legacy instances in
|
||||
Publisher and give user ability to run conversion script.
|
||||
|
||||
Convertor logic should be very simple. Method 'find_instances' is to
|
||||
look for legacy instances in scene a possibly call
|
||||
pre-implemented 'add_convertor_item'.
|
||||
|
||||
User will have ability to trigger conversion which is executed by calling
|
||||
'convert' which should call 'remove_convertor_item' when is done.
|
||||
|
||||
It does make sense to add only one or none legacy item to create context
|
||||
for convertor as it's not possible to choose which instace are converted
|
||||
and which are not.
|
||||
|
||||
Convertor can use 'collection_shared_data' property like creators. Also
|
||||
can store any information to it's object for conversion purposes.
|
||||
|
||||
Args:
|
||||
create_context
|
||||
"""
|
||||
|
||||
_log = None
|
||||
|
||||
def __init__(self, create_context):
|
||||
self._create_context = create_context
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
"""Logger of the plugin.
|
||||
|
||||
Returns:
|
||||
logging.Logger: Logger with name of the plugin.
|
||||
"""
|
||||
|
||||
if self._log is None:
|
||||
self._log = Logger.get_logger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
||||
@abstractproperty
|
||||
def identifier(self):
|
||||
"""Converted identifier.
|
||||
|
||||
Returns:
|
||||
str: Converted identifier unique for all converters in host.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_instances(self):
|
||||
"""Look for legacy instances in the scene.
|
||||
|
||||
Should call 'add_convertor_item' if there is at least one instance to
|
||||
convert.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def convert(self):
|
||||
"""Conversion code."""
|
||||
|
||||
pass
|
||||
|
||||
@property
|
||||
def create_context(self):
|
||||
"""Quick access to create context."""
|
||||
|
||||
return self._create_context
|
||||
|
||||
@property
|
||||
def collection_shared_data(self):
|
||||
"""Access to shared data that can be used during 'find_instances'.
|
||||
|
||||
Retruns:
|
||||
Dict[str, Any]: Shared data.
|
||||
|
||||
Raises:
|
||||
UnavailableSharedData: When called out of collection phase.
|
||||
"""
|
||||
|
||||
return self._create_context.collection_shared_data
|
||||
|
||||
def add_convertor_item(self, label):
|
||||
"""Add item to CreateContext.
|
||||
|
||||
Args:
|
||||
label (str): Label of item which will show in UI.
|
||||
"""
|
||||
|
||||
self._create_context.add_convertor_item(self.identifier, label)
|
||||
|
||||
def remove_convertor_item(self):
|
||||
"""Remove legacy item from create context when conversion finished."""
|
||||
|
||||
self._create_context.remove_convertor_item(self.identifier)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class BaseCreator:
|
||||
"""Plugin that create and modify instance data before publishing process.
|
||||
|
|
@ -469,6 +574,10 @@ def discover_creator_plugins():
|
|||
return discover(BaseCreator)
|
||||
|
||||
|
||||
def discover_convertor_plugins():
|
||||
return discover(SubsetConvertorPlugin)
|
||||
|
||||
|
||||
def discover_legacy_creator_plugins():
|
||||
from openpype.lib import Logger
|
||||
|
||||
|
|
@ -526,6 +635,9 @@ def register_creator_plugin(plugin):
|
|||
elif issubclass(plugin, LegacyCreator):
|
||||
register_plugin(LegacyCreator, plugin)
|
||||
|
||||
elif issubclass(plugin, SubsetConvertorPlugin):
|
||||
register_plugin(SubsetConvertorPlugin, plugin)
|
||||
|
||||
|
||||
def deregister_creator_plugin(plugin):
|
||||
if issubclass(plugin, BaseCreator):
|
||||
|
|
@ -534,12 +646,17 @@ def deregister_creator_plugin(plugin):
|
|||
elif issubclass(plugin, LegacyCreator):
|
||||
deregister_plugin(LegacyCreator, plugin)
|
||||
|
||||
elif issubclass(plugin, SubsetConvertorPlugin):
|
||||
deregister_plugin(SubsetConvertorPlugin, plugin)
|
||||
|
||||
|
||||
def register_creator_plugin_path(path):
|
||||
register_plugin_path(BaseCreator, path)
|
||||
register_plugin_path(LegacyCreator, path)
|
||||
register_plugin_path(SubsetConvertorPlugin, path)
|
||||
|
||||
|
||||
def deregister_creator_plugin_path(path):
|
||||
deregister_plugin_path(BaseCreator, path)
|
||||
deregister_plugin_path(LegacyCreator, path)
|
||||
deregister_plugin_path(SubsetConvertorPlugin, path)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@
|
|||
"overlay-messages": {
|
||||
"close-btn": "#D3D8DE",
|
||||
"bg-success": "#458056",
|
||||
"bg-success-hover": "#55a066"
|
||||
"bg-success-hover": "#55a066",
|
||||
"bg-error": "#AD2E2E",
|
||||
"bg-error-hover": "#C93636"
|
||||
},
|
||||
"tab-widget": {
|
||||
"bg": "#21252B",
|
||||
|
|
|
|||
|
|
@ -688,22 +688,23 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
}
|
||||
|
||||
/* Messages overlay */
|
||||
#OverlayMessageWidget {
|
||||
OverlayMessageWidget {
|
||||
border-radius: 0.2em;
|
||||
background: {color:bg-buttons};
|
||||
}
|
||||
|
||||
#OverlayMessageWidget:hover {
|
||||
background: {color:bg-button-hover};
|
||||
}
|
||||
#OverlayMessageWidget {
|
||||
background: {color:overlay-messages:bg-success};
|
||||
}
|
||||
#OverlayMessageWidget:hover {
|
||||
|
||||
OverlayMessageWidget:hover {
|
||||
background: {color:overlay-messages:bg-success-hover};
|
||||
}
|
||||
|
||||
#OverlayMessageWidget QWidget {
|
||||
OverlayMessageWidget[type="error"] {
|
||||
background: {color:overlay-messages:bg-error};
|
||||
}
|
||||
OverlayMessageWidget[type="error"]:hover {
|
||||
background: {color:overlay-messages:bg-error-hover};
|
||||
}
|
||||
|
||||
OverlayMessageWidget QWidget {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ from Qt import QtCore
|
|||
# ID of context item in instance view
|
||||
CONTEXT_ID = "context"
|
||||
CONTEXT_LABEL = "Options"
|
||||
# Not showed anywhere - used as identifier
|
||||
CONTEXT_GROUP = "__ContextGroup__"
|
||||
|
||||
CONVERTOR_ITEM_GROUP = "Incompatible subsets"
|
||||
|
||||
# Allowed symbols for subset name (and variant)
|
||||
# - characters, numbers, unsercore and dash
|
||||
|
|
@ -17,6 +21,8 @@ SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2
|
|||
IS_GROUP_ROLE = QtCore.Qt.UserRole + 3
|
||||
CREATOR_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 4
|
||||
FAMILY_ROLE = QtCore.Qt.UserRole + 5
|
||||
GROUP_ROLE = QtCore.Qt.UserRole + 6
|
||||
CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 7
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
|
|||
|
|
@ -33,12 +33,18 @@ from openpype.pipeline.create import (
|
|||
)
|
||||
from openpype.pipeline.create.context import (
|
||||
CreatorsOperationFailed,
|
||||
ConvertorsOperationFailed,
|
||||
)
|
||||
|
||||
# Define constant for plugin orders offset
|
||||
PLUGIN_ORDER_OFFSET = 0.5
|
||||
|
||||
|
||||
class CardMessageTypes:
|
||||
standard = None
|
||||
error = "error"
|
||||
|
||||
|
||||
class MainThreadItem:
|
||||
"""Callback with args and kwargs."""
|
||||
|
||||
|
|
@ -1242,6 +1248,14 @@ class AbstractPublisherController(object):
|
|||
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def convertor_items(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def trigger_convertor_items(self, convertor_identifiers):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_comment(self, comment):
|
||||
"""Set comment on pyblish context.
|
||||
|
|
@ -1255,7 +1269,9 @@ class AbstractPublisherController(object):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def emit_card_message(self, message):
|
||||
def emit_card_message(
|
||||
self, message, message_type=CardMessageTypes.standard
|
||||
):
|
||||
"""Emit a card message which can have a lifetime.
|
||||
|
||||
This is for UI purposes. Method can be extended to more arguments
|
||||
|
|
@ -1606,6 +1622,10 @@ class PublisherController(BasePublisherController):
|
|||
"""Current instances in create context."""
|
||||
return self._create_context.instances_by_id
|
||||
|
||||
@property
|
||||
def convertor_items(self):
|
||||
return self._create_context.convertor_items_by_id
|
||||
|
||||
@property
|
||||
def _creators(self):
|
||||
"""All creators loaded in create context."""
|
||||
|
|
@ -1731,6 +1751,17 @@ class PublisherController(BasePublisherController):
|
|||
}
|
||||
)
|
||||
|
||||
try:
|
||||
self._create_context.find_convertor_items()
|
||||
except ConvertorsOperationFailed as exc:
|
||||
self._emit_event(
|
||||
"convertors.find.failed",
|
||||
{
|
||||
"title": "Collection of unsupported subset failed",
|
||||
"failed_info": exc.failed_info
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
self._create_context.execute_autocreators()
|
||||
|
||||
|
|
@ -1747,8 +1778,16 @@ class PublisherController(BasePublisherController):
|
|||
|
||||
self._on_create_instance_change()
|
||||
|
||||
def emit_card_message(self, message):
|
||||
self._emit_event("show.card.message", {"message": message})
|
||||
def emit_card_message(
|
||||
self, message, message_type=CardMessageTypes.standard
|
||||
):
|
||||
self._emit_event(
|
||||
"show.card.message",
|
||||
{
|
||||
"message": message,
|
||||
"message_type": message_type
|
||||
}
|
||||
)
|
||||
|
||||
def get_creator_attribute_definitions(self, instances):
|
||||
"""Collect creator attribute definitions for multuple instances.
|
||||
|
|
@ -1866,6 +1905,30 @@ class PublisherController(BasePublisherController):
|
|||
variant, task_name, asset_doc, project_name, instance=instance
|
||||
)
|
||||
|
||||
def trigger_convertor_items(self, convertor_identifiers):
|
||||
self.save_changes()
|
||||
|
||||
success = True
|
||||
try:
|
||||
self._create_context.run_convertors(convertor_identifiers)
|
||||
|
||||
except ConvertorsOperationFailed as exc:
|
||||
success = False
|
||||
self._emit_event(
|
||||
"convertors.convert.failed",
|
||||
{
|
||||
"title": "Conversion failed",
|
||||
"failed_info": exc.failed_info
|
||||
}
|
||||
)
|
||||
|
||||
if success:
|
||||
self.emit_card_message("Conversion finished")
|
||||
else:
|
||||
self.emit_card_message("Conversion failed", CardMessageTypes.error)
|
||||
|
||||
self.reset()
|
||||
|
||||
def create(
|
||||
self, creator_identifier, subset_name, instance_data, options
|
||||
):
|
||||
|
|
@ -1912,7 +1975,6 @@ class PublisherController(BasePublisherController):
|
|||
Args:
|
||||
instance_ids (List[str]): List of instance ids to remove.
|
||||
"""
|
||||
# TODO expect instance ids instead of instances
|
||||
# QUESTION Expect that instances are really removed? In that case save
|
||||
# reset is not required and save changes too.
|
||||
self.save_changes()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ from .widgets import (
|
|||
)
|
||||
from ..constants import (
|
||||
CONTEXT_ID,
|
||||
CONTEXT_LABEL
|
||||
CONTEXT_LABEL,
|
||||
CONTEXT_GROUP,
|
||||
CONVERTOR_ITEM_GROUP,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -57,15 +59,12 @@ class SelectionTypes:
|
|||
extend_to = SelectionType("extend_to")
|
||||
|
||||
|
||||
class GroupWidget(QtWidgets.QWidget):
|
||||
"""Widget wrapping instances under group."""
|
||||
|
||||
class BaseGroupWidget(QtWidgets.QWidget):
|
||||
selected = QtCore.Signal(str, str, SelectionType)
|
||||
active_changed = QtCore.Signal()
|
||||
removed_selected = QtCore.Signal()
|
||||
|
||||
def __init__(self, group_name, group_icons, parent):
|
||||
super(GroupWidget, self).__init__(parent)
|
||||
def __init__(self, group_name, parent):
|
||||
super(BaseGroupWidget, self).__init__(parent)
|
||||
|
||||
label_widget = QtWidgets.QLabel(group_name, self)
|
||||
|
||||
|
|
@ -86,10 +85,9 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
layout.addLayout(label_layout, 0)
|
||||
|
||||
self._group = group_name
|
||||
self._group_icons = group_icons
|
||||
|
||||
self._widgets_by_id = {}
|
||||
self._ordered_instance_ids = []
|
||||
self._ordered_item_ids = []
|
||||
|
||||
self._label_widget = label_widget
|
||||
self._content_layout = layout
|
||||
|
|
@ -104,7 +102,12 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
|
||||
return self._group
|
||||
|
||||
def get_selected_instance_ids(self):
|
||||
def get_widget_by_item_id(self, item_id):
|
||||
"""Get instance widget by it's id."""
|
||||
|
||||
return self._widgets_by_id.get(item_id)
|
||||
|
||||
def get_selected_item_ids(self):
|
||||
"""Selected instance ids.
|
||||
|
||||
Returns:
|
||||
|
|
@ -139,13 +142,80 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
|
||||
return [
|
||||
self._widgets_by_id[instance_id]
|
||||
for instance_id in self._ordered_instance_ids
|
||||
for instance_id in self._ordered_item_ids
|
||||
]
|
||||
|
||||
def get_widget_by_instance_id(self, instance_id):
|
||||
"""Get instance widget by it's id."""
|
||||
def _remove_all_except(self, item_ids):
|
||||
item_ids = set(item_ids)
|
||||
# Remove instance widgets that are not in passed instances
|
||||
for item_id in tuple(self._widgets_by_id.keys()):
|
||||
if item_id in item_ids:
|
||||
continue
|
||||
|
||||
return self._widgets_by_id.get(instance_id)
|
||||
widget = self._widgets_by_id.pop(item_id)
|
||||
if widget.is_selected:
|
||||
self.removed_selected.emit()
|
||||
|
||||
widget.setVisible(False)
|
||||
self._content_layout.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
|
||||
def _update_ordered_item_ids(self):
|
||||
ordered_item_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_item_ids.append(widget.id)
|
||||
|
||||
self._ordered_item_ids = ordered_item_ids
|
||||
|
||||
def _on_widget_selection(self, instance_id, group_id, selection_type):
|
||||
self.selected.emit(instance_id, group_id, selection_type)
|
||||
|
||||
|
||||
class ConvertorItemsGroupWidget(BaseGroupWidget):
|
||||
def update_items(self, items_by_id):
|
||||
items_by_label = collections.defaultdict(list)
|
||||
for item in items_by_id.values():
|
||||
items_by_label[item.label].append(item)
|
||||
|
||||
# Remove instance widgets that are not in passed instances
|
||||
self._remove_all_except(items_by_id.keys())
|
||||
|
||||
# Sort instances by subset name
|
||||
sorted_labels = list(sorted(items_by_label.keys()))
|
||||
|
||||
# Add new instances to widget
|
||||
widget_idx = 1
|
||||
for label in sorted_labels:
|
||||
for item in items_by_label[label]:
|
||||
if item.id in self._widgets_by_id:
|
||||
widget = self._widgets_by_id[item.id]
|
||||
widget.update_item(item)
|
||||
else:
|
||||
widget = ConvertorItemCardWidget(item, self)
|
||||
widget.selected.connect(self._on_widget_selection)
|
||||
self._widgets_by_id[item.id] = widget
|
||||
self._content_layout.insertWidget(widget_idx, widget)
|
||||
widget_idx += 1
|
||||
|
||||
self._update_ordered_item_ids()
|
||||
|
||||
|
||||
class InstanceGroupWidget(BaseGroupWidget):
|
||||
"""Widget wrapping instances under group."""
|
||||
|
||||
active_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, group_icons, *args, **kwargs):
|
||||
super(InstanceGroupWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
self._group_icons = group_icons
|
||||
|
||||
def update_icons(self, group_icons):
|
||||
self._group_icons = group_icons
|
||||
|
||||
def update_instance_values(self):
|
||||
"""Trigger update on instance widgets."""
|
||||
|
|
@ -153,14 +223,6 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
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)
|
||||
widget.deleteLater()
|
||||
|
||||
def update_instances(self, instances):
|
||||
"""Update instances for the group.
|
||||
|
||||
|
|
@ -178,17 +240,7 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
instances_by_subset_name[subset_name].append(instance)
|
||||
|
||||
# Remove instance widgets that are not in passed instances
|
||||
for instance_id in tuple(self._widgets_by_id.keys()):
|
||||
if instance_id in instances_by_id:
|
||||
continue
|
||||
|
||||
widget = self._widgets_by_id.pop(instance_id)
|
||||
if widget.is_selected:
|
||||
self.removed_selected.emit()
|
||||
|
||||
widget.setVisible(False)
|
||||
self._content_layout.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
self._remove_all_except(instances_by_id.keys())
|
||||
|
||||
# Sort instances by subset name
|
||||
sorted_subset_names = list(sorted(instances_by_subset_name.keys()))
|
||||
|
|
@ -211,18 +263,7 @@ class GroupWidget(QtWidgets.QWidget):
|
|||
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)
|
||||
self._update_ordered_item_ids()
|
||||
|
||||
|
||||
class CardWidget(BaseClickableFrame):
|
||||
|
|
@ -284,7 +325,7 @@ class ContextCardWidget(CardWidget):
|
|||
super(ContextCardWidget, self).__init__(parent)
|
||||
|
||||
self._id = CONTEXT_ID
|
||||
self._group_identifier = ""
|
||||
self._group_identifier = CONTEXT_GROUP
|
||||
|
||||
icon_widget = PublishPixmapLabel(None, self)
|
||||
icon_widget.setObjectName("FamilyIconLabel")
|
||||
|
|
@ -304,6 +345,40 @@ class ContextCardWidget(CardWidget):
|
|||
self._label_widget = label_widget
|
||||
|
||||
|
||||
class ConvertorItemCardWidget(CardWidget):
|
||||
"""Card for global context.
|
||||
|
||||
Is not visually under group widget and is always at the top of card view.
|
||||
"""
|
||||
|
||||
def __init__(self, item, parent):
|
||||
super(ConvertorItemCardWidget, self).__init__(parent)
|
||||
|
||||
self._id = item.id
|
||||
self.identifier = item.identifier
|
||||
self._group_identifier = CONVERTOR_ITEM_GROUP
|
||||
|
||||
icon_widget = IconValuePixmapLabel("fa.magic", self)
|
||||
icon_widget.setObjectName("FamilyIconLabel")
|
||||
|
||||
label_widget = QtWidgets.QLabel(item.label, self)
|
||||
|
||||
icon_layout = QtWidgets.QHBoxLayout()
|
||||
icon_layout.setContentsMargins(10, 5, 5, 5)
|
||||
icon_layout.addWidget(icon_widget)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 5, 10, 5)
|
||||
layout.addLayout(icon_layout, 0)
|
||||
layout.addWidget(label_widget, 1)
|
||||
|
||||
self._icon_widget = icon_widget
|
||||
self._label_widget = label_widget
|
||||
|
||||
def update_instance_values(self):
|
||||
pass
|
||||
|
||||
|
||||
class InstanceCardWidget(CardWidget):
|
||||
"""Card widget representing instance."""
|
||||
|
||||
|
|
@ -481,6 +556,7 @@ class InstanceCardView(AbstractInstanceView):
|
|||
self._content_widget = content_widget
|
||||
|
||||
self._context_widget = None
|
||||
self._convertor_items_group = None
|
||||
self._widgets_by_group = {}
|
||||
self._ordered_groups = []
|
||||
|
||||
|
|
@ -513,6 +589,9 @@ class InstanceCardView(AbstractInstanceView):
|
|||
):
|
||||
output.append(self._context_widget)
|
||||
|
||||
if self._convertor_items_group is not None:
|
||||
output.extend(self._convertor_items_group.get_selected_widgets())
|
||||
|
||||
for group_widget in self._widgets_by_group.values():
|
||||
for widget in group_widget.get_selected_widgets():
|
||||
output.append(widget)
|
||||
|
|
@ -526,23 +605,19 @@ class InstanceCardView(AbstractInstanceView):
|
|||
):
|
||||
output.append(CONTEXT_ID)
|
||||
|
||||
if self._convertor_items_group is not None:
|
||||
output.extend(self._convertor_items_group.get_selected_item_ids())
|
||||
|
||||
for group_widget in self._widgets_by_group.values():
|
||||
output.extend(group_widget.get_selected_instance_ids())
|
||||
output.extend(group_widget.get_selected_item_ids())
|
||||
return output
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh instances in view based on CreatedContext."""
|
||||
# Create context item if is not already existing
|
||||
# - this must be as first thing to do as context item should be at the
|
||||
# top
|
||||
if self._context_widget is None:
|
||||
widget = ContextCardWidget(self._content_widget)
|
||||
widget.selected.connect(self._on_widget_selection)
|
||||
|
||||
self._context_widget = widget
|
||||
self._make_sure_context_widget_exists()
|
||||
|
||||
self.selection_changed.emit()
|
||||
self._content_layout.insertWidget(0, widget)
|
||||
self._update_convertor_items_group()
|
||||
|
||||
# Prepare instances by group and identifiers by group
|
||||
instances_by_group = collections.defaultdict(list)
|
||||
|
|
@ -573,17 +648,21 @@ class InstanceCardView(AbstractInstanceView):
|
|||
# Keep track of widget indexes
|
||||
# - we start with 1 because Context item as at the top
|
||||
widget_idx = 1
|
||||
if self._convertor_items_group is not None:
|
||||
widget_idx += 1
|
||||
|
||||
for group_name in sorted_group_names:
|
||||
group_icons = {
|
||||
idenfier: self._controller.get_creator_icon(idenfier)
|
||||
for idenfier in identifiers_by_group[group_name]
|
||||
}
|
||||
if group_name in self._widgets_by_group:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
else:
|
||||
group_icons = {
|
||||
idenfier: self._controller.get_creator_icon(idenfier)
|
||||
for idenfier in identifiers_by_group[group_name]
|
||||
}
|
||||
group_widget.update_icons(group_icons)
|
||||
|
||||
group_widget = GroupWidget(
|
||||
group_name, group_icons, self._content_widget
|
||||
else:
|
||||
group_widget = InstanceGroupWidget(
|
||||
group_icons, group_name, self._content_widget
|
||||
)
|
||||
group_widget.active_changed.connect(self._on_active_changed)
|
||||
group_widget.selected.connect(self._on_widget_selection)
|
||||
|
|
@ -595,7 +674,10 @@ class InstanceCardView(AbstractInstanceView):
|
|||
instances_by_group[group_name]
|
||||
)
|
||||
|
||||
ordered_group_names = [""]
|
||||
self._update_ordered_group_nameS()
|
||||
|
||||
def _update_ordered_group_nameS(self):
|
||||
ordered_group_names = [CONTEXT_GROUP]
|
||||
for idx in range(self._content_layout.count()):
|
||||
if idx > 0:
|
||||
item = self._content_layout.itemAt(idx)
|
||||
|
|
@ -605,6 +687,43 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
self._ordered_groups = ordered_group_names
|
||||
|
||||
def _make_sure_context_widget_exists(self):
|
||||
# Create context item if is not already existing
|
||||
# - this must be as first thing to do as context item should be at the
|
||||
# top
|
||||
if self._context_widget is not None:
|
||||
return
|
||||
|
||||
widget = ContextCardWidget(self._content_widget)
|
||||
widget.selected.connect(self._on_widget_selection)
|
||||
|
||||
self._context_widget = widget
|
||||
|
||||
self.selection_changed.emit()
|
||||
self._content_layout.insertWidget(0, widget)
|
||||
|
||||
def _update_convertor_items_group(self):
|
||||
convertor_items = self._controller.convertor_items
|
||||
if not convertor_items and self._convertor_items_group is None:
|
||||
return
|
||||
|
||||
if not convertor_items:
|
||||
self._convertor_items_group.setVisible(False)
|
||||
self._content_layout.removeWidget(self._convertor_items_group)
|
||||
self._convertor_items_group.deleteLater()
|
||||
self._convertor_items_group = None
|
||||
return
|
||||
|
||||
if self._convertor_items_group is None:
|
||||
group_widget = ConvertorItemsGroupWidget(
|
||||
CONVERTOR_ITEM_GROUP, self._content_widget
|
||||
)
|
||||
group_widget.selected.connect(self._on_widget_selection)
|
||||
self._content_layout.insertWidget(1, group_widget)
|
||||
self._convertor_items_group = group_widget
|
||||
|
||||
self._convertor_items_group.update_items(convertor_items)
|
||||
|
||||
def refresh_instance_states(self):
|
||||
"""Trigger update of instances on group widgets."""
|
||||
for widget in self._widgets_by_group.values():
|
||||
|
|
@ -621,9 +740,13 @@ class InstanceCardView(AbstractInstanceView):
|
|||
"""
|
||||
if instance_id == CONTEXT_ID:
|
||||
new_widget = self._context_widget
|
||||
|
||||
else:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
new_widget = group_widget.get_widget_by_instance_id(instance_id)
|
||||
if group_name == CONVERTOR_ITEM_GROUP:
|
||||
group_widget = self._convertor_items_group
|
||||
else:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
new_widget = group_widget.get_widget_by_item_id(instance_id)
|
||||
|
||||
if selection_type is SelectionTypes.clear:
|
||||
self._select_item_clear(instance_id, group_name, new_widget)
|
||||
|
|
@ -668,7 +791,10 @@ class InstanceCardView(AbstractInstanceView):
|
|||
if instance_id == CONTEXT_ID:
|
||||
remove_group = True
|
||||
else:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
if group_name == CONVERTOR_ITEM_GROUP:
|
||||
group_widget = self._convertor_items_group
|
||||
else:
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
if not group_widget.get_selected_widgets():
|
||||
remove_group = True
|
||||
|
||||
|
|
@ -749,7 +875,7 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
# If start group is not set then use context item group name
|
||||
if start_group is None:
|
||||
start_group = ""
|
||||
start_group = CONTEXT_GROUP
|
||||
|
||||
# If start instance id is not filled then use context id (similar to
|
||||
# group)
|
||||
|
|
@ -777,10 +903,13 @@ class InstanceCardView(AbstractInstanceView):
|
|||
# Go through ordered groups (from top to bottom) and change selection
|
||||
for name in self._ordered_groups:
|
||||
# Prepare sorted instance widgets
|
||||
if name == "":
|
||||
if name == CONTEXT_GROUP:
|
||||
sorted_widgets = [self._context_widget]
|
||||
else:
|
||||
group_widget = self._widgets_by_group[name]
|
||||
if name == CONVERTOR_ITEM_GROUP:
|
||||
group_widget = self._convertor_items_group
|
||||
else:
|
||||
group_widget = self._widgets_by_group[name]
|
||||
sorted_widgets = group_widget.get_ordered_widgets()
|
||||
|
||||
# Change selection based on explicit selection if start group
|
||||
|
|
@ -892,6 +1021,8 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
def get_selected_items(self):
|
||||
"""Get selected instance ids and context."""
|
||||
|
||||
convertor_identifiers = []
|
||||
instances = []
|
||||
selected_widgets = self._get_selected_widgets()
|
||||
|
||||
|
|
@ -899,37 +1030,56 @@ class InstanceCardView(AbstractInstanceView):
|
|||
for widget in selected_widgets:
|
||||
if widget is self._context_widget:
|
||||
context_selected = True
|
||||
else:
|
||||
|
||||
elif isinstance(widget, InstanceCardWidget):
|
||||
instances.append(widget.id)
|
||||
|
||||
return instances, context_selected
|
||||
elif isinstance(widget, ConvertorItemCardWidget):
|
||||
convertor_identifiers.append(widget.identifier)
|
||||
|
||||
def set_selected_items(self, instance_ids, context_selected):
|
||||
return instances, context_selected, convertor_identifiers
|
||||
|
||||
def set_selected_items(
|
||||
self, instance_ids, context_selected, convertor_identifiers
|
||||
):
|
||||
s_instance_ids = set(instance_ids)
|
||||
cur_ids, cur_context = self.get_selected_items()
|
||||
s_convertor_identifiers = set(convertor_identifiers)
|
||||
cur_ids, cur_context, cur_convertor_identifiers = (
|
||||
self.get_selected_items()
|
||||
)
|
||||
if (
|
||||
set(cur_ids) == s_instance_ids
|
||||
and cur_context == context_selected
|
||||
and set(cur_convertor_identifiers) == s_convertor_identifiers
|
||||
):
|
||||
return
|
||||
|
||||
selected_groups = []
|
||||
selected_instances = []
|
||||
if context_selected:
|
||||
selected_groups.append("")
|
||||
selected_groups.append(CONTEXT_GROUP)
|
||||
selected_instances.append(CONTEXT_ID)
|
||||
|
||||
self._context_widget.set_selected(context_selected)
|
||||
|
||||
for group_name in self._ordered_groups:
|
||||
if group_name == "":
|
||||
if group_name == CONTEXT_GROUP:
|
||||
continue
|
||||
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
is_convertor_group = group_name == CONVERTOR_ITEM_GROUP
|
||||
if is_convertor_group:
|
||||
group_widget = self._convertor_items_group
|
||||
else:
|
||||
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:
|
||||
if is_convertor_group:
|
||||
is_in = widget.identifier in s_convertor_identifiers
|
||||
else:
|
||||
is_in = widget.id in s_instance_ids
|
||||
if is_in:
|
||||
selected_instances.append(widget.id)
|
||||
group_selected = True
|
||||
select = True
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ from ..constants import (
|
|||
SORT_VALUE_ROLE,
|
||||
IS_GROUP_ROLE,
|
||||
CONTEXT_ID,
|
||||
CONTEXT_LABEL
|
||||
CONTEXT_LABEL,
|
||||
GROUP_ROLE,
|
||||
CONVERTER_IDENTIFIER_ROLE,
|
||||
CONVERTOR_ITEM_GROUP,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -330,6 +333,9 @@ class InstanceTreeView(QtWidgets.QTreeView):
|
|||
"""Ids of selected instances."""
|
||||
instance_ids = set()
|
||||
for index in self.selectionModel().selectedIndexes():
|
||||
if index.data(CONVERTER_IDENTIFIER_ROLE) is not None:
|
||||
continue
|
||||
|
||||
instance_id = index.data(INSTANCE_ID_ROLE)
|
||||
if instance_id is not None:
|
||||
instance_ids.add(instance_id)
|
||||
|
|
@ -439,26 +445,35 @@ class InstanceListView(AbstractInstanceView):
|
|||
self._group_items = {}
|
||||
self._group_widgets = {}
|
||||
self._widgets_by_id = {}
|
||||
# Group by instance id for handling of active state
|
||||
self._group_by_instance_id = {}
|
||||
self._context_item = None
|
||||
self._context_widget = None
|
||||
|
||||
self._convertor_group_item = None
|
||||
self._convertor_group_widget = None
|
||||
self._convertor_items_by_id = {}
|
||||
|
||||
self._instance_view = instance_view
|
||||
self._instance_delegate = instance_delegate
|
||||
self._instance_model = instance_model
|
||||
self._proxy_model = proxy_model
|
||||
|
||||
def _on_expand(self, index):
|
||||
group_name = index.data(SORT_VALUE_ROLE)
|
||||
group_widget = self._group_widgets.get(group_name)
|
||||
if group_widget:
|
||||
group_widget.set_expanded(True)
|
||||
self._update_widget_expand_state(index, True)
|
||||
|
||||
def _on_collapse(self, index):
|
||||
group_name = index.data(SORT_VALUE_ROLE)
|
||||
group_widget = self._group_widgets.get(group_name)
|
||||
self._update_widget_expand_state(index, False)
|
||||
|
||||
def _update_widget_expand_state(self, index, expanded):
|
||||
group_name = index.data(GROUP_ROLE)
|
||||
if group_name == CONVERTOR_ITEM_GROUP:
|
||||
group_widget = self._convertor_group_widget
|
||||
else:
|
||||
group_widget = self._group_widgets.get(group_name)
|
||||
|
||||
if group_widget:
|
||||
group_widget.set_expanded(False)
|
||||
group_widget.set_expanded(expanded)
|
||||
|
||||
def _on_toggle_request(self, toggle):
|
||||
selected_instance_ids = self._instance_view.get_selected_instance_ids()
|
||||
|
|
@ -517,6 +532,16 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
def refresh(self):
|
||||
"""Refresh instances in the view."""
|
||||
# Sort view at the end of refresh
|
||||
# - is turned off until any change in view happens
|
||||
sort_at_the_end = False
|
||||
# Create or use already existing context item
|
||||
# - context widget does not change so we don't have to update anything
|
||||
if self._make_sure_context_item_exists():
|
||||
sort_at_the_end = True
|
||||
|
||||
self._update_convertor_items_group()
|
||||
|
||||
# Prepare instances by their groups
|
||||
instances_by_group_name = collections.defaultdict(list)
|
||||
group_names = set()
|
||||
|
|
@ -525,75 +550,12 @@ class InstanceListView(AbstractInstanceView):
|
|||
group_names.add(group_label)
|
||||
instances_by_group_name[group_label].append(instance)
|
||||
|
||||
# Sort view at the end of refresh
|
||||
# - is turned off until any change in view happens
|
||||
sort_at_the_end = False
|
||||
|
||||
# Access to root item of main model
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
|
||||
# Create or use already existing context item
|
||||
# - context widget does not change so we don't have to update anything
|
||||
context_item = None
|
||||
if self._context_item is None:
|
||||
sort_at_the_end = True
|
||||
context_item = QtGui.QStandardItem()
|
||||
context_item.setData(0, SORT_VALUE_ROLE)
|
||||
context_item.setData(CONTEXT_ID, INSTANCE_ID_ROLE)
|
||||
|
||||
root_item.appendRow(context_item)
|
||||
|
||||
index = self._instance_model.index(
|
||||
context_item.row(), context_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(index)
|
||||
widget = ListContextWidget(self._instance_view)
|
||||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
|
||||
self._context_widget = widget
|
||||
self._context_item = context_item
|
||||
|
||||
# Create new groups based on prepared `instances_by_group_name`
|
||||
new_group_items = []
|
||||
for group_name in group_names:
|
||||
if group_name in self._group_items:
|
||||
continue
|
||||
|
||||
group_item = QtGui.QStandardItem()
|
||||
group_item.setData(group_name, SORT_VALUE_ROLE)
|
||||
group_item.setData(True, IS_GROUP_ROLE)
|
||||
group_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
self._group_items[group_name] = group_item
|
||||
new_group_items.append(group_item)
|
||||
|
||||
# Add new group items to root item if there are any
|
||||
if new_group_items:
|
||||
# Trigger sort at the end
|
||||
if self._make_sure_groups_exists(group_names):
|
||||
sort_at_the_end = True
|
||||
root_item.appendRows(new_group_items)
|
||||
|
||||
# Create widget for each new group item and store it for future usage
|
||||
for group_item in new_group_items:
|
||||
index = self._instance_model.index(
|
||||
group_item.row(), group_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(index)
|
||||
group_name = group_item.data(SORT_VALUE_ROLE)
|
||||
widget = InstanceListGroupWidget(group_name, self._instance_view)
|
||||
widget.expand_changed.connect(self._on_group_expand_request)
|
||||
widget.toggle_requested.connect(self._on_group_toggle_request)
|
||||
self._group_widgets[group_name] = widget
|
||||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
|
||||
# Remove groups that are not available anymore
|
||||
for group_name in tuple(self._group_items.keys()):
|
||||
if group_name in group_names:
|
||||
continue
|
||||
|
||||
group_item = self._group_items.pop(group_name)
|
||||
root_item.removeRow(group_item.row())
|
||||
widget = self._group_widgets.pop(group_name)
|
||||
widget.deleteLater()
|
||||
self._remove_groups_except(group_names)
|
||||
|
||||
# Store which groups should be expanded at the end
|
||||
expand_groups = set()
|
||||
|
|
@ -652,6 +614,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
# Create new item and store it as new
|
||||
item = QtGui.QStandardItem()
|
||||
item.setData(instance["subset"], SORT_VALUE_ROLE)
|
||||
item.setData(instance["subset"], GROUP_ROLE)
|
||||
item.setData(instance_id, INSTANCE_ID_ROLE)
|
||||
new_items.append(item)
|
||||
new_items_with_instance.append((item, instance))
|
||||
|
|
@ -717,13 +680,152 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
self._instance_view.expand(proxy_index)
|
||||
|
||||
def _make_sure_context_item_exists(self):
|
||||
if self._context_item is not None:
|
||||
return False
|
||||
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
context_item = QtGui.QStandardItem()
|
||||
context_item.setData(0, SORT_VALUE_ROLE)
|
||||
context_item.setData(CONTEXT_ID, INSTANCE_ID_ROLE)
|
||||
|
||||
root_item.appendRow(context_item)
|
||||
|
||||
index = self._instance_model.index(
|
||||
context_item.row(), context_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(index)
|
||||
widget = ListContextWidget(self._instance_view)
|
||||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
|
||||
self._context_widget = widget
|
||||
self._context_item = context_item
|
||||
return True
|
||||
|
||||
def _update_convertor_items_group(self):
|
||||
created_new_items = False
|
||||
convertor_items_by_id = self._controller.convertor_items
|
||||
group_item = self._convertor_group_item
|
||||
if not convertor_items_by_id and group_item is None:
|
||||
return created_new_items
|
||||
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
if not convertor_items_by_id:
|
||||
root_item.removeRow(group_item.row())
|
||||
self._convertor_group_widget.deleteLater()
|
||||
self._convertor_group_widget = None
|
||||
self._convertor_items_by_id = {}
|
||||
return created_new_items
|
||||
|
||||
if group_item is None:
|
||||
created_new_items = True
|
||||
group_item = QtGui.QStandardItem()
|
||||
group_item.setData(CONVERTOR_ITEM_GROUP, GROUP_ROLE)
|
||||
group_item.setData(1, SORT_VALUE_ROLE)
|
||||
group_item.setData(True, IS_GROUP_ROLE)
|
||||
group_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
root_item.appendRow(group_item)
|
||||
|
||||
index = self._instance_model.index(
|
||||
group_item.row(), group_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(index)
|
||||
widget = InstanceListGroupWidget(
|
||||
CONVERTOR_ITEM_GROUP, self._instance_view
|
||||
)
|
||||
widget.toggle_checkbox.setVisible(False)
|
||||
widget.expand_changed.connect(
|
||||
self._on_convertor_group_expand_request
|
||||
)
|
||||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
|
||||
self._convertor_group_item = group_item
|
||||
self._convertor_group_widget = widget
|
||||
|
||||
for row in reversed(range(group_item.rowCount())):
|
||||
child_item = group_item.child(row)
|
||||
child_identifier = child_item.data(CONVERTER_IDENTIFIER_ROLE)
|
||||
if child_identifier not in convertor_items_by_id:
|
||||
self._convertor_items_by_id.pop(child_identifier, None)
|
||||
group_item.removeRows(row, 1)
|
||||
|
||||
new_items = []
|
||||
for identifier, convertor_item in convertor_items_by_id.items():
|
||||
item = self._convertor_items_by_id.get(identifier)
|
||||
if item is None:
|
||||
created_new_items = True
|
||||
item = QtGui.QStandardItem(convertor_item.label)
|
||||
new_items.append(item)
|
||||
item.setData(convertor_item.id, INSTANCE_ID_ROLE)
|
||||
item.setData(convertor_item.label, SORT_VALUE_ROLE)
|
||||
item.setData(CONVERTOR_ITEM_GROUP, GROUP_ROLE)
|
||||
item.setData(
|
||||
convertor_item.identifier, CONVERTER_IDENTIFIER_ROLE
|
||||
)
|
||||
self._convertor_items_by_id[identifier] = item
|
||||
|
||||
if new_items:
|
||||
group_item.appendRows(new_items)
|
||||
|
||||
return created_new_items
|
||||
|
||||
def _make_sure_groups_exists(self, group_names):
|
||||
new_group_items = []
|
||||
for group_name in group_names:
|
||||
if group_name in self._group_items:
|
||||
continue
|
||||
|
||||
group_item = QtGui.QStandardItem()
|
||||
group_item.setData(group_name, GROUP_ROLE)
|
||||
group_item.setData(group_name, SORT_VALUE_ROLE)
|
||||
group_item.setData(True, IS_GROUP_ROLE)
|
||||
group_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
self._group_items[group_name] = group_item
|
||||
new_group_items.append(group_item)
|
||||
|
||||
# Add new group items to root item if there are any
|
||||
if not new_group_items:
|
||||
return False
|
||||
|
||||
# Access to root item of main model
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
root_item.appendRows(new_group_items)
|
||||
|
||||
# Create widget for each new group item and store it for future usage
|
||||
for group_item in new_group_items:
|
||||
index = self._instance_model.index(
|
||||
group_item.row(), group_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(index)
|
||||
group_name = group_item.data(GROUP_ROLE)
|
||||
widget = InstanceListGroupWidget(group_name, self._instance_view)
|
||||
widget.expand_changed.connect(self._on_group_expand_request)
|
||||
widget.toggle_requested.connect(self._on_group_toggle_request)
|
||||
self._group_widgets[group_name] = widget
|
||||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
|
||||
return True
|
||||
|
||||
def _remove_groups_except(self, group_names):
|
||||
# Remove groups that are not available anymore
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
for group_name in tuple(self._group_items.keys()):
|
||||
if group_name in group_names:
|
||||
continue
|
||||
|
||||
group_item = self._group_items.pop(group_name)
|
||||
root_item.removeRow(group_item.row())
|
||||
widget = self._group_widgets.pop(group_name)
|
||||
widget.deleteLater()
|
||||
|
||||
def refresh_instance_states(self):
|
||||
"""Trigger update of all instances."""
|
||||
for widget in self._widgets_by_id.values():
|
||||
widget.update_instance_values()
|
||||
|
||||
def _on_active_changed(self, changed_instance_id, new_value):
|
||||
selected_instance_ids, _ = self.get_selected_items()
|
||||
selected_instance_ids, _, _ = self.get_selected_items()
|
||||
|
||||
selected_ids = set()
|
||||
found = False
|
||||
|
|
@ -774,6 +876,16 @@ class InstanceListView(AbstractInstanceView):
|
|||
proxy_index = self._proxy_model.mapFromSource(group_index)
|
||||
self._instance_view.setExpanded(proxy_index, expanded)
|
||||
|
||||
def _on_convertor_group_expand_request(self, _, expanded):
|
||||
group_item = self._convertor_group_item
|
||||
if not group_item:
|
||||
return
|
||||
group_index = self._instance_model.index(
|
||||
group_item.row(), group_item.column()
|
||||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(group_index)
|
||||
self._instance_view.setExpanded(proxy_index, expanded)
|
||||
|
||||
def _on_group_toggle_request(self, group_name, state):
|
||||
if state == QtCore.Qt.PartiallyChecked:
|
||||
return
|
||||
|
|
@ -807,10 +919,17 @@ class InstanceListView(AbstractInstanceView):
|
|||
tuple<list, bool>: Selected instance ids and boolean if context
|
||||
is selected.
|
||||
"""
|
||||
|
||||
instance_ids = []
|
||||
convertor_identifiers = []
|
||||
context_selected = False
|
||||
|
||||
for index in self._instance_view.selectionModel().selectedIndexes():
|
||||
convertor_identifier = index.data(CONVERTER_IDENTIFIER_ROLE)
|
||||
if convertor_identifier is not None:
|
||||
convertor_identifiers.append(convertor_identifier)
|
||||
continue
|
||||
|
||||
instance_id = index.data(INSTANCE_ID_ROLE)
|
||||
if not context_selected and instance_id == CONTEXT_ID:
|
||||
context_selected = True
|
||||
|
|
@ -818,14 +937,20 @@ class InstanceListView(AbstractInstanceView):
|
|||
elif instance_id is not None:
|
||||
instance_ids.append(instance_id)
|
||||
|
||||
return instance_ids, context_selected
|
||||
return instance_ids, context_selected, convertor_identifiers
|
||||
|
||||
def set_selected_items(self, instance_ids, context_selected):
|
||||
def set_selected_items(
|
||||
self, instance_ids, context_selected, convertor_identifiers
|
||||
):
|
||||
s_instance_ids = set(instance_ids)
|
||||
cur_ids, cur_context = self.get_selected_items()
|
||||
s_convertor_identifiers = set(convertor_identifiers)
|
||||
cur_ids, cur_context, cur_convertor_identifiers = (
|
||||
self.get_selected_items()
|
||||
)
|
||||
if (
|
||||
set(cur_ids) == s_instance_ids
|
||||
and cur_context == context_selected
|
||||
and set(cur_convertor_identifiers) == s_convertor_identifiers
|
||||
):
|
||||
return
|
||||
|
||||
|
|
@ -851,20 +976,35 @@ class InstanceListView(AbstractInstanceView):
|
|||
(item.child(row), list(new_parent_items))
|
||||
)
|
||||
|
||||
instance_id = item.data(INSTANCE_ID_ROLE)
|
||||
if not instance_id:
|
||||
convertor_identifier = item.data(CONVERTER_IDENTIFIER_ROLE)
|
||||
|
||||
select = False
|
||||
expand_parent = True
|
||||
if convertor_identifier is not None:
|
||||
if convertor_identifier in s_convertor_identifiers:
|
||||
select = True
|
||||
else:
|
||||
instance_id = item.data(INSTANCE_ID_ROLE)
|
||||
if instance_id == CONTEXT_ID:
|
||||
if context_selected:
|
||||
select = True
|
||||
expand_parent = False
|
||||
|
||||
elif instance_id in s_instance_ids:
|
||||
select = True
|
||||
|
||||
if not select:
|
||||
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)
|
||||
select_indexes.append(item.index())
|
||||
if not expand_parent:
|
||||
continue
|
||||
|
||||
elif context_selected and instance_id == CONTEXT_ID:
|
||||
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)
|
||||
|
||||
selection_model = view.selectionModel()
|
||||
if not select_indexes:
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
subset_attributes_widget.instance_context_changed.connect(
|
||||
self._on_instance_context_change
|
||||
)
|
||||
subset_attributes_widget.convert_requested.connect(
|
||||
self._on_convert_requested
|
||||
)
|
||||
|
||||
# --- Controller callbacks ---
|
||||
controller.event_system.add_callback(
|
||||
|
|
@ -201,7 +204,7 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
self.create_requested.emit()
|
||||
|
||||
def _on_delete_clicked(self):
|
||||
instance_ids, _ = self.get_selected_items()
|
||||
instance_ids, _, _ = self.get_selected_items()
|
||||
|
||||
# Ask user if he really wants to remove instances
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
|
|
@ -235,7 +238,9 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
if self._refreshing_instances:
|
||||
return
|
||||
|
||||
instance_ids, context_selected = self.get_selected_items()
|
||||
instance_ids, context_selected, convertor_identifiers = (
|
||||
self.get_selected_items()
|
||||
)
|
||||
|
||||
# Disable delete button if nothing is selected
|
||||
self._delete_btn.setEnabled(len(instance_ids) > 0)
|
||||
|
|
@ -246,7 +251,7 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
for instance_id in instance_ids
|
||||
]
|
||||
self._subset_attributes_widget.set_current_instances(
|
||||
instances, context_selected
|
||||
instances, context_selected, convertor_identifiers
|
||||
)
|
||||
|
||||
def _on_active_changed(self):
|
||||
|
|
@ -315,6 +320,10 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
|
||||
self.instance_context_changed.emit()
|
||||
|
||||
def _on_convert_requested(self):
|
||||
_, _, convertor_identifiers = self.get_selected_items()
|
||||
self._controller.trigger_convertor_items(convertor_identifiers)
|
||||
|
||||
def get_selected_items(self):
|
||||
view = self._subset_views_layout.currentWidget()
|
||||
return view.get_selected_items()
|
||||
|
|
@ -332,8 +341,12 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
else:
|
||||
new_view.refresh_instance_states()
|
||||
|
||||
instance_ids, context_selected = old_view.get_selected_items()
|
||||
new_view.set_selected_items(instance_ids, context_selected)
|
||||
instance_ids, context_selected, convertor_identifiers = (
|
||||
old_view.get_selected_items()
|
||||
)
|
||||
new_view.set_selected_items(
|
||||
instance_ids, context_selected, convertor_identifiers
|
||||
)
|
||||
|
||||
self._subset_views_layout.setCurrentIndex(new_idx)
|
||||
|
||||
|
|
|
|||
|
|
@ -1461,6 +1461,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
└───────────────────────────────┘
|
||||
"""
|
||||
instance_context_changed = QtCore.Signal()
|
||||
convert_requested = QtCore.Signal()
|
||||
|
||||
def __init__(self, controller, parent):
|
||||
super(SubsetAttributesWidget, self).__init__(parent)
|
||||
|
|
@ -1479,9 +1480,53 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
|
||||
# BOTTOM PART
|
||||
bottom_widget = QtWidgets.QWidget(self)
|
||||
creator_attrs_widget = CreatorAttrsWidget(
|
||||
controller, bottom_widget
|
||||
|
||||
# Wrap Creator attributes to widget to be able add convert button
|
||||
creator_widget = QtWidgets.QWidget(bottom_widget)
|
||||
|
||||
# Convert button widget (with layout to handle stretch)
|
||||
convert_widget = QtWidgets.QWidget(creator_widget)
|
||||
convert_label = QtWidgets.QLabel(creator_widget)
|
||||
# Set the label text with 'setText' to apply html
|
||||
convert_label.setText(
|
||||
(
|
||||
"Found old publishable subsets"
|
||||
" incompatible with new publisher."
|
||||
"<br/><br/>Press the <b>update subsets</b> button"
|
||||
" to automatically update them"
|
||||
" to be able to publish again."
|
||||
)
|
||||
)
|
||||
convert_label.setWordWrap(True)
|
||||
convert_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
convert_btn = QtWidgets.QPushButton(
|
||||
"Update subsets", convert_widget
|
||||
)
|
||||
convert_separator = QtWidgets.QFrame(convert_widget)
|
||||
convert_separator.setObjectName("Separator")
|
||||
convert_separator.setMinimumHeight(1)
|
||||
convert_separator.setMaximumHeight(1)
|
||||
|
||||
convert_layout = QtWidgets.QGridLayout(convert_widget)
|
||||
convert_layout.setContentsMargins(5, 0, 5, 0)
|
||||
convert_layout.setVerticalSpacing(10)
|
||||
convert_layout.addWidget(convert_label, 0, 0, 1, 3)
|
||||
convert_layout.addWidget(convert_btn, 1, 1)
|
||||
convert_layout.addWidget(convert_separator, 2, 0, 1, 3)
|
||||
convert_layout.setColumnStretch(0, 1)
|
||||
convert_layout.setColumnStretch(1, 0)
|
||||
convert_layout.setColumnStretch(2, 1)
|
||||
|
||||
# Creator attributes widget
|
||||
creator_attrs_widget = CreatorAttrsWidget(
|
||||
controller, creator_widget
|
||||
)
|
||||
creator_layout = QtWidgets.QVBoxLayout(creator_widget)
|
||||
creator_layout.setContentsMargins(0, 0, 0, 0)
|
||||
creator_layout.addWidget(convert_widget, 0)
|
||||
creator_layout.addWidget(creator_attrs_widget, 1)
|
||||
|
||||
publish_attrs_widget = PublishPluginAttrsWidget(
|
||||
controller, bottom_widget
|
||||
)
|
||||
|
|
@ -1492,7 +1537,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
|
||||
bottom_layout = QtWidgets.QHBoxLayout(bottom_widget)
|
||||
bottom_layout.setContentsMargins(0, 0, 0, 0)
|
||||
bottom_layout.addWidget(creator_attrs_widget, 1)
|
||||
bottom_layout.addWidget(creator_widget, 1)
|
||||
bottom_layout.addWidget(bottom_separator, 0)
|
||||
bottom_layout.addWidget(publish_attrs_widget, 1)
|
||||
|
||||
|
|
@ -1505,6 +1550,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
layout.addWidget(top_bottom, 0)
|
||||
layout.addWidget(bottom_widget, 1)
|
||||
|
||||
self._convertor_identifiers = None
|
||||
self._current_instances = None
|
||||
self._context_selected = False
|
||||
self._all_instances_valid = True
|
||||
|
|
@ -1512,9 +1558,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
global_attrs_widget.instance_context_changed.connect(
|
||||
self._on_instance_context_changed
|
||||
)
|
||||
convert_btn.clicked.connect(self._on_convert_click)
|
||||
|
||||
self._controller = controller
|
||||
|
||||
self._convert_widget = convert_widget
|
||||
|
||||
self.global_attrs_widget = global_attrs_widget
|
||||
|
||||
self.creator_attrs_widget = creator_attrs_widget
|
||||
|
|
@ -1537,7 +1586,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
|
||||
self.instance_context_changed.emit()
|
||||
|
||||
def set_current_instances(self, instances, context_selected):
|
||||
def _on_convert_click(self):
|
||||
self.convert_requested.emit()
|
||||
|
||||
def set_current_instances(
|
||||
self, instances, context_selected, convertor_identifiers
|
||||
):
|
||||
"""Change currently selected items.
|
||||
|
||||
Args:
|
||||
|
|
@ -1551,10 +1605,13 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
|
|||
all_valid = False
|
||||
break
|
||||
|
||||
s_convertor_identifiers = set(convertor_identifiers)
|
||||
self._convertor_identifiers = s_convertor_identifiers
|
||||
self._current_instances = instances
|
||||
self._context_selected = context_selected
|
||||
self._all_instances_valid = all_valid
|
||||
|
||||
self._convert_widget.setVisible(len(s_convertor_identifiers) > 0)
|
||||
self.global_attrs_widget.set_current_instances(instances)
|
||||
self.creator_attrs_widget.set_current_instances(instances)
|
||||
self.publish_attrs_widget.set_current_instances(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import collections
|
||||
import copy
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from openpype import (
|
||||
|
|
@ -224,10 +225,10 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
# Floating publish frame
|
||||
publish_frame = PublishFrame(controller, self.footer_border, self)
|
||||
|
||||
creators_dialog_message_timer = QtCore.QTimer()
|
||||
creators_dialog_message_timer.setInterval(100)
|
||||
creators_dialog_message_timer.timeout.connect(
|
||||
self._on_creators_message_timeout
|
||||
errors_dialog_message_timer = QtCore.QTimer()
|
||||
errors_dialog_message_timer.setInterval(100)
|
||||
errors_dialog_message_timer.timeout.connect(
|
||||
self._on_errors_message_timeout
|
||||
)
|
||||
|
||||
help_btn.clicked.connect(self._on_help_click)
|
||||
|
|
@ -268,16 +269,22 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
"show.card.message", self._on_overlay_message
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"instances.collection.failed", self._instance_collection_failed
|
||||
"instances.collection.failed", self._on_creator_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"instances.save.failed", self._instance_save_failed
|
||||
"instances.save.failed", self._on_creator_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"instances.remove.failed", self._instance_remove_failed
|
||||
"instances.remove.failed", self._on_creator_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"instances.create.failed", self._instance_create_failed
|
||||
"instances.create.failed", self._on_creator_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"convertors.convert.failed", self._on_convertor_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"convertors.find.failed", self._on_convertor_error
|
||||
)
|
||||
|
||||
# Store extra header widget for TrayPublisher
|
||||
|
|
@ -325,8 +332,8 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
self._restart_timer = None
|
||||
self._publish_frame_visible = None
|
||||
|
||||
self._creators_messages_to_show = collections.deque()
|
||||
self._creators_dialog_message_timer = creators_dialog_message_timer
|
||||
self._error_messages_to_show = collections.deque()
|
||||
self._errors_dialog_message_timer = errors_dialog_message_timer
|
||||
|
||||
self._set_publish_visibility(False)
|
||||
|
||||
|
|
@ -357,7 +364,10 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
self._update_publish_frame_rect()
|
||||
|
||||
def _on_overlay_message(self, event):
|
||||
self._overlay_object.add_message(event["message"])
|
||||
self._overlay_object.add_message(
|
||||
event["message"],
|
||||
event.get("message_type")
|
||||
)
|
||||
|
||||
def _on_first_show(self):
|
||||
self.resize(self.default_width, self.default_height)
|
||||
|
|
@ -604,37 +614,49 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
0, window_size.height() - height
|
||||
)
|
||||
|
||||
def add_message_dialog(self, title, failed_info):
|
||||
self._creators_messages_to_show.append((title, failed_info))
|
||||
self._creators_dialog_message_timer.start()
|
||||
def add_error_message_dialog(self, title, failed_info, message_start=None):
|
||||
self._error_messages_to_show.append(
|
||||
(title, failed_info, message_start)
|
||||
)
|
||||
self._errors_dialog_message_timer.start()
|
||||
|
||||
def _on_creators_message_timeout(self):
|
||||
if not self._creators_messages_to_show:
|
||||
self._creators_dialog_message_timer.stop()
|
||||
def _on_errors_message_timeout(self):
|
||||
if not self._error_messages_to_show:
|
||||
self._errors_dialog_message_timer.stop()
|
||||
return
|
||||
|
||||
item = self._creators_messages_to_show.popleft()
|
||||
title, failed_info = item
|
||||
dialog = CreatorsErrorMessageBox(title, failed_info, self)
|
||||
item = self._error_messages_to_show.popleft()
|
||||
title, failed_info, message_start = item
|
||||
dialog = ErrorsMessageBox(
|
||||
title, failed_info, message_start, self
|
||||
)
|
||||
dialog.exec_()
|
||||
dialog.deleteLater()
|
||||
|
||||
def _instance_collection_failed(self, event):
|
||||
self.add_message_dialog(event["title"], event["failed_info"])
|
||||
def _on_creator_error(self, event):
|
||||
new_failed_info = []
|
||||
for item in event["failed_info"]:
|
||||
new_item = copy.deepcopy(item)
|
||||
new_item["label"] = new_item.pop("creator_label")
|
||||
new_item["identifier"] = new_item.pop("creator_identifier")
|
||||
new_failed_info.append(new_item)
|
||||
self.add_error_message_dialog(event["title"], new_failed_info, "Creator:")
|
||||
|
||||
def _instance_save_failed(self, event):
|
||||
self.add_message_dialog(event["title"], event["failed_info"])
|
||||
|
||||
def _instance_remove_failed(self, event):
|
||||
self.add_message_dialog(event["title"], event["failed_info"])
|
||||
|
||||
def _instance_create_failed(self, event):
|
||||
self.add_message_dialog(event["title"], event["failed_info"])
|
||||
def _on_convertor_error(self, event):
|
||||
new_failed_info = []
|
||||
for item in event["failed_info"]:
|
||||
new_item = copy.deepcopy(item)
|
||||
new_item["identifier"] = new_item.pop("convertor_identifier")
|
||||
new_failed_info.append(new_item)
|
||||
self.add_error_message_dialog(
|
||||
event["title"], new_failed_info, "Convertor:"
|
||||
)
|
||||
|
||||
|
||||
class CreatorsErrorMessageBox(ErrorMessageBox):
|
||||
def __init__(self, error_title, failed_info, parent):
|
||||
class ErrorsMessageBox(ErrorMessageBox):
|
||||
def __init__(self, error_title, failed_info, message_start, parent):
|
||||
self._failed_info = failed_info
|
||||
self._message_start = message_start
|
||||
self._info_with_id = [
|
||||
# Id must be string when used in tab widget
|
||||
{"id": str(idx), "info": info}
|
||||
|
|
@ -644,7 +666,7 @@ class CreatorsErrorMessageBox(ErrorMessageBox):
|
|||
self._tabs_widget = None
|
||||
self._stack_layout = None
|
||||
|
||||
super(CreatorsErrorMessageBox, self).__init__(error_title, parent)
|
||||
super(ErrorsMessageBox, self).__init__(error_title, parent)
|
||||
|
||||
layout = self.layout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -659,17 +681,21 @@ class CreatorsErrorMessageBox(ErrorMessageBox):
|
|||
def _get_report_data(self):
|
||||
output = []
|
||||
for info in self._failed_info:
|
||||
creator_label = info["creator_label"]
|
||||
creator_identifier = info["creator_identifier"]
|
||||
report_message = "Creator:"
|
||||
if creator_label:
|
||||
report_message += " {} ({})".format(
|
||||
creator_label, creator_identifier)
|
||||
item_label = info.get("label")
|
||||
item_identifier = info["identifier"]
|
||||
if item_label:
|
||||
report_message = "{} ({})".format(
|
||||
item_label, item_identifier)
|
||||
else:
|
||||
report_message += " {}".format(creator_identifier)
|
||||
report_message = "{}".format(item_identifier)
|
||||
|
||||
if self._message_start:
|
||||
report_message = "{} {}".format(
|
||||
self._message_start, report_message
|
||||
)
|
||||
|
||||
report_message += "\n\nError: {}".format(info["message"])
|
||||
formatted_traceback = info["traceback"]
|
||||
formatted_traceback = info.get("traceback")
|
||||
if formatted_traceback:
|
||||
report_message += "\n\n{}".format(formatted_traceback)
|
||||
output.append(report_message)
|
||||
|
|
@ -686,11 +712,10 @@ class CreatorsErrorMessageBox(ErrorMessageBox):
|
|||
item_id = item["id"]
|
||||
info = item["info"]
|
||||
message = info["message"]
|
||||
formatted_traceback = info["traceback"]
|
||||
creator_label = info["creator_label"]
|
||||
creator_identifier = info["creator_identifier"]
|
||||
if not creator_label:
|
||||
creator_label = creator_identifier
|
||||
formatted_traceback = info.get("traceback")
|
||||
item_label = info.get("label")
|
||||
if not item_label:
|
||||
item_label = info["identifier"]
|
||||
|
||||
msg_widget = QtWidgets.QWidget(stack_widget)
|
||||
msg_layout = QtWidgets.QVBoxLayout(msg_widget)
|
||||
|
|
@ -710,7 +735,7 @@ class CreatorsErrorMessageBox(ErrorMessageBox):
|
|||
|
||||
msg_layout.addStretch(1)
|
||||
|
||||
tabs_widget.add_tab(creator_label, item_id)
|
||||
tabs_widget.add_tab(item_label, item_id)
|
||||
stack_layout.addWidget(msg_widget)
|
||||
if first:
|
||||
first = False
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.14.5"
|
||||
__version__ = "3.14.6-nightly.1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue