mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
5faf287756
21 changed files with 451 additions and 101 deletions
|
|
@ -49,7 +49,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
|
|||
))
|
||||
return
|
||||
|
||||
self.log.info("Last workfile does not exits.")
|
||||
self.log.info("Last workfile does not exist.")
|
||||
|
||||
project_name = self.data["project_name"]
|
||||
asset_name = self.data["asset_name"]
|
||||
|
|
|
|||
|
|
@ -1660,9 +1660,13 @@ def find_free_space_to_paste_nodes(
|
|||
def launch_workfiles_app():
|
||||
'''Function letting start workfiles after start of host
|
||||
'''
|
||||
# get state from settings
|
||||
open_at_start = get_current_project_settings()["nuke"].get(
|
||||
"general", {}).get("open_workfile_at_start")
|
||||
from openpype.lib import (
|
||||
env_value_to_bool
|
||||
)
|
||||
# get all imortant settings
|
||||
open_at_start = env_value_to_bool(
|
||||
env_key="OPENPYPE_WORKFILE_TOOL_ON_START",
|
||||
default=None)
|
||||
|
||||
# return if none is defined
|
||||
if not open_at_start:
|
||||
|
|
|
|||
|
|
@ -1302,10 +1302,18 @@ def _prepare_last_workfile(data, workdir):
|
|||
)
|
||||
data["start_last_workfile"] = start_last_workfile
|
||||
|
||||
workfile_startup = should_workfile_tool_start(
|
||||
project_name, app.host_name, task_name
|
||||
)
|
||||
data["workfile_startup"] = workfile_startup
|
||||
|
||||
# Store boolean as "0"(False) or "1"(True)
|
||||
data["env"]["AVALON_OPEN_LAST_WORKFILE"] = (
|
||||
str(int(bool(start_last_workfile)))
|
||||
)
|
||||
data["env"]["OPENPYPE_WORKFILE_TOOL_ON_START"] = (
|
||||
str(int(bool(workfile_startup)))
|
||||
)
|
||||
|
||||
_sub_msg = "" if start_last_workfile else " not"
|
||||
log.debug(
|
||||
|
|
@ -1344,40 +1352,9 @@ def _prepare_last_workfile(data, workdir):
|
|||
data["last_workfile_path"] = last_workfile_path
|
||||
|
||||
|
||||
def should_start_last_workfile(
|
||||
project_name, host_name, task_name, default_output=False
|
||||
def get_option_from_settings(
|
||||
startup_presets, host_name, task_name, default_output
|
||||
):
|
||||
"""Define if host should start last version workfile if possible.
|
||||
|
||||
Default output is `False`. Can be overriden with environment variable
|
||||
`AVALON_OPEN_LAST_WORKFILE`, valid values without case sensitivity are
|
||||
`"0", "1", "true", "false", "yes", "no"`.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
host_name (str): Name of host which is launched. In avalon's
|
||||
application context it's value stored in app definition under
|
||||
key `"application_dir"`. Is not case sensitive.
|
||||
task_name (str): Name of task which is used for launching the host.
|
||||
Task name is not case sensitive.
|
||||
|
||||
Returns:
|
||||
bool: True if host should start workfile.
|
||||
|
||||
"""
|
||||
|
||||
project_settings = get_project_settings(project_name)
|
||||
startup_presets = (
|
||||
project_settings
|
||||
["global"]
|
||||
["tools"]
|
||||
["Workfiles"]
|
||||
["last_workfile_on_startup"]
|
||||
)
|
||||
|
||||
if not startup_presets:
|
||||
return default_output
|
||||
|
||||
host_name_lowered = host_name.lower()
|
||||
task_name_lowered = task_name.lower()
|
||||
|
||||
|
|
@ -1421,6 +1398,82 @@ def should_start_last_workfile(
|
|||
return default_output
|
||||
|
||||
|
||||
def should_start_last_workfile(
|
||||
project_name, host_name, task_name, default_output=False
|
||||
):
|
||||
"""Define if host should start last version workfile if possible.
|
||||
|
||||
Default output is `False`. Can be overriden with environment variable
|
||||
`AVALON_OPEN_LAST_WORKFILE`, valid values without case sensitivity are
|
||||
`"0", "1", "true", "false", "yes", "no"`.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
host_name (str): Name of host which is launched. In avalon's
|
||||
application context it's value stored in app definition under
|
||||
key `"application_dir"`. Is not case sensitive.
|
||||
task_name (str): Name of task which is used for launching the host.
|
||||
Task name is not case sensitive.
|
||||
|
||||
Returns:
|
||||
bool: True if host should start workfile.
|
||||
|
||||
"""
|
||||
|
||||
project_settings = get_project_settings(project_name)
|
||||
startup_presets = (
|
||||
project_settings
|
||||
["global"]
|
||||
["tools"]
|
||||
["Workfiles"]
|
||||
["last_workfile_on_startup"]
|
||||
)
|
||||
|
||||
if not startup_presets:
|
||||
return default_output
|
||||
|
||||
return get_option_from_settings(
|
||||
startup_presets, host_name, task_name, default_output)
|
||||
|
||||
|
||||
def should_workfile_tool_start(
|
||||
project_name, host_name, task_name, default_output=False
|
||||
):
|
||||
"""Define if host should start workfile tool at host launch.
|
||||
|
||||
Default output is `False`. Can be overriden with environment variable
|
||||
`OPENPYPE_WORKFILE_TOOL_ON_START`, valid values without case sensitivity are
|
||||
`"0", "1", "true", "false", "yes", "no"`.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
host_name (str): Name of host which is launched. In avalon's
|
||||
application context it's value stored in app definition under
|
||||
key `"application_dir"`. Is not case sensitive.
|
||||
task_name (str): Name of task which is used for launching the host.
|
||||
Task name is not case sensitive.
|
||||
|
||||
Returns:
|
||||
bool: True if host should start workfile.
|
||||
|
||||
"""
|
||||
|
||||
project_settings = get_project_settings(project_name)
|
||||
startup_presets = (
|
||||
project_settings
|
||||
["global"]
|
||||
["tools"]
|
||||
["Workfiles"]
|
||||
["open_workfile_tool_on_startup"]
|
||||
)
|
||||
|
||||
if not startup_presets:
|
||||
return default_output
|
||||
|
||||
return get_option_from_settings(
|
||||
startup_presets, host_name, task_name, default_output)
|
||||
|
||||
|
||||
def compile_list_of_regexes(in_list):
|
||||
"""Convert strings in entered list to compiled regex objects."""
|
||||
regexes = list()
|
||||
|
|
|
|||
|
|
@ -11,29 +11,44 @@ from avalon.api import AvalonMongoDB
|
|||
|
||||
|
||||
class AppplicationsAction(BaseAction):
|
||||
"""Application Action class.
|
||||
|
||||
Args:
|
||||
session (ftrack_api.Session): Session where action will be registered.
|
||||
label (str): A descriptive string identifing your action.
|
||||
varaint (str, optional): To group actions together, give them the same
|
||||
label and specify a unique variant per action.
|
||||
identifier (str): An unique identifier for app.
|
||||
description (str): A verbose descriptive text for you action.
|
||||
icon (str): Url path to icon which will be shown in Ftrack web.
|
||||
"""
|
||||
"""Applications Action class."""
|
||||
|
||||
type = "Application"
|
||||
label = "Application action"
|
||||
identifier = "pype_app.{}.".format(str(uuid4()))
|
||||
|
||||
identifier = "openpype_app"
|
||||
_launch_identifier_with_id = None
|
||||
|
||||
icon_url = os.environ.get("OPENPYPE_STATICS_SERVER")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
super(AppplicationsAction, self).__init__(*args, **kwargs)
|
||||
|
||||
self.application_manager = ApplicationManager()
|
||||
self.dbcon = AvalonMongoDB()
|
||||
|
||||
@property
|
||||
def discover_identifier(self):
|
||||
if self._discover_identifier is None:
|
||||
self._discover_identifier = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._discover_identifier
|
||||
|
||||
@property
|
||||
def launch_identifier(self):
|
||||
if self._launch_identifier is None:
|
||||
self._launch_identifier = "{}.*".format(self.identifier)
|
||||
return self._launch_identifier
|
||||
|
||||
@property
|
||||
def launch_identifier_with_id(self):
|
||||
if self._launch_identifier_with_id is None:
|
||||
self._launch_identifier_with_id = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._launch_identifier_with_id
|
||||
|
||||
def construct_requirements_validations(self):
|
||||
# Override validation as this action does not need them
|
||||
return
|
||||
|
|
@ -56,7 +71,7 @@ class AppplicationsAction(BaseAction):
|
|||
" and data.actionIdentifier={0}"
|
||||
" and source.user.username={1}"
|
||||
).format(
|
||||
self.identifier + "*",
|
||||
self.launch_identifier,
|
||||
self.session.api_user
|
||||
)
|
||||
self.session.event_hub.subscribe(
|
||||
|
|
@ -136,12 +151,29 @@ class AppplicationsAction(BaseAction):
|
|||
"label": app.group.label,
|
||||
"variant": app.label,
|
||||
"description": None,
|
||||
"actionIdentifier": self.identifier + app_name,
|
||||
"actionIdentifier": "{}.{}".format(
|
||||
self.launch_identifier_with_id, app_name
|
||||
),
|
||||
"icon": app_icon
|
||||
})
|
||||
|
||||
return items
|
||||
|
||||
def _launch(self, event):
|
||||
event_identifier = event["data"]["actionIdentifier"]
|
||||
# Check if identifier is same
|
||||
# - show message that acion may not be triggered on this machine
|
||||
if event_identifier.startswith(self.launch_identifier_with_id):
|
||||
return BaseAction._launch(self, event)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"message": (
|
||||
"There are running more OpenPype processes"
|
||||
" where Application can be launched."
|
||||
)
|
||||
}
|
||||
|
||||
def launch(self, session, entities, event):
|
||||
"""Callback method for the custom action.
|
||||
|
||||
|
|
@ -162,7 +194,8 @@ class AppplicationsAction(BaseAction):
|
|||
*event* the unmodified original event
|
||||
"""
|
||||
identifier = event["data"]["actionIdentifier"]
|
||||
app_name = identifier[len(self.identifier):]
|
||||
id_identifier_len = len(self.launch_identifier_with_id) + 1
|
||||
app_name = identifier[id_identifier_len:]
|
||||
|
||||
entity = entities[0]
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,24 @@ class MultipleNotes(BaseAction):
|
|||
#: Action label.
|
||||
label = 'Multiple Notes'
|
||||
#: Action description.
|
||||
description = 'Add same note to multiple Asset Versions'
|
||||
description = 'Add same note to multiple entities'
|
||||
icon = statics_icon("ftrack", "action_icons", "MultipleNotes.svg")
|
||||
|
||||
def discover(self, session, entities, event):
|
||||
''' Validation '''
|
||||
valid = True
|
||||
|
||||
# Check for multiple selection.
|
||||
if len(entities) < 2:
|
||||
valid = False
|
||||
|
||||
# Check for valid entities.
|
||||
valid_entity_types = ['assetversion', 'task']
|
||||
for entity in entities:
|
||||
if entity.entity_type.lower() != 'assetversion':
|
||||
if entity.entity_type.lower() not in valid_entity_types:
|
||||
valid = False
|
||||
break
|
||||
|
||||
return valid
|
||||
|
||||
def interface(self, session, entities, event):
|
||||
|
|
@ -58,7 +66,7 @@ class MultipleNotes(BaseAction):
|
|||
|
||||
splitter = {
|
||||
'type': 'label',
|
||||
'value': '{}'.format(200*"-")
|
||||
'value': '{}'.format(200 * "-")
|
||||
}
|
||||
|
||||
items = []
|
||||
|
|
|
|||
|
|
@ -428,9 +428,11 @@ class PrepareProjectLocal(BaseAction):
|
|||
|
||||
# Trigger create project structure action
|
||||
if create_project_structure_checked:
|
||||
self.trigger_action(
|
||||
self.create_project_structure_identifier, event
|
||||
trigger_identifier = "{}.{}".format(
|
||||
self.create_project_structure_identifier,
|
||||
self.process_identifier()
|
||||
)
|
||||
self.trigger_action(trigger_identifier, event)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ class ActionShowWhereIRun(BaseAction):
|
|||
|
||||
return False
|
||||
|
||||
@property
|
||||
def launch_identifier(self):
|
||||
return self.identifier
|
||||
|
||||
def launch(self, session, entities, event):
|
||||
# Don't show info when was launch from this session
|
||||
if session.event_hub.id == event.get("data", {}).get("event_hub_id"):
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ class BaseAction(BaseHandler):
|
|||
icon = None
|
||||
type = 'Action'
|
||||
|
||||
_discover_identifier = None
|
||||
_launch_identifier = None
|
||||
|
||||
settings_frack_subkey = "user_handlers"
|
||||
settings_enabled_key = "enabled"
|
||||
|
||||
|
|
@ -42,6 +45,22 @@ class BaseAction(BaseHandler):
|
|||
|
||||
super().__init__(session)
|
||||
|
||||
@property
|
||||
def discover_identifier(self):
|
||||
if self._discover_identifier is None:
|
||||
self._discover_identifier = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._discover_identifier
|
||||
|
||||
@property
|
||||
def launch_identifier(self):
|
||||
if self._launch_identifier is None:
|
||||
self._launch_identifier = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._launch_identifier
|
||||
|
||||
def register(self):
|
||||
'''
|
||||
Registers the action, subscribing the the discover and launch topics.
|
||||
|
|
@ -60,7 +79,7 @@ class BaseAction(BaseHandler):
|
|||
' and data.actionIdentifier={0}'
|
||||
' and source.user.username={1}'
|
||||
).format(
|
||||
self.identifier,
|
||||
self.launch_identifier,
|
||||
self.session.api_user
|
||||
)
|
||||
self.session.event_hub.subscribe(
|
||||
|
|
@ -86,7 +105,7 @@ class BaseAction(BaseHandler):
|
|||
'label': self.label,
|
||||
'variant': self.variant,
|
||||
'description': self.description,
|
||||
'actionIdentifier': self.identifier,
|
||||
'actionIdentifier': self.discover_identifier,
|
||||
'icon': self.icon,
|
||||
}]
|
||||
}
|
||||
|
|
@ -309,6 +328,78 @@ class BaseAction(BaseHandler):
|
|||
return True
|
||||
|
||||
|
||||
class LocalAction(BaseAction):
|
||||
"""Action that warn user when more Processes with same action are running.
|
||||
|
||||
Action is launched all the time but if id does not match id of current
|
||||
instanace then message is shown to user.
|
||||
|
||||
Handy for actions where matters if is executed on specific machine.
|
||||
"""
|
||||
_full_launch_identifier = None
|
||||
|
||||
@property
|
||||
def discover_identifier(self):
|
||||
if self._discover_identifier is None:
|
||||
self._discover_identifier = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._discover_identifier
|
||||
|
||||
@property
|
||||
def launch_identifier(self):
|
||||
"""Catch all topics with same identifier."""
|
||||
if self._launch_identifier is None:
|
||||
self._launch_identifier = "{}.*".format(self.identifier)
|
||||
return self._launch_identifier
|
||||
|
||||
@property
|
||||
def full_launch_identifier(self):
|
||||
"""Catch all topics with same identifier."""
|
||||
if self._full_launch_identifier is None:
|
||||
self._full_launch_identifier = "{}.{}".format(
|
||||
self.identifier, self.process_identifier()
|
||||
)
|
||||
return self._full_launch_identifier
|
||||
|
||||
def _discover(self, event):
|
||||
entities = self._translate_event(event)
|
||||
if not entities:
|
||||
return
|
||||
|
||||
accepts = self.discover(self.session, entities, event)
|
||||
if not accepts:
|
||||
return
|
||||
|
||||
self.log.debug("Discovering action with selection: {0}".format(
|
||||
event["data"].get("selection", [])
|
||||
))
|
||||
|
||||
return {
|
||||
"items": [{
|
||||
"label": self.label,
|
||||
"variant": self.variant,
|
||||
"description": self.description,
|
||||
"actionIdentifier": self.discover_identifier,
|
||||
"icon": self.icon,
|
||||
}]
|
||||
}
|
||||
|
||||
def _launch(self, event):
|
||||
event_identifier = event["data"]["actionIdentifier"]
|
||||
# Check if identifier is same
|
||||
# - show message that acion may not be triggered on this machine
|
||||
if event_identifier != self.full_launch_identifier:
|
||||
return {
|
||||
"success": False,
|
||||
"message": (
|
||||
"There are running more OpenPype processes"
|
||||
" where this action could be launched."
|
||||
)
|
||||
}
|
||||
return super(LocalAction, self)._launch(event)
|
||||
|
||||
|
||||
class ServerAction(BaseAction):
|
||||
"""Action class meant to be used on event server.
|
||||
|
||||
|
|
@ -318,6 +409,14 @@ class ServerAction(BaseAction):
|
|||
|
||||
settings_frack_subkey = "events"
|
||||
|
||||
@property
|
||||
def discover_identifier(self):
|
||||
return self.identifier
|
||||
|
||||
@property
|
||||
def launch_identifier(self):
|
||||
return self.identifier
|
||||
|
||||
def register(self):
|
||||
"""Register subcription to Ftrack event hub."""
|
||||
self.session.event_hub.subscribe(
|
||||
|
|
@ -328,5 +427,5 @@ class ServerAction(BaseAction):
|
|||
|
||||
launch_subscription = (
|
||||
"topic=ftrack.action.launch and data.actionIdentifier={0}"
|
||||
).format(self.identifier)
|
||||
).format(self.launch_identifier)
|
||||
self.session.event_hub.subscribe(launch_subscription, self._launch)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import tempfile
|
||||
import json
|
||||
import functools
|
||||
import uuid
|
||||
import datetime
|
||||
import traceback
|
||||
import time
|
||||
|
|
@ -36,6 +37,7 @@ class BaseHandler(object):
|
|||
<description> - a verbose descriptive text for you action
|
||||
<icon> - icon in ftrack
|
||||
'''
|
||||
_process_id = None
|
||||
# Default priority is 100
|
||||
priority = 100
|
||||
# Type is just for logging purpose (e.g.: Action, Event, Application,...)
|
||||
|
|
@ -70,6 +72,13 @@ class BaseHandler(object):
|
|||
self.register = self.register_decorator(self.register)
|
||||
self.launch = self.launch_log(self.launch)
|
||||
|
||||
@staticmethod
|
||||
def process_identifier():
|
||||
"""Helper property to have """
|
||||
if not BaseHandler._process_id:
|
||||
BaseHandler._process_id = str(uuid.uuid4())
|
||||
return BaseHandler._process_id
|
||||
|
||||
# Decorator
|
||||
def register_decorator(self, func):
|
||||
@functools.wraps(func)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ class LogsWindow(QtWidgets.QWidget):
|
|||
def __init__(self, parent=None):
|
||||
super(LogsWindow, self).__init__(parent)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.setWindowTitle("Logs viewer")
|
||||
|
||||
self.resize(1400, 800)
|
||||
log_detail = OutputWidget(parent=self)
|
||||
logs_widget = LogsWidget(log_detail, parent=self)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout()
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
|
||||
log_splitter = QtWidgets.QSplitter(self)
|
||||
log_splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
|
|
@ -24,5 +25,4 @@ class LogsWindow(QtWidgets.QWidget):
|
|||
self.logs_widget = logs_widget
|
||||
self.log_detail = log_detail
|
||||
|
||||
self.setLayout(main_layout)
|
||||
self.setWindowTitle("Logs")
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
|
|
|||
|
|
@ -77,12 +77,10 @@ class CustomCombo(QtWidgets.QWidget):
|
|||
toolbutton.setMenu(toolmenu)
|
||||
toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(toolbutton)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
toolmenu.selection_changed.connect(self.selection_changed)
|
||||
|
||||
self.toolbutton = toolbutton
|
||||
|
|
@ -141,7 +139,6 @@ class LogsWidget(QtWidgets.QWidget):
|
|||
filter_layout.addWidget(refresh_btn)
|
||||
|
||||
view = QtWidgets.QTreeView(self)
|
||||
view.setAllColumnsShowFocus(True)
|
||||
view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
|
@ -229,9 +226,9 @@ class OutputWidget(QtWidgets.QWidget):
|
|||
super(OutputWidget, self).__init__(parent=parent)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
show_timecode_checkbox = QtWidgets.QCheckBox("Show timestamp")
|
||||
show_timecode_checkbox = QtWidgets.QCheckBox("Show timestamp", self)
|
||||
|
||||
output_text = QtWidgets.QTextEdit()
|
||||
output_text = QtWidgets.QTextEdit(self)
|
||||
output_text.setReadOnly(True)
|
||||
# output_text.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from openpype.api import Logger
|
|||
|
||||
log = Logger().get_logger("Event processor")
|
||||
|
||||
|
||||
class TimersManagerModuleRestApi:
|
||||
"""
|
||||
REST API endpoint used for calling from hosts when context change
|
||||
|
|
@ -22,6 +23,11 @@ class TimersManagerModuleRestApi:
|
|||
self.prefix + "/start_timer",
|
||||
self.start_timer
|
||||
)
|
||||
self.server_manager.add_route(
|
||||
"POST",
|
||||
self.prefix + "/stop_timer",
|
||||
self.stop_timer
|
||||
)
|
||||
|
||||
async def start_timer(self, request):
|
||||
data = await request.json()
|
||||
|
|
@ -38,3 +44,7 @@ class TimersManagerModuleRestApi:
|
|||
self.module.stop_timers()
|
||||
self.module.start_timer(project_name, asset_name, task_name, hierarchy)
|
||||
return Response(status=200)
|
||||
|
||||
async def stop_timer(self, request):
|
||||
self.module.stop_timers()
|
||||
return Response(status=200)
|
||||
|
|
|
|||
|
|
@ -260,6 +260,13 @@
|
|||
"enabled": true
|
||||
}
|
||||
],
|
||||
"open_workfile_tool_on_startup": [
|
||||
{
|
||||
"hosts": [],
|
||||
"tasks": [],
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"sw_folders": {
|
||||
"compositing": [
|
||||
"nuke",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
from .input_entities import InputEntity
|
||||
from .exceptions import EntitySchemaError
|
||||
from .lib import (
|
||||
|
|
@ -118,30 +119,43 @@ class HostsEnumEntity(BaseEnumEntity):
|
|||
implementation instead of application name.
|
||||
"""
|
||||
schema_types = ["hosts-enum"]
|
||||
all_host_names = [
|
||||
"aftereffects",
|
||||
"blender",
|
||||
"celaction",
|
||||
"fusion",
|
||||
"harmony",
|
||||
"hiero",
|
||||
"houdini",
|
||||
"maya",
|
||||
"nuke",
|
||||
"photoshop",
|
||||
"resolve",
|
||||
"tvpaint",
|
||||
"unreal",
|
||||
"standalonepublisher"
|
||||
]
|
||||
|
||||
def _item_initalization(self):
|
||||
self.multiselection = self.schema_data.get("multiselection", True)
|
||||
self.use_empty_value = self.schema_data.get(
|
||||
"use_empty_value", not self.multiselection
|
||||
)
|
||||
use_empty_value = False
|
||||
if not self.multiselection:
|
||||
use_empty_value = self.schema_data.get(
|
||||
"use_empty_value", use_empty_value
|
||||
)
|
||||
self.use_empty_value = use_empty_value
|
||||
|
||||
hosts_filter = self.schema_data.get("hosts_filter") or []
|
||||
self.hosts_filter = hosts_filter
|
||||
|
||||
custom_labels = self.schema_data.get("custom_labels") or {}
|
||||
|
||||
host_names = [
|
||||
"aftereffects",
|
||||
"blender",
|
||||
"celaction",
|
||||
"fusion",
|
||||
"harmony",
|
||||
"hiero",
|
||||
"houdini",
|
||||
"maya",
|
||||
"nuke",
|
||||
"photoshop",
|
||||
"resolve",
|
||||
"tvpaint",
|
||||
"unreal",
|
||||
"standalonepublisher"
|
||||
]
|
||||
host_names = copy.deepcopy(self.all_host_names)
|
||||
if hosts_filter:
|
||||
for host_name in tuple(host_names):
|
||||
if host_name not in hosts_filter:
|
||||
host_names.remove(host_name)
|
||||
|
||||
if self.use_empty_value:
|
||||
host_names.insert(0, "")
|
||||
# Add default label for empty value if not available
|
||||
|
|
@ -173,6 +187,44 @@ class HostsEnumEntity(BaseEnumEntity):
|
|||
# GUI attribute
|
||||
self.placeholder = self.schema_data.get("placeholder")
|
||||
|
||||
def schema_validations(self):
|
||||
if self.hosts_filter:
|
||||
enum_len = len(self.enum_items)
|
||||
if (
|
||||
enum_len == 0
|
||||
or (enum_len == 1 and self.use_empty_value)
|
||||
):
|
||||
joined_filters = ", ".join([
|
||||
'"{}"'.format(item)
|
||||
for item in self.hosts_filter
|
||||
])
|
||||
reason = (
|
||||
"All host names were removed after applying"
|
||||
" host filters. {}"
|
||||
).format(joined_filters)
|
||||
raise EntitySchemaError(self, reason)
|
||||
|
||||
invalid_filters = set()
|
||||
for item in self.hosts_filter:
|
||||
if item not in self.all_host_names:
|
||||
invalid_filters.add(item)
|
||||
|
||||
if invalid_filters:
|
||||
joined_filters = ", ".join([
|
||||
'"{}"'.format(item)
|
||||
for item in self.hosts_filter
|
||||
])
|
||||
expected_hosts = ", ".join([
|
||||
'"{}"'.format(item)
|
||||
for item in self.all_host_names
|
||||
])
|
||||
self.log.warning((
|
||||
"Host filters containt invalid host names:"
|
||||
" \"{}\" Expected values are {}"
|
||||
).format(joined_filters, expected_hosts))
|
||||
|
||||
super(HostsEnumEntity, self).schema_validations()
|
||||
|
||||
|
||||
class AppsEnumEntity(BaseEnumEntity):
|
||||
schema_types = ["apps-enum"]
|
||||
|
|
|
|||
|
|
@ -379,6 +379,9 @@ How output of the schema could look like on save:
|
|||
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
|
||||
- it is possible to add empty value (represented with empty string) with setting `"use_empty_value"` to `True` (Default: `False`)
|
||||
- it is possible to set `"custom_labels"` for host names where key `""` is empty value (Default: `{}`)
|
||||
- to filter host names it is required to define `"hosts_filter"` which is list of host names that will be available
|
||||
- do not pass empty string if `use_empty_value` is enabled
|
||||
- ignoring host names would be more dangerous in some cases
|
||||
```
|
||||
{
|
||||
"key": "host",
|
||||
|
|
@ -389,7 +392,10 @@ How output of the schema could look like on save:
|
|||
"custom_labels": {
|
||||
"": "N/A",
|
||||
"nuke": "Nuke"
|
||||
}
|
||||
},
|
||||
"hosts_filter": [
|
||||
"nuke"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,57 @@
|
|||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"multiselection": true
|
||||
"multiselection": true,
|
||||
"hosts_filter": [
|
||||
"aftereffects",
|
||||
"blender",
|
||||
"celaction",
|
||||
"fusion",
|
||||
"harmony",
|
||||
"hiero",
|
||||
"houdini",
|
||||
"maya",
|
||||
"nuke",
|
||||
"photoshop",
|
||||
"resolve",
|
||||
"tvpaint",
|
||||
"unreal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
"label": "Tasks",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "open_workfile_tool_on_startup",
|
||||
"label": "Open workfile tool on launch",
|
||||
"is_group": true,
|
||||
"use_label_wrap": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"multiselection": true,
|
||||
"hosts_filter": [
|
||||
"nuke"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ QWidget:disabled {
|
|||
color: {color:font-disabled};
|
||||
}
|
||||
|
||||
QLabel {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
QAbstractSpinBox, QLineEdit, QPlainTextEdit, QTextEdit {
|
||||
border: 1px solid {color:border};
|
||||
|
|
@ -97,7 +101,7 @@ QToolButton:disabled {
|
|||
background: {color:bg-buttons-disabled};
|
||||
}
|
||||
|
||||
QToolButton[popupMode="1"] {
|
||||
QToolButton[popupMode="1"], QToolButton[popupMode="MenuButtonPopup"] {
|
||||
/* make way for the popup button */
|
||||
padding-right: 20px;
|
||||
border: 1px solid {color:bg-buttons};
|
||||
|
|
@ -340,6 +344,11 @@ QAbstractItemView {
|
|||
selection-background-color: transparent;
|
||||
}
|
||||
|
||||
QAbstractItemView::item {
|
||||
/* `border: none` hide outline of selected item. */
|
||||
border: none;
|
||||
}
|
||||
|
||||
QAbstractItemView:disabled{
|
||||
background: {color:bg-view-disabled};
|
||||
alternate-background-color: {color:bg-view-alternate-disabled};
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
msg = "<br><br>".join(warnings)
|
||||
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
dialog.setWindowTitle("Save warnings")
|
||||
dialog.setText(msg)
|
||||
dialog.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
dialog.exec_()
|
||||
|
|
@ -303,6 +304,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
except Exception as exc:
|
||||
formatted_traceback = traceback.format_exception(*sys.exc_info())
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
dialog.setWindowTitle("Unexpected error")
|
||||
msg = "Unexpected error happened!\n\nError: {}".format(str(exc))
|
||||
dialog.setText(msg)
|
||||
dialog.setDetailedText("\n".join(formatted_traceback))
|
||||
|
|
@ -392,6 +394,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
except Exception as exc:
|
||||
formatted_traceback = traceback.format_exception(*sys.exc_info())
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
dialog.setWindowTitle("Unexpected error")
|
||||
msg = "Unexpected error happened!\n\nError: {}".format(str(exc))
|
||||
dialog.setText(msg)
|
||||
dialog.setDetailedText("\n".join(formatted_traceback))
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ class MainWidget(QtWidgets.QWidget):
|
|||
super(MainWidget, self).showEvent(event)
|
||||
if self._reset_on_show:
|
||||
self._reset_on_show = False
|
||||
self.reset()
|
||||
# Trigger reset with 100ms delay
|
||||
QtCore.QTimer.singleShot(100, self.reset)
|
||||
|
||||
def _show_password_dialog(self):
|
||||
if self._password_dialog:
|
||||
|
|
@ -107,6 +108,8 @@ class MainWidget(QtWidgets.QWidget):
|
|||
self._password_dialog = None
|
||||
if password_passed:
|
||||
self.reset()
|
||||
if not self.isVisible():
|
||||
self.show()
|
||||
else:
|
||||
self.close()
|
||||
|
||||
|
|
@ -141,7 +144,10 @@ class MainWidget(QtWidgets.QWidget):
|
|||
# Don't show dialog if there are not registered slots for
|
||||
# `trigger_restart` signal.
|
||||
# - For example when settings are runnin as standalone tool
|
||||
if self.receivers(self.trigger_restart) < 1:
|
||||
# - PySide2 and PyQt5 compatible way how to find out
|
||||
method_index = self.metaObject().indexOfMethod("trigger_restart()")
|
||||
method = self.metaObject().method(method_index)
|
||||
if not self.isSignalConnected(method):
|
||||
return
|
||||
|
||||
dialog = RestartDialog(self)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from .resources import get_resource
|
||||
from avalon import style
|
||||
|
||||
|
||||
class ComponentItem(QtWidgets.QFrame):
|
||||
|
|
@ -61,7 +60,7 @@ class ComponentItem(QtWidgets.QFrame):
|
|||
name="menu", size=QtCore.QSize(22, 22)
|
||||
)
|
||||
|
||||
self.action_menu = QtWidgets.QMenu()
|
||||
self.action_menu = QtWidgets.QMenu(self.btn_action_menu)
|
||||
|
||||
expanding_sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
|
||||
|
|
@ -229,7 +228,6 @@ class ComponentItem(QtWidgets.QFrame):
|
|||
if not self.btn_action_menu.isVisible():
|
||||
self.btn_action_menu.setVisible(True)
|
||||
self.btn_action_menu.clicked.connect(self.show_actions)
|
||||
self.action_menu.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def set_repre_name_valid(self, valid):
|
||||
self.has_valid_repre = valid
|
||||
|
|
|
|||
|
|
@ -693,16 +693,16 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
)
|
||||
return
|
||||
|
||||
file_path = os.path.join(self.root, work_file)
|
||||
file_path = os.path.join(os.path.normpath(self.root), work_file)
|
||||
|
||||
pipeline.emit("before.workfile.save", file_path)
|
||||
pipeline.emit("before.workfile.save", [file_path])
|
||||
|
||||
self._enter_session() # Make sure we are in the right session
|
||||
self.host.save_file(file_path)
|
||||
|
||||
self.set_asset_task(self._asset, self._task)
|
||||
|
||||
pipeline.emit("after.workfile.save", file_path)
|
||||
pipeline.emit("after.workfile.save", [file_path])
|
||||
|
||||
self.workfile_created.emit(file_path)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue