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
4dcddc55b6
32 changed files with 833 additions and 411 deletions
|
|
@ -76,6 +76,8 @@ class LoadSequence(api.Loader):
|
|||
file = file.replace("\\", "/")
|
||||
|
||||
repr_cont = context["representation"]["context"]
|
||||
assert repr_cont.get("frame"), "Representation is not sequence"
|
||||
|
||||
if "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
|
|
@ -170,6 +172,7 @@ class LoadSequence(api.Loader):
|
|||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
repr_cont = representation["context"]
|
||||
assert repr_cont.get("frame"), "Representation is not sequence"
|
||||
|
||||
file = api.get_representation_path(representation)
|
||||
|
||||
|
|
|
|||
33
openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py
Normal file
33
openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import pyblish
|
||||
import nuke
|
||||
|
||||
|
||||
class FixProxyMode(pyblish.api.Action):
|
||||
"""
|
||||
Togger off proxy switch OFF
|
||||
"""
|
||||
|
||||
label = "Proxy toggle to OFF"
|
||||
icon = "wrench"
|
||||
on = "failed"
|
||||
|
||||
def process(self, context, plugin):
|
||||
rootNode = nuke.root()
|
||||
rootNode["proxy"].setValue(False)
|
||||
|
||||
|
||||
@pyblish.api.log
|
||||
class ValidateProxyMode(pyblish.api.ContextPlugin):
|
||||
"""Validate active proxy mode"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Proxy Mode"
|
||||
hosts = ["nuke"]
|
||||
actions = [FixProxyMode]
|
||||
|
||||
def process(self, context):
|
||||
|
||||
rootNode = nuke.root()
|
||||
isProxy = rootNode["proxy"].value()
|
||||
|
||||
assert not isProxy, "Proxy mode should be toggled OFF"
|
||||
|
|
@ -58,7 +58,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
# use first frame as thumbnail if is sequence of jpegs
|
||||
full_thumbnail_path = os.path.join(
|
||||
thumbnail_repre["stagingDir"], file
|
||||
)
|
||||
)
|
||||
self.log.info(
|
||||
"For thumbnail is used file: {}".format(full_thumbnail_path)
|
||||
)
|
||||
|
|
@ -101,14 +101,11 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
jpeg_items.append("\"{}\"".format(full_thumbnail_path))
|
||||
|
||||
subprocess_jpeg = " ".join(jpeg_items)
|
||||
subprocess_args = openpype.lib.split_command_to_list(
|
||||
subprocess_jpeg
|
||||
)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(" ".join(subprocess_args)))
|
||||
self.log.debug("Executing: {}".format(subprocess_jpeg))
|
||||
openpype.api.run_subprocess(
|
||||
subprocess_args, shell=True, logger=self.log
|
||||
subprocess_jpeg, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# remove thumbnail key from origin repre
|
||||
|
|
@ -119,7 +116,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
|
||||
# create new thumbnail representation
|
||||
representation = {
|
||||
'name': 'jpg',
|
||||
'name': 'thumbnail',
|
||||
'ext': 'jpg',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir,
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor):
|
|||
joined_args = " ".join(ffmpeg_args)
|
||||
self.log.info(f"Processing: {joined_args}")
|
||||
openpype.api.run_subprocess(
|
||||
ffmpeg_args, shell=True, logger=self.log
|
||||
ffmpeg_args, logger=self.log
|
||||
)
|
||||
|
||||
repre = {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ from .execute import (
|
|||
get_pype_execute_args,
|
||||
execute,
|
||||
run_subprocess,
|
||||
split_command_to_list,
|
||||
path_to_subprocess_arg,
|
||||
CREATE_NO_WINDOW
|
||||
)
|
||||
|
|
@ -174,7 +173,6 @@ __all__ = [
|
|||
"get_pype_execute_args",
|
||||
"execute",
|
||||
"run_subprocess",
|
||||
"split_command_to_list",
|
||||
"path_to_subprocess_arg",
|
||||
"CREATE_NO_WINDOW",
|
||||
|
||||
|
|
|
|||
|
|
@ -147,36 +147,6 @@ def path_to_subprocess_arg(path):
|
|||
return subprocess.list2cmdline([path])
|
||||
|
||||
|
||||
def split_command_to_list(string_command):
|
||||
"""Split string subprocess command to list.
|
||||
|
||||
Should be able to split complex subprocess command to separated arguments:
|
||||
`"C:\\ffmpeg folder\\ffmpeg.exe" -i \"D:\\input.mp4\\" \"D:\\output.mp4\"`
|
||||
|
||||
Should result into list:
|
||||
`["C:\ffmpeg folder\ffmpeg.exe", "-i", "D:\input.mp4", "D:\output.mp4"]`
|
||||
|
||||
This may be required on few versions of python where subprocess can handle
|
||||
only list of arguments.
|
||||
|
||||
To be able do that is using `shlex` python module.
|
||||
|
||||
Args:
|
||||
string_command(str): Full subprocess command.
|
||||
|
||||
Returns:
|
||||
list: Command separated into individual arguments.
|
||||
"""
|
||||
if not string_command:
|
||||
return []
|
||||
|
||||
kwargs = {}
|
||||
# Use 'posix' argument only on windows
|
||||
if platform.system().lower() == "windows":
|
||||
kwargs["posix"] = False
|
||||
return shlex.split(string_command, **kwargs)
|
||||
|
||||
|
||||
def get_pype_execute_args(*args):
|
||||
"""Arguments to run pype command.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@ import os
|
|||
import openpype
|
||||
from openpype import resources
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype_interfaces import (
|
||||
ITrayModule,
|
||||
IWebServerRoutes
|
||||
)
|
||||
from openpype_interfaces import ITrayModule
|
||||
|
||||
|
||||
class AvalonModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
||||
class AvalonModule(OpenPypeModule, ITrayModule):
|
||||
name = "avalon"
|
||||
|
||||
def initialize(self, modules_settings):
|
||||
|
|
@ -71,13 +68,6 @@ class AvalonModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
|||
exc_info=True
|
||||
)
|
||||
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Implementation of IWebServerRoutes interface."""
|
||||
|
||||
if self.tray_initialized:
|
||||
from .rest_api import AvalonRestApiResource
|
||||
self.rest_api_obj = AvalonRestApiResource(self, server_manager)
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, tray_menu):
|
||||
from Qt import QtWidgets
|
||||
|
|
@ -105,3 +95,10 @@ class AvalonModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
|||
# for Windows
|
||||
self.libraryloader.activateWindow()
|
||||
self.libraryloader.refresh()
|
||||
|
||||
# Webserver module implementation
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Add routes for webserver."""
|
||||
if self.tray_initialized:
|
||||
from .rest_api import AvalonRestApiResource
|
||||
self.rest_api_obj = AvalonRestApiResource(self, server_manager)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ from openpype.modules import OpenPypeModule
|
|||
from openpype_interfaces import (
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
IFtrackEventHandlerPaths,
|
||||
ITimersManager
|
||||
IFtrackEventHandlerPaths
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -20,8 +19,7 @@ class ClockifyModule(
|
|||
OpenPypeModule,
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
IFtrackEventHandlerPaths,
|
||||
ITimersManager
|
||||
IFtrackEventHandlerPaths
|
||||
):
|
||||
name = "clockify"
|
||||
|
||||
|
|
@ -39,6 +37,11 @@ class ClockifyModule(
|
|||
|
||||
self.clockapi = ClockifyAPI(master_parent=self)
|
||||
|
||||
# TimersManager attributes
|
||||
# - set `timers_manager_connector` only in `tray_init`
|
||||
self.timers_manager_connector = None
|
||||
self._timers_manager_module = None
|
||||
|
||||
def get_global_environments(self):
|
||||
return {
|
||||
"CLOCKIFY_WORKSPACE": self.workspace_name
|
||||
|
|
@ -61,6 +64,9 @@ class ClockifyModule(
|
|||
self.bool_timer_run = False
|
||||
self.bool_api_key_set = self.clockapi.set_api()
|
||||
|
||||
# Define itself as TimersManager connector
|
||||
self.timers_manager_connector = self
|
||||
|
||||
def tray_start(self):
|
||||
if self.bool_api_key_set is False:
|
||||
self.show_settings()
|
||||
|
|
@ -162,10 +168,6 @@ class ClockifyModule(
|
|||
self.set_menu_visibility()
|
||||
time.sleep(5)
|
||||
|
||||
def stop_timer(self):
|
||||
"""Implementation of ITimersManager."""
|
||||
self.clockapi.finish_time_entry()
|
||||
|
||||
def signed_in(self):
|
||||
if not self.timer_manager:
|
||||
return
|
||||
|
|
@ -176,8 +178,60 @@ class ClockifyModule(
|
|||
if self.timer_manager.is_running:
|
||||
self.start_timer_manager(self.timer_manager.last_task)
|
||||
|
||||
def on_message_widget_close(self):
|
||||
self.message_widget = None
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, parent_menu):
|
||||
# Menu for Tray App
|
||||
from Qt import QtWidgets
|
||||
menu = QtWidgets.QMenu("Clockify", parent_menu)
|
||||
menu.setProperty("submenu", "on")
|
||||
|
||||
# Actions
|
||||
action_show_settings = QtWidgets.QAction("Settings", menu)
|
||||
action_stop_timer = QtWidgets.QAction("Stop timer", menu)
|
||||
|
||||
menu.addAction(action_show_settings)
|
||||
menu.addAction(action_stop_timer)
|
||||
|
||||
action_show_settings.triggered.connect(self.show_settings)
|
||||
action_stop_timer.triggered.connect(self.stop_timer)
|
||||
|
||||
self.action_stop_timer = action_stop_timer
|
||||
|
||||
self.set_menu_visibility()
|
||||
|
||||
parent_menu.addMenu(menu)
|
||||
|
||||
def show_settings(self):
|
||||
self.widget_settings.input_api_key.setText(self.clockapi.get_api_key())
|
||||
self.widget_settings.show()
|
||||
|
||||
def set_menu_visibility(self):
|
||||
self.action_stop_timer.setVisible(self.bool_timer_run)
|
||||
|
||||
# --- TimersManager connection methods ---
|
||||
def register_timers_manager(self, timer_manager_module):
|
||||
"""Store TimersManager for future use."""
|
||||
self._timers_manager_module = timer_manager_module
|
||||
|
||||
def timer_started(self, data):
|
||||
"""Tell TimersManager that timer started."""
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_started(self._module.id, data)
|
||||
|
||||
def timer_stopped(self):
|
||||
"""Tell TimersManager that timer stopped."""
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_stopped(self._module.id)
|
||||
|
||||
def stop_timer(self):
|
||||
"""Called from TimersManager to stop timer."""
|
||||
self.clockapi.finish_time_entry()
|
||||
|
||||
def start_timer(self, input_data):
|
||||
"""Implementation of ITimersManager."""
|
||||
"""Called from TimersManager to start timer."""
|
||||
# If not api key is not entered then skip
|
||||
if not self.clockapi.get_api_key():
|
||||
return
|
||||
|
|
@ -234,36 +288,3 @@ class ClockifyModule(
|
|||
self.clockapi.start_time_entry(
|
||||
description, project_id, tag_ids=tag_ids
|
||||
)
|
||||
|
||||
def on_message_widget_close(self):
|
||||
self.message_widget = None
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, parent_menu):
|
||||
# Menu for Tray App
|
||||
from Qt import QtWidgets
|
||||
menu = QtWidgets.QMenu("Clockify", parent_menu)
|
||||
menu.setProperty("submenu", "on")
|
||||
|
||||
# Actions
|
||||
action_show_settings = QtWidgets.QAction("Settings", menu)
|
||||
action_stop_timer = QtWidgets.QAction("Stop timer", menu)
|
||||
|
||||
menu.addAction(action_show_settings)
|
||||
menu.addAction(action_stop_timer)
|
||||
|
||||
action_show_settings.triggered.connect(self.show_settings)
|
||||
action_stop_timer.triggered.connect(self.stop_timer)
|
||||
|
||||
self.action_stop_timer = action_stop_timer
|
||||
|
||||
self.set_menu_visibility()
|
||||
|
||||
parent_menu.addMenu(menu)
|
||||
|
||||
def show_settings(self):
|
||||
self.widget_settings.input_api_key.setText(self.clockapi.get_api_key())
|
||||
self.widget_settings.show()
|
||||
|
||||
def set_menu_visibility(self):
|
||||
self.action_stop_timer.setVisible(self.bool_timer_run)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from openpype.modules import OpenPypeModule
|
|||
from openpype_interfaces import (
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
ITimersManager,
|
||||
ILaunchHookPaths,
|
||||
ISettingsChangeListener,
|
||||
IFtrackEventHandlerPaths
|
||||
|
|
@ -21,7 +20,6 @@ class FtrackModule(
|
|||
OpenPypeModule,
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
ITimersManager,
|
||||
ILaunchHookPaths,
|
||||
ISettingsChangeListener
|
||||
):
|
||||
|
|
@ -61,6 +59,10 @@ class FtrackModule(
|
|||
self.user_event_handlers_paths = user_event_handlers_paths
|
||||
self.tray_module = None
|
||||
|
||||
# TimersManager connection
|
||||
self.timers_manager_connector = None
|
||||
self._timers_manager_module = None
|
||||
|
||||
def get_global_environments(self):
|
||||
"""Ftrack's global environments."""
|
||||
return {
|
||||
|
|
@ -102,16 +104,6 @@ class FtrackModule(
|
|||
elif key == "user":
|
||||
self.user_event_handlers_paths.extend(value)
|
||||
|
||||
def start_timer(self, data):
|
||||
"""Implementation of ITimersManager interface."""
|
||||
if self.tray_module:
|
||||
self.tray_module.start_timer_manager(data)
|
||||
|
||||
def stop_timer(self):
|
||||
"""Implementation of ITimersManager interface."""
|
||||
if self.tray_module:
|
||||
self.tray_module.stop_timer_manager()
|
||||
|
||||
def on_system_settings_save(
|
||||
self, old_value, new_value, changes, new_value_metadata
|
||||
):
|
||||
|
|
@ -343,7 +335,10 @@ class FtrackModule(
|
|||
|
||||
def tray_init(self):
|
||||
from .tray import FtrackTrayWrapper
|
||||
|
||||
self.tray_module = FtrackTrayWrapper(self)
|
||||
# Module is it's own connector to TimersManager
|
||||
self.timers_manager_connector = self
|
||||
|
||||
def tray_menu(self, parent_menu):
|
||||
return self.tray_module.tray_menu(parent_menu)
|
||||
|
|
@ -357,3 +352,23 @@ class FtrackModule(
|
|||
def set_credentials_to_env(self, username, api_key):
|
||||
os.environ["FTRACK_API_USER"] = username or ""
|
||||
os.environ["FTRACK_API_KEY"] = api_key or ""
|
||||
|
||||
# --- TimersManager connection methods ---
|
||||
def start_timer(self, data):
|
||||
if self.tray_module:
|
||||
self.tray_module.start_timer_manager(data)
|
||||
|
||||
def stop_timer(self):
|
||||
if self.tray_module:
|
||||
self.tray_module.stop_timer_manager()
|
||||
|
||||
def register_timers_manager(self, timer_manager_module):
|
||||
self._timers_manager_module = timer_manager_module
|
||||
|
||||
def timer_started(self, data):
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_started(self.id, data)
|
||||
|
||||
def timer_stopped(self):
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_stopped(self.id)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,10 @@ import json
|
|||
import appdirs
|
||||
import requests
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype_interfaces import (
|
||||
ITrayModule,
|
||||
IWebServerRoutes
|
||||
)
|
||||
from openpype_interfaces import ITrayModule
|
||||
|
||||
|
||||
class MusterModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
||||
class MusterModule(OpenPypeModule, ITrayModule):
|
||||
"""
|
||||
Module handling Muster Render credentials. This will display dialog
|
||||
asking for user credentials for Muster if not already specified.
|
||||
|
|
@ -73,13 +70,6 @@ class MusterModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
|||
|
||||
parent.addMenu(menu)
|
||||
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Implementation of IWebServerRoutes interface."""
|
||||
if self.tray_initialized:
|
||||
from .rest_api import MusterModuleRestApi
|
||||
|
||||
self.rest_api_obj = MusterModuleRestApi(self, server_manager)
|
||||
|
||||
def load_credentials(self):
|
||||
"""
|
||||
Get credentials from JSON file
|
||||
|
|
@ -139,6 +129,14 @@ class MusterModule(OpenPypeModule, ITrayModule, IWebServerRoutes):
|
|||
if self.widget_login:
|
||||
self.widget_login.show()
|
||||
|
||||
# Webserver module implementation
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Add routes for Muster login."""
|
||||
if self.tray_initialized:
|
||||
from .rest_api import MusterModuleRestApi
|
||||
|
||||
self.rest_api_obj = MusterModuleRestApi(self, server_manager)
|
||||
|
||||
def _requests_post(self, *args, **kwargs):
|
||||
""" Wrapper for requests, disabling SSL certificate validation if
|
||||
DONT_VERIFY_SSL environment variable is found. This is useful when
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
from abc import abstractmethod
|
||||
from openpype.modules import OpenPypeInterface
|
||||
|
||||
|
||||
class ITimersManager(OpenPypeInterface):
|
||||
timer_manager_module = None
|
||||
|
||||
@abstractmethod
|
||||
def stop_timer(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def start_timer(self, data):
|
||||
pass
|
||||
|
||||
def timer_started(self, data):
|
||||
if not self.timer_manager_module:
|
||||
return
|
||||
|
||||
self.timer_manager_module.timer_started(self.id, data)
|
||||
|
||||
def timer_stopped(self):
|
||||
if not self.timer_manager_module:
|
||||
return
|
||||
|
||||
self.timer_manager_module.timer_stopped(self.id)
|
||||
|
|
@ -4,28 +4,95 @@ from openpype.modules import OpenPypeModule
|
|||
from openpype_interfaces import (
|
||||
ITimersManager,
|
||||
ITrayService,
|
||||
IIdleManager,
|
||||
IWebServerRoutes
|
||||
IIdleManager
|
||||
)
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
|
||||
class TimersManager(
|
||||
OpenPypeModule, ITrayService, IIdleManager, IWebServerRoutes
|
||||
):
|
||||
class ExampleTimersManagerConnector:
|
||||
"""Timers manager can handle timers of multiple modules/addons.
|
||||
|
||||
Module must have object under `timers_manager_connector` attribute with
|
||||
few methods. This is example class of the object that could be stored under
|
||||
module.
|
||||
|
||||
Required methods are 'stop_timer' and 'start_timer'.
|
||||
|
||||
# TODO pass asset document instead of `hierarchy`
|
||||
Example of `data` that are passed during changing timer:
|
||||
```
|
||||
data = {
|
||||
"project_name": project_name,
|
||||
"task_name": task_name,
|
||||
"task_type": task_type,
|
||||
"hierarchy": hierarchy
|
||||
}
|
||||
```
|
||||
"""
|
||||
# Not needed at all
|
||||
def __init__(self, module):
|
||||
# Store timer manager module to be able call it's methods when needed
|
||||
self._timers_manager_module = None
|
||||
|
||||
# Store module which want to use timers manager to have access
|
||||
self._module = module
|
||||
|
||||
# Required
|
||||
def stop_timer(self):
|
||||
"""Called by timers manager when module should stop timer."""
|
||||
self._module.stop_timer()
|
||||
|
||||
# Required
|
||||
def start_timer(self, data):
|
||||
"""Method called by timers manager when should start timer."""
|
||||
self._module.start_timer(data)
|
||||
|
||||
# Optional
|
||||
def register_timers_manager(self, timer_manager_module):
|
||||
"""Method called by timers manager where it's object is passed.
|
||||
|
||||
This is moment when timers manager module can be store to be able
|
||||
call it's callbacks (e.g. timer started).
|
||||
"""
|
||||
self._timers_manager_module = timer_manager_module
|
||||
|
||||
# Custom implementation
|
||||
def timer_started(self, data):
|
||||
"""This is example of possibility to trigger callbacks on manager."""
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_started(self._module.id, data)
|
||||
|
||||
# Custom implementation
|
||||
def timer_stopped(self):
|
||||
if self._timers_manager_module is not None:
|
||||
self._timers_manager_module.timer_stopped(self._module.id)
|
||||
|
||||
|
||||
class TimersManager(OpenPypeModule, ITrayService, IIdleManager):
|
||||
""" Handles about Timers.
|
||||
|
||||
Should be able to start/stop all timers at once.
|
||||
If IdleManager is imported then is able to handle about stop timers
|
||||
when user idles for a long time (set in presets).
|
||||
|
||||
To be able use this advantage module has to have attribute with name
|
||||
`timers_manager_connector` which has two methods 'stop_timer'
|
||||
and 'start_timer'. Optionally may have `register_timers_manager` where
|
||||
object of TimersManager module is passed to be able call it's callbacks.
|
||||
|
||||
See `ExampleTimersManagerConnector`.
|
||||
"""
|
||||
name = "timers_manager"
|
||||
label = "Timers Service"
|
||||
|
||||
_required_methods = (
|
||||
"stop_timer",
|
||||
"start_timer"
|
||||
)
|
||||
|
||||
def initialize(self, modules_settings):
|
||||
timers_settings = modules_settings[self.name]
|
||||
|
||||
self.enabled = timers_settings["enabled"]
|
||||
|
||||
auto_stop = timers_settings["auto_stop"]
|
||||
# When timer will stop if idle manager is running (minutes)
|
||||
full_time = int(timers_settings["full_time"] * 60)
|
||||
|
|
@ -44,7 +111,8 @@ class TimersManager(
|
|||
self.widget_user_idle = None
|
||||
self.signal_handler = None
|
||||
|
||||
self.modules = []
|
||||
self._connectors_by_module_id = {}
|
||||
self._modules_by_id = {}
|
||||
|
||||
def tray_init(self):
|
||||
from .widget_user_idle import WidgetUserIdle, SignalHandler
|
||||
|
|
@ -58,13 +126,6 @@ class TimersManager(
|
|||
"""Nothing special for TimersManager."""
|
||||
return
|
||||
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Implementation of IWebServerRoutes interface."""
|
||||
if self.tray_initialized:
|
||||
from .rest_api import TimersManagerModuleRestApi
|
||||
self.rest_api_obj = TimersManagerModuleRestApi(self,
|
||||
server_manager)
|
||||
|
||||
def start_timer(self, project_name, asset_name, task_name, hierarchy):
|
||||
"""
|
||||
Start timer for 'project_name', 'asset_name' and 'task_name'
|
||||
|
|
@ -106,17 +167,35 @@ class TimersManager(
|
|||
self.timer_started(None, data)
|
||||
|
||||
def timer_started(self, source_id, data):
|
||||
for module in self.modules:
|
||||
if module.id != source_id:
|
||||
module.start_timer(data)
|
||||
for module_id, connector in self._connectors_by_module_id.items():
|
||||
if module_id == source_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
connector.start_timer(data)
|
||||
except Exception:
|
||||
self.log.info(
|
||||
"Failed to start timer on connector {}".format(
|
||||
str(connector)
|
||||
)
|
||||
)
|
||||
|
||||
self.last_task = data
|
||||
self.is_running = True
|
||||
|
||||
def timer_stopped(self, source_id):
|
||||
for module in self.modules:
|
||||
if module.id != source_id:
|
||||
module.stop_timer()
|
||||
for module_id, connector in self._connectors_by_module_id.items():
|
||||
if module_id == source_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
connector.stop_timer()
|
||||
except Exception:
|
||||
self.log.info(
|
||||
"Failed to stop timer on connector {}".format(
|
||||
str(connector)
|
||||
)
|
||||
)
|
||||
|
||||
def restart_timers(self):
|
||||
if self.last_task is not None:
|
||||
|
|
@ -130,15 +209,40 @@ class TimersManager(
|
|||
self.widget_user_idle.refresh_context()
|
||||
self.is_running = False
|
||||
|
||||
for module in self.modules:
|
||||
module.stop_timer()
|
||||
self.timer_stopper(None)
|
||||
|
||||
def connect_with_modules(self, enabled_modules):
|
||||
for module in enabled_modules:
|
||||
if not isinstance(module, ITimersManager):
|
||||
connector = getattr(module, "timers_manager_connector", None)
|
||||
if connector is None:
|
||||
continue
|
||||
module.timer_manager_module = self
|
||||
self.modules.append(module)
|
||||
|
||||
missing_methods = set()
|
||||
for method_name in self._required_methods:
|
||||
if not hasattr(connector, method_name):
|
||||
missing_methods.add(method_name)
|
||||
|
||||
if missing_methods:
|
||||
joined = ", ".join(
|
||||
['"{}"'.format(name for name in missing_methods)]
|
||||
)
|
||||
self.log.info((
|
||||
"Module \"{}\" has missing required methods {}."
|
||||
).format(module.name, joined))
|
||||
continue
|
||||
|
||||
self._connectors_by_module_id[module.id] = connector
|
||||
self._modules_by_id[module.id] = module
|
||||
|
||||
# Optional method
|
||||
if hasattr(connector, "register_timers_manager"):
|
||||
try:
|
||||
connector.register_timers_manager(self)
|
||||
except Exception:
|
||||
self.log.info((
|
||||
"Failed to register timers manager"
|
||||
" for connector of module \"{}\"."
|
||||
).format(module.name))
|
||||
|
||||
def callbacks_by_idle_time(self):
|
||||
"""Implementation of IIdleManager interface."""
|
||||
|
|
@ -205,6 +309,15 @@ class TimersManager(
|
|||
if self.widget_user_idle.bool_is_showed is False:
|
||||
self.widget_user_idle.show()
|
||||
|
||||
# Webserver module implementation
|
||||
def webserver_initialization(self, server_manager):
|
||||
"""Add routes for timers to be able start/stop with rest api."""
|
||||
if self.tray_initialized:
|
||||
from .rest_api import TimersManagerModuleRestApi
|
||||
self.rest_api_obj = TimersManagerModuleRestApi(
|
||||
self, server_manager
|
||||
)
|
||||
|
||||
def change_timer_from_host(self, project_name, asset_name, task_name):
|
||||
"""Prepared method for calling change timers on REST api"""
|
||||
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
from abc import abstractmethod
|
||||
from openpype.modules import OpenPypeInterface
|
||||
|
||||
|
||||
class IWebServerRoutes(OpenPypeInterface):
|
||||
"""Other modules interface to register their routes."""
|
||||
@abstractmethod
|
||||
def webserver_initialization(self, server_manager):
|
||||
pass
|
||||
|
|
@ -1,12 +1,31 @@
|
|||
"""WebServerModule spawns aiohttp server in asyncio loop.
|
||||
|
||||
Main usage of the module is in OpenPype tray where make sense to add ability
|
||||
of other modules to add theirs routes. Module which would want use that
|
||||
option must have implemented method `webserver_initialization` which must
|
||||
expect `WebServerManager` object where is possible to add routes or paths
|
||||
with handlers.
|
||||
|
||||
WebServerManager is by default created only in tray.
|
||||
|
||||
It is possible to create server manager without using module logic at all
|
||||
using `create_new_server_manager`. That can be handy for standalone scripts
|
||||
with predefined host and port and separated routes and logic.
|
||||
|
||||
Running multiple servers in one process is not recommended and probably won't
|
||||
work as expected. It is because of few limitations connected to asyncio module.
|
||||
|
||||
When module's `create_server_manager` is called it is also set environment
|
||||
variable "OPENPYPE_WEBSERVER_URL". Which should lead to root access point
|
||||
of server.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
|
||||
from openpype import resources
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype_interfaces import (
|
||||
ITrayService,
|
||||
IWebServerRoutes
|
||||
)
|
||||
from openpype_interfaces import ITrayService
|
||||
|
||||
|
||||
class WebServerModule(OpenPypeModule, ITrayService):
|
||||
|
|
@ -28,8 +47,15 @@ class WebServerModule(OpenPypeModule, ITrayService):
|
|||
return
|
||||
|
||||
for module in enabled_modules:
|
||||
if isinstance(module, IWebServerRoutes):
|
||||
if not hasattr(module, "webserver_initialization"):
|
||||
continue
|
||||
|
||||
try:
|
||||
module.webserver_initialization(self.server_manager)
|
||||
except Exception:
|
||||
self.log.warning((
|
||||
"Failed to connect module \"{}\" to webserver."
|
||||
).format(module.name))
|
||||
|
||||
def tray_init(self):
|
||||
self.create_server_manager()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from openpype.lib import (
|
|||
get_ffmpeg_tool_path,
|
||||
|
||||
run_subprocess,
|
||||
split_command_to_list,
|
||||
path_to_subprocess_arg,
|
||||
|
||||
should_decompress,
|
||||
|
|
@ -116,19 +115,19 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
|
|||
jpeg_items.append(path_to_subprocess_arg(full_output_path))
|
||||
|
||||
subprocess_command = " ".join(jpeg_items)
|
||||
subprocess_args = split_command_to_list(subprocess_command)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("{}".format(subprocess_command))
|
||||
try: # temporary until oiiotool is supported cross platform
|
||||
run_subprocess(
|
||||
subprocess_args, shell=True, logger=self.log
|
||||
subprocess_command, shell=True, logger=self.log
|
||||
)
|
||||
except RuntimeError as exp:
|
||||
if "Compression" in str(exp):
|
||||
self.log.debug("Unsupported compression on input files. " +
|
||||
"Skipping!!!")
|
||||
return
|
||||
self.log.warning("Conversion crashed", exc_info=True)
|
||||
raise
|
||||
|
||||
if "representations" not in instance.data:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import pyblish
|
|||
import openpype.api
|
||||
from openpype.lib import (
|
||||
get_ffmpeg_tool_path,
|
||||
split_command_to_list,
|
||||
path_to_subprocess_arg
|
||||
)
|
||||
import tempfile
|
||||
|
|
@ -62,13 +61,10 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
|||
cmd += self.create_cmd(audio_inputs)
|
||||
cmd += path_to_subprocess_arg(audio_temp_fpath)
|
||||
|
||||
# Split command to list for subprocess
|
||||
cmd_list = split_command_to_list(cmd)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(cmd))
|
||||
openpype.api.run_subprocess(
|
||||
cmd_list, logger=self.log
|
||||
cmd, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# remove empty
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from openpype.lib import (
|
|||
get_ffmpeg_tool_path,
|
||||
ffprobe_streams,
|
||||
|
||||
split_command_to_list,
|
||||
path_to_subprocess_arg,
|
||||
|
||||
should_decompress,
|
||||
|
|
@ -220,15 +219,12 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
raise NotImplementedError
|
||||
|
||||
subprcs_cmd = " ".join(ffmpeg_args)
|
||||
subprocess_args = split_command_to_list(subprcs_cmd)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug(
|
||||
"Executing: {}".format(" ".join(subprocess_args))
|
||||
)
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
|
||||
openpype.api.run_subprocess(
|
||||
subprocess_args, shell=True, logger=self.log
|
||||
subprcs_cmd, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# delete files added to fill gaps
|
||||
|
|
|
|||
|
|
@ -200,16 +200,14 @@ class ExtractReviewSlate(openpype.api.Extractor):
|
|||
" ".join(input_args),
|
||||
" ".join(output_args)
|
||||
]
|
||||
slate_subprocess_args = openpype.lib.split_command_to_list(
|
||||
" ".join(slate_args)
|
||||
)
|
||||
slate_subprocess_cmd = " ".join(slate_args)
|
||||
|
||||
# run slate generation subprocess
|
||||
self.log.debug(
|
||||
"Slate Executing: {}".format(" ".join(slate_subprocess_args))
|
||||
"Slate Executing: {}".format(slate_subprocess_cmd)
|
||||
)
|
||||
openpype.api.run_subprocess(
|
||||
slate_subprocess_args, shell=True, logger=self.log
|
||||
slate_subprocess_cmd, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# create ffmpeg concat text file path
|
||||
|
|
@ -244,7 +242,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
|
|||
"Executing concat: {}".format(" ".join(concat_args))
|
||||
)
|
||||
openpype.api.run_subprocess(
|
||||
concat_args, shell=True, logger=self.log
|
||||
concat_args, logger=self.log
|
||||
)
|
||||
|
||||
self.log.debug("__ repre[tags]: {}".format(repre["tags"]))
|
||||
|
|
|
|||
|
|
@ -297,6 +297,15 @@
|
|||
"textures"
|
||||
]
|
||||
}
|
||||
},
|
||||
"loader": {
|
||||
"family_filter_profiles": [
|
||||
{
|
||||
"hosts": [],
|
||||
"task_types": [],
|
||||
"filter_families": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets[ftrack.Library]\": {\"characters[ftrack]\": {}, \"locations[ftrack]\": {}}, \"shots[ftrack.Sequence]\": {\"scripts\": {}, \"editorial[ftrack.Folder]\": {}}}}",
|
||||
|
|
|
|||
|
|
@ -206,6 +206,48 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "loader",
|
||||
"label": "Loader",
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"key": "family_filter_profiles",
|
||||
"label": "Family filtering",
|
||||
"use_label_wrap": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"type": "task-types-enum",
|
||||
"key": "task_types",
|
||||
"label": "Task types"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "template",
|
||||
"name": "template_publish_families",
|
||||
"template_data": {
|
||||
"key": "filter_families",
|
||||
"label": "Filter families",
|
||||
"multiselection": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"__default_values__": {
|
||||
"multiselection": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "{key}",
|
||||
"label": "{label}",
|
||||
"multiselection": "{multiselection}",
|
||||
"type": "enum",
|
||||
"enum_items": [
|
||||
{"action": "action"},
|
||||
{"animation": "animation"},
|
||||
{"audio": "audio"},
|
||||
{"camera": "camera"},
|
||||
{"editorial": "editorial"},
|
||||
{"layout": "layout"},
|
||||
{"look": "look"},
|
||||
{"mayaAscii": "mayaAscii"},
|
||||
{"model": "model"},
|
||||
{"pointcache": "pointcache"},
|
||||
{"reference": "reference"},
|
||||
{"render": "render"},
|
||||
{"review": "review"},
|
||||
{"rig": "rig"},
|
||||
{"setdress": "setdress"},
|
||||
{"workfile": "workfile"},
|
||||
{"xgen": "xgen"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import sys
|
||||
import time
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
|
@ -9,7 +8,7 @@ from openpype.tools.utils import lib as tools_lib
|
|||
from openpype.tools.loader.widgets import (
|
||||
ThumbnailWidget,
|
||||
VersionWidget,
|
||||
FamilyListWidget,
|
||||
FamilyListView,
|
||||
RepresentationWidget
|
||||
)
|
||||
from openpype.tools.utils.widgets import AssetWidget
|
||||
|
|
@ -65,7 +64,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
assets = AssetWidget(
|
||||
self.dbcon, multiselection=True, parent=self
|
||||
)
|
||||
families = FamilyListWidget(
|
||||
families = FamilyListView(
|
||||
self.dbcon, self.family_config_cache, parent=self
|
||||
)
|
||||
subsets = LibrarySubsetWidget(
|
||||
|
|
@ -151,6 +150,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
assets.view.clicked.connect(self.on_assetview_click)
|
||||
subsets.active_changed.connect(self.on_subsetschanged)
|
||||
subsets.version_changed.connect(self.on_versionschanged)
|
||||
subsets.refreshed.connect(self._on_subset_refresh)
|
||||
self.combo_projects.currentTextChanged.connect(self.on_project_change)
|
||||
|
||||
self.sync_server = sync_server
|
||||
|
|
@ -242,6 +242,12 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
"Config `%s` has no function `install`" % _config.__name__
|
||||
)
|
||||
|
||||
subsets = self.data["widgets"]["subsets"]
|
||||
representations = self.data["widgets"]["representations"]
|
||||
|
||||
subsets.on_project_change(self.dbcon.Session["AVALON_PROJECT"])
|
||||
representations.on_project_change(self.dbcon.Session["AVALON_PROJECT"])
|
||||
|
||||
self.family_config_cache.refresh()
|
||||
self.groups_config.refresh()
|
||||
|
||||
|
|
@ -252,12 +258,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
title = "{} - {}".format(self.tool_title, project_name)
|
||||
self.setWindowTitle(title)
|
||||
|
||||
subsets = self.data["widgets"]["subsets"]
|
||||
subsets.on_project_change(self.dbcon.Session["AVALON_PROJECT"])
|
||||
|
||||
representations = self.data["widgets"]["representations"]
|
||||
representations.on_project_change(self.dbcon.Session["AVALON_PROJECT"])
|
||||
|
||||
@property
|
||||
def current_project(self):
|
||||
if (
|
||||
|
|
@ -288,6 +288,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self.echo("Fetching version..")
|
||||
tools_lib.schedule(self._versionschanged, 150, channel="mongo")
|
||||
|
||||
def _on_subset_refresh(self, has_item):
|
||||
subsets_widget = self.data["widgets"]["subsets"]
|
||||
families_view = self.data["widgets"]["families"]
|
||||
|
||||
subsets_widget.set_loading_state(loading=False, empty=not has_item)
|
||||
families = subsets_widget.get_subsets_families()
|
||||
families_view.set_enabled_families(families)
|
||||
|
||||
def set_context(self, context, refresh=True):
|
||||
self.echo("Setting context: {}".format(context))
|
||||
lib.schedule(
|
||||
|
|
@ -312,13 +320,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
assert project_doc, "This is a bug"
|
||||
|
||||
assets_widget = self.data["widgets"]["assets"]
|
||||
families_view = self.data["widgets"]["families"]
|
||||
families_view.set_enabled_families(set())
|
||||
families_view.refresh()
|
||||
|
||||
assets_widget.model.stop_fetch_thread()
|
||||
assets_widget.refresh()
|
||||
assets_widget.setFocus()
|
||||
|
||||
families = self.data["widgets"]["families"]
|
||||
families.refresh()
|
||||
|
||||
def clear_assets_underlines(self):
|
||||
last_asset_ids = self.data["state"]["assetIds"]
|
||||
if not last_asset_ids:
|
||||
|
|
@ -337,8 +346,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
|
||||
def _assetschanged(self):
|
||||
"""Selected assets have changed"""
|
||||
t1 = time.time()
|
||||
|
||||
assets_widget = self.data["widgets"]["assets"]
|
||||
subsets_widget = self.data["widgets"]["subsets"]
|
||||
subsets_model = subsets_widget.model
|
||||
|
|
@ -365,14 +372,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
empty=True
|
||||
)
|
||||
|
||||
def on_refreshed(has_item):
|
||||
empty = not has_item
|
||||
subsets_widget.set_loading_state(loading=False, empty=empty)
|
||||
subsets_model.refreshed.disconnect()
|
||||
self.echo("Duration: %.3fs" % (time.time() - t1))
|
||||
|
||||
subsets_model.refreshed.connect(on_refreshed)
|
||||
|
||||
subsets_model.set_assets(asset_ids)
|
||||
subsets_widget.view.setColumnHidden(
|
||||
subsets_model.Columns.index("asset"),
|
||||
|
|
@ -386,9 +385,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self.data["state"]["assetIds"] = asset_ids
|
||||
|
||||
representations = self.data["widgets"]["representations"]
|
||||
representations.set_version_ids([]) # reset repre list
|
||||
|
||||
self.echo("Duration: %.3fs" % (time.time() - t1))
|
||||
# reset repre list
|
||||
representations.set_version_ids([])
|
||||
|
||||
def _subsetschanged(self):
|
||||
asset_ids = self.data["state"]["assetIds"]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import sys
|
||||
import time
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from avalon import api, io, style, pipeline
|
||||
|
|
@ -11,7 +10,7 @@ from openpype.tools.utils import lib
|
|||
from .widgets import (
|
||||
SubsetWidget,
|
||||
VersionWidget,
|
||||
FamilyListWidget,
|
||||
FamilyListView,
|
||||
ThumbnailWidget,
|
||||
RepresentationWidget,
|
||||
OverlayFrame
|
||||
|
|
@ -64,7 +63,7 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
assets = AssetWidget(io, multiselection=True, parent=self)
|
||||
assets.set_current_asset_btn_visibility(True)
|
||||
|
||||
families = FamilyListWidget(io, self.family_config_cache, self)
|
||||
families = FamilyListView(io, self.family_config_cache, self)
|
||||
subsets = SubsetWidget(
|
||||
io,
|
||||
self.groups_config,
|
||||
|
|
@ -146,6 +145,7 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
assets.view.clicked.connect(self.on_assetview_click)
|
||||
subsets.active_changed.connect(self.on_subsetschanged)
|
||||
subsets.version_changed.connect(self.on_versionschanged)
|
||||
subsets.refreshed.connect(self._on_subset_refresh)
|
||||
|
||||
subsets.load_started.connect(self._on_load_start)
|
||||
subsets.load_ended.connect(self._on_load_end)
|
||||
|
|
@ -215,6 +215,14 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
def _hide_overlay(self):
|
||||
self._overlay_frame.setVisible(False)
|
||||
|
||||
def _on_subset_refresh(self, has_item):
|
||||
subsets_widget = self.data["widgets"]["subsets"]
|
||||
families_view = self.data["widgets"]["families"]
|
||||
|
||||
subsets_widget.set_loading_state(loading=False, empty=not has_item)
|
||||
families = subsets_widget.get_subsets_families()
|
||||
families_view.set_enabled_families(families)
|
||||
|
||||
def _on_load_end(self):
|
||||
# Delay hiding as click events happened during loading should be
|
||||
# blocked
|
||||
|
|
@ -223,8 +231,11 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
# ------------------------------
|
||||
|
||||
def on_context_task_change(self, *args, **kwargs):
|
||||
# Change to context asset on context change
|
||||
assets_widget = self.data["widgets"]["assets"]
|
||||
families_view = self.data["widgets"]["families"]
|
||||
# Refresh families config
|
||||
families_view.refresh()
|
||||
# Change to context asset on context change
|
||||
assets_widget.select_assets(io.Session["AVALON_ASSET"])
|
||||
|
||||
def _refresh(self):
|
||||
|
|
@ -238,8 +249,8 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
assets_widget.refresh()
|
||||
assets_widget.setFocus()
|
||||
|
||||
families = self.data["widgets"]["families"]
|
||||
families.refresh()
|
||||
families_view = self.data["widgets"]["families"]
|
||||
families_view.refresh()
|
||||
|
||||
def clear_assets_underlines(self):
|
||||
"""Clear colors from asset data to remove colored underlines
|
||||
|
|
@ -264,8 +275,6 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
|
||||
def _assetschanged(self):
|
||||
"""Selected assets have changed"""
|
||||
t1 = time.time()
|
||||
|
||||
assets_widget = self.data["widgets"]["assets"]
|
||||
subsets_widget = self.data["widgets"]["subsets"]
|
||||
subsets_model = subsets_widget.model
|
||||
|
|
@ -283,14 +292,6 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
empty=True
|
||||
)
|
||||
|
||||
def on_refreshed(has_item):
|
||||
empty = not has_item
|
||||
subsets_widget.set_loading_state(loading=False, empty=empty)
|
||||
subsets_model.refreshed.disconnect()
|
||||
self.echo("Duration: %.3fs" % (time.time() - t1))
|
||||
|
||||
subsets_model.refreshed.connect(on_refreshed)
|
||||
|
||||
subsets_model.set_assets(asset_ids)
|
||||
subsets_widget.view.setColumnHidden(
|
||||
subsets_model.Columns.index("asset"),
|
||||
|
|
@ -304,7 +305,8 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
self.data["state"]["assetIds"] = asset_ids
|
||||
|
||||
representations = self.data["widgets"]["representations"]
|
||||
representations.set_version_ids([]) # reset repre list
|
||||
# reset repre list
|
||||
representations.set_version_ids([])
|
||||
|
||||
def _subsetschanged(self):
|
||||
asset_ids = self.data["state"]["assetIds"]
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ class BaseRepresentationModel(object):
|
|||
|
||||
|
||||
class SubsetsModel(TreeModel, BaseRepresentationModel):
|
||||
|
||||
doc_fetched = QtCore.Signal()
|
||||
refreshed = QtCore.Signal(bool)
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
"name": 1,
|
||||
"parent": 1,
|
||||
"schema": 1,
|
||||
"families": 1,
|
||||
"data.families": 1,
|
||||
"data.subsetGroup": 1
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +190,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
self._grouping = state
|
||||
self.on_doc_fetched()
|
||||
|
||||
def get_subsets_families(self):
|
||||
return self._doc_payload.get("subset_families") or set()
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
# Trigger additional edit when `version` column changed
|
||||
# because it also updates the information in other columns
|
||||
|
|
@ -354,10 +356,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
},
|
||||
self.subset_doc_projection
|
||||
)
|
||||
for subset in subset_docs:
|
||||
subset_families = set()
|
||||
for subset_doc in subset_docs:
|
||||
if self._doc_fetching_stop:
|
||||
return
|
||||
subset_docs_by_id[subset["_id"]] = subset
|
||||
|
||||
families = subset_doc.get("data", {}).get("families")
|
||||
if families:
|
||||
subset_families.add(families[0])
|
||||
|
||||
subset_docs_by_id[subset_doc["_id"]] = subset_doc
|
||||
|
||||
subset_ids = list(subset_docs_by_id.keys())
|
||||
_pipeline = [
|
||||
|
|
@ -428,6 +436,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
self._doc_payload = {
|
||||
"asset_docs_by_id": asset_docs_by_id,
|
||||
"subset_docs_by_id": subset_docs_by_id,
|
||||
"subset_families": subset_families,
|
||||
"last_versions_by_subset_id": last_versions_by_subset_id
|
||||
}
|
||||
|
||||
|
|
@ -851,10 +860,9 @@ class SubsetFilterProxyModel(GroupMemberFilterProxyModel):
|
|||
class FamiliesFilterProxyModel(GroupMemberFilterProxyModel):
|
||||
"""Filters to specified families"""
|
||||
|
||||
def __init__(self, family_config_cache, *args, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FamiliesFilterProxyModel, self).__init__(*args, **kwargs)
|
||||
self._families = set()
|
||||
self.family_config_cache = family_config_cache
|
||||
|
||||
def familyFilter(self):
|
||||
return self._families
|
||||
|
|
@ -886,10 +894,6 @@ class FamiliesFilterProxyModel(GroupMemberFilterProxyModel):
|
|||
if not family:
|
||||
return True
|
||||
|
||||
family_config = self.family_config_cache.family_config(family)
|
||||
if family_config.get("hideFilter"):
|
||||
return False
|
||||
|
||||
# We want to keep the families which are not in the list
|
||||
return family in self._families
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
version_changed = QtCore.Signal() # version state changed for a subset
|
||||
load_started = QtCore.Signal()
|
||||
load_ended = QtCore.Signal()
|
||||
refreshed = QtCore.Signal(bool)
|
||||
|
||||
default_widths = (
|
||||
("subset", 200),
|
||||
|
|
@ -158,7 +159,7 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
grouping=enable_grouping
|
||||
)
|
||||
proxy = SubsetFilterProxyModel()
|
||||
family_proxy = FamiliesFilterProxyModel(family_config_cache)
|
||||
family_proxy = FamiliesFilterProxyModel()
|
||||
family_proxy.setSourceModel(proxy)
|
||||
|
||||
subset_filter = QtWidgets.QLineEdit()
|
||||
|
|
@ -242,9 +243,13 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
|
||||
self.filter.textChanged.connect(self.proxy.setFilterRegExp)
|
||||
self.filter.textChanged.connect(self.view.expandAll)
|
||||
model.refreshed.connect(self.refreshed)
|
||||
|
||||
self.model.refresh()
|
||||
|
||||
def get_subsets_families(self):
|
||||
return self.model.get_subsets_families()
|
||||
|
||||
def set_family_filters(self, families):
|
||||
self.family_proxy.setFamiliesFilter(families)
|
||||
|
||||
|
|
@ -846,36 +851,17 @@ class VersionWidget(QtWidgets.QWidget):
|
|||
self.data.set_version(version_doc)
|
||||
|
||||
|
||||
class FamilyListWidget(QtWidgets.QListWidget):
|
||||
"""A Widget that lists all available families"""
|
||||
class FamilyModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, dbcon, family_config_cache):
|
||||
super(FamilyModel, self).__init__()
|
||||
|
||||
NameRole = QtCore.Qt.UserRole + 1
|
||||
active_changed = QtCore.Signal(list)
|
||||
|
||||
def __init__(self, dbcon, family_config_cache, parent=None):
|
||||
super(FamilyListWidget, self).__init__(parent=parent)
|
||||
|
||||
self.family_config_cache = family_config_cache
|
||||
self.dbcon = dbcon
|
||||
self.family_config_cache = family_config_cache
|
||||
|
||||
multi_select = QtWidgets.QAbstractItemView.ExtendedSelection
|
||||
self.setSelectionMode(multi_select)
|
||||
self.setAlternatingRowColors(True)
|
||||
# Enable RMB menu
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.show_right_mouse_menu)
|
||||
|
||||
self.itemChanged.connect(self._on_item_changed)
|
||||
self._items_by_family = {}
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the listed families.
|
||||
|
||||
This gets all unique families and adds them as checkable items to
|
||||
the list.
|
||||
|
||||
"""
|
||||
|
||||
families = []
|
||||
families = set()
|
||||
if self.dbcon.Session.get("AVALON_PROJECT"):
|
||||
result = list(self.dbcon.aggregate([
|
||||
{"$match": {
|
||||
|
|
@ -890,81 +876,228 @@ class FamilyListWidget(QtWidgets.QListWidget):
|
|||
}}
|
||||
]))
|
||||
if result:
|
||||
families = result[0]["families"]
|
||||
families = set(result[0]["families"])
|
||||
|
||||
# Rebuild list
|
||||
self.blockSignals(True)
|
||||
self.clear()
|
||||
for name in sorted(families):
|
||||
family = self.family_config_cache.family_config(name)
|
||||
if family.get("hideFilter"):
|
||||
continue
|
||||
root_item = self.invisibleRootItem()
|
||||
|
||||
label = family.get("label", name)
|
||||
icon = family.get("icon", None)
|
||||
for family in tuple(self._items_by_family.keys()):
|
||||
if family not in families:
|
||||
item = self._items_by_family.pop(family)
|
||||
root_item.removeRow(item.row())
|
||||
|
||||
# TODO: This should be more managable by the artist
|
||||
# Temporarily implement support for a default state in the project
|
||||
# configuration
|
||||
state = family.get("state", True)
|
||||
state = QtCore.Qt.Checked if state else QtCore.Qt.Unchecked
|
||||
self.family_config_cache.refresh()
|
||||
|
||||
new_items = []
|
||||
for family in families:
|
||||
family_config = self.family_config_cache.family_config(family)
|
||||
label = family_config.get("label", family)
|
||||
icon = family_config.get("icon", None)
|
||||
|
||||
if family_config.get("state", True):
|
||||
state = QtCore.Qt.Checked
|
||||
else:
|
||||
state = QtCore.Qt.Unchecked
|
||||
|
||||
if family not in self._items_by_family:
|
||||
item = QtGui.QStandardItem(label)
|
||||
item.setFlags(
|
||||
QtCore.Qt.ItemIsEnabled
|
||||
| QtCore.Qt.ItemIsSelectable
|
||||
| QtCore.Qt.ItemIsUserCheckable
|
||||
)
|
||||
new_items.append(item)
|
||||
self._items_by_family[family] = item
|
||||
|
||||
else:
|
||||
item = self._items_by_family[label]
|
||||
item.setData(label, QtCore.Qt.DisplayRole)
|
||||
|
||||
item = QtWidgets.QListWidgetItem(parent=self)
|
||||
item.setText(label)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
item.setData(self.NameRole, name)
|
||||
item.setCheckState(state)
|
||||
|
||||
if icon:
|
||||
item.setIcon(icon)
|
||||
|
||||
self.addItem(item)
|
||||
self.blockSignals(False)
|
||||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
||||
self.active_changed.emit(self.get_filters())
|
||||
|
||||
def get_filters(self):
|
||||
class FamilyProxyFiler(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FamilyProxyFiler, self).__init__(*args, **kwargs)
|
||||
|
||||
self._filtering_enabled = False
|
||||
self._enabled_families = set()
|
||||
|
||||
def set_enabled_families(self, families):
|
||||
if self._enabled_families == families:
|
||||
return
|
||||
|
||||
self._enabled_families = families
|
||||
if self._filtering_enabled:
|
||||
self.invalidateFilter()
|
||||
|
||||
def is_filter_enabled(self):
|
||||
return self._filtering_enabled
|
||||
|
||||
def set_filter_enabled(self, enabled=None):
|
||||
if enabled is None:
|
||||
enabled = not self._filtering_enabled
|
||||
elif self._filtering_enabled == enabled:
|
||||
return
|
||||
|
||||
self._filtering_enabled = enabled
|
||||
self.invalidateFilter()
|
||||
|
||||
def filterAcceptsRow(self, row, parent):
|
||||
if not self._filtering_enabled:
|
||||
return True
|
||||
|
||||
if not self._enabled_families:
|
||||
return False
|
||||
|
||||
index = self.sourceModel().index(row, self.filterKeyColumn(), parent)
|
||||
if index.data(QtCore.Qt.DisplayRole) in self._enabled_families:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FamilyListView(QtWidgets.QListView):
|
||||
active_changed = QtCore.Signal(list)
|
||||
|
||||
def __init__(self, dbcon, family_config_cache, parent=None):
|
||||
super(FamilyListView, self).__init__(parent=parent)
|
||||
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
family_model = FamilyModel(dbcon, family_config_cache)
|
||||
proxy_model = FamilyProxyFiler()
|
||||
proxy_model.setDynamicSortFilter(True)
|
||||
proxy_model.setSourceModel(family_model)
|
||||
|
||||
self.setModel(proxy_model)
|
||||
|
||||
family_model.dataChanged.connect(self._on_data_change)
|
||||
self.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
self._family_model = family_model
|
||||
self._proxy_model = proxy_model
|
||||
|
||||
def set_enabled_families(self, families):
|
||||
self._proxy_model.set_enabled_families(families)
|
||||
|
||||
self.set_enabled_family_filtering(True)
|
||||
|
||||
def set_enabled_family_filtering(self, enabled=None):
|
||||
self._proxy_model.set_filter_enabled(enabled)
|
||||
|
||||
def refresh(self):
|
||||
self._family_model.refresh()
|
||||
|
||||
self.active_changed.emit(self.get_enabled_families())
|
||||
|
||||
def get_enabled_families(self):
|
||||
"""Return the checked family items"""
|
||||
model = self._family_model
|
||||
checked_families = []
|
||||
for row in range(model.rowCount()):
|
||||
index = model.index(row, 0)
|
||||
if index.data(QtCore.Qt.CheckStateRole) == QtCore.Qt.Checked:
|
||||
family = index.data(QtCore.Qt.DisplayRole)
|
||||
checked_families.append(family)
|
||||
|
||||
items = [self.item(i) for i in
|
||||
range(self.count())]
|
||||
return checked_families
|
||||
|
||||
return [item.data(self.NameRole) for item in items if
|
||||
item.checkState() == QtCore.Qt.Checked]
|
||||
def set_all_unchecked(self):
|
||||
self._set_checkstates(False, self._get_all_indexes())
|
||||
|
||||
def _on_item_changed(self):
|
||||
self.active_changed.emit(self.get_filters())
|
||||
def set_all_checked(self):
|
||||
self._set_checkstates(True, self._get_all_indexes())
|
||||
|
||||
def _get_all_indexes(self):
|
||||
indexes = []
|
||||
model = self._family_model
|
||||
for row in range(model.rowCount()):
|
||||
index = model.index(row, 0)
|
||||
indexes.append(index)
|
||||
return indexes
|
||||
|
||||
def _set_checkstates(self, checked, indexes):
|
||||
if not indexes:
|
||||
return
|
||||
|
||||
if checked is None:
|
||||
state = None
|
||||
elif checked:
|
||||
state = QtCore.Qt.Checked
|
||||
else:
|
||||
state = QtCore.Qt.Unchecked
|
||||
|
||||
def _set_checkstate_all(self, state):
|
||||
_state = QtCore.Qt.Checked if state is True else QtCore.Qt.Unchecked
|
||||
self.blockSignals(True)
|
||||
for i in range(self.count()):
|
||||
item = self.item(i)
|
||||
item.setCheckState(_state)
|
||||
|
||||
for index in indexes:
|
||||
index_state = index.data(QtCore.Qt.CheckStateRole)
|
||||
if index_state == state:
|
||||
continue
|
||||
|
||||
new_state = state
|
||||
if new_state is None:
|
||||
if index_state == QtCore.Qt.Checked:
|
||||
new_state = QtCore.Qt.Unchecked
|
||||
else:
|
||||
new_state = QtCore.Qt.Checked
|
||||
|
||||
index.model().setData(index, new_state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
self.blockSignals(False)
|
||||
self.active_changed.emit(self.get_filters())
|
||||
|
||||
def show_right_mouse_menu(self, pos):
|
||||
self.active_changed.emit(self.get_enabled_families())
|
||||
|
||||
def _change_selection_state(self, checked):
|
||||
indexes = self.selectionModel().selectedIndexes()
|
||||
self._set_checkstates(checked, indexes)
|
||||
|
||||
def _on_data_change(self, *_args):
|
||||
self.active_changed.emit(self.get_enabled_families())
|
||||
|
||||
def _on_context_menu(self, pos):
|
||||
"""Build RMB menu under mouse at current position (within widget)"""
|
||||
|
||||
# Get mouse position
|
||||
globalpos = self.viewport().mapToGlobal(pos)
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
# Add enable all action
|
||||
state_checked = QtWidgets.QAction(menu, text="Enable All")
|
||||
state_checked.triggered.connect(
|
||||
lambda: self._set_checkstate_all(True))
|
||||
action_check_all = QtWidgets.QAction(menu)
|
||||
action_check_all.setText("Enable All")
|
||||
action_check_all.triggered.connect(self.set_all_checked)
|
||||
# Add disable all action
|
||||
state_unchecked = QtWidgets.QAction(menu, text="Disable All")
|
||||
state_unchecked.triggered.connect(
|
||||
lambda: self._set_checkstate_all(False))
|
||||
action_uncheck_all = QtWidgets.QAction(menu)
|
||||
action_uncheck_all.setText("Disable All")
|
||||
action_uncheck_all.triggered.connect(self.set_all_unchecked)
|
||||
|
||||
menu.addAction(state_checked)
|
||||
menu.addAction(state_unchecked)
|
||||
menu.addAction(action_check_all)
|
||||
menu.addAction(action_uncheck_all)
|
||||
|
||||
menu.exec_(globalpos)
|
||||
# Get mouse position
|
||||
global_pos = self.viewport().mapToGlobal(pos)
|
||||
menu.exec_(global_pos)
|
||||
|
||||
def event(self, event):
|
||||
if not event.type() == QtCore.QEvent.KeyPress:
|
||||
pass
|
||||
|
||||
elif event.key() == QtCore.Qt.Key_Space:
|
||||
self._change_selection_state(None)
|
||||
return True
|
||||
|
||||
elif event.key() == QtCore.Qt.Key_Backspace:
|
||||
self._change_selection_state(False)
|
||||
return True
|
||||
|
||||
elif event.key() == QtCore.Qt.Key_Return:
|
||||
self._change_selection_state(True)
|
||||
return True
|
||||
|
||||
return super(FamilyListView, self).event(event)
|
||||
|
||||
|
||||
class RepresentationWidget(QtWidgets.QWidget):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import json
|
|||
from Qt import QtWidgets, QtGui, QtCore
|
||||
from openpype.tools.settings import CHILD_OFFSET
|
||||
from .widgets import ExpandingWidget
|
||||
from .lib import create_deffered_value_change_timer
|
||||
|
||||
|
||||
class BaseWidget(QtWidgets.QWidget):
|
||||
|
|
@ -329,6 +330,20 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class InputWidget(BaseWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InputWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
# Input widgets have always timer available (but may not be used).
|
||||
self._value_change_timer = create_deffered_value_change_timer(
|
||||
self._on_value_change_timer
|
||||
)
|
||||
|
||||
def start_value_timer(self):
|
||||
self._value_change_timer.start()
|
||||
|
||||
def _on_value_change_timer(self):
|
||||
pass
|
||||
|
||||
def create_ui(self):
|
||||
if self.entity.use_label_wrap:
|
||||
label = None
|
||||
|
|
|
|||
|
|
@ -609,14 +609,23 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
self.project_list_widget.refresh()
|
||||
|
||||
def _on_reset_crash(self):
|
||||
self.project_list_widget.setEnabled(False)
|
||||
self._set_enabled_project_list(False)
|
||||
super(ProjectWidget, self)._on_reset_crash()
|
||||
|
||||
def _on_reset_success(self):
|
||||
if not self.project_list_widget.isEnabled():
|
||||
self.project_list_widget.setEnabled(True)
|
||||
self._set_enabled_project_list(True)
|
||||
super(ProjectWidget, self)._on_reset_success()
|
||||
|
||||
def _set_enabled_project_list(self, enabled):
|
||||
if (
|
||||
enabled
|
||||
and self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
enabled = False
|
||||
if self.project_list_widget.isEnabled() != enabled:
|
||||
self.project_list_widget.setEnabled(enabled)
|
||||
|
||||
def _create_root_entity(self):
|
||||
self.entity = ProjectSettings(change_state=False)
|
||||
self.entity.on_change_callbacks.append(self._on_entity_change)
|
||||
|
|
@ -637,7 +646,8 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
|
||||
if self.modify_defaults_checkbox:
|
||||
self.modify_defaults_checkbox.setEnabled(True)
|
||||
self.project_list_widget.setEnabled(True)
|
||||
|
||||
self._set_enabled_project_list(True)
|
||||
|
||||
except DefaultsNotDefined:
|
||||
if not self.modify_defaults_checkbox:
|
||||
|
|
@ -646,7 +656,7 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
self.entity.set_defaults_state()
|
||||
self.modify_defaults_checkbox.setChecked(True)
|
||||
self.modify_defaults_checkbox.setEnabled(False)
|
||||
self.project_list_widget.setEnabled(False)
|
||||
self._set_enabled_project_list(False)
|
||||
|
||||
except StudioDefaultsNotDefined:
|
||||
self.select_default_project()
|
||||
|
|
@ -666,8 +676,10 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
self._set_enabled_project_list(False)
|
||||
if not self.entity.is_in_defaults_state():
|
||||
self.reset()
|
||||
else:
|
||||
self._set_enabled_project_list(True)
|
||||
if not self.entity.is_in_studio_state():
|
||||
self.reset()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from uuid import uuid4
|
|||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .base import BaseWidget
|
||||
from .lib import create_deffered_value_change_timer
|
||||
from .widgets import (
|
||||
ExpandingWidget,
|
||||
IconButton
|
||||
|
|
@ -284,6 +285,10 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
|
||||
self.confirm_btn = None
|
||||
|
||||
self._key_change_timer = create_deffered_value_change_timer(
|
||||
self._on_timeout
|
||||
)
|
||||
|
||||
if collapsible_key:
|
||||
self.create_collapsible_ui()
|
||||
else:
|
||||
|
|
@ -516,6 +521,10 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
if self.ignore_input_changes:
|
||||
return
|
||||
|
||||
self._key_change_timer.start()
|
||||
|
||||
def _on_timeout(self):
|
||||
key = self.key_value()
|
||||
is_key_duplicated = self.entity_widget.validate_key_duplication(
|
||||
self.temp_key, key, self
|
||||
)
|
||||
|
|
|
|||
|
|
@ -400,7 +400,9 @@ class TextWidget(InputWidget):
|
|||
def _on_value_change(self):
|
||||
if self.ignore_input_changes:
|
||||
return
|
||||
self.start_value_timer()
|
||||
|
||||
def _on_value_change_timer(self):
|
||||
self.entity.set(self.input_value())
|
||||
|
||||
|
||||
|
|
@ -474,6 +476,9 @@ class NumberWidget(InputWidget):
|
|||
if self.ignore_input_changes:
|
||||
return
|
||||
|
||||
self.start_value_timer()
|
||||
|
||||
def _on_value_change_timer(self):
|
||||
value = self.input_field.value()
|
||||
if self._slider_widget is not None and not self._ignore_input_change:
|
||||
self._ignore_slider_change = True
|
||||
|
|
@ -571,7 +576,9 @@ class RawJsonWidget(InputWidget):
|
|||
def _on_value_change(self):
|
||||
if self.ignore_input_changes:
|
||||
return
|
||||
self.start_value_timer()
|
||||
|
||||
def _on_value_change_timer(self):
|
||||
self._is_invalid = self.input_field.has_invalid_value()
|
||||
if not self.is_invalid:
|
||||
self.entity.set(self.input_field.json_value())
|
||||
|
|
@ -786,4 +793,7 @@ class PathInputWidget(InputWidget):
|
|||
def _on_value_change(self):
|
||||
if self.ignore_input_changes:
|
||||
return
|
||||
self.start_value_timer()
|
||||
|
||||
def _on_value_change_timer(self):
|
||||
self.entity.set(self.input_value())
|
||||
|
|
|
|||
18
openpype/tools/settings/settings/lib.py
Normal file
18
openpype/tools/settings/settings/lib.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from Qt import QtCore
|
||||
|
||||
# Offset of value change trigger in ms
|
||||
VALUE_CHANGE_OFFSET_MS = 300
|
||||
|
||||
|
||||
def create_deffered_value_change_timer(callback):
|
||||
"""Deffer value change callback.
|
||||
|
||||
UI won't trigger all callbacks on each value change but after predefined
|
||||
time. Timer is reset on each start so callback is triggered after user
|
||||
finish editing.
|
||||
"""
|
||||
timer = QtCore.QTimer()
|
||||
timer.setSingleShot(True)
|
||||
timer.setInterval(VALUE_CHANGE_OFFSET_MS)
|
||||
timer.timeout.connect(callback)
|
||||
return timer
|
||||
|
|
@ -146,6 +146,15 @@ QSlider::handle:vertical {
|
|||
border: 1px solid #464b54;
|
||||
background: #21252B;
|
||||
}
|
||||
|
||||
#ProjectListWidget QListView:disabled {
|
||||
background: #282C34;
|
||||
}
|
||||
|
||||
#ProjectListWidget QListView::item:disabled {
|
||||
color: #4e5254;
|
||||
}
|
||||
|
||||
#ProjectListWidget QLabel {
|
||||
background: transparent;
|
||||
font-weight: bold;
|
||||
|
|
@ -249,8 +258,6 @@ QTabBar::tab:!selected:hover {
|
|||
background: #333840;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QTabBar::tab:first:selected {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
@ -405,12 +412,15 @@ QHeaderView::section {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed {
|
||||
QAbstractItemView::item:pressed {
|
||||
background: #78879b;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active {
|
||||
QAbstractItemView::item:selected:active {
|
||||
background: #3d8ec9;
|
||||
}
|
||||
QAbstractItemView::item:selected:!active {
|
||||
background: #3d8ec9;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,12 @@ import collections
|
|||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from avalon import io, api, style
|
||||
import avalon.api
|
||||
from avalon import style
|
||||
from avalon.vendor import qtawesome
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._jobs = dict()
|
||||
|
||||
|
||||
class SharedObjects:
|
||||
# Variable for family cache in global context
|
||||
# QUESTION is this safe? More than one tool can refresh at the same time.
|
||||
family_cache = None
|
||||
|
||||
|
||||
def global_family_cache():
|
||||
if SharedObjects.family_cache is None:
|
||||
SharedObjects.family_cache = FamilyConfigCache(io)
|
||||
return SharedObjects.family_cache
|
||||
from openpype.api import get_project_settings
|
||||
from openpype.lib import filter_profiles
|
||||
|
||||
|
||||
def format_version(value, hero_version=False):
|
||||
|
|
@ -66,6 +55,10 @@ def defer(delay, func):
|
|||
return func()
|
||||
|
||||
|
||||
class SharedObjects:
|
||||
jobs = {}
|
||||
|
||||
|
||||
def schedule(func, time, channel="default"):
|
||||
"""Run `func` at a later `time` in a dedicated `channel`
|
||||
|
||||
|
|
@ -77,7 +70,7 @@ def schedule(func, time, channel="default"):
|
|||
"""
|
||||
|
||||
try:
|
||||
self._jobs[channel].stop()
|
||||
SharedObjects.jobs[channel].stop()
|
||||
except (AttributeError, KeyError, RuntimeError):
|
||||
pass
|
||||
|
||||
|
|
@ -86,7 +79,7 @@ def schedule(func, time, channel="default"):
|
|||
timer.timeout.connect(func)
|
||||
timer.start(time)
|
||||
|
||||
self._jobs[channel] = timer
|
||||
SharedObjects.jobs[channel] = timer
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -98,7 +91,6 @@ def dummy():
|
|||
.. pass
|
||||
|
||||
"""
|
||||
|
||||
yield
|
||||
|
||||
|
||||
|
|
@ -289,11 +281,12 @@ def preserve_selection(tree_view, column=0, role=None, current_index=True):
|
|||
class FamilyConfigCache:
|
||||
default_color = "#0091B2"
|
||||
_default_icon = None
|
||||
_default_item = None
|
||||
|
||||
def __init__(self, dbcon):
|
||||
self.dbcon = dbcon
|
||||
self.family_configs = {}
|
||||
self._family_filters_set = False
|
||||
self._require_refresh = True
|
||||
|
||||
@classmethod
|
||||
def default_icon(cls):
|
||||
|
|
@ -303,17 +296,27 @@ class FamilyConfigCache:
|
|||
)
|
||||
return cls._default_icon
|
||||
|
||||
@classmethod
|
||||
def default_item(cls):
|
||||
if cls._default_item is None:
|
||||
cls._default_item = {"icon": cls.default_icon()}
|
||||
return cls._default_item
|
||||
|
||||
def family_config(self, family_name):
|
||||
"""Get value from config with fallback to default"""
|
||||
return self.family_configs.get(family_name, self.default_item())
|
||||
if self._require_refresh:
|
||||
self._refresh()
|
||||
|
||||
def refresh(self):
|
||||
item = self.family_configs.get(family_name)
|
||||
if not item:
|
||||
item = {
|
||||
"icon": self.default_icon()
|
||||
}
|
||||
if self._family_filters_set:
|
||||
item["state"] = False
|
||||
return item
|
||||
|
||||
def refresh(self, force=False):
|
||||
self._require_refresh = True
|
||||
|
||||
if force:
|
||||
self._refresh()
|
||||
|
||||
def _refresh(self):
|
||||
"""Get the family configurations from the database
|
||||
|
||||
The configuration must be stored on the project under `config`.
|
||||
|
|
@ -329,62 +332,62 @@ class FamilyConfigCache:
|
|||
It is possible to override the default behavior and set specific
|
||||
families checked. For example we only want the families imagesequence
|
||||
and camera to be visible in the Loader.
|
||||
|
||||
# This will turn every item off
|
||||
api.data["familyStateDefault"] = False
|
||||
|
||||
# Only allow the imagesequence and camera
|
||||
api.data["familyStateToggled"] = ["imagesequence", "camera"]
|
||||
|
||||
"""
|
||||
self._require_refresh = False
|
||||
self._family_filters_set = False
|
||||
|
||||
self.family_configs.clear()
|
||||
|
||||
families = []
|
||||
# Skip if we're not in host context
|
||||
if not avalon.api.registered_host():
|
||||
return
|
||||
|
||||
# Update the icons from the project configuration
|
||||
project_name = self.dbcon.Session.get("AVALON_PROJECT")
|
||||
if project_name:
|
||||
project_doc = self.dbcon.find_one(
|
||||
{"type": "project"},
|
||||
projection={"config.families": True}
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
asset_name = os.environ.get("AVALON_ASSET")
|
||||
task_name = os.environ.get("AVALON_TASK")
|
||||
if not all((project_name, asset_name, task_name)):
|
||||
return
|
||||
|
||||
matching_item = None
|
||||
project_settings = get_project_settings(project_name)
|
||||
profiles = (
|
||||
project_settings
|
||||
["global"]
|
||||
["tools"]
|
||||
["loader"]
|
||||
["family_filter_profiles"]
|
||||
)
|
||||
if profiles:
|
||||
asset_doc = self.dbcon.find_one(
|
||||
{"type": "asset", "name": asset_name},
|
||||
{"data.tasks": True}
|
||||
)
|
||||
tasks_info = asset_doc.get("data", {}).get("tasks") or {}
|
||||
task_type = tasks_info.get(task_name, {}).get("type")
|
||||
profiles_filter = {
|
||||
"task_types": task_type,
|
||||
"hosts": os.environ["AVALON_APP"]
|
||||
}
|
||||
matching_item = filter_profiles(profiles, profiles_filter)
|
||||
|
||||
if not project_doc:
|
||||
print((
|
||||
"Project \"{}\" not found!"
|
||||
" Can't refresh family icons cache."
|
||||
).format(project_name))
|
||||
else:
|
||||
families = project_doc["config"].get("families") or []
|
||||
families = []
|
||||
if matching_item:
|
||||
families = matching_item["filter_families"]
|
||||
|
||||
# Check if any family state are being overwritten by the configuration
|
||||
default_state = api.data.get("familiesStateDefault", True)
|
||||
toggled = set(api.data.get("familiesStateToggled") or [])
|
||||
if not families:
|
||||
return
|
||||
|
||||
self._family_filters_set = True
|
||||
|
||||
# Replace icons with a Qt icon we can use in the user interfaces
|
||||
for family in families:
|
||||
name = family["name"]
|
||||
# Set family icon
|
||||
icon = family.get("icon", None)
|
||||
if icon:
|
||||
family["icon"] = qtawesome.icon(
|
||||
"fa.{}".format(icon),
|
||||
color=self.default_color
|
||||
)
|
||||
else:
|
||||
family["icon"] = self.default_icon()
|
||||
family_info = {
|
||||
"name": family,
|
||||
"icon": self.default_icon(),
|
||||
"state": True
|
||||
}
|
||||
|
||||
# Update state
|
||||
if name in toggled:
|
||||
state = True
|
||||
else:
|
||||
state = default_state
|
||||
family["state"] = state
|
||||
|
||||
self.family_configs[name] = family
|
||||
|
||||
return self.family_configs
|
||||
self.family_configs[family] = family_info
|
||||
|
||||
|
||||
class GroupsConfig:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue