mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
6bcf645c9d
16 changed files with 319 additions and 249 deletions
148
igniter/tools.py
148
igniter/tools.py
|
|
@ -1,18 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Tools used in **Igniter** GUI.
|
||||
|
||||
Functions ``compose_url()`` and ``decompose_url()`` are the same as in
|
||||
``openpype.lib`` and they are here to avoid importing OpenPype module before its
|
||||
version is decided.
|
||||
|
||||
"""
|
||||
import sys
|
||||
"""Tools used in **Igniter** GUI."""
|
||||
import os
|
||||
from typing import Dict, Union
|
||||
from typing import Union
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from pathlib import Path
|
||||
import platform
|
||||
|
||||
import certifi
|
||||
from pymongo import MongoClient
|
||||
from pymongo.errors import (
|
||||
ServerSelectionTimeoutError,
|
||||
|
|
@ -22,89 +16,32 @@ from pymongo.errors import (
|
|||
)
|
||||
|
||||
|
||||
def decompose_url(url: str) -> Dict:
|
||||
"""Decompose mongodb url to its separate components.
|
||||
|
||||
Args:
|
||||
url (str): Mongodb url.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of components.
|
||||
def should_add_certificate_path_to_mongo_url(mongo_url):
|
||||
"""Check if should add ca certificate to mongo url.
|
||||
|
||||
Since 30.9.2021 cloud mongo requires newer certificates that are not
|
||||
available on most of workstation. This adds path to certifi certificate
|
||||
which is valid for it. To add the certificate path url must have scheme
|
||||
'mongodb+srv' or has 'ssl=true' or 'tls=true' in url query.
|
||||
"""
|
||||
components = {
|
||||
"scheme": None,
|
||||
"host": None,
|
||||
"port": None,
|
||||
"username": None,
|
||||
"password": None,
|
||||
"auth_db": None
|
||||
}
|
||||
parsed = urlparse(mongo_url)
|
||||
query = parse_qs(parsed.query)
|
||||
lowered_query_keys = set(key.lower() for key in query.keys())
|
||||
add_certificate = False
|
||||
# Check if url 'ssl' or 'tls' are set to 'true'
|
||||
for key in ("ssl", "tls"):
|
||||
if key in query and "true" in query["ssl"]:
|
||||
add_certificate = True
|
||||
break
|
||||
|
||||
result = urlparse(url)
|
||||
if result.scheme is None:
|
||||
_url = "mongodb://{}".format(url)
|
||||
result = urlparse(_url)
|
||||
# Check if url contains 'mongodb+srv'
|
||||
if not add_certificate and parsed.scheme == "mongodb+srv":
|
||||
add_certificate = True
|
||||
|
||||
components["scheme"] = result.scheme
|
||||
components["host"] = result.hostname
|
||||
try:
|
||||
components["port"] = result.port
|
||||
except ValueError:
|
||||
raise RuntimeError("invalid port specified")
|
||||
components["username"] = result.username
|
||||
components["password"] = result.password
|
||||
|
||||
try:
|
||||
components["auth_db"] = parse_qs(result.query)['authSource'][0]
|
||||
except KeyError:
|
||||
# no auth db provided, mongo will use the one we are connecting to
|
||||
pass
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def compose_url(scheme: str = None,
|
||||
host: str = None,
|
||||
username: str = None,
|
||||
password: str = None,
|
||||
port: int = None,
|
||||
auth_db: str = None) -> str:
|
||||
"""Compose mongodb url from its individual components.
|
||||
|
||||
Args:
|
||||
scheme (str, optional):
|
||||
host (str, optional):
|
||||
username (str, optional):
|
||||
password (str, optional):
|
||||
port (str, optional):
|
||||
auth_db (str, optional):
|
||||
|
||||
Returns:
|
||||
str: mongodb url
|
||||
|
||||
"""
|
||||
|
||||
url = "{scheme}://"
|
||||
|
||||
if username and password:
|
||||
url += "{username}:{password}@"
|
||||
|
||||
url += "{host}"
|
||||
if port:
|
||||
url += ":{port}"
|
||||
|
||||
if auth_db:
|
||||
url += "?authSource={auth_db}"
|
||||
|
||||
return url.format(**{
|
||||
"scheme": scheme,
|
||||
"host": host,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"port": port,
|
||||
"auth_db": auth_db
|
||||
})
|
||||
# Check if url does already contain certificate path
|
||||
if add_certificate and "tlscafile" in lowered_query_keys:
|
||||
add_certificate = False
|
||||
return add_certificate
|
||||
|
||||
|
||||
def validate_mongo_connection(cnx: str) -> (bool, str):
|
||||
|
|
@ -121,12 +58,18 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
|
|||
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
|
||||
return False, "Not mongodb schema"
|
||||
|
||||
kwargs = {
|
||||
"serverSelectionTimeoutMS": 2000
|
||||
}
|
||||
# Add certificate path if should be required
|
||||
if should_add_certificate_path_to_mongo_url(cnx):
|
||||
kwargs["ssl_ca_certs"] = certifi.where()
|
||||
|
||||
try:
|
||||
client = MongoClient(
|
||||
cnx,
|
||||
serverSelectionTimeoutMS=2000
|
||||
)
|
||||
client = MongoClient(cnx, **kwargs)
|
||||
client.server_info()
|
||||
with client.start_session():
|
||||
pass
|
||||
client.close()
|
||||
except ServerSelectionTimeoutError as e:
|
||||
return False, f"Cannot connect to server {cnx} - {e}"
|
||||
|
|
@ -152,10 +95,7 @@ def validate_mongo_string(mongo: str) -> (bool, str):
|
|||
"""
|
||||
if not mongo:
|
||||
return True, "empty string"
|
||||
parsed = urlparse(mongo)
|
||||
if parsed.scheme in ["mongodb", "mongodb+srv"]:
|
||||
return validate_mongo_connection(mongo)
|
||||
return False, "not valid mongodb schema"
|
||||
return validate_mongo_connection(mongo)
|
||||
|
||||
|
||||
def validate_path_string(path: str) -> (bool, str):
|
||||
|
|
@ -195,21 +135,13 @@ def get_openpype_global_settings(url: str) -> dict:
|
|||
Returns:
|
||||
dict: With settings data. Empty dictionary is returned if not found.
|
||||
"""
|
||||
try:
|
||||
components = decompose_url(url)
|
||||
except RuntimeError:
|
||||
return {}
|
||||
mongo_kwargs = {
|
||||
"host": compose_url(**components),
|
||||
"serverSelectionTimeoutMS": 2000
|
||||
}
|
||||
port = components.get("port")
|
||||
if port is not None:
|
||||
mongo_kwargs["port"] = int(port)
|
||||
kwargs = {}
|
||||
if should_add_certificate_path_to_mongo_url(url):
|
||||
kwargs["ssl_ca_certs"] = certifi.where()
|
||||
|
||||
try:
|
||||
# Create mongo connection
|
||||
client = MongoClient(**mongo_kwargs)
|
||||
client = MongoClient(url, **kwargs)
|
||||
# Access settings collection
|
||||
col = client["openpype"]["settings"]
|
||||
# Query global settings
|
||||
|
|
|
|||
|
|
@ -3,4 +3,17 @@ from avalon.tvpaint import pipeline
|
|||
|
||||
|
||||
class Creator(PypeCreatorMixin, pipeline.Creator):
|
||||
pass
|
||||
@classmethod
|
||||
def get_dynamic_data(cls, *args, **kwargs):
|
||||
dynamic_data = super(Creator, cls).get_dynamic_data(*args, **kwargs)
|
||||
|
||||
# Change asset and name by current workfile context
|
||||
workfile_context = pipeline.get_current_workfile_context()
|
||||
asset_name = workfile_context.get("asset")
|
||||
task_name = workfile_context.get("task")
|
||||
if "asset" not in dynamic_data and asset_name:
|
||||
dynamic_data["asset"] = asset_name
|
||||
|
||||
if "task" not in dynamic_data and task_name:
|
||||
dynamic_data["task"] = task_name
|
||||
return dynamic_data
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
self._copy_image(eq_frame_filepath, new_filepath)
|
||||
layer_files_by_frame[frame_idx] = new_filepath
|
||||
|
||||
elif pre_behavior == "loop":
|
||||
elif pre_behavior in ("loop", "repeat"):
|
||||
# Loop backwards from last frame of layer
|
||||
for frame_idx in reversed(range(mark_in_index, frame_start_index)):
|
||||
eq_frame_idx_offset = (
|
||||
|
|
@ -678,7 +678,7 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
self._copy_image(eq_frame_filepath, new_filepath)
|
||||
layer_files_by_frame[frame_idx] = new_filepath
|
||||
|
||||
elif post_behavior == "loop":
|
||||
elif post_behavior in ("loop", "repeat"):
|
||||
# Loop backwards from last frame of layer
|
||||
for frame_idx in range(frame_end_index + 1, mark_out_index + 1):
|
||||
eq_frame_idx = frame_idx % frame_count
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
"""Functions useful for delivery action or loader"""
|
||||
import os
|
||||
import shutil
|
||||
import glob
|
||||
import clique
|
||||
import collections
|
||||
|
||||
|
||||
def collect_frames(files):
|
||||
"""
|
||||
Returns dict of source path and its frame, if from sequence
|
||||
|
|
@ -228,7 +230,16 @@ def process_sequence(
|
|||
Returns:
|
||||
(collections.defaultdict , int)
|
||||
"""
|
||||
if not os.path.exists(src_path):
|
||||
|
||||
def hash_path_exist(myPath):
|
||||
res = myPath.replace('#', '*')
|
||||
glob_search_results = glob.glob(res)
|
||||
if len(glob_search_results) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if not hash_path_exist(src_path):
|
||||
msg = "{} doesn't exist for {}".format(src_path,
|
||||
repre["_id"])
|
||||
report_items["Source file was not found"].append(msg)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import sys
|
|||
import time
|
||||
import logging
|
||||
import pymongo
|
||||
import certifi
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
from urlparse import urlparse, parse_qs
|
||||
|
|
@ -85,12 +86,33 @@ def get_default_components():
|
|||
return decompose_url(mongo_url)
|
||||
|
||||
|
||||
def extract_port_from_url(url):
|
||||
parsed_url = urlparse(url)
|
||||
if parsed_url.scheme is None:
|
||||
_url = "mongodb://{}".format(url)
|
||||
parsed_url = urlparse(_url)
|
||||
return parsed_url.port
|
||||
def should_add_certificate_path_to_mongo_url(mongo_url):
|
||||
"""Check if should add ca certificate to mongo url.
|
||||
|
||||
Since 30.9.2021 cloud mongo requires newer certificates that are not
|
||||
available on most of workstation. This adds path to certifi certificate
|
||||
which is valid for it. To add the certificate path url must have scheme
|
||||
'mongodb+srv' or has 'ssl=true' or 'tls=true' in url query.
|
||||
"""
|
||||
parsed = urlparse(mongo_url)
|
||||
query = parse_qs(parsed.query)
|
||||
lowered_query_keys = set(key.lower() for key in query.keys())
|
||||
add_certificate = False
|
||||
# Check if url 'ssl' or 'tls' are set to 'true'
|
||||
for key in ("ssl", "tls"):
|
||||
if key in query and "true" in query["ssl"]:
|
||||
add_certificate = True
|
||||
break
|
||||
|
||||
# Check if url contains 'mongodb+srv'
|
||||
if not add_certificate and parsed.scheme == "mongodb+srv":
|
||||
add_certificate = True
|
||||
|
||||
# Check if url does already contain certificate path
|
||||
if add_certificate and "tlscafile" in lowered_query_keys:
|
||||
add_certificate = False
|
||||
|
||||
return add_certificate
|
||||
|
||||
|
||||
def validate_mongo_connection(mongo_uri):
|
||||
|
|
@ -106,26 +128,9 @@ def validate_mongo_connection(mongo_uri):
|
|||
passed so probably couldn't connect to mongo server.
|
||||
|
||||
"""
|
||||
parsed = urlparse(mongo_uri)
|
||||
# Force validation of scheme
|
||||
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
|
||||
raise pymongo.errors.InvalidURI((
|
||||
"Invalid URI scheme:"
|
||||
" URI must begin with 'mongodb://' or 'mongodb+srv://'"
|
||||
))
|
||||
# we have mongo connection string. Let's try if we can connect.
|
||||
components = decompose_url(mongo_uri)
|
||||
mongo_args = {
|
||||
"host": compose_url(**components),
|
||||
"serverSelectionTimeoutMS": 1000
|
||||
}
|
||||
port = components.get("port")
|
||||
if port is not None:
|
||||
mongo_args["port"] = int(port)
|
||||
|
||||
# Create connection
|
||||
client = pymongo.MongoClient(**mongo_args)
|
||||
client.server_info()
|
||||
client = OpenPypeMongoConnection.create_connection(
|
||||
mongo_uri, retry_attempts=1
|
||||
)
|
||||
client.close()
|
||||
|
||||
|
||||
|
|
@ -151,6 +156,8 @@ class OpenPypeMongoConnection:
|
|||
# Naive validation of existing connection
|
||||
try:
|
||||
connection.server_info()
|
||||
with connection.start_session():
|
||||
pass
|
||||
except Exception:
|
||||
connection = None
|
||||
|
||||
|
|
@ -162,38 +169,53 @@ class OpenPypeMongoConnection:
|
|||
return connection
|
||||
|
||||
@classmethod
|
||||
def create_connection(cls, mongo_url, timeout=None):
|
||||
def create_connection(cls, mongo_url, timeout=None, retry_attempts=None):
|
||||
parsed = urlparse(mongo_url)
|
||||
# Force validation of scheme
|
||||
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
|
||||
raise pymongo.errors.InvalidURI((
|
||||
"Invalid URI scheme:"
|
||||
" URI must begin with 'mongodb://' or 'mongodb+srv://'"
|
||||
))
|
||||
|
||||
if timeout is None:
|
||||
timeout = int(os.environ.get("AVALON_TIMEOUT") or 1000)
|
||||
|
||||
kwargs = {
|
||||
"host": mongo_url,
|
||||
"serverSelectionTimeoutMS": timeout
|
||||
}
|
||||
if should_add_certificate_path_to_mongo_url(mongo_url):
|
||||
kwargs["ssl_ca_certs"] = certifi.where()
|
||||
|
||||
port = extract_port_from_url(mongo_url)
|
||||
if port is not None:
|
||||
kwargs["port"] = int(port)
|
||||
mongo_client = pymongo.MongoClient(mongo_url, **kwargs)
|
||||
|
||||
mongo_client = pymongo.MongoClient(**kwargs)
|
||||
if retry_attempts is None:
|
||||
retry_attempts = 3
|
||||
|
||||
for _retry in range(3):
|
||||
elif not retry_attempts:
|
||||
retry_attempts = 1
|
||||
|
||||
last_exc = None
|
||||
valid = False
|
||||
t1 = time.time()
|
||||
for attempt in range(1, retry_attempts + 1):
|
||||
try:
|
||||
t1 = time.time()
|
||||
mongo_client.server_info()
|
||||
|
||||
except Exception:
|
||||
cls.log.warning("Retrying...")
|
||||
time.sleep(1)
|
||||
timeout *= 1.5
|
||||
|
||||
else:
|
||||
with mongo_client.start_session():
|
||||
pass
|
||||
valid = True
|
||||
break
|
||||
|
||||
else:
|
||||
raise IOError((
|
||||
"ERROR: Couldn't connect to {} in less than {:.3f}ms"
|
||||
).format(mongo_url, timeout))
|
||||
except Exception as exc:
|
||||
last_exc = exc
|
||||
if attempt < retry_attempts:
|
||||
cls.log.warning(
|
||||
"Attempt {} failed. Retrying... ".format(attempt)
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
if not valid:
|
||||
raise last_exc
|
||||
|
||||
cls.log.info("Connected to {}, delay {:.3f}s".format(
|
||||
mongo_url, time.time() - t1
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from openpype_modules.ftrack.lib import (
|
|||
CUST_ATTR_GROUP,
|
||||
CUST_ATTR_TOOLS,
|
||||
CUST_ATTR_APPLICATIONS,
|
||||
CUST_ATTR_INTENT,
|
||||
|
||||
default_custom_attributes_definition,
|
||||
app_definitions_from_app_manager,
|
||||
|
|
@ -431,7 +432,7 @@ class CustomAttributes(BaseAction):
|
|||
|
||||
intent_custom_attr_data = {
|
||||
"label": "Intent",
|
||||
"key": "intent",
|
||||
"key": CUST_ATTR_INTENT,
|
||||
"type": "enumerator",
|
||||
"entity_type": "assetversion",
|
||||
"group": CUST_ATTR_GROUP,
|
||||
|
|
|
|||
|
|
@ -230,7 +230,13 @@ class FtrackModule(
|
|||
return
|
||||
|
||||
import ftrack_api
|
||||
from openpype_modules.ftrack.lib import get_openpype_attr
|
||||
from openpype_modules.ftrack.lib import (
|
||||
get_openpype_attr,
|
||||
default_custom_attributes_definition,
|
||||
CUST_ATTR_TOOLS,
|
||||
CUST_ATTR_APPLICATIONS,
|
||||
CUST_ATTR_INTENT
|
||||
)
|
||||
|
||||
try:
|
||||
session = self.create_ftrack_session()
|
||||
|
|
@ -255,6 +261,15 @@ class FtrackModule(
|
|||
|
||||
project_id = project_entity["id"]
|
||||
|
||||
ca_defs = default_custom_attributes_definition()
|
||||
hierarchical_attrs = ca_defs.get("is_hierarchical") or {}
|
||||
project_attrs = ca_defs.get("show") or {}
|
||||
ca_keys = (
|
||||
set(hierarchical_attrs.keys())
|
||||
| set(project_attrs.keys())
|
||||
| {CUST_ATTR_TOOLS, CUST_ATTR_APPLICATIONS, CUST_ATTR_INTENT}
|
||||
)
|
||||
|
||||
cust_attr, hier_attr = get_openpype_attr(session)
|
||||
cust_attr_by_key = {attr["key"]: attr for attr in cust_attr}
|
||||
hier_attrs_by_key = {attr["key"]: attr for attr in hier_attr}
|
||||
|
|
@ -262,6 +277,9 @@ class FtrackModule(
|
|||
failed = {}
|
||||
missing = {}
|
||||
for key, value in attributes_changes.items():
|
||||
if key not in ca_keys:
|
||||
continue
|
||||
|
||||
configuration = hier_attrs_by_key.get(key)
|
||||
if not configuration:
|
||||
configuration = cust_attr_by_key.get(key)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ from openpype.lib import (
|
|||
get_pype_execute_args,
|
||||
OpenPypeMongoConnection,
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
get_build_version,
|
||||
validate_mongo_connection
|
||||
)
|
||||
from openpype_modules.ftrack import FTRACK_MODULE_DIR
|
||||
from openpype_modules.ftrack.lib import credentials
|
||||
|
|
@ -36,11 +37,15 @@ class MongoPermissionsError(Exception):
|
|||
def check_mongo_url(mongo_uri, log_error=False):
|
||||
"""Checks if mongo server is responding"""
|
||||
try:
|
||||
client = pymongo.MongoClient(mongo_uri)
|
||||
# Force connection on a request as the connect=True parameter of
|
||||
# MongoClient seems to be useless here
|
||||
client.server_info()
|
||||
client.close()
|
||||
validate_mongo_connection(mongo_uri)
|
||||
|
||||
except pymongo.errors.InvalidURI as err:
|
||||
if log_error:
|
||||
print("Can't connect to MongoDB at {} because: {}".format(
|
||||
mongo_uri, err
|
||||
))
|
||||
return False
|
||||
|
||||
except pymongo.errors.ServerSelectionTimeoutError as err:
|
||||
if log_error:
|
||||
print("Can't connect to MongoDB at {} because: {}".format(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ from .constants import (
|
|||
CUST_ATTR_AUTO_SYNC,
|
||||
CUST_ATTR_GROUP,
|
||||
CUST_ATTR_TOOLS,
|
||||
CUST_ATTR_APPLICATIONS
|
||||
CUST_ATTR_APPLICATIONS,
|
||||
CUST_ATTR_INTENT
|
||||
)
|
||||
from .settings import (
|
||||
get_ftrack_event_mongo_info
|
||||
|
|
|
|||
|
|
@ -10,3 +10,5 @@ CUST_ATTR_AUTO_SYNC = "avalon_auto_sync"
|
|||
CUST_ATTR_APPLICATIONS = "applications"
|
||||
# Environment tools custom attribute
|
||||
CUST_ATTR_TOOLS = "tools_env"
|
||||
# Intent custom attribute name
|
||||
CUST_ATTR_INTENT = "intent"
|
||||
|
|
|
|||
|
|
@ -8,16 +8,14 @@ import platform
|
|||
|
||||
from openpype.api import Logger
|
||||
from openpype.api import get_system_settings
|
||||
from openpype.modules.default_modules.sync_server.providers.abstract_provider \
|
||||
import AbstractProvider
|
||||
|
||||
from .abstract_provider import AbstractProvider
|
||||
log = Logger().get_logger("SyncServer")
|
||||
|
||||
pysftp = None
|
||||
try:
|
||||
import pysftp
|
||||
except (ImportError, SyntaxError):
|
||||
if six.PY3:
|
||||
six.reraise(*sys.exc_info())
|
||||
pass
|
||||
|
||||
# handle imports from Python 2 hosts - in those only basic methods are used
|
||||
log.warning("Import failed, imported from Python 2, operations will fail.")
|
||||
|
|
@ -43,7 +41,7 @@ class SFTPHandler(AbstractProvider):
|
|||
self.project_name = project_name
|
||||
self.site_name = site_name
|
||||
self.root = None
|
||||
self.conn = None
|
||||
self._conn = None
|
||||
|
||||
self.presets = presets
|
||||
if not self.presets:
|
||||
|
|
@ -65,11 +63,17 @@ class SFTPHandler(AbstractProvider):
|
|||
self.sftp_key = provider_presets["sftp_key"]
|
||||
self.sftp_key_pass = provider_presets["sftp_key_pass"]
|
||||
|
||||
self.conn = self._get_conn()
|
||||
|
||||
self._tree = None
|
||||
self.active = True
|
||||
|
||||
@property
|
||||
def conn(self):
|
||||
"""SFTP connection, cannot be used in all places though."""
|
||||
if not self._conn:
|
||||
self._conn = self._get_conn()
|
||||
|
||||
return self._conn
|
||||
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns True if provider is activated, eg. has working credentials.
|
||||
|
|
@ -323,6 +327,7 @@ class SFTPHandler(AbstractProvider):
|
|||
if not self.file_path_exists(path):
|
||||
raise FileNotFoundError("File {} to be deleted doesn't exist."
|
||||
.format(path))
|
||||
|
||||
self.conn.remove(path)
|
||||
|
||||
def list_folder(self, folder_path):
|
||||
|
|
@ -396,6 +401,9 @@ class SFTPHandler(AbstractProvider):
|
|||
Returns:
|
||||
pysftp.Connection
|
||||
"""
|
||||
if not pysftp:
|
||||
raise ImportError
|
||||
|
||||
cnopts = pysftp.CnOpts()
|
||||
cnopts.hostkeys = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import os
|
||||
import subprocess
|
||||
from avalon import api
|
||||
from openpype.api import ApplicationManager
|
||||
|
||||
|
||||
def existing_djv_path():
|
||||
djv_paths = os.environ.get("DJV_PATH") or ""
|
||||
for path in djv_paths.split(os.pathsep):
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
app_manager = ApplicationManager()
|
||||
djv_list = []
|
||||
|
||||
for app_name, app in app_manager.applications.items():
|
||||
if 'djv' in app_name and app.find_executable():
|
||||
djv_list.append(app_name)
|
||||
|
||||
return djv_list
|
||||
|
||||
class OpenInDJV(api.Loader):
|
||||
"""Open Image Sequence with system default"""
|
||||
|
||||
djv_path = existing_djv_path()
|
||||
families = ["*"] if djv_path else []
|
||||
djv_list = existing_djv_path()
|
||||
families = ["*"] if djv_list else []
|
||||
representations = [
|
||||
"cin", "dpx", "avi", "dv", "gif", "flv", "mkv", "mov", "mpg", "mpeg",
|
||||
"mp4", "m4v", "mxf", "iff", "z", "ifl", "jpeg", "jpg", "jfif", "lut",
|
||||
"1dl", "exr", "pic", "png", "ppm", "pnm", "pgm", "pbm", "rla", "rpf",
|
||||
"sgi", "rgba", "rgb", "bw", "tga", "tiff", "tif", "img"
|
||||
"sgi", "rgba", "rgb", "bw", "tga", "tiff", "tif", "img", "h264",
|
||||
]
|
||||
|
||||
label = "Open in DJV"
|
||||
|
|
@ -41,20 +43,18 @@ class OpenInDJV(api.Loader):
|
|||
)
|
||||
|
||||
if not remainder:
|
||||
seqeunce = collections[0]
|
||||
first_image = list(seqeunce)[0]
|
||||
sequence = collections[0]
|
||||
first_image = list(sequence)[0]
|
||||
else:
|
||||
first_image = self.fname
|
||||
filepath = os.path.normpath(os.path.join(directory, first_image))
|
||||
|
||||
self.log.info("Opening : {}".format(filepath))
|
||||
|
||||
cmd = [
|
||||
# DJV path
|
||||
os.path.normpath(self.djv_path),
|
||||
# PATH TO COMPONENT
|
||||
os.path.normpath(filepath)
|
||||
]
|
||||
last_djv_version = sorted(self.djv_list)[-1]
|
||||
|
||||
# Run DJV with these commands
|
||||
subprocess.Popen(cmd)
|
||||
app_manager = ApplicationManager()
|
||||
djv = app_manager.applications.get(last_djv_version)
|
||||
djv.arguments.append(filepath)
|
||||
|
||||
app_manager.launch(last_djv_version)
|
||||
|
|
|
|||
16
openpype/tools/settings/settings/constants.py
Normal file
16
openpype/tools/settings/settings/constants.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from Qt import QtCore
|
||||
|
||||
|
||||
DEFAULT_PROJECT_LABEL = "< Default >"
|
||||
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1
|
||||
PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2
|
||||
PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3
|
||||
|
||||
|
||||
__all__ = (
|
||||
"DEFAULT_PROJECT_LABEL",
|
||||
|
||||
"PROJECT_NAME_ROLE",
|
||||
"PROJECT_IS_ACTIVE_ROLE",
|
||||
"PROJECT_IS_SELECTED_ROLE"
|
||||
)
|
||||
|
|
@ -7,6 +7,12 @@ from avalon.mongodb import (
|
|||
)
|
||||
|
||||
from openpype.settings.lib import get_system_settings
|
||||
from .constants import (
|
||||
DEFAULT_PROJECT_LABEL,
|
||||
PROJECT_NAME_ROLE,
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_IS_SELECTED_ROLE
|
||||
)
|
||||
|
||||
|
||||
class SettingsLineEdit(QtWidgets.QLineEdit):
|
||||
|
|
@ -602,10 +608,63 @@ class NiceCheckbox(QtWidgets.QFrame):
|
|||
return super(NiceCheckbox, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class ProjectListModel(QtGui.QStandardItemModel):
|
||||
sort_role = QtCore.Qt.UserRole + 10
|
||||
filter_role = QtCore.Qt.UserRole + 11
|
||||
selected_role = QtCore.Qt.UserRole + 12
|
||||
class ProjectModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, only_active, *args, **kwargs):
|
||||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
||||
self.dbcon = None
|
||||
|
||||
self._only_active = only_active
|
||||
self._default_item = None
|
||||
self._items_by_name = {}
|
||||
|
||||
def set_dbcon(self, dbcon):
|
||||
self.dbcon = dbcon
|
||||
|
||||
def refresh(self):
|
||||
new_items = []
|
||||
if self._default_item is None:
|
||||
item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL)
|
||||
item.setData(None, PROJECT_NAME_ROLE)
|
||||
item.setData(True, PROJECT_IS_ACTIVE_ROLE)
|
||||
item.setData(False, PROJECT_IS_SELECTED_ROLE)
|
||||
new_items.append(item)
|
||||
self._default_item = item
|
||||
|
||||
project_names = set()
|
||||
if self.dbcon is not None:
|
||||
for project_doc in self.dbcon.projects(
|
||||
projection={"name": 1, "data.active": 1},
|
||||
only_active=self._only_active
|
||||
):
|
||||
project_name = project_doc["name"]
|
||||
project_names.add(project_name)
|
||||
if project_name in self._items_by_name:
|
||||
item = self._items_by_name[project_name]
|
||||
else:
|
||||
item = QtGui.QStandardItem(project_name)
|
||||
|
||||
self._items_by_name[project_name] = item
|
||||
new_items.append(item)
|
||||
|
||||
is_active = project_doc.get("data", {}).get("active", True)
|
||||
item.setData(project_name, PROJECT_NAME_ROLE)
|
||||
item.setData(is_active, PROJECT_IS_ACTIVE_ROLE)
|
||||
item.setData(False, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
if not is_active:
|
||||
font = item.font()
|
||||
font.setItalic(True)
|
||||
item.setFont(font)
|
||||
|
||||
root_item = self.invisibleRootItem()
|
||||
for project_name in tuple(self._items_by_name.keys()):
|
||||
if project_name not in project_names:
|
||||
item = self._items_by_name.pop(project_name)
|
||||
root_item.removeRow(item.row())
|
||||
|
||||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
||||
|
||||
class ProjectListView(QtWidgets.QListView):
|
||||
|
|
@ -618,19 +677,36 @@ class ProjectListView(QtWidgets.QListView):
|
|||
super(ProjectListView, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class ProjectListSortFilterProxy(QtCore.QSortFilterProxyModel):
|
||||
|
||||
class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectListSortFilterProxy, self).__init__(*args, **kwargs)
|
||||
super(ProjectSortFilterProxy, self).__init__(*args, **kwargs)
|
||||
self._enable_filter = True
|
||||
|
||||
def lessThan(self, left_index, right_index):
|
||||
if left_index.data(PROJECT_NAME_ROLE) is None:
|
||||
return True
|
||||
|
||||
if right_index.data(PROJECT_NAME_ROLE) is None:
|
||||
return False
|
||||
|
||||
left_is_active = left_index.data(PROJECT_IS_ACTIVE_ROLE)
|
||||
right_is_active = right_index.data(PROJECT_IS_ACTIVE_ROLE)
|
||||
if right_is_active == left_is_active:
|
||||
return super(ProjectSortFilterProxy, self).lessThan(
|
||||
left_index, right_index
|
||||
)
|
||||
|
||||
if left_is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
def filterAcceptsRow(self, source_row, source_parent):
|
||||
if not self._enable_filter:
|
||||
return True
|
||||
|
||||
index = self.sourceModel().index(source_row, 0, source_parent)
|
||||
is_active = bool(index.data(self.filterRole()))
|
||||
is_selected = bool(index.data(ProjectListModel.selected_role))
|
||||
is_selected = bool(index.data(PROJECT_IS_SELECTED_ROLE))
|
||||
|
||||
return is_active or is_selected
|
||||
|
||||
|
|
@ -643,7 +719,6 @@ class ProjectListSortFilterProxy(QtCore.QSortFilterProxyModel):
|
|||
|
||||
|
||||
class ProjectListWidget(QtWidgets.QWidget):
|
||||
default = "< Default >"
|
||||
project_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent, only_active=False):
|
||||
|
|
@ -657,13 +732,10 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
label_widget = QtWidgets.QLabel("Projects")
|
||||
|
||||
project_list = ProjectListView(self)
|
||||
project_model = ProjectListModel()
|
||||
project_proxy = ProjectListSortFilterProxy()
|
||||
|
||||
project_proxy.setFilterRole(ProjectListModel.filter_role)
|
||||
project_proxy.setSortRole(ProjectListModel.sort_role)
|
||||
project_proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
project_model = ProjectModel(only_active)
|
||||
project_proxy = ProjectSortFilterProxy()
|
||||
|
||||
project_proxy.setFilterRole(PROJECT_IS_ACTIVE_ROLE)
|
||||
project_proxy.setSourceModel(project_model)
|
||||
project_list.setModel(project_proxy)
|
||||
|
||||
|
|
@ -693,13 +765,14 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
|
||||
project_list.left_mouse_released_at.connect(self.on_item_clicked)
|
||||
|
||||
self._default_project_item = None
|
||||
|
||||
self.project_list = project_list
|
||||
self.project_proxy = project_proxy
|
||||
self.project_model = project_model
|
||||
self.inactive_chk = inactive_chk
|
||||
|
||||
self.dbcon = None
|
||||
self._only_active = only_active
|
||||
|
||||
def on_item_clicked(self, new_index):
|
||||
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
|
||||
|
|
@ -746,12 +819,12 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
return not self._parent.entity.has_unsaved_changes
|
||||
|
||||
def project_name(self):
|
||||
if self.current_project == self.default:
|
||||
if self.current_project == DEFAULT_PROJECT_LABEL:
|
||||
return None
|
||||
return self.current_project
|
||||
|
||||
def select_default_project(self):
|
||||
self.select_project(self.default)
|
||||
self.select_project(DEFAULT_PROJECT_LABEL)
|
||||
|
||||
def select_project(self, project_name):
|
||||
model = self.project_model
|
||||
|
|
@ -759,10 +832,10 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
|
||||
found_items = model.findItems(project_name)
|
||||
if not found_items:
|
||||
found_items = model.findItems(self.default)
|
||||
found_items = model.findItems(DEFAULT_PROJECT_LABEL)
|
||||
|
||||
index = model.indexFromItem(found_items[0])
|
||||
model.setData(index, True, ProjectListModel.selected_role)
|
||||
model.setData(index, True, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
index = proxy.mapFromSource(index)
|
||||
|
||||
|
|
@ -777,9 +850,6 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
selected_project = index.data(QtCore.Qt.DisplayRole)
|
||||
break
|
||||
|
||||
model = self.project_model
|
||||
model.clear()
|
||||
|
||||
mongo_url = os.environ["OPENPYPE_MONGO"]
|
||||
|
||||
# Force uninstall of whole avalon connection if url does not match
|
||||
|
|
@ -797,35 +867,8 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
self.dbcon = None
|
||||
self.current_project = None
|
||||
|
||||
items = [(self.default, True)]
|
||||
|
||||
if self.dbcon:
|
||||
|
||||
for doc in self.dbcon.projects(
|
||||
projection={"name": 1, "data.active": 1},
|
||||
only_active=self._only_active
|
||||
):
|
||||
items.append(
|
||||
(doc["name"], doc.get("data", {}).get("active", True))
|
||||
)
|
||||
|
||||
for project_name, is_active in items:
|
||||
|
||||
row = QtGui.QStandardItem(project_name)
|
||||
row.setData(is_active, ProjectListModel.filter_role)
|
||||
row.setData(False, ProjectListModel.selected_role)
|
||||
|
||||
if is_active:
|
||||
row.setData(project_name, ProjectListModel.sort_role)
|
||||
|
||||
else:
|
||||
row.setData("~" + project_name, ProjectListModel.sort_role)
|
||||
|
||||
font = row.font()
|
||||
font.setItalic(True)
|
||||
row.setFont(font)
|
||||
|
||||
model.appendRow(row)
|
||||
self.project_model.set_dbcon(self.dbcon)
|
||||
self.project_model.refresh()
|
||||
|
||||
self.project_proxy.sort(0)
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8aee68fa10ab4d79be1a91e7728a609748e7c3c6
|
||||
Subproject commit 4b80f81e66aca593784be8b299110a0b6541276f
|
||||
4
start.py
4
start.py
|
|
@ -102,9 +102,6 @@ import subprocess
|
|||
import site
|
||||
from pathlib import Path
|
||||
|
||||
from igniter.tools import get_openpype_global_settings
|
||||
|
||||
|
||||
# OPENPYPE_ROOT is variable pointing to build (or code) directory
|
||||
# WARNING `OPENPYPE_ROOT` must be defined before igniter import
|
||||
# - igniter changes cwd which cause that filepath of this script won't lead
|
||||
|
|
@ -192,6 +189,7 @@ else:
|
|||
import igniter # noqa: E402
|
||||
from igniter import BootstrapRepos # noqa: E402
|
||||
from igniter.tools import (
|
||||
get_openpype_global_settings,
|
||||
get_openpype_path_from_db,
|
||||
validate_mongo_connection
|
||||
) # noqa
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue