mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch '2.x/develop' into 2.x/feature/harmony_flag_inventory
# Conflicts: # pype/hosts/harmony/__init__.py
This commit is contained in:
commit
d2efd2e791
39 changed files with 682 additions and 369 deletions
|
|
@ -6,6 +6,12 @@ from pypeapp import (
|
|||
execute
|
||||
)
|
||||
|
||||
from pypeapp.lib.mongo import (
|
||||
decompose_url,
|
||||
compose_url,
|
||||
get_default_components
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
Extractor,
|
||||
|
||||
|
|
@ -44,6 +50,9 @@ __all__ = [
|
|||
"project_overrides_dir_path",
|
||||
"config",
|
||||
"execute",
|
||||
"decompose_url",
|
||||
"compose_url",
|
||||
"get_default_components",
|
||||
|
||||
# plugin classes
|
||||
"Extractor",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import six
|
|||
import avalon.api
|
||||
from .api import config
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import contextlib
|
|||
|
||||
from avalon import schema
|
||||
from avalon.vendor import requests
|
||||
from avalon.io import extract_port_from_url
|
||||
|
||||
# Third-party dependencies
|
||||
import pymongo
|
||||
|
|
@ -72,8 +73,17 @@ class DbConnector(object):
|
|||
self.Session.update(self._from_environment())
|
||||
|
||||
timeout = int(self.Session["AVALON_TIMEOUT"])
|
||||
self._mongo_client = pymongo.MongoClient(
|
||||
self.Session["AVALON_MONGO"], serverSelectionTimeoutMS=timeout)
|
||||
mongo_url = self.Session["AVALON_MONGO"]
|
||||
kwargs = {
|
||||
"host": mongo_url,
|
||||
"serverSelectionTimeoutMS": timeout
|
||||
}
|
||||
|
||||
port = extract_port_from_url(mongo_url)
|
||||
if port is not None:
|
||||
kwargs["port"] = int(port)
|
||||
|
||||
self._mongo_client = pymongo.MongoClient(**kwargs)
|
||||
|
||||
for retry in range(3):
|
||||
try:
|
||||
|
|
@ -381,6 +391,10 @@ class DbConnector(object):
|
|||
if document is None:
|
||||
break
|
||||
|
||||
if document.get("type") == "master_version":
|
||||
_document = self.find_one({"_id": document["version_id"]})
|
||||
document["data"] = _document["data"]
|
||||
|
||||
parents.append(document)
|
||||
|
||||
return parents
|
||||
|
|
|
|||
|
|
@ -4,17 +4,14 @@ import json
|
|||
import bson
|
||||
import bson.json_util
|
||||
from pype.modules.rest_api import RestApi, abort, CallbackResult
|
||||
from pype.modules.ftrack.lib.custom_db_connector import DbConnector
|
||||
from pype.modules.ftrack.lib.io_nonsingleton import DbConnector
|
||||
|
||||
|
||||
class AvalonRestApi(RestApi):
|
||||
dbcon = DbConnector(
|
||||
os.environ["AVALON_MONGO"],
|
||||
os.environ["AVALON_DB"]
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dbcon = DbConnector()
|
||||
self.dbcon.install()
|
||||
|
||||
@RestApi.route("/projects/<project_name>", url_prefix="/avalon", methods="GET")
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ import time
|
|||
import uuid
|
||||
|
||||
import ftrack_api
|
||||
import pymongo
|
||||
from pype.modules.ftrack.lib import credentials
|
||||
from pype.modules.ftrack.ftrack_server.lib import (
|
||||
ftrack_events_mongo_settings, check_ftrack_url
|
||||
check_ftrack_url, get_ftrack_event_mongo_info
|
||||
)
|
||||
|
||||
import socket_thread
|
||||
|
||||
|
||||
|
|
@ -30,22 +32,19 @@ class MongoPermissionsError(Exception):
|
|||
|
||||
def check_mongo_url(host, port, log_error=False):
|
||||
"""Checks if mongo server is responding"""
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.create_connection(
|
||||
(host, port),
|
||||
timeout=1
|
||||
)
|
||||
return True
|
||||
except socket.error as err:
|
||||
client = pymongo.MongoClient(host=host, port=port)
|
||||
# Force connection on a request as the connect=True parameter of
|
||||
# MongoClient seems to be useless here
|
||||
client.server_info()
|
||||
except pymongo.errors.ServerSelectionTimeoutError as err:
|
||||
if log_error:
|
||||
print("Can't connect to MongoDB at {}:{} because: {}".format(
|
||||
host, port, err
|
||||
))
|
||||
return False
|
||||
finally:
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate_credentials(url, user, api):
|
||||
|
|
@ -190,9 +189,10 @@ def main_loop(ftrack_url):
|
|||
|
||||
os.environ["FTRACK_EVENT_SUB_ID"] = str(uuid.uuid1())
|
||||
# Get mongo hostname and port for testing mongo connection
|
||||
mongo_list = ftrack_events_mongo_settings()
|
||||
mongo_hostname = mongo_list[0]
|
||||
mongo_port = mongo_list[1]
|
||||
|
||||
mongo_uri, mongo_port, database_name, collection_name = (
|
||||
get_ftrack_event_mongo_info()
|
||||
)
|
||||
|
||||
# Current file
|
||||
file_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
|
@ -270,13 +270,12 @@ def main_loop(ftrack_url):
|
|||
ftrack_accessible = check_ftrack_url(ftrack_url)
|
||||
|
||||
if not mongo_accessible:
|
||||
mongo_accessible = check_mongo_url(mongo_hostname, mongo_port)
|
||||
mongo_accessible = check_mongo_url(mongo_uri, mongo_port)
|
||||
|
||||
# Run threads only if Ftrack is accessible
|
||||
if not ftrack_accessible or not mongo_accessible:
|
||||
if not mongo_accessible and not printed_mongo_error:
|
||||
mongo_url = mongo_hostname + ":" + mongo_port
|
||||
print("Can't access Mongo {}".format(mongo_url))
|
||||
print("Can't access Mongo {}".format(mongo_uri))
|
||||
|
||||
if not ftrack_accessible and not printed_ftrack_error:
|
||||
print("Can't access Ftrack {}".format(ftrack_url))
|
||||
|
|
|
|||
|
|
@ -18,12 +18,13 @@ import ftrack_api.operation
|
|||
import ftrack_api._centralized_storage_scenario
|
||||
import ftrack_api.event
|
||||
from ftrack_api.logging import LazyLogMessage as L
|
||||
try:
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
except ImportError:
|
||||
from urlparse import urlparse, parse_qs
|
||||
|
||||
from pype.api import Logger
|
||||
from pype.api import (
|
||||
Logger,
|
||||
get_default_components,
|
||||
decompose_url,
|
||||
compose_url
|
||||
)
|
||||
|
||||
from pype.modules.ftrack.lib.custom_db_connector import DbConnector
|
||||
|
||||
|
|
@ -32,69 +33,29 @@ TOPIC_STATUS_SERVER = "pype.event.server.status"
|
|||
TOPIC_STATUS_SERVER_RESULT = "pype.event.server.status.result"
|
||||
|
||||
|
||||
def ftrack_events_mongo_settings():
|
||||
host = None
|
||||
port = None
|
||||
username = None
|
||||
password = None
|
||||
collection = None
|
||||
database = None
|
||||
auth_db = ""
|
||||
|
||||
if os.environ.get('FTRACK_EVENTS_MONGO_URL'):
|
||||
result = urlparse(os.environ['FTRACK_EVENTS_MONGO_URL'])
|
||||
|
||||
host = result.hostname
|
||||
try:
|
||||
port = result.port
|
||||
except ValueError:
|
||||
raise RuntimeError("invalid port specified")
|
||||
username = result.username
|
||||
password = result.password
|
||||
try:
|
||||
database = result.path.lstrip("/").split("/")[0]
|
||||
collection = result.path.lstrip("/").split("/")[1]
|
||||
except IndexError:
|
||||
if not database:
|
||||
raise RuntimeError("missing database name for logging")
|
||||
try:
|
||||
auth_db = parse_qs(result.query)['authSource'][0]
|
||||
except KeyError:
|
||||
# no auth db provided, mongo will use the one we are connecting to
|
||||
pass
|
||||
else:
|
||||
host = os.environ.get('FTRACK_EVENTS_MONGO_HOST')
|
||||
port = int(os.environ.get('FTRACK_EVENTS_MONGO_PORT', "0"))
|
||||
database = os.environ.get('FTRACK_EVENTS_MONGO_DB')
|
||||
username = os.environ.get('FTRACK_EVENTS_MONGO_USER')
|
||||
password = os.environ.get('FTRACK_EVENTS_MONGO_PASSWORD')
|
||||
collection = os.environ.get('FTRACK_EVENTS_MONGO_COL')
|
||||
auth_db = os.environ.get('FTRACK_EVENTS_MONGO_AUTH_DB', 'avalon')
|
||||
|
||||
return host, port, database, username, password, collection, auth_db
|
||||
|
||||
|
||||
def get_ftrack_event_mongo_info():
|
||||
host, port, database, username, password, collection, auth_db = (
|
||||
ftrack_events_mongo_settings()
|
||||
database_name = (
|
||||
os.environ.get("FTRACK_EVENTS_MONGO_DB") or "pype"
|
||||
)
|
||||
collection_name = (
|
||||
os.environ.get("FTRACK_EVENTS_MONGO_COL") or "ftrack_events"
|
||||
)
|
||||
user_pass = ""
|
||||
if username and password:
|
||||
user_pass = "{}:{}@".format(username, password)
|
||||
|
||||
socket_path = "{}:{}".format(host, port)
|
||||
mongo_url = os.environ.get("FTRACK_EVENTS_MONGO_URL")
|
||||
if mongo_url is not None:
|
||||
components = decompose_url(mongo_url)
|
||||
_used_ftrack_url = True
|
||||
else:
|
||||
components = get_default_components()
|
||||
_used_ftrack_url = False
|
||||
|
||||
dab = ""
|
||||
if database:
|
||||
dab = "/{}".format(database)
|
||||
if not _used_ftrack_url or components["database"] is None:
|
||||
components["database"] = database_name
|
||||
components["collection"] = collection_name
|
||||
|
||||
auth = ""
|
||||
if auth_db:
|
||||
auth = "?authSource={}".format(auth_db)
|
||||
uri = compose_url(components)
|
||||
|
||||
url = "mongodb://{}{}{}{}".format(user_pass, socket_path, dab, auth)
|
||||
|
||||
return url, database, collection
|
||||
return uri, components["port"], database_name, collection_name
|
||||
|
||||
|
||||
def check_ftrack_url(url, log_errors=True):
|
||||
|
|
@ -198,16 +159,17 @@ class StorerEventHub(SocketBaseEventHub):
|
|||
class ProcessEventHub(SocketBaseEventHub):
|
||||
|
||||
hearbeat_msg = b"processor"
|
||||
url, database, table_name = get_ftrack_event_mongo_info()
|
||||
uri, port, database, table_name = get_ftrack_event_mongo_info()
|
||||
|
||||
is_table_created = False
|
||||
pypelog = Logger().get_logger("Session Processor")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.dbcon = DbConnector(
|
||||
mongo_url=self.url,
|
||||
database_name=self.database,
|
||||
table_name=self.table_name
|
||||
self.uri,
|
||||
self.port,
|
||||
self.database,
|
||||
self.table_name
|
||||
)
|
||||
super(ProcessEventHub, self).__init__(*args, **kwargs)
|
||||
|
||||
|
|
@ -269,7 +231,7 @@ class ProcessEventHub(SocketBaseEventHub):
|
|||
def load_events(self):
|
||||
"""Load not processed events sorted by stored date"""
|
||||
ago_date = datetime.datetime.now() - datetime.timedelta(days=3)
|
||||
result = self.dbcon.delete_many({
|
||||
self.dbcon.delete_many({
|
||||
"pype_data.stored": {"$lte": ago_date},
|
||||
"pype_data.is_processed": True
|
||||
})
|
||||
|
|
|
|||
|
|
@ -23,12 +23,8 @@ class SessionFactory:
|
|||
session = None
|
||||
|
||||
|
||||
url, database, table_name = get_ftrack_event_mongo_info()
|
||||
dbcon = DbConnector(
|
||||
mongo_url=url,
|
||||
database_name=database,
|
||||
table_name=table_name
|
||||
)
|
||||
uri, port, database, table_name = get_ftrack_event_mongo_info()
|
||||
dbcon = DbConnector(uri, port, database, table_name)
|
||||
|
||||
# ignore_topics = ["ftrack.meta.connected"]
|
||||
ignore_topics = []
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import atexit
|
|||
|
||||
# Third-party dependencies
|
||||
import pymongo
|
||||
from pype.api import decompose_url
|
||||
|
||||
|
||||
class NotActiveTable(Exception):
|
||||
|
|
@ -63,13 +64,29 @@ class DbConnector:
|
|||
log = logging.getLogger(__name__)
|
||||
timeout = 1000
|
||||
|
||||
def __init__(self, mongo_url, database_name, table_name=None):
|
||||
def __init__(
|
||||
self, uri, port=None, database_name=None, table_name=None
|
||||
):
|
||||
self._mongo_client = None
|
||||
self._sentry_client = None
|
||||
self._sentry_logging_handler = None
|
||||
self._database = None
|
||||
self._is_installed = False
|
||||
self._mongo_url = mongo_url
|
||||
|
||||
self._uri = uri
|
||||
components = decompose_url(uri)
|
||||
if port is None:
|
||||
port = components.get("port")
|
||||
|
||||
if database_name is None:
|
||||
database_name = components.get("database")
|
||||
|
||||
if database_name is None:
|
||||
raise ValueError(
|
||||
"Database is not defined for connection. {}".format(uri)
|
||||
)
|
||||
|
||||
self._port = port
|
||||
self._database_name = database_name
|
||||
|
||||
self.active_table = table_name
|
||||
|
|
@ -95,10 +112,16 @@ class DbConnector:
|
|||
atexit.register(self.uninstall)
|
||||
logging.basicConfig()
|
||||
|
||||
self._mongo_client = pymongo.MongoClient(
|
||||
self._mongo_url,
|
||||
serverSelectionTimeoutMS=self.timeout
|
||||
)
|
||||
kwargs = {
|
||||
"host": self._uri,
|
||||
"serverSelectionTimeoutMS": self.timeout
|
||||
}
|
||||
if self._port is not None:
|
||||
kwargs["port"] = self._port
|
||||
|
||||
self._mongo_client = pymongo.MongoClient(**kwargs)
|
||||
if self._port is None:
|
||||
self._port = self._mongo_client.PORT
|
||||
|
||||
for retry in range(3):
|
||||
try:
|
||||
|
|
@ -113,11 +136,11 @@ class DbConnector:
|
|||
else:
|
||||
raise IOError(
|
||||
"ERROR: Couldn't connect to %s in "
|
||||
"less than %.3f ms" % (self._mongo_url, self.timeout)
|
||||
"less than %.3f ms" % (self._uri, self.timeout)
|
||||
)
|
||||
|
||||
self.log.info("Connected to %s, delay %.3f s" % (
|
||||
self._mongo_url, time.time() - t1
|
||||
self._uri, time.time() - t1
|
||||
))
|
||||
|
||||
self._database = self._mongo_client[self._database_name]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import contextlib
|
|||
|
||||
from avalon import schema
|
||||
from avalon.vendor import requests
|
||||
from avalon.io import extract_port_from_url
|
||||
|
||||
# Third-party dependencies
|
||||
import pymongo
|
||||
|
|
@ -72,8 +73,17 @@ class DbConnector(object):
|
|||
self.Session.update(self._from_environment())
|
||||
|
||||
timeout = int(self.Session["AVALON_TIMEOUT"])
|
||||
self._mongo_client = pymongo.MongoClient(
|
||||
self.Session["AVALON_MONGO"], serverSelectionTimeoutMS=timeout)
|
||||
mongo_url = self.Session["AVALON_MONGO"]
|
||||
kwargs = {
|
||||
"host": mongo_url,
|
||||
"serverSelectionTimeoutMS": timeout
|
||||
}
|
||||
|
||||
port = extract_port_from_url(mongo_url)
|
||||
if port is not None:
|
||||
kwargs["port"] = int(port)
|
||||
|
||||
self._mongo_client = pymongo.MongoClient(**kwargs)
|
||||
|
||||
for retry in range(3):
|
||||
try:
|
||||
|
|
@ -381,6 +391,10 @@ class DbConnector(object):
|
|||
if document is None:
|
||||
break
|
||||
|
||||
if document.get("type") == "master_version":
|
||||
_document = self.find_one({"_id": document["version_id"]})
|
||||
document["data"] = _document["data"]
|
||||
|
||||
parents.append(document)
|
||||
|
||||
return parents
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import os
|
||||
import collections
|
||||
from Qt import QtCore
|
||||
from pype.api import Logger
|
||||
from pypeapp.lib.log import _bootstrap_mongo_log
|
||||
from pypeapp.lib.log import _bootstrap_mongo_log, LOG_COLLECTION_NAME
|
||||
|
||||
log = Logger().get_logger("LogModel", "LoggingModule")
|
||||
|
||||
|
|
@ -41,11 +40,11 @@ class LogModel(QtCore.QAbstractItemModel):
|
|||
super(LogModel, self).__init__(parent)
|
||||
self._root_node = Node()
|
||||
|
||||
collection = os.environ.get('PYPE_LOG_MONGO_COL')
|
||||
database = _bootstrap_mongo_log()
|
||||
self.dbcon = None
|
||||
if collection in database.list_collection_names():
|
||||
self.dbcon = database[collection]
|
||||
# Crash if connection is not possible to skip this module
|
||||
database = _bootstrap_mongo_log()
|
||||
if LOG_COLLECTION_NAME in database.list_collection_names():
|
||||
self.dbcon = database[LOG_COLLECTION_NAME]
|
||||
|
||||
def add_log(self, log):
|
||||
node = Node(log)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
from Qt import QtWidgets
|
||||
|
||||
from pype.api import Logger
|
||||
|
||||
from ..gui.app import LogsWindow
|
||||
|
||||
log = Logger().get_logger("LoggingModule", "logging")
|
||||
|
||||
|
||||
class LoggingModule:
|
||||
def __init__(self, main_parent=None, parent=None):
|
||||
self.parent = parent
|
||||
self.log = Logger().get_logger(self.__class__.__name__, "logging")
|
||||
|
||||
self.window = LogsWindow()
|
||||
try:
|
||||
self.window = LogsWindow()
|
||||
self.tray_menu = self._tray_menu
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Couldn't set Logging GUI due to error.", exc_info=True
|
||||
)
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, parent_menu):
|
||||
def _tray_menu(self, parent_menu):
|
||||
# Menu for Tray App
|
||||
menu = QtWidgets.QMenu('Logging', parent_menu)
|
||||
# menu.setProperty('submenu', 'on')
|
||||
|
|
|
|||
|
|
@ -48,8 +48,18 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin):
|
|||
|
||||
data = asset_entity['data']
|
||||
|
||||
context.data["frameStart"] = data.get("frameStart")
|
||||
context.data["frameEnd"] = data.get("frameEnd")
|
||||
frame_start = data.get("frameStart")
|
||||
if frame_start is None:
|
||||
frame_start = 1
|
||||
self.log.warning("Missing frame start. Defaulting to 1.")
|
||||
|
||||
frame_end = data.get("frameEnd")
|
||||
if frame_end is None:
|
||||
frame_end = 2
|
||||
self.log.warning("Missing frame end. Defaulting to 2.")
|
||||
|
||||
context.data["frameStart"] = frame_start
|
||||
context.data["frameEnd"] = frame_end
|
||||
|
||||
handles = data.get("handles") or 0
|
||||
handle_start = data.get("handleStart")
|
||||
|
|
@ -72,7 +82,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin):
|
|||
context.data["handleStart"] = int(handle_start)
|
||||
context.data["handleEnd"] = int(handle_end)
|
||||
|
||||
frame_start_h = data.get("frameStart") - context.data["handleStart"]
|
||||
frame_end_h = data.get("frameEnd") + context.data["handleEnd"]
|
||||
frame_start_h = frame_start - context.data["handleStart"]
|
||||
frame_end_h = frame_end + context.data["handleEnd"]
|
||||
context.data["frameStartHandle"] = frame_start_h
|
||||
context.data["frameEndHandle"] = frame_end_h
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ import pyblish.api
|
|||
|
||||
def _get_script():
|
||||
"""Get path to the image sequence script."""
|
||||
from pathlib import Path
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
try:
|
||||
from pype.scripts import publish_filesequence
|
||||
|
|
@ -26,6 +29,7 @@ def _get_script():
|
|||
module_path = module_path[: -len(".pyc")] + ".py"
|
||||
|
||||
path = Path(os.path.normpath(module_path)).resolve(strict=True)
|
||||
assert path is not None, ("Cannot determine path")
|
||||
|
||||
return str(path)
|
||||
|
||||
|
|
@ -840,7 +844,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
|
||||
# add audio to metadata file if available
|
||||
audio_file = context.data.get("audioFile")
|
||||
if os.path.isfile(audio_file):
|
||||
if audio_file and os.path.isfile(audio_file):
|
||||
publish_job.update({"audio": audio_file})
|
||||
|
||||
# pass Ftrack credentials in case of Muster
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@ except ImportError:
|
|||
import errno
|
||||
|
||||
|
||||
class ValidateFFmpegInstalled(pyblish.api.Validator):
|
||||
class ValidateFFmpegInstalled(pyblish.api.ContextPlugin):
|
||||
"""Validate availability of ffmpeg tool in PATH"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = 'Validate ffmpeg installation'
|
||||
families = ['review']
|
||||
optional = True
|
||||
|
||||
def is_tool(self, name):
|
||||
|
|
@ -27,7 +26,7 @@ class ValidateFFmpegInstalled(pyblish.api.Validator):
|
|||
return False
|
||||
return True
|
||||
|
||||
def process(self, instance):
|
||||
def process(self, context):
|
||||
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
self.log.info("ffmpeg path: `{}`".format(ffmpeg_path))
|
||||
if self.is_tool(ffmpeg_path) is False:
|
||||
|
|
|
|||
42
pype/plugins/harmony/load/load_audio.py
Normal file
42
pype/plugins/harmony/load/load_audio.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from avalon import api, harmony
|
||||
|
||||
|
||||
func = """
|
||||
function getUniqueColumnName( column_prefix )
|
||||
{
|
||||
var suffix = 0;
|
||||
// finds if unique name for a column
|
||||
var column_name = column_prefix;
|
||||
while(suffix < 2000)
|
||||
{
|
||||
if(!column.type(column_name))
|
||||
break;
|
||||
|
||||
suffix = suffix + 1;
|
||||
column_name = column_prefix + "_" + suffix;
|
||||
}
|
||||
return column_name;
|
||||
}
|
||||
|
||||
function func(args)
|
||||
{
|
||||
var uniqueColumnName = getUniqueColumnName(args[0]);
|
||||
column.add(uniqueColumnName , "SOUND");
|
||||
column.importSound(uniqueColumnName, 1, args[1]);
|
||||
}
|
||||
func
|
||||
"""
|
||||
|
||||
|
||||
class ImportAudioLoader(api.Loader):
|
||||
"""Import audio."""
|
||||
|
||||
families = ["shot"]
|
||||
representations = ["wav"]
|
||||
label = "Import Audio"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
wav_file = api.get_representation_path(context["representation"])
|
||||
harmony.send(
|
||||
{"function": func, "args": [context["subset"]["name"], wav_file]}
|
||||
)
|
||||
|
|
@ -14,18 +14,6 @@ class ImportTemplateLoader(api.Loader):
|
|||
label = "Import Template"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
# Make backdrops from metadata.
|
||||
backdrops = context["representation"]["data"].get("backdrops", [])
|
||||
|
||||
func = """function func(args)
|
||||
{
|
||||
Backdrop.addBackdrop("Top", args[0]);
|
||||
}
|
||||
func
|
||||
"""
|
||||
for backdrop in backdrops:
|
||||
harmony.send({"function": func, "args": [backdrop]})
|
||||
|
||||
# Import template.
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
zip_file = api.get_representation_path(context["representation"])
|
||||
|
|
@ -33,19 +21,6 @@ class ImportTemplateLoader(api.Loader):
|
|||
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
||||
zip_ref.extractall(template_path)
|
||||
|
||||
func = """function func(args)
|
||||
{
|
||||
var template_path = args[0];
|
||||
var drag_object = copyPaste.copyFromTemplate(
|
||||
template_path, 0, 0, copyPaste.getCurrentCreateOptions()
|
||||
);
|
||||
copyPaste.pasteNewNodes(
|
||||
drag_object, "", copyPaste.getCurrentPasteOptions()
|
||||
);
|
||||
}
|
||||
func
|
||||
"""
|
||||
|
||||
func = """function func(args)
|
||||
{
|
||||
var template_path = args[0];
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ class ExtractRender(pyblish.api.InstancePlugin):
|
|||
scene.currentScene(),
|
||||
scene.getFrameRate(),
|
||||
scene.getStartFrame(),
|
||||
scene.getStopFrame()
|
||||
scene.getStopFrame(),
|
||||
sound.getSoundtrackAll().path()
|
||||
]
|
||||
}
|
||||
func
|
||||
|
|
@ -41,6 +42,7 @@ class ExtractRender(pyblish.api.InstancePlugin):
|
|||
frame_rate = result[3]
|
||||
frame_start = result[4]
|
||||
frame_end = result[5]
|
||||
audio_path = result[6]
|
||||
|
||||
# Set output path to temp folder.
|
||||
path = tempfile.mkdtemp()
|
||||
|
|
@ -111,6 +113,7 @@ class ExtractRender(pyblish.api.InstancePlugin):
|
|||
mov_path = os.path.join(path, instance.data["name"] + ".mov")
|
||||
args = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", audio_path,
|
||||
"-i",
|
||||
os.path.join(path, collection.head + "%04d" + collection.tail),
|
||||
mov_path
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@ class ExtractSaveScene(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.ExtractorOrder - 0.49
|
||||
hosts = ["harmony"]
|
||||
|
||||
def process(self, instance):
|
||||
def process(self, context):
|
||||
harmony.save_scene()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import os
|
|||
import shutil
|
||||
|
||||
import pype.api
|
||||
from avalon import harmony
|
||||
import avalon.harmony
|
||||
import pype.hosts.harmony
|
||||
|
||||
|
||||
class ExtractTemplate(pype.api.Extractor):
|
||||
|
|
@ -14,6 +15,7 @@ class ExtractTemplate(pype.api.Extractor):
|
|||
|
||||
def process(self, instance):
|
||||
staging_dir = self.staging_dir(instance)
|
||||
filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name))
|
||||
|
||||
self.log.info("Outputting template to {}".format(staging_dir))
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ class ExtractTemplate(pype.api.Extractor):
|
|||
unique_backdrops = [backdrops[x] for x in set(backdrops.keys())]
|
||||
|
||||
# Get non-connected nodes within backdrops.
|
||||
all_nodes = harmony.send(
|
||||
all_nodes = avalon.harmony.send(
|
||||
{"function": "node.subNodes", "args": ["Top"]}
|
||||
)["result"]
|
||||
for node in [x for x in all_nodes if x not in dependencies]:
|
||||
|
|
@ -43,48 +45,9 @@ class ExtractTemplate(pype.api.Extractor):
|
|||
dependencies.remove(instance[0])
|
||||
|
||||
# Export template.
|
||||
func = """function func(args)
|
||||
{
|
||||
// Add an extra node just so a new group can be created.
|
||||
var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0);
|
||||
var template_group = node.createGroup(temp_node, "temp_group");
|
||||
node.deleteNode( template_group + "/temp_note" );
|
||||
|
||||
// This will make Node View to focus on the new group.
|
||||
selection.clearSelection();
|
||||
selection.addNodeToSelection(template_group);
|
||||
Action.perform("onActionEnterGroup()", "Node View");
|
||||
|
||||
// Recreate backdrops in group.
|
||||
for (var i = 0 ; i < args[0].length; i++)
|
||||
{
|
||||
Backdrop.addBackdrop(template_group, args[0][i]);
|
||||
};
|
||||
|
||||
// Copy-paste the selected nodes into the new group.
|
||||
var drag_object = copyPaste.copy(args[1], 1, frame.numberOf, "");
|
||||
copyPaste.pasteNewNodes(drag_object, template_group, "");
|
||||
|
||||
// Select all nodes within group and export as template.
|
||||
Action.perform( "selectAll()", "Node View" );
|
||||
copyPaste.createTemplateFromSelection(args[2], args[3]);
|
||||
|
||||
// Unfocus the group in Node view, delete all nodes and backdrops
|
||||
// created during the process.
|
||||
Action.perform("onActionUpToParent()", "Node View");
|
||||
node.deleteNode(template_group, true, true);
|
||||
}
|
||||
func
|
||||
"""
|
||||
harmony.send({
|
||||
"function": func,
|
||||
"args": [
|
||||
unique_backdrops,
|
||||
dependencies,
|
||||
"{}.tpl".format(instance.name),
|
||||
staging_dir
|
||||
]
|
||||
})
|
||||
pype.hosts.harmony.export_template(
|
||||
unique_backdrops, dependencies, filepath
|
||||
)
|
||||
|
||||
# Prep representation.
|
||||
os.chdir(staging_dir)
|
||||
|
|
@ -131,7 +94,7 @@ class ExtractTemplate(pype.api.Extractor):
|
|||
}
|
||||
func
|
||||
"""
|
||||
return harmony.send(
|
||||
return avalon.harmony.send(
|
||||
{"function": func, "args": [node]}
|
||||
)["result"]
|
||||
|
||||
|
|
@ -150,7 +113,7 @@ class ExtractTemplate(pype.api.Extractor):
|
|||
func
|
||||
"""
|
||||
|
||||
current_dependencies = harmony.send(
|
||||
current_dependencies = avalon.harmony.send(
|
||||
{"function": func, "args": [node]}
|
||||
)["result"]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import os
|
|||
import shutil
|
||||
|
||||
import pype.api
|
||||
import avalon.harmony
|
||||
import pype.hosts.harmony
|
||||
|
||||
|
||||
class ExtractWorkfile(pype.api.Extractor):
|
||||
|
|
@ -12,17 +14,25 @@ class ExtractWorkfile(pype.api.Extractor):
|
|||
families = ["workfile"]
|
||||
|
||||
def process(self, instance):
|
||||
file_path = instance.context.data["currentFile"]
|
||||
# Export template.
|
||||
backdrops = avalon.harmony.send(
|
||||
{"function": "Backdrop.backdrops", "args": ["Top"]}
|
||||
)["result"]
|
||||
nodes = avalon.harmony.send(
|
||||
{"function": "node.subNodes", "args": ["Top"]}
|
||||
)["result"]
|
||||
staging_dir = self.staging_dir(instance)
|
||||
filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name))
|
||||
|
||||
pype.hosts.harmony.export_template(backdrops, nodes, filepath)
|
||||
|
||||
# Prep representation.
|
||||
os.chdir(staging_dir)
|
||||
shutil.make_archive(
|
||||
instance.name,
|
||||
"{}".format(instance.name),
|
||||
"zip",
|
||||
os.path.dirname(file_path)
|
||||
os.path.join(staging_dir, "{}.tpl".format(instance.name))
|
||||
)
|
||||
zip_path = os.path.join(staging_dir, instance.name + ".zip")
|
||||
self.log.info(f"Output zip file: {zip_path}")
|
||||
|
||||
representation = {
|
||||
"name": "tpl",
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ class IncrementWorkfile(pyblish.api.InstancePlugin):
|
|||
errored_plugins = get_errored_plugins_from_data(instance.context)
|
||||
if errored_plugins:
|
||||
raise RuntimeError(
|
||||
"Skipping incrementing current file because submission to"
|
||||
" deadline failed."
|
||||
"Skipping incrementing current file because publishing failed."
|
||||
)
|
||||
|
||||
scene_dir = version_up(
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
expected_settings = pype.hosts.harmony.get_asset_settings()
|
||||
|
||||
# Harmony is expected to start at 1.
|
||||
frame_start = expected_settings["frameStart"]
|
||||
frame_end = expected_settings["frameEnd"]
|
||||
expected_settings["frameEnd"] = frame_end - frame_start + 1
|
||||
expected_settings["frameStart"] = 1
|
||||
|
||||
func = """function func()
|
||||
{
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class CreateRender(avalon.maya.Creator):
|
|||
self.data["framesPerTask"] = 1
|
||||
self.data["whitelist"] = False
|
||||
self.data["machineList"] = ""
|
||||
self.data["useMayaBatch"] = True
|
||||
self.data["useMayaBatch"] = False
|
||||
self.data["vrayScene"] = False
|
||||
# Disable for now as this feature is not working yet
|
||||
# self.data["assScene"] = False
|
||||
|
|
|
|||
|
|
@ -332,9 +332,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
options["extendFrames"] = extend_frames
|
||||
options["overrideExistingFrame"] = override_frames
|
||||
|
||||
maya_render_plugin = "MayaBatch"
|
||||
if not attributes.get("useMayaBatch", True):
|
||||
maya_render_plugin = "MayaCmd"
|
||||
maya_render_plugin = "MayaPype"
|
||||
if attributes.get("useMayaBatch", True):
|
||||
maya_render_plugin = "MayaBatch"
|
||||
|
||||
options["mayaRenderPlugin"] = maya_render_plugin
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ payload_skeleton = {
|
|||
"BatchName": None, # Top-level group name
|
||||
"Name": None, # Job name, as seen in Monitor
|
||||
"UserName": None,
|
||||
"Plugin": "MayaBatch",
|
||||
"Plugin": "MayaPype",
|
||||
"Frames": "{start}-{end}x{step}",
|
||||
"Comment": None,
|
||||
},
|
||||
|
|
@ -274,7 +274,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
step=int(self._instance.data["byFrameStep"]))
|
||||
|
||||
payload_skeleton["JobInfo"]["Plugin"] = self._instance.data.get(
|
||||
"mayaRenderPlugin", "MayaBatch")
|
||||
"mayaRenderPlugin", "MayaPype")
|
||||
|
||||
payload_skeleton["JobInfo"]["BatchName"] = filename
|
||||
# Job name, as seen in Monitor
|
||||
|
|
@ -311,12 +311,14 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
"AVALON_TASK",
|
||||
"PYPE_USERNAME",
|
||||
"PYPE_DEV",
|
||||
"PYPE_LOG_NO_COLORS"
|
||||
"PYPE_LOG_NO_COLORS",
|
||||
"PYPE_SETUP_PATH"
|
||||
]
|
||||
|
||||
environment = dict({key: os.environ[key] for key in keys
|
||||
if key in os.environ}, **api.Session)
|
||||
environment["PYPE_LOG_NO_COLORS"] = "1"
|
||||
environment["PYPE_MAYA_VERSION"] = cmds.about(v=True)
|
||||
payload_skeleton["JobInfo"].update({
|
||||
"EnvironmentKeyValue%d" % index: "{key}={value}".format(
|
||||
key=key,
|
||||
|
|
@ -428,7 +430,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
int(self._instance.data["frameStartHandle"]),
|
||||
int(self._instance.data["frameEndHandle"])),
|
||||
|
||||
"Plugin": "MayaBatch",
|
||||
"Plugin": self._instance.data.get(
|
||||
"mayaRenderPlugin", "MayaPype"),
|
||||
"FramesPerTask": self._instance.data.get("framesPerTask", 1)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,77 @@
|
|||
from avalon import photoshop
|
||||
from avalon import api, photoshop
|
||||
from avalon.vendor import Qt
|
||||
|
||||
|
||||
class CreateImage(photoshop.Creator):
|
||||
class CreateImage(api.Creator):
|
||||
"""Image folder for publish."""
|
||||
|
||||
name = "imageDefault"
|
||||
label = "Image"
|
||||
family = "image"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateImage, self).__init__(*args, **kwargs)
|
||||
def process(self):
|
||||
groups = []
|
||||
layers = []
|
||||
create_group = False
|
||||
group_constant = photoshop.get_com_objects().constants().psLayerSet
|
||||
if (self.options or {}).get("useSelection"):
|
||||
multiple_instances = False
|
||||
selection = photoshop.get_selected_layers()
|
||||
|
||||
if len(selection) > 1:
|
||||
# Ask user whether to create one image or image per selected
|
||||
# item.
|
||||
msg_box = Qt.QtWidgets.QMessageBox()
|
||||
msg_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg_box.setText(
|
||||
"Multiple layers selected."
|
||||
"\nDo you want to make one image per layer?"
|
||||
)
|
||||
msg_box.setStandardButtons(
|
||||
Qt.QtWidgets.QMessageBox.Yes |
|
||||
Qt.QtWidgets.QMessageBox.No |
|
||||
Qt.QtWidgets.QMessageBox.Cancel
|
||||
)
|
||||
ret = msg_box.exec_()
|
||||
if ret == Qt.QtWidgets.QMessageBox.Yes:
|
||||
multiple_instances = True
|
||||
elif ret == Qt.QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
if multiple_instances:
|
||||
for item in selection:
|
||||
if item.LayerType == group_constant:
|
||||
groups.append(item)
|
||||
else:
|
||||
layers.append(item)
|
||||
else:
|
||||
group = photoshop.group_selected_layers()
|
||||
group.Name = self.name
|
||||
groups.append(group)
|
||||
|
||||
elif len(selection) == 1:
|
||||
# One selected item. Use group if its a LayerSet (group), else
|
||||
# create a new group.
|
||||
if selection[0].LayerType == group_constant:
|
||||
groups.append(selection[0])
|
||||
else:
|
||||
layers.append(selection[0])
|
||||
elif len(selection) == 0:
|
||||
# No selection creates an empty group.
|
||||
create_group = True
|
||||
else:
|
||||
create_group = True
|
||||
|
||||
if create_group:
|
||||
group = photoshop.app().ActiveDocument.LayerSets.Add()
|
||||
group.Name = self.name
|
||||
groups.append(group)
|
||||
|
||||
for layer in layers:
|
||||
photoshop.select_layers([layer])
|
||||
group = photoshop.group_selected_layers()
|
||||
group.Name = layer.Name
|
||||
groups.append(group)
|
||||
|
||||
for group in groups:
|
||||
photoshop.imprint(group, self.data)
|
||||
|
|
|
|||
29
pype/plugins/photoshop/publish/increment_workfile.py
Normal file
29
pype/plugins/photoshop/publish/increment_workfile.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import pyblish.api
|
||||
from pype.action import get_errored_plugins_from_data
|
||||
from pype.lib import version_up
|
||||
from avalon import photoshop
|
||||
|
||||
|
||||
class IncrementWorkfile(pyblish.api.InstancePlugin):
|
||||
"""Increment the current workfile.
|
||||
|
||||
Saves the current scene with an increased version number.
|
||||
"""
|
||||
|
||||
label = "Increment Workfile"
|
||||
order = pyblish.api.IntegratorOrder + 9.0
|
||||
hosts = ["photoshop"]
|
||||
families = ["workfile"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
errored_plugins = get_errored_plugins_from_data(instance.context)
|
||||
if errored_plugins:
|
||||
raise RuntimeError(
|
||||
"Skipping incrementing current file because publishing failed."
|
||||
)
|
||||
|
||||
scene_path = version_up(instance.context.data["currentFile"])
|
||||
photoshop.app().ActiveDocument.SaveAs(scene_path)
|
||||
|
||||
self.log.info("Incremented workfile to: {}".format(scene_path))
|
||||
44
pype/plugins/photoshop/publish/validate_naming.py
Normal file
44
pype/plugins/photoshop/publish/validate_naming.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
|
||||
class ValidateNamingRepair(pyblish.api.Action):
|
||||
"""Repair the instance asset."""
|
||||
|
||||
label = "Repair"
|
||||
icon = "wrench"
|
||||
on = "failed"
|
||||
|
||||
def process(self, context, plugin):
|
||||
|
||||
# Get the errored instances
|
||||
failed = []
|
||||
for result in context.data["results"]:
|
||||
if (result["error"] is not None and result["instance"] is not None
|
||||
and result["instance"] not in failed):
|
||||
failed.append(result["instance"])
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
instances = pyblish.api.instances_by_plugin(failed, plugin)
|
||||
|
||||
for instance in instances:
|
||||
instance[0].Name = instance.data["name"].replace(" ", "_")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ValidateNaming(pyblish.api.InstancePlugin):
|
||||
"""Validate the instance name.
|
||||
|
||||
Spaces in names are not allowed. Will be replace with underscores.
|
||||
"""
|
||||
|
||||
label = "Validate Naming"
|
||||
hosts = ["photoshop"]
|
||||
order = pype.api.ValidateContentsOrder
|
||||
families = ["image"]
|
||||
actions = [ValidateNamingRepair]
|
||||
|
||||
def process(self, instance):
|
||||
msg = "Name \"{}\" is not allowed.".format(instance.data["name"])
|
||||
assert " " not in instance.data["name"], msg
|
||||
|
|
@ -60,14 +60,8 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
# options to be more flexible.
|
||||
asset_name = asset_name.split("_")[0]
|
||||
|
||||
shot_number = 10
|
||||
instances = []
|
||||
for track in tracks:
|
||||
self.log.info(track)
|
||||
|
||||
if "audio" in track.name.lower():
|
||||
continue
|
||||
|
||||
instances = []
|
||||
for child in track.each_child():
|
||||
|
||||
# Transitions are ignored, because Clips have the full frame
|
||||
|
|
@ -75,10 +69,13 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
if isinstance(child, otio.schema.transition.Transition):
|
||||
continue
|
||||
|
||||
# Hardcoded to expect a shot name of "[name].[extension]"
|
||||
child_name = os.path.splitext(child.name)[0].lower()
|
||||
name = f"{asset_name}_{child_name}"
|
||||
|
||||
frame_start = child.range_in_parent().start_time.value
|
||||
frame_end = child.range_in_parent().end_time_inclusive().value
|
||||
|
||||
name = f"{asset_name}_sh{shot_number:04}"
|
||||
label = f"{name} (framerange: {frame_start}-{frame_end})"
|
||||
instances.append(
|
||||
instance.context.create_instance(**{
|
||||
|
|
@ -96,8 +93,6 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
})
|
||||
)
|
||||
|
||||
shot_number += 10
|
||||
|
||||
visual_hierarchy = [asset_entity]
|
||||
while True:
|
||||
visual_parent = io.find_one(
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ class ExtractShot(pype.api.Extractor):
|
|||
os.path.dirname(editorial_path), basename + ".mov"
|
||||
)
|
||||
shot_mov = os.path.join(staging_dir, instance.data["name"] + ".mov")
|
||||
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
args = [
|
||||
"ffmpeg",
|
||||
ffmpeg_path,
|
||||
"-ss", str(instance.data["frameStart"] / fps),
|
||||
"-i", input_path,
|
||||
"-t", str(
|
||||
|
|
@ -58,7 +59,7 @@ class ExtractShot(pype.api.Extractor):
|
|||
shot_jpegs = os.path.join(
|
||||
staging_dir, instance.data["name"] + ".%04d.jpeg"
|
||||
)
|
||||
args = ["ffmpeg", "-i", shot_mov, shot_jpegs]
|
||||
args = [ffmpeg_path, "-i", shot_mov, shot_jpegs]
|
||||
self.log.info(f"Processing: {args}")
|
||||
output = pype.lib._subprocess(args)
|
||||
self.log.info(output)
|
||||
|
|
@ -79,7 +80,7 @@ class ExtractShot(pype.api.Extractor):
|
|||
|
||||
# Generate wav file.
|
||||
shot_wav = os.path.join(staging_dir, instance.data["name"] + ".wav")
|
||||
args = ["ffmpeg", "-i", shot_mov, shot_wav]
|
||||
args = [ffmpeg_path, "-i", shot_mov, shot_wav]
|
||||
self.log.info(f"Processing: {args}")
|
||||
output = pype.lib._subprocess(args)
|
||||
self.log.info(output)
|
||||
|
|
|
|||
23
pype/plugins/standalonepublisher/publish/validate_shots.py
Normal file
23
pype/plugins/standalonepublisher/publish/validate_shots.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
|
||||
class ValidateShots(pyblish.api.ContextPlugin):
|
||||
"""Validate there is a "mov" next to the editorial file."""
|
||||
|
||||
label = "Validate Shots"
|
||||
hosts = ["standalonepublisher"]
|
||||
order = pype.api.ValidateContentsOrder
|
||||
|
||||
def process(self, context):
|
||||
shot_names = []
|
||||
duplicate_names = []
|
||||
for instance in context:
|
||||
name = instance.data["name"]
|
||||
if name in shot_names:
|
||||
duplicate_names.append(name)
|
||||
else:
|
||||
shot_names.append(name)
|
||||
|
||||
msg = "There are duplicate shot names:\n{}".format(duplicate_names)
|
||||
assert not duplicate_names, msg
|
||||
|
|
@ -491,3 +491,24 @@ QToolButton {
|
|||
|
||||
#TerminalFilerBtn[type="log_critical"]:checked {color: rgb(255, 79, 117);}
|
||||
#TerminalFilerBtn[type="log_critical"] {color: rgba(255, 79, 117, 63);}
|
||||
|
||||
#SuspendLogsBtn {
|
||||
background: #444;
|
||||
border: none;
|
||||
border-top-right-radius: 7px;
|
||||
border-bottom-right-radius: 7px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
font-family: "FontAwesome";
|
||||
font-size: 11pt;
|
||||
color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#SuspendLogsBtn:hover {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
#SuspendLogsBtn:disabled {
|
||||
background: #4c4c4c;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ an active window manager; such as via Travis-CI.
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import inspect
|
||||
|
||||
from Qt import QtCore
|
||||
|
||||
|
|
@ -60,11 +61,15 @@ class Controller(QtCore.QObject):
|
|||
# store OrderGroups - now it is a singleton
|
||||
order_groups = util.OrderGroups
|
||||
|
||||
# When instance is toggled
|
||||
instance_toggled = QtCore.Signal(object, object, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Controller, self).__init__(parent)
|
||||
self.context = None
|
||||
self.plugins = {}
|
||||
self.optional_default = {}
|
||||
self.instance_toggled.connect(self._on_instance_toggled)
|
||||
|
||||
def reset_variables(self):
|
||||
# Data internal to the GUI itself
|
||||
|
|
@ -81,7 +86,6 @@ class Controller(QtCore.QObject):
|
|||
# - passing collectors order disables plugin/instance toggle
|
||||
self.collectors_order = None
|
||||
self.collect_state = 0
|
||||
self.collected = False
|
||||
|
||||
# - passing validators order disables validate button and gives ability
|
||||
# to know when to stop on validate button press
|
||||
|
|
@ -415,3 +419,19 @@ class Controller(QtCore.QObject):
|
|||
|
||||
for plugin in self.plugins:
|
||||
del(plugin)
|
||||
|
||||
def _on_instance_toggled(self, instance, old_value, new_value):
|
||||
callbacks = pyblish.api.registered_callbacks().get("instanceToggled")
|
||||
if not callbacks:
|
||||
return
|
||||
|
||||
for callback in callbacks:
|
||||
try:
|
||||
callback(instance, old_value, new_value)
|
||||
except Exception:
|
||||
print(
|
||||
"Callback for `instanceToggled` crashed. {}".format(
|
||||
os.path.abspath(inspect.getfile(callback))
|
||||
)
|
||||
)
|
||||
traceback.print_exception(*sys.exc_info())
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ from .awesome import tags as awesome
|
|||
import Qt
|
||||
from Qt import QtCore, QtGui
|
||||
from six import text_type
|
||||
from six.moves import queue
|
||||
from .vendor import qtawesome
|
||||
from .constants import PluginStates, InstanceStates, GroupStates, Roles
|
||||
|
||||
|
|
@ -49,6 +48,7 @@ TerminalDetailType = QtGui.QStandardItem.UserType + 4
|
|||
|
||||
class QAwesomeTextIconFactory:
|
||||
icons = {}
|
||||
|
||||
@classmethod
|
||||
def icon(cls, icon_name):
|
||||
if icon_name not in cls.icons:
|
||||
|
|
@ -58,6 +58,7 @@ class QAwesomeTextIconFactory:
|
|||
|
||||
class QAwesomeIconFactory:
|
||||
icons = {}
|
||||
|
||||
@classmethod
|
||||
def icon(cls, icon_name, icon_color):
|
||||
if icon_name not in cls.icons:
|
||||
|
|
@ -489,12 +490,8 @@ class PluginModel(QtGui.QStandardItemModel):
|
|||
new_records = result.get("records") or []
|
||||
if not has_warning:
|
||||
for record in new_records:
|
||||
if not hasattr(record, "levelname"):
|
||||
continue
|
||||
|
||||
if str(record.levelname).lower() in [
|
||||
"warning", "critical", "error"
|
||||
]:
|
||||
level_no = record.get("levelno")
|
||||
if level_no and level_no >= 30:
|
||||
new_flag_states[PluginStates.HasWarning] = True
|
||||
break
|
||||
|
||||
|
|
@ -788,12 +785,8 @@ class InstanceModel(QtGui.QStandardItemModel):
|
|||
new_records = result.get("records") or []
|
||||
if not has_warning:
|
||||
for record in new_records:
|
||||
if not hasattr(record, "levelname"):
|
||||
continue
|
||||
|
||||
if str(record.levelname).lower() in [
|
||||
"warning", "critical", "error"
|
||||
]:
|
||||
level_no = record.get("levelno")
|
||||
if level_no and level_no >= 30:
|
||||
new_flag_states[InstanceStates.HasWarning] = True
|
||||
break
|
||||
|
||||
|
|
@ -1009,7 +1002,7 @@ class ArtistProxy(QtCore.QAbstractProxyModel):
|
|||
return QtCore.QModelIndex()
|
||||
|
||||
|
||||
class TerminalModel(QtGui.QStandardItemModel):
|
||||
class TerminalDetailItem(QtGui.QStandardItem):
|
||||
key_label_record_map = (
|
||||
("instance", "Instance"),
|
||||
("msg", "Message"),
|
||||
|
|
@ -1022,6 +1015,57 @@ class TerminalModel(QtGui.QStandardItemModel):
|
|||
("msecs", "Millis")
|
||||
)
|
||||
|
||||
def __init__(self, record_item):
|
||||
self.record_item = record_item
|
||||
self.msg = None
|
||||
msg = record_item.get("msg")
|
||||
if msg is None:
|
||||
msg = record_item["label"].split("\n")[0]
|
||||
|
||||
super(TerminalDetailItem, self).__init__(msg)
|
||||
|
||||
def data(self, role=QtCore.Qt.DisplayRole):
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
if self.msg is None:
|
||||
self.msg = self.compute_detail_text(self.record_item)
|
||||
return self.msg
|
||||
return super(TerminalDetailItem, self).data(role)
|
||||
|
||||
def compute_detail_text(self, item_data):
|
||||
if item_data["type"] == "info":
|
||||
return item_data["label"]
|
||||
|
||||
html_text = ""
|
||||
for key, title in self.key_label_record_map:
|
||||
if key not in item_data:
|
||||
continue
|
||||
value = item_data[key]
|
||||
text = (
|
||||
str(value)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('\n', '<br/>')
|
||||
.replace(' ', ' ')
|
||||
)
|
||||
|
||||
title_tag = (
|
||||
'<span style=\" font-size:8pt; font-weight:600;'
|
||||
# ' background-color:#bbb; color:#333;\" >{}:</span> '
|
||||
' color:#fff;\" >{}:</span> '
|
||||
).format(title)
|
||||
|
||||
html_text += (
|
||||
'<tr><td width="100%" align=left>{}</td></tr>'
|
||||
'<tr><td width="100%">{}</td></tr>'
|
||||
).format(title_tag, text)
|
||||
|
||||
html_text = '<table width="100%" cellspacing="3">{}</table>'.format(
|
||||
html_text
|
||||
)
|
||||
return html_text
|
||||
|
||||
|
||||
class TerminalModel(QtGui.QStandardItemModel):
|
||||
item_icon_name = {
|
||||
"info": "fa.info",
|
||||
"record": "fa.circle",
|
||||
|
|
@ -1053,38 +1097,38 @@ class TerminalModel(QtGui.QStandardItemModel):
|
|||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.items_to_set_widget = queue.Queue()
|
||||
self.clear()
|
||||
|
||||
def prepare_records(self, result):
|
||||
def prepare_records(self, result, suspend_logs):
|
||||
prepared_records = []
|
||||
instance_name = None
|
||||
instance = result["instance"]
|
||||
if instance is not None:
|
||||
instance_name = instance.data["name"]
|
||||
|
||||
for record in result.get("records") or []:
|
||||
if isinstance(record, dict):
|
||||
record_item = record
|
||||
else:
|
||||
record_item = {
|
||||
"label": text_type(record.msg),
|
||||
"type": "record",
|
||||
"levelno": record.levelno,
|
||||
"threadName": record.threadName,
|
||||
"name": record.name,
|
||||
"filename": record.filename,
|
||||
"pathname": record.pathname,
|
||||
"lineno": record.lineno,
|
||||
"msg": text_type(record.msg),
|
||||
"msecs": record.msecs,
|
||||
"levelname": record.levelname
|
||||
}
|
||||
if not suspend_logs:
|
||||
for record in result.get("records") or []:
|
||||
if isinstance(record, dict):
|
||||
record_item = record
|
||||
else:
|
||||
record_item = {
|
||||
"label": text_type(record.msg),
|
||||
"type": "record",
|
||||
"levelno": record.levelno,
|
||||
"threadName": record.threadName,
|
||||
"name": record.name,
|
||||
"filename": record.filename,
|
||||
"pathname": record.pathname,
|
||||
"lineno": record.lineno,
|
||||
"msg": text_type(record.msg),
|
||||
"msecs": record.msecs,
|
||||
"levelname": record.levelname
|
||||
}
|
||||
|
||||
if instance_name is not None:
|
||||
record_item["instance"] = instance_name
|
||||
if instance_name is not None:
|
||||
record_item["instance"] = instance_name
|
||||
|
||||
prepared_records.append(record_item)
|
||||
prepared_records.append(record_item)
|
||||
|
||||
error = result.get("error")
|
||||
if error:
|
||||
|
|
@ -1140,49 +1184,14 @@ class TerminalModel(QtGui.QStandardItemModel):
|
|||
|
||||
self.appendRow(top_item)
|
||||
|
||||
detail_text = self.prepare_detail_text(record_item)
|
||||
detail_item = QtGui.QStandardItem(detail_text)
|
||||
detail_item = TerminalDetailItem(record_item)
|
||||
detail_item.setData(TerminalDetailType, Roles.TypeRole)
|
||||
top_item.appendRow(detail_item)
|
||||
self.items_to_set_widget.put(detail_item)
|
||||
|
||||
def update_with_result(self, result):
|
||||
for record in result["records"]:
|
||||
self.append(record)
|
||||
|
||||
def prepare_detail_text(self, item_data):
|
||||
if item_data["type"] == "info":
|
||||
return item_data["label"]
|
||||
|
||||
html_text = ""
|
||||
for key, title in self.key_label_record_map:
|
||||
if key not in item_data:
|
||||
continue
|
||||
value = item_data[key]
|
||||
text = (
|
||||
str(value)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('\n', '<br/>')
|
||||
.replace(' ', ' ')
|
||||
)
|
||||
|
||||
title_tag = (
|
||||
'<span style=\" font-size:8pt; font-weight:600;'
|
||||
# ' background-color:#bbb; color:#333;\" >{}:</span> '
|
||||
' color:#fff;\" >{}:</span> '
|
||||
).format(title)
|
||||
|
||||
html_text += (
|
||||
'<tr><td width="100%" align=left>{}</td></tr>'
|
||||
'<tr><td width="100%">{}</td></tr>'
|
||||
).format(title_tag, text)
|
||||
|
||||
html_text = '<table width="100%" cellspacing="3">{}</table>'.format(
|
||||
html_text
|
||||
)
|
||||
return html_text
|
||||
|
||||
|
||||
class TerminalProxy(QtCore.QSortFilterProxyModel):
|
||||
filter_buttons_checks = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
from Qt import QtCore, QtWidgets
|
||||
from . import model
|
||||
from .constants import Roles
|
||||
# Imported when used
|
||||
widgets = None
|
||||
|
||||
|
||||
def _import_widgets():
|
||||
global widgets
|
||||
if widgets is None:
|
||||
from . import widgets
|
||||
|
||||
|
||||
class ArtistView(QtWidgets.QListView):
|
||||
|
|
@ -151,6 +159,8 @@ class TerminalView(QtWidgets.QTreeView):
|
|||
|
||||
self.clicked.connect(self.item_expand)
|
||||
|
||||
_import_widgets()
|
||||
|
||||
def event(self, event):
|
||||
if not event.type() == QtCore.QEvent.KeyPress:
|
||||
return super(TerminalView, self).event(event)
|
||||
|
|
@ -190,6 +200,23 @@ class TerminalView(QtWidgets.QTreeView):
|
|||
self.updateGeometry()
|
||||
self.scrollToBottom()
|
||||
|
||||
def expand(self, index):
|
||||
"""Wrapper to set widget for expanded index."""
|
||||
model = index.model()
|
||||
row_count = model.rowCount(index)
|
||||
is_new = False
|
||||
for child_idx in range(row_count):
|
||||
child_index = model.index(child_idx, index.column(), index)
|
||||
widget = self.indexWidget(child_index)
|
||||
if widget is None:
|
||||
is_new = True
|
||||
msg = child_index.data(QtCore.Qt.DisplayRole)
|
||||
widget = widgets.TerminalDetail(msg)
|
||||
self.setIndexWidget(child_index, widget)
|
||||
super(TerminalView, self).expand(index)
|
||||
if is_new:
|
||||
self.updateGeometries()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(self.__class__, self).resizeEvent(event)
|
||||
self.model().layoutChanged.emit()
|
||||
|
|
|
|||
|
|
@ -321,11 +321,6 @@ class PerspectiveWidget(QtWidgets.QWidget):
|
|||
data = {"records": records}
|
||||
self.terminal_model.reset()
|
||||
self.terminal_model.update_with_result(data)
|
||||
while not self.terminal_model.items_to_set_widget.empty():
|
||||
item = self.terminal_model.items_to_set_widget.get()
|
||||
widget = TerminalDetail(item.data(QtCore.Qt.DisplayRole))
|
||||
index = self.terminal_proxy.mapFromSource(item.index())
|
||||
self.terminal_view.setIndexWidget(index, widget)
|
||||
|
||||
self.records.button_toggle_text.setText(
|
||||
"{} ({})".format(self.l_rec, len_records)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class Window(QtWidgets.QDialog):
|
|||
def __init__(self, controller, parent=None):
|
||||
super(Window, self).__init__(parent=parent)
|
||||
|
||||
self._suspend_logs = False
|
||||
# Use plastique style for specific ocations
|
||||
# TODO set style name via environment variable
|
||||
low_keys = {
|
||||
|
|
@ -95,6 +96,18 @@ class Window(QtWidgets.QDialog):
|
|||
header_tab_terminal = QtWidgets.QRadioButton(header_tab_widget)
|
||||
header_spacer = QtWidgets.QWidget(header_tab_widget)
|
||||
|
||||
button_suspend_logs_widget = QtWidgets.QWidget()
|
||||
button_suspend_logs_widget_layout = QtWidgets.QHBoxLayout(
|
||||
button_suspend_logs_widget
|
||||
)
|
||||
button_suspend_logs_widget_layout.setContentsMargins(0, 10, 0, 10)
|
||||
button_suspend_logs = QtWidgets.QPushButton(header_widget)
|
||||
button_suspend_logs.setFixedWidth(7)
|
||||
button_suspend_logs.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Preferred,
|
||||
QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
button_suspend_logs_widget_layout.addWidget(button_suspend_logs)
|
||||
header_aditional_btns = QtWidgets.QWidget(header_tab_widget)
|
||||
|
||||
aditional_btns_layout = QtWidgets.QHBoxLayout(header_aditional_btns)
|
||||
|
|
@ -109,9 +122,11 @@ class Window(QtWidgets.QDialog):
|
|||
layout_tab.addWidget(header_tab_artist, 0)
|
||||
layout_tab.addWidget(header_tab_overview, 0)
|
||||
layout_tab.addWidget(header_tab_terminal, 0)
|
||||
layout_tab.addWidget(button_suspend_logs_widget, 0)
|
||||
|
||||
# Compress items to the left
|
||||
layout_tab.addWidget(header_spacer, 1)
|
||||
layout_tab.addWidget(header_aditional_btns, 1)
|
||||
layout_tab.addWidget(header_aditional_btns, 0)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(header_widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -226,6 +241,10 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
footer_info = QtWidgets.QLabel(footer_widget)
|
||||
footer_spacer = QtWidgets.QWidget(footer_widget)
|
||||
|
||||
footer_button_stop = QtWidgets.QPushButton(
|
||||
awesome["stop"], footer_widget
|
||||
)
|
||||
footer_button_reset = QtWidgets.QPushButton(
|
||||
awesome["refresh"], footer_widget
|
||||
)
|
||||
|
|
@ -235,14 +254,12 @@ class Window(QtWidgets.QDialog):
|
|||
footer_button_play = QtWidgets.QPushButton(
|
||||
awesome["play"], footer_widget
|
||||
)
|
||||
footer_button_stop = QtWidgets.QPushButton(
|
||||
awesome["stop"], footer_widget
|
||||
)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(5, 5, 5, 5)
|
||||
layout.addWidget(footer_info, 0)
|
||||
layout.addWidget(footer_spacer, 1)
|
||||
|
||||
layout.addWidget(footer_button_stop, 0)
|
||||
layout.addWidget(footer_button_reset, 0)
|
||||
layout.addWidget(footer_button_validate, 0)
|
||||
|
|
@ -342,10 +359,11 @@ class Window(QtWidgets.QDialog):
|
|||
"TerminalView": terminal_view,
|
||||
|
||||
# Buttons
|
||||
"Play": footer_button_play,
|
||||
"Validate": footer_button_validate,
|
||||
"Reset": footer_button_reset,
|
||||
"SuspendLogsBtn": button_suspend_logs,
|
||||
"Stop": footer_button_stop,
|
||||
"Reset": footer_button_reset,
|
||||
"Validate": footer_button_validate,
|
||||
"Play": footer_button_play,
|
||||
|
||||
# Misc
|
||||
"HeaderSpacer": header_spacer,
|
||||
|
|
@ -370,10 +388,11 @@ class Window(QtWidgets.QDialog):
|
|||
overview_page,
|
||||
terminal_page,
|
||||
footer_widget,
|
||||
footer_button_play,
|
||||
footer_button_validate,
|
||||
button_suspend_logs,
|
||||
footer_button_stop,
|
||||
footer_button_reset,
|
||||
footer_button_validate,
|
||||
footer_button_play,
|
||||
footer_spacer,
|
||||
closing_placeholder
|
||||
):
|
||||
|
|
@ -415,10 +434,11 @@ class Window(QtWidgets.QDialog):
|
|||
QtCore.Qt.DirectConnection
|
||||
)
|
||||
|
||||
artist_view.toggled.connect(self.on_item_toggled)
|
||||
overview_instance_view.toggled.connect(self.on_item_toggled)
|
||||
overview_plugin_view.toggled.connect(self.on_item_toggled)
|
||||
artist_view.toggled.connect(self.on_instance_toggle)
|
||||
overview_instance_view.toggled.connect(self.on_instance_toggle)
|
||||
overview_plugin_view.toggled.connect(self.on_plugin_toggle)
|
||||
|
||||
button_suspend_logs.clicked.connect(self.on_suspend_clicked)
|
||||
footer_button_stop.clicked.connect(self.on_stop_clicked)
|
||||
footer_button_reset.clicked.connect(self.on_reset_clicked)
|
||||
footer_button_validate.clicked.connect(self.on_validate_clicked)
|
||||
|
|
@ -442,10 +462,11 @@ class Window(QtWidgets.QDialog):
|
|||
self.terminal_filters_widget = terminal_filters_widget
|
||||
|
||||
self.footer_widget = footer_widget
|
||||
self.button_suspend_logs = button_suspend_logs
|
||||
self.footer_button_stop = footer_button_stop
|
||||
self.footer_button_reset = footer_button_reset
|
||||
self.footer_button_validate = footer_button_validate
|
||||
self.footer_button_play = footer_button_play
|
||||
self.footer_button_stop = footer_button_stop
|
||||
|
||||
self.overview_instance_view = overview_instance_view
|
||||
self.overview_plugin_view = overview_plugin_view
|
||||
|
|
@ -537,7 +558,29 @@ class Window(QtWidgets.QDialog):
|
|||
):
|
||||
instance_item.setData(enable_value, Roles.IsEnabledRole)
|
||||
|
||||
def on_item_toggled(self, index, state=None):
|
||||
def on_instance_toggle(self, index, state=None):
|
||||
"""An item is requesting to be toggled"""
|
||||
if not index.data(Roles.IsOptionalRole):
|
||||
return self.info("This item is mandatory")
|
||||
|
||||
if self.controller.collect_state != 1:
|
||||
return self.info("Cannot toggle")
|
||||
|
||||
current_state = index.data(QtCore.Qt.CheckStateRole)
|
||||
if state is None:
|
||||
state = not current_state
|
||||
|
||||
instance_id = index.data(Roles.ObjectIdRole)
|
||||
instance_item = self.instance_model.instance_items[instance_id]
|
||||
instance_item.setData(state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
self.controller.instance_toggled.emit(
|
||||
instance_item.instance, current_state, state
|
||||
)
|
||||
|
||||
self.update_compatibility()
|
||||
|
||||
def on_plugin_toggle(self, index, state=None):
|
||||
"""An item is requesting to be toggled"""
|
||||
if not index.data(Roles.IsOptionalRole):
|
||||
return self.info("This item is mandatory")
|
||||
|
|
@ -548,7 +591,10 @@ class Window(QtWidgets.QDialog):
|
|||
if state is None:
|
||||
state = not index.data(QtCore.Qt.CheckStateRole)
|
||||
|
||||
index.model().setData(index, state, QtCore.Qt.CheckStateRole)
|
||||
plugin_id = index.data(Roles.ObjectIdRole)
|
||||
plugin_item = self.plugin_model.plugin_items[plugin_id]
|
||||
plugin_item.setData(state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
self.update_compatibility()
|
||||
|
||||
def on_tab_changed(self, target):
|
||||
|
|
@ -587,6 +633,13 @@ class Window(QtWidgets.QDialog):
|
|||
self.footer_button_play.setEnabled(False)
|
||||
self.footer_button_stop.setEnabled(False)
|
||||
|
||||
def on_suspend_clicked(self):
|
||||
self._suspend_logs = not self._suspend_logs
|
||||
if self.state["current_page"] == "terminal":
|
||||
self.on_tab_changed("overview")
|
||||
|
||||
self.tabs["terminal"].setVisible(not self._suspend_logs)
|
||||
|
||||
def on_comment_entered(self):
|
||||
"""The user has typed a comment."""
|
||||
self.controller.context.data["comment"] = self.comment_box.text()
|
||||
|
|
@ -701,14 +754,14 @@ class Window(QtWidgets.QDialog):
|
|||
self.on_tab_changed(self.state["current_page"])
|
||||
self.update_compatibility()
|
||||
|
||||
self.footer_button_validate.setEnabled(True)
|
||||
self.footer_button_reset.setEnabled(True)
|
||||
self.footer_button_stop.setEnabled(False)
|
||||
self.footer_button_play.setEnabled(True)
|
||||
self.footer_button_play.setFocus()
|
||||
self.button_suspend_logs.setEnabled(False)
|
||||
|
||||
self.footer_button_validate.setEnabled(False)
|
||||
self.footer_button_reset.setEnabled(False)
|
||||
self.footer_button_stop.setEnabled(True)
|
||||
self.footer_button_play.setEnabled(False)
|
||||
|
||||
def on_passed_group(self, order):
|
||||
|
||||
for group_item in self.instance_model.group_items.values():
|
||||
if self.overview_instance_view.isExpanded(group_item.index()):
|
||||
continue
|
||||
|
|
@ -740,16 +793,28 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
def on_was_stopped(self):
|
||||
errored = self.controller.errored
|
||||
self.footer_button_play.setEnabled(not errored)
|
||||
self.footer_button_validate.setEnabled(
|
||||
not errored and not self.controller.validated
|
||||
)
|
||||
if self.controller.collect_state == 0:
|
||||
self.footer_button_play.setEnabled(False)
|
||||
self.footer_button_validate.setEnabled(False)
|
||||
else:
|
||||
self.footer_button_play.setEnabled(not errored)
|
||||
self.footer_button_validate.setEnabled(
|
||||
not errored and not self.controller.validated
|
||||
)
|
||||
self.footer_button_play.setFocus()
|
||||
|
||||
self.footer_button_reset.setEnabled(True)
|
||||
self.footer_button_stop.setEnabled(False)
|
||||
if errored:
|
||||
self.footer_widget.setProperty("success", 0)
|
||||
self.footer_widget.style().polish(self.footer_widget)
|
||||
|
||||
suspend_log_bool = (
|
||||
self.controller.collect_state == 1
|
||||
and not self.controller.stopped
|
||||
)
|
||||
self.button_suspend_logs.setEnabled(suspend_log_bool)
|
||||
|
||||
def on_was_skipped(self, plugin):
|
||||
plugin_item = self.plugin_model.plugin_items[plugin.id]
|
||||
plugin_item.setData(
|
||||
|
|
@ -809,17 +874,15 @@ class Window(QtWidgets.QDialog):
|
|||
if self.tabs["artist"].isChecked():
|
||||
self.tabs["overview"].toggle()
|
||||
|
||||
result["records"] = self.terminal_model.prepare_records(result)
|
||||
result["records"] = self.terminal_model.prepare_records(
|
||||
result,
|
||||
self._suspend_logs
|
||||
)
|
||||
|
||||
plugin_item = self.plugin_model.update_with_result(result)
|
||||
instance_item = self.instance_model.update_with_result(result)
|
||||
|
||||
self.terminal_model.update_with_result(result)
|
||||
while not self.terminal_model.items_to_set_widget.empty():
|
||||
item = self.terminal_model.items_to_set_widget.get()
|
||||
widget = widgets.TerminalDetail(item.data(QtCore.Qt.DisplayRole))
|
||||
index = self.terminal_proxy.mapFromSource(item.index())
|
||||
self.terminal_view.setIndexWidget(index, widget)
|
||||
|
||||
self.update_compatibility()
|
||||
|
||||
|
|
@ -872,16 +935,19 @@ class Window(QtWidgets.QDialog):
|
|||
self.footer_button_validate.setEnabled(False)
|
||||
self.footer_button_play.setEnabled(False)
|
||||
|
||||
self.button_suspend_logs.setEnabled(False)
|
||||
|
||||
util.defer(5, self.controller.validate)
|
||||
|
||||
def publish(self):
|
||||
self.info(self.tr("Preparing publish.."))
|
||||
|
||||
self.footer_button_stop.setEnabled(True)
|
||||
self.footer_button_reset.setEnabled(False)
|
||||
self.footer_button_validate.setEnabled(False)
|
||||
self.footer_button_play.setEnabled(False)
|
||||
|
||||
self.button_suspend_logs.setEnabled(False)
|
||||
|
||||
util.defer(5, self.controller.publish)
|
||||
|
||||
def act(self, plugin_item, action):
|
||||
|
|
@ -913,30 +979,24 @@ class Window(QtWidgets.QDialog):
|
|||
plugin_item = self.plugin_model.plugin_items[result["plugin"].id]
|
||||
action_state = plugin_item.data(Roles.PluginActionProgressRole)
|
||||
action_state |= PluginActionStates.HasFinished
|
||||
result["records"] = self.terminal_model.prepare_records(result)
|
||||
result["records"] = self.terminal_model.prepare_records(
|
||||
result,
|
||||
self._suspend_logs
|
||||
)
|
||||
|
||||
error = result.get("error")
|
||||
if error:
|
||||
records = result.get("records") or []
|
||||
if result.get("error"):
|
||||
action_state |= PluginActionStates.HasFailed
|
||||
fname, line_no, func, exc = error.traceback
|
||||
|
||||
records.append({
|
||||
"label": str(error),
|
||||
"type": "error",
|
||||
"filename": str(fname),
|
||||
"lineno": str(line_no),
|
||||
"func": str(func),
|
||||
"traceback": error.formatted_traceback
|
||||
})
|
||||
|
||||
result["records"] = records
|
||||
|
||||
plugin_item.setData(action_state, Roles.PluginActionProgressRole)
|
||||
|
||||
self.plugin_model.update_with_result(result)
|
||||
self.instance_model.update_with_result(result)
|
||||
self.terminal_model.update_with_result(result)
|
||||
plugin_item = self.plugin_model.update_with_result(result)
|
||||
instance_item = self.instance_model.update_with_result(result)
|
||||
|
||||
if self.perspective_widget.isVisible():
|
||||
self.perspective_widget.update_context(
|
||||
plugin_item, instance_item
|
||||
)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Perform post-flight checks before closing
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "2.9.1"
|
||||
__version__ = "2.10.0"
|
||||
|
|
|
|||
|
|
@ -56,13 +56,6 @@
|
|||
"pattern": "^\\w*$",
|
||||
"example": "maya2016"
|
||||
},
|
||||
"AVALON_MONGO": {
|
||||
"description": "Address to the asset database",
|
||||
"type": "string",
|
||||
"pattern": "^mongodb://[\\w/@:.]*$",
|
||||
"example": "mongodb://localhost:27017",
|
||||
"default": "mongodb://localhost:27017"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue