[Automated] Merged develop into main

This commit is contained in:
pypebot 2021-12-01 04:35:17 +01:00 committed by GitHub
commit 8eadfda065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 1336 additions and 1114 deletions

View file

@ -4,7 +4,7 @@ import logging
from avalon import io
from avalon import api as avalon
from avalon.vendor import Qt
from Qt import QtWidgets
from openpype import lib, api
import pyblish.api as pyblish
import openpype.hosts.aftereffects
@ -41,10 +41,10 @@ def check_inventory():
# Warn about outdated containers.
print("Starting new QApplication..")
app = Qt.QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication(sys.argv)
message_box = Qt.QtWidgets.QMessageBox()
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Warning)
msg = "There are outdated containers in the scene."
message_box.setText(msg)
message_box.exec_()

View file

@ -1,5 +1,5 @@
import openpype.api
from avalon.vendor import Qt
from Qt import QtWidgets
from avalon import aftereffects
import logging
@ -56,7 +56,7 @@ class CreateRender(openpype.api.Creator):
stub.rename_item(item.id, stub.PUBLISH_ICON + self.data["subset"])
def _show_msg(self, txt):
msg = Qt.QtWidgets.QMessageBox()
msg.setIcon(Qt.QtWidgets.QMessageBox.Warning)
msg = QtWidgets.QMessageBox()
msg.setIcon(QtWidgets.QMessageBox.Warning)
msg.setText(txt)
msg.exec_()

View file

@ -1,6 +1,6 @@
import sys
from avalon.vendor.Qt import QtGui
from Qt import QtGui
import avalon.fusion
from avalon import io

View file

@ -1,5 +1,5 @@
from avalon import api, style
from avalon.vendor.Qt import QtGui, QtWidgets
from Qt import QtGui, QtWidgets
import avalon.fusion

View file

@ -1,4 +1,4 @@
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
from avalon.vendor import qtawesome
import avalon.fusion as avalon

View file

@ -2,12 +2,13 @@ import os
import glob
import logging
from Qt import QtWidgets, QtCore
import avalon.io as io
import avalon.api as api
import avalon.pipeline as pipeline
import avalon.fusion
import avalon.style as style
from avalon.vendor.Qt import QtWidgets, QtCore
from avalon.vendor import qtawesome as qta

View file

@ -5,13 +5,13 @@ import os
import re
import sys
import ast
import shutil
import hiero
from Qt import QtWidgets
import avalon.api as avalon
import avalon.io
from avalon.vendor.Qt import QtWidgets
from openpype.api import (Logger, Anatomy, get_anatomy_settings)
from . import tags
import shutil
from compiler.ast import flatten
try:

View file

@ -6,6 +6,7 @@ from avalon.vendor import qargparse
import avalon.api as avalon
import openpype.api as openpype
from . import lib
from copy import deepcopy
log = openpype.Logger().get_logger(__name__)
@ -799,7 +800,8 @@ class PublishClip:
# increasing steps by index of rename iteration
self.count_steps *= self.rename_index
hierarchy_formating_data = dict()
hierarchy_formating_data = {}
hierarchy_data = deepcopy(self.hierarchy_data)
_data = self.track_item_default_data.copy()
if self.ui_inputs:
# adding tag metadata from ui
@ -824,19 +826,19 @@ class PublishClip:
_data.update({"shot": self.shot_num})
# solve # in test to pythonic expression
for _k, _v in self.hierarchy_data.items():
for _k, _v in hierarchy_data.items():
if "#" not in _v["value"]:
continue
self.hierarchy_data[
hierarchy_data[
_k]["value"] = self._replace_hash_to_expression(
_k, _v["value"])
# fill up pythonic expresisons in hierarchy data
for k, _v in self.hierarchy_data.items():
for k, _v in hierarchy_data.items():
hierarchy_formating_data[k] = _v["value"].format(**_data)
else:
# if no gui mode then just pass default data
hierarchy_formating_data = self.hierarchy_data
hierarchy_formating_data = hierarchy_data
tag_hierarchy_data = self._solve_tag_hierarchy_data(
hierarchy_formating_data
@ -886,30 +888,38 @@ class PublishClip:
"families": [self.data["family"]]
}
def _convert_to_entity(self, key):
def _convert_to_entity(self, type, template):
""" Converting input key to key with type. """
# convert to entity type
entity_type = self.types.get(key, None)
entity_type = self.types.get(type, None)
assert entity_type, "Missing entity type for `{}`".format(
key
type
)
# first collect formating data to use for formating template
formating_data = {}
for _k, _v in self.hierarchy_data.items():
value = _v["value"].format(
**self.track_item_default_data)
formating_data[_k] = value
return {
"entity_type": entity_type,
"entity_name": self.hierarchy_data[key]["value"].format(
**self.track_item_default_data
"entity_name": template.format(
**formating_data
)
}
def _create_parents(self):
""" Create parents and return it in list. """
self.parents = list()
self.parents = []
patern = re.compile(self.parents_search_patern)
par_split = [patern.findall(t).pop()
par_split = [(patern.findall(t).pop(), t)
for t in self.hierarchy.split("/")]
for key in par_split:
parent = self._convert_to_entity(key)
for type, template in par_split:
parent = self._convert_to_entity(type, template)
self.parents.append(parent)

View file

@ -138,7 +138,7 @@ def on_save(_):
def on_open(_):
"""On scene open let's assume the containers have changed."""
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
from openpype.widgets import popup
cmds.evalDeferred(

View file

@ -6,19 +6,19 @@ import platform
import uuid
import math
import bson
import json
import logging
import itertools
import contextlib
from collections import OrderedDict, defaultdict
from math import ceil
from six import string_types
import bson
from maya import cmds, mel
import maya.api.OpenMaya as om
from avalon import api, maya, io, pipeline
from avalon.vendor.six import string_types
import avalon.maya.lib
import avalon.maya.interactive
@ -1936,7 +1936,7 @@ def validate_fps():
if current_fps != fps:
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
from ...widgets import popup
# Find maya main window
@ -2694,7 +2694,7 @@ def update_content_on_context_change():
def show_message(title, msg):
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
from openpype.widgets import message_window
# Find maya main window

View file

@ -133,7 +133,7 @@ class ImportMayaLoader(api.Loader):
"""
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
accept = QtWidgets.QMessageBox.Ok
buttons = accept | QtWidgets.QMessageBox.Cancel

View file

@ -386,10 +386,13 @@ class CollectLook(pyblish.api.InstancePlugin):
self.log.info("Collected resources: {}".format(instance.data["resources"]))
# Log a warning when no relevant sets were retrieved for the look.
if not instance.data["lookData"]["relationships"]:
self.log.warning("No sets found for the nodes in the instance: "
"%s" % instance[:])
# Log warning when no relevant sets were retrieved for the look.
if (
not instance.data["lookData"]["relationships"]
and "model" not in self.families
):
self.log.warning("No sets found for the nodes in the "
"instance: %s" % instance[:])
# Ensure unique shader sets
# Add shader sets to the instance for unify ID validation

View file

@ -275,7 +275,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin):
list: file sequence.
"""
from avalon.vendor import clique
import clique
escaped = re.escape(filepath)
re_pattern = escaped.replace(pattern, "-?[0-9]+")

View file

@ -1,13 +1,14 @@
import os
import json
import getpass
import appdirs
import platform
import appdirs
import requests
from maya import cmds
from avalon import api
from avalon.vendor import requests
import pyblish.api
from openpype.hosts.maya.api import lib

View file

@ -89,8 +89,8 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin):
"""
from Qt import QtWidgets
from openpype.hosts.maya.api import lib
from avalon.vendor.Qt import QtWidgets
# Store namespace in variable, cosmetics thingy
messagebox = QtWidgets.QMessageBox

View file

@ -9,7 +9,7 @@ from openpype.hosts.maya.api import lib
class ValidateModelContent(pyblish.api.InstancePlugin):
"""Adheres to the content of 'model' family
- Must have one top group.
- Must have one top group. (configurable)
- Must only contain: transforms, meshes and groups
"""
@ -20,6 +20,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin):
label = "Model Content"
actions = [openpype.hosts.maya.api.action.SelectInvalidAction]
validate_top_group = True
@classmethod
def get_invalid(cls, instance):
@ -59,12 +61,13 @@ class ValidateModelContent(pyblish.api.InstancePlugin):
# Top group
assemblies = cmds.ls(content_instance, assemblies=True, long=True)
if len(assemblies) != 1:
if len(assemblies) != 1 and cls.validate_top_group:
cls.log.error("Must have exactly one top group")
if len(assemblies) == 0:
cls.log.warning("No top group found. "
"(Are there objects in the instance?"
" Or is it parented in another group?)")
return assemblies
if len(assemblies) == 0:
cls.log.warning("No top group found. "
"(Are there objects in the instance?"
" Or is it parented in another group?)")
return assemblies or True
def _is_visible(node):

View file

@ -1,9 +1,10 @@
import os
import json
import appdirs
import requests
import pyblish.api
from avalon.vendor import requests
from openpype.plugin import contextplugin_should_run
import openpype.hosts.maya.api.action

View file

@ -70,7 +70,8 @@ def install():
family_states = [
"write",
"review",
"nukenodes"
"nukenodes",
"model",
"gizmo"
]

View file

@ -27,7 +27,7 @@ class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator):
self.data["subset"]):
msg = ("The subset name `{0}` is already used on a node in"
"this workfile.".format(self.data["subset"]))
self.log.error(msg + '\n\nPlease use other subset name!')
self.log.error(msg + "\n\nPlease use other subset name!")
raise NameError("`{0}: {1}".format(__name__, msg))
return
@ -53,7 +53,7 @@ class NukeLoader(api.Loader):
container_id = None
def reset_container_id(self):
self.container_id = ''.join(random.choice(
self.container_id = "".join(random.choice(
string.ascii_uppercase + string.digits) for _ in range(10))
def get_container_id(self, node):
@ -61,7 +61,7 @@ class NukeLoader(api.Loader):
return id_knob.value() if id_knob else None
def get_members(self, source):
"""Return nodes that has same 'containerId' as `source`"""
"""Return nodes that has same "containerId" as `source`"""
source_id = self.get_container_id(source)
return [node for node in nuke.allNodes(recurseGroups=True)
if self.get_container_id(node) == source_id
@ -116,11 +116,13 @@ class ExporterReview(object):
def __init__(self,
klass,
instance
instance,
multiple_presets=True
):
self.log = klass.log
self.instance = instance
self.multiple_presets = multiple_presets
self.path_in = self.instance.data.get("path", None)
self.staging_dir = self.instance.data["stagingDir"]
self.collection = self.instance.data.get("collection", None)
@ -152,12 +154,10 @@ class ExporterReview(object):
def get_representation_data(self, tags=None, range=False):
add_tags = tags or []
repre = {
'outputName': self.name,
'name': self.name,
'ext': self.ext,
'files': self.file,
"name": self.name,
"ext": self.ext,
"files": self.file,
"stagingDir": self.staging_dir,
"tags": [self.name.replace("_", "-")] + add_tags
}
@ -168,6 +168,9 @@ class ExporterReview(object):
"frameEnd": self.last_frame,
})
if self.multiple_presets:
repre["outputName"] = self.name
self.data["representations"].append(repre)
def get_view_input_process_node(self):
@ -183,19 +186,19 @@ class ExporterReview(object):
anlib.reset_selection()
ipn_orig = None
for v in nuke.allNodes(filter="Viewer"):
ip = v['input_process'].getValue()
ipn = v['input_process_node'].getValue()
ip = v["input_process"].getValue()
ipn = v["input_process_node"].getValue()
if "VIEWER_INPUT" not in ipn and ip:
ipn_orig = nuke.toNode(ipn)
ipn_orig.setSelected(True)
if ipn_orig:
# copy selected to clipboard
nuke.nodeCopy('%clipboard%')
nuke.nodeCopy("%clipboard%")
# reset selection
anlib.reset_selection()
# paste node and selection is on it only
nuke.nodePaste('%clipboard%')
nuke.nodePaste("%clipboard%")
# assign to variable
ipn = nuke.selectedNode()
@ -234,9 +237,11 @@ class ExporterReviewLut(ExporterReview):
ext=None,
cube_size=None,
lut_size=None,
lut_style=None):
lut_style=None,
multiple_presets=True):
# initialize parent class
super(ExporterReviewLut, self).__init__(klass, instance)
super(ExporterReviewLut, self).__init__(
klass, instance, multiple_presets)
# deal with now lut defined in viewer lut
if hasattr(klass, "viewer_lut_raw"):
@ -349,9 +354,11 @@ class ExporterReviewMov(ExporterReview):
instance,
name=None,
ext=None,
multiple_presets=True
):
# initialize parent class
super(ExporterReviewMov, self).__init__(klass, instance)
super(ExporterReviewMov, self).__init__(
klass, instance, multiple_presets)
# passing presets for nodes to self
self.nodes = klass.nodes if hasattr(klass, "nodes") else {}

View file

@ -0,0 +1,85 @@
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import plugin
import nuke
class CreateModel(plugin.PypeCreator):
"""Add Publishable Model Geometry"""
name = "model"
label = "Create 3d Model"
family = "model"
icon = "cube"
defaults = ["Main"]
def __init__(self, *args, **kwargs):
super(CreateModel, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
self.node_color = "0xff3200ff"
return
def process(self):
nodes = list()
if (self.options or {}).get("useSelection"):
nodes = self.nodes
for n in nodes:
n['selected'].setValue(0)
end_nodes = list()
# get the latest nodes in tree for selecion
for n in nodes:
x = n
end = 0
while end == 0:
try:
x = x.dependent()[0]
except:
end_node = x
end = 1
end_nodes.append(end_node)
# set end_nodes
end_nodes = list(set(end_nodes))
# check if nodes is 3d nodes
for n in end_nodes:
n['selected'].setValue(1)
sn = nuke.createNode("Scene")
if not sn.input(0):
end_nodes.remove(n)
nuke.delete(sn)
# loop over end nodes
for n in end_nodes:
n['selected'].setValue(1)
self.nodes = nuke.selectedNodes()
nodes = self.nodes
if len(nodes) >= 1:
# loop selected nodes
for n in nodes:
data = self.data.copy()
if len(nodes) > 1:
# rename subset name only if more
# then one node are selected
subset = self.family + n["name"].value().capitalize()
data["subset"] = subset
# change node color
n["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
anlib.set_avalon_knob_data(n, data)
return True
else:
msg = str("Please select nodes you "
"wish to add to a container")
self.log.error(msg)
nuke.message(msg)
return
else:
# if selected is off then create one node
model_node = nuke.createNode("WriteGeo")
model_node["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
instance = anlib.set_avalon_knob_data(model_node, self.data)
return instance

View file

@ -0,0 +1,187 @@
from avalon import api, io
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
import nuke
class AlembicModelLoader(api.Loader):
"""
This will load alembic model into script.
"""
families = ["model"]
representations = ["abc"]
label = "Load Alembic Model"
icon = "cube"
color = "orange"
node_color = "0x4ecd91ff"
def load(self, context, name, namespace, data):
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
fps = version_data.get("fps") or nuke.root()["fps"].getValue()
namespace = namespace or context['asset']['name']
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
with anlib.maintained_selection():
model_node = nuke.createNode(
"ReadGeo2",
"name {} file {} ".format(
object_name, file),
inpanel=False
)
model_node.forceValidate()
model_node["frame_rate"].setValue(float(fps))
# workaround because nuke's bug is not adding
# animation keys properly
xpos = model_node.xpos()
ypos = model_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(model_node)
nuke.nodePaste("%clipboard%")
model_node = nuke.toNode(object_name)
model_node.setXYpos(xpos, ypos)
# color node by correct color by actual version
self.node_version_color(version, model_node)
return containerise(
node=model_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
def update(self, container, representation):
"""
Called by Scene Inventory when look should be updated to current
version.
If any reference edits cannot be applied, eg. shader renamed and
material not present, reference is unloaded and cleaned.
All failed edits are highlighted to the user via message box.
Args:
container: object that has look to be updated
representation: (dict): relationship data to get proper
representation from DB and persisted
data in .json
Returns:
None
"""
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
object_name = container['objectName']
# get corresponding node
model_node = nuke.toNode(object_name)
# get main variables
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
fps = version_data.get("fps") or nuke.root()["fps"].getValue()
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = api.get_representation_path(representation).replace("\\", "/")
with anlib.maintained_selection():
model_node = nuke.toNode(object_name)
model_node['selected'].setValue(True)
# collect input output dependencies
dependencies = model_node.dependencies()
dependent = model_node.dependent()
model_node["frame_rate"].setValue(float(fps))
model_node["file"].setValue(file)
# workaround because nuke's bug is
# not adding animation keys properly
xpos = model_node.xpos()
ypos = model_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(model_node)
nuke.nodePaste("%clipboard%")
model_node = nuke.toNode(object_name)
model_node.setXYpos(xpos, ypos)
# link to original input nodes
for i, input in enumerate(dependencies):
model_node.setInput(i, input)
# link to original output nodes
for d in dependent:
index = next((i for i, dpcy in enumerate(
d.dependencies())
if model_node is dpcy), 0)
d.setInput(index, model_node)
# color node by correct color by actual version
self.node_version_color(version, model_node)
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(model_node, data_imprint)
def node_version_color(self, version, node):
""" Coloring a node by correct color by actual version
"""
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
node["tile_color"].setValue(int("0xd88467ff", 16))
else:
node["tile_color"].setValue(int(self.node_color, 16))
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,49 @@
import pyblish.api
import nuke
@pyblish.api.log
class CollectModel(pyblish.api.InstancePlugin):
"""Collect Model node instance and its content
"""
order = pyblish.api.CollectorOrder + 0.22
label = "Collect Model"
hosts = ["nuke"]
families = ["model"]
def process(self, instance):
grpn = instance[0]
# add family to familiess
instance.data["families"].insert(0, instance.data["family"])
# make label nicer
instance.data["label"] = grpn.name()
# Get frame range
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
# Add version data to instance
version_data = {
"handles": handle_start,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"colorspace": nuke.root().knob('workingSpaceLUT').value(),
"families": [instance.data["family"]] + instance.data["families"],
"subset": instance.data["subset"],
"fps": instance.context.data["fps"]
}
instance.data.update({
"versionData": version_data,
"frameStart": first_frame,
"frameEnd": last_frame
})
self.log.info("Model content collected: `{}`".format(instance[:]))
self.log.info("Model instance collected: `{}`".format(instance))

View file

@ -0,0 +1,103 @@
import nuke
import os
import pyblish.api
import openpype.api
from avalon.nuke import lib as anlib
from pprint import pformat
class ExtractModel(openpype.api.Extractor):
""" 3D model exctractor
"""
label = 'Exctract Model'
order = pyblish.api.ExtractorOrder
families = ["model"]
hosts = ["nuke"]
# presets
write_geo_knobs = [
("file_type", "abc"),
("storageFormat", "Ogawa"),
("writeGeometries", True),
("writePointClouds", False),
("writeAxes", False)
]
def process(self, instance):
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
self.log.info("instance.data: `{}`".format(
pformat(instance.data)))
rm_nodes = list()
model_node = instance[0]
self.log.info("Crating additional nodes")
subset = instance.data["subset"]
staging_dir = self.staging_dir(instance)
extension = next((k[1] for k in self.write_geo_knobs
if k[0] == "file_type"), None)
if not extension:
raise RuntimeError(
"Bad config for extension in presets. "
"Talk to your supervisor or pipeline admin")
# create file name and path
filename = subset + ".{}".format(extension)
file_path = os.path.join(staging_dir, filename).replace("\\", "/")
with anlib.maintained_selection():
# select model node
anlib.select_nodes([model_node])
# create write geo node
wg_n = nuke.createNode("WriteGeo")
wg_n["file"].setValue(file_path)
# add path to write to
for k, v in self.write_geo_knobs:
wg_n[k].setValue(v)
rm_nodes.append(wg_n)
# write out model
nuke.execute(
wg_n,
int(first_frame),
int(last_frame)
)
# erase additional nodes
for n in rm_nodes:
nuke.delete(n)
self.log.info(file_path)
# create representation data
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': extension,
'ext': extension,
'files': filename,
"stagingDir": staging_dir,
"frameStart": first_frame,
"frameEnd": last_frame
}
instance.data["representations"].append(representation)
instance.data.update({
"path": file_path,
"outputDir": staging_dir,
"ext": extension,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
})
self.log.info("Extracted instance '{0}' to: {1}".format(
instance.name, file_path))

View file

@ -31,7 +31,7 @@ class ExtractReviewDataMov(openpype.api.Extractor):
instance.data["representations"] = []
staging_dir = os.path.normpath(
os.path.dirname(instance.data['path']))
os.path.dirname(instance.data["path"]))
instance.data["stagingDir"] = staging_dir
@ -83,9 +83,15 @@ class ExtractReviewDataMov(openpype.api.Extractor):
"Baking output `{}` with settings: {}".format(
o_name, o_data))
# check if settings have more then one preset
# so we dont need to add outputName to representation
# in case there is only one preset
multiple_presets = bool(len(self.outputs.keys()) > 1)
# create exporter instance
exporter = plugin.ExporterReviewMov(
self, instance, o_name, o_data["extension"])
self, instance, o_name, o_data["extension"],
multiple_presets)
if "render.farm" in families:
if "review" in instance.data["families"]:

View file

@ -2,9 +2,10 @@ import os
import sys
import logging
from Qt import QtWidgets
from avalon import io
from avalon import api as avalon
from avalon.vendor import Qt
from openpype import lib
from pyblish import api as pyblish
import openpype.hosts.photoshop
@ -38,10 +39,10 @@ def check_inventory():
# Warn about outdated containers.
print("Starting new QApplication..")
app = Qt.QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication(sys.argv)
message_box = Qt.QtWidgets.QMessageBox()
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Warning)
msg = "There are outdated containers in the scene."
message_box.setText(msg)
message_box.exec_()

View file

@ -1,5 +1,5 @@
from Qt import QtWidgets
import openpype.api
from avalon.vendor import Qt
from avalon import photoshop
@ -26,21 +26,21 @@ class CreateImage(openpype.api.Creator):
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 = QtWidgets.QMessageBox()
msg_box.setIcon(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
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No |
QtWidgets.QMessageBox.Cancel
)
ret = msg_box.exec_()
if ret == Qt.QtWidgets.QMessageBox.Yes:
if ret == QtWidgets.QMessageBox.Yes:
multiple_instances = True
elif ret == Qt.QtWidgets.QMessageBox.Cancel:
elif ret == QtWidgets.QMessageBox.Cancel:
return
if multiple_instances:

View file

@ -61,6 +61,9 @@ class OpenPypeMenu(QtWidgets.QWidget):
inventory_btn = QtWidgets.QPushButton("Inventory ...", self)
subsetm_btn = QtWidgets.QPushButton("Subset Manager ...", self)
libload_btn = QtWidgets.QPushButton("Library ...", self)
experimental_btn = QtWidgets.QPushButton(
"Experimental tools ...", self
)
# rename_btn = QtWidgets.QPushButton("Rename", self)
# set_colorspace_btn = QtWidgets.QPushButton(
# "Set colorspace from presets", self
@ -91,6 +94,8 @@ class OpenPypeMenu(QtWidgets.QWidget):
# layout.addWidget(set_colorspace_btn)
# layout.addWidget(reset_resolution_btn)
layout.addWidget(Spacer(15, self))
layout.addWidget(experimental_btn)
self.setLayout(layout)
@ -104,6 +109,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
# rename_btn.clicked.connect(self.on_rename_clicked)
# set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked)
# reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked)
experimental_btn.clicked.connect(self.on_experimental_clicked)
def on_workfile_clicked(self):
print("Clicked Workfile")
@ -142,6 +148,9 @@ class OpenPypeMenu(QtWidgets.QWidget):
def on_reset_resolution_clicked(self):
print("Clicked Reset Resolution")
def on_experimental_clicked(self):
host_tools.show_experimental_tools_dialog()
def launch_pype_menu():
app = QtWidgets.QApplication(sys.argv)

View file

@ -89,8 +89,10 @@ class Anatomy:
self.project_name = project_name
self._data = get_anatomy_settings(project_name, site_name)
self._data = self._prepare_anatomy_data(
get_anatomy_settings(project_name, site_name)
)
self._site_name = site_name
self._templates_obj = Templates(self)
self._roots_obj = Roots(self)
@ -121,9 +123,36 @@ class Anatomy:
"""
return get_default_anatomy_settings(clear_metadata=False)
@staticmethod
def _prepare_anatomy_data(anatomy_data):
"""Prepare anatomy data for futher processing.
Method added to replace `{task}` with `{task[name]}` in templates.
"""
templates_data = anatomy_data.get("templates")
if templates_data:
# Replace `{task}` with `{task[name]}` in templates
value_queue = collections.deque()
value_queue.append(templates_data)
while value_queue:
item = value_queue.popleft()
if not isinstance(item, dict):
continue
for key in tuple(item.keys()):
value = item[key]
if isinstance(value, dict):
value_queue.append(value)
elif isinstance(value, StringType):
item[key] = value.replace("{task}", "{task[name]}")
return anatomy_data
def reset(self):
"""Reset values of cached data in templates and roots objects."""
self._data = get_anatomy_settings(self.project_name)
self._data = self._prepare_anatomy_data(
get_anatomy_settings(self.project_name, self._site_name)
)
self.templates_obj.reset()
self.roots_obj.reset()
@ -981,6 +1010,14 @@ class Templates:
TemplateResult: Filled or partially filled template containing all
data needed or missing for filling template.
"""
task_data = data.get("task")
if (
isinstance(task_data, StringType)
and "{task[name]}" in orig_template
):
# Change task to dictionary if template expect dictionary
data["task"] = {"name": task_data}
template, missing_optional, invalid_optional = (
self._filter_optional(orig_template, data)
)
@ -990,13 +1027,6 @@ class Templates:
missing_required = []
replace_keys = []
task_data = data.get("task")
if (
isinstance(task_data, StringType)
and "{task[name]}" in orig_template
):
data["task"] = {"name": task_data}
for group in self.key_pattern.findall(template):
orig_key = group[1:-1]
key = str(orig_key)

View file

@ -271,9 +271,29 @@ def _load_modules():
log = PypeLogger.get_logger("ModulesLoader")
# Look for OpenPype modules in paths defined with `get_module_dirs`
dirpaths = get_module_dirs()
# Import default modules imported from 'openpype.modules'
for default_module_name in (
"settings_action",
"launcher_action",
"project_manager_action",
"standalonepublish_action",
):
try:
default_module = __import__(
"openpype.modules.{}".format(default_module_name),
fromlist=("", )
)
setattr(openpype_modules, default_module_name, default_module)
except Exception:
msg = (
"Failed to import default module '{}'."
).format(default_module_name)
log.error(msg, exc_info=True)
# Look for OpenPype modules in paths defined with `get_module_dirs`
# - dynamically imported OpenPype modules and addons
dirpaths = get_module_dirs()
for dirpath in dirpaths:
if not os.path.exists(dirpath):
log.warning((

View file

@ -1,10 +1,10 @@
import os
import json
import requests
import hou
from avalon import api, io
from avalon.vendor import requests
import pyblish.api

View file

@ -2,8 +2,8 @@ import os
import json
import getpass
import requests
from avalon import api
from avalon.vendor import requests
import pyblish.api

View file

@ -1,10 +1,11 @@
import os
import re
import json
import getpass
import requests
from avalon import api
from avalon.vendor import requests
import re
import pyblish.api
import nuke

View file

@ -5,10 +5,11 @@ import os
import json
import re
from copy import copy, deepcopy
import requests
import clique
import openpype.api
from avalon import api, io
from avalon.vendor import requests, clique
import pyblish.api

View file

@ -1,7 +1,7 @@
import pyblish.api
from avalon.vendor import requests
import os
import requests
import pyblish.api
class ValidateDeadlineConnection(pyblish.api.InstancePlugin):

View file

@ -1,8 +1,8 @@
import os
import json
import pyblish.api
import requests
from avalon.vendor import requests
import pyblish.api
from openpype.lib.abstract_submit_deadline import requests_get
from openpype.lib.delivery import collect_frames

View file

@ -17,7 +17,7 @@ def collect(root,
frame_end=None):
"""Collect sequence collections in root"""
from avalon.vendor import clique
import clique
files = []
for filename in os.listdir(root):

View file

@ -1,9 +0,0 @@
from .settings_action import (
LocalSettingsAction,
SettingsAction
)
__all__ = (
"LocalSettingsAction",
"SettingsAction"
)

View file

@ -95,8 +95,10 @@ class TimersManager(OpenPypeModule, ITrayService):
message_time = int(timers_settings["message_time"] * 60)
auto_stop = timers_settings["auto_stop"]
platform_name = platform.system().lower()
# Turn of auto stop on MacOs because pynput requires root permissions
if platform.system().lower() == "darwin" or full_time <= 0:
# and on linux can cause thread locks on application close
if full_time <= 0 or platform_name in ("darwin", "linux"):
auto_stop = False
self.auto_stop = auto_stop

View file

@ -18,7 +18,7 @@ class CopyFile(api.Loader):
@staticmethod
def copy_file_to_clipboard(path):
from avalon.vendor.Qt import QtCore, QtWidgets
from Qt import QtCore, QtWidgets
clipboard = QtWidgets.QApplication.clipboard()
assert clipboard, "Must have running QApplication instance"

View file

@ -19,7 +19,7 @@ class CopyFilePath(api.Loader):
@staticmethod
def copy_path_to_clipboard(path):
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
clipboard = QtWidgets.QApplication.clipboard()
assert clipboard, "Must have running QApplication instance"

View file

@ -5,9 +5,9 @@ import uuid
import clique
from pymongo import UpdateOne
import ftrack_api
from Qt import QtWidgets, QtCore
from avalon import api, style
from avalon.vendor.Qt import QtWidgets, QtCore
from avalon.vendor import qargparse
from avalon.api import AvalonMongoDB
import avalon.pipeline

View file

@ -32,7 +32,7 @@ class OpenInDJV(api.Loader):
def load(self, context, name, namespace, data):
directory = os.path.dirname(self.fname)
from avalon.vendor import clique
import clique
pattern = clique.PATTERNS["frames"]
files = os.listdir(directory)

View file

@ -27,7 +27,7 @@ class Openfile(api.Loader):
color = "orange"
def load(self, context, name, namespace, data):
from avalon.vendor import clique
import clique
directory = os.path.dirname(self.fname)
pattern = clique.PATTERNS["frames"]

View file

@ -1010,10 +1010,11 @@ class ExtractReview(pyblish.api.InstancePlugin):
streams = ffprobe_streams(
full_input_path_single_file, self.log
)
except Exception:
except Exception as exc:
raise AssertionError((
"FFprobe couldn't read information about input file: \"{}\""
).format(full_input_path_single_file))
"FFprobe couldn't read information about input file: \"{}\"."
" Error message: {}"
).format(full_input_path_single_file, str(exc)))
# Try to find first stream with defined 'width' and 'height'
# - this is to avoid order of streams where audio can be as first

View file

@ -275,6 +275,11 @@
"optional": true,
"active": true
},
"ValidateModelContent": {
"enabled": true,
"optional": false,
"validate_top_group": true
},
"ValidateNoAnimation": {
"enabled": false,
"optional": true,

View file

@ -214,6 +214,30 @@
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateModelContent",
"label": "Validate Model Content",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "validate_top_group",
"label": "Validate one top group"
}
]
},
{
"type": "dict",
"collapsible": true,

View file

@ -24,6 +24,9 @@
{
"nukenodes": "nukenodes"
},
{
"model": "model"
},
{
"camera": "camera"
},

View file

@ -59,7 +59,12 @@
"color-selected": "#F0F2F5",
"color-hover": "#F0F2F5"
},
"nice-checkbox": {
"bg-checked": "#56a06f",
"bg-unchecked": "#434b56",
"bg-checker": "#D3D8DE",
"bg-checker-hover": "#F0F2F5"
},
"loader": {
"asset-view": {
"selected": "rgba(168, 175, 189, 0.6)",
@ -79,6 +84,34 @@
"bg-expander-hover": "#2d6c9f",
"bg-expander-selected-hover": "#3784c5"
}
},
"settings": {
"invalid-light": "#C93636",
"invalid-dark": "#AD2E2E",
"modified-light": "#46b1f3",
"modified-mid": "#189AEA",
"modified-dark": "#106AA2",
"studio-light": "#73C990",
"studio-dark": "#56a06f",
"studio-label-hover": "#FFFFFF",
"project-light": "#FFA64D",
"project-mid": "#FF8C1A",
"project-dark": "#E67300",
"label-fg": "#969b9e",
"label-fg-hover": "#b8c1c5",
"breadcrumbs-btn-bg": "rgba(127, 127, 127, 60)",
"breadcrumbs-btn-bg-hover": "rgba(127, 127, 127, 90)",
"content-hightlighted": "rgba(19, 26, 32, 15)",
"focus-border": "#839caf",
"image-btn": "#bfccd6",
"image-btn-hover": "#189aea",
"image-btn-disabled": "#bfccd6"
}
}
}

View file

@ -660,15 +660,6 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
/* Globally used names */
#Separator {
background: {color:bg-menu-separator};
}
#IconButton {
padding: 4px 4px 4px 4px;
}
/* Password dialog*/
#PasswordBtn {
border: none;
@ -971,7 +962,235 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: transparent;
}
/* Settings - NOT USED YET
- we need to define font family for settings UI */
#SettingsMainWidget {
background: #141a1f;
}
/* Change focus borders. */
#SettingsMainWidget QAbstractSpinBox:focus, #SettingsMainWidget QLineEdit:focus, #SettingsMainWidget QPlainTextEdit:focus, #SettingsMainWidget QTextEdit:focus {
border-color: {color:settings:focus-border};
}
/* Modify tab widget for settings */
#SettingsMainWidget QTabWidget::pane {
border-top-style: none;
}
#SettingsMainWidget QTabBar {
background: transparent;
}
#SettingsMainWidget QTabBar::tab {
border: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
padding: 5px;
}
#SettingsMainWidget QTabBar::tab:selected {
background: {color:bg};
border-color: #9B9B9B;
border-bottom-color: #C2C7CB;
}
#SettingsMainWidget QTabBar::tab:!selected {
margin-top: 2px;
background: #21252B;
}
#SettingsMainWidget QTabBar::tab:!selected:hover {
background: #333840;
}
#SettingsMainWidget QTabBar::tab:first:selected {
margin-left: 0;
}
#SettingsMainWidget QTabBar::tab:last:selected {
margin-right: 0;
}
#SettingsMainWidget QTabBar::tab:only-one {
margin: 0;
}
#SettingsToolIconBtn {
border: 0px solid #bfccd6;
background-color: transparent;
}
#SettingsToolBtn {
border: 1px solid #bfccd6;
border-radius: 10px;
background-color: transparent;
}
#SettingsToolBtn:hover {
border-color: #189aea;
color: {color:settings:modified-light};
background-color: transparent;
}
#SettingsToolBtn:disabled {
background-color: #464b54;
}
#ExpandToggleBtn {
background: transparent;
}
#SettingsLabel {
background: transparent;
color: {color:settings:label-fg};
}
#SettingsLabel:hover {color: {color:settings:label-fg-hover};}
#SettingsLabel[state="studio"] {color: {color:settings:studio-light};}
#SettingsLabel[state="studio"]:hover {color: {color:settings:studio-label-hover};}
#SettingsLabel[state="modified"] {color: {color:settings:modified-mid};}
#SettingsLabel[state="modified"]:hover {color: {color:settings:modified-light};}
#SettingsLabel[state="overriden-modified"] {color: {color:settings:modified-mid};}
#SettingsLabel[state="overriden-modified"]:hover {color: {color:settings:modified-light};}
#SettingsLabel[state="overriden"] {color: {color:settings:project-mid};}
#SettingsLabel[state="overriden"]:hover {color: {color:settings:project-light};}
#SettingsLabel[state="invalid"] {color:{color:settings:invalid-dark};}
#SettingsLabel[state="invalid"]:hover {color: {color:settings:invalid-dark};}
/* TODO Replace these with explicit widget types if possible */
#SettingsMainWidget QWidget[input-state="modified"] {
border-color: {color:settings:modified-mid};
}
#SettingsMainWidget QWidget[input-state="overriden-modified"] {
border-color: {color:settings:modified-mid};
}
#SettingsMainWidget QWidget[input-state="overriden"] {
border-color: {color:settings:project-mid};
}
#SettingsMainWidget QWidget[input-state="invalid"] {
border-color: {color:settings:invalid-dark};
}
#GroupWidget {
border-bottom: 1px solid #21252B;
}
#ProjectListWidget QLabel {
background: transparent;
font-weight: bold;
}
#MultiSelectionComboBox {
font-size: 12px;
}
#DictKey[state="modified"] {border-color: {color:settings:modified-mid};}
#DictKey[state="invalid"] {border-color: {color:settings:invalid-dark};}
#ExpandLabel {
font-weight: bold;
color: {color:settings:label-fg};
}
#ExpandLabel:hover {
color: {color:settings:label-fg-hover};
}
#ContentWidget {
background-color: transparent;
}
#ContentWidget[content_state="hightlighted"] {
background-color: {color:settings:content-hightlighted};
}
#SideLineWidget {
background-color: #333942;
border-style: solid;
border-color: #4e5254;
border-left-width: 3px;
border-bottom-width: 0px;
border-right-width: 0px;
border-top-width: 0px;
}
#SideLineWidget:hover {
border-color: #7d8386;
}
#SideLineWidget[state="child-studio"] {border-color: {color:settings:studio-dark};}
#SideLineWidget[state="child-studio"]:hover {border-color: {color:settings:studio-light};}
#SideLineWidget[state="child-modified"] {border-color: {color:settings:modified-dark};}
#SideLineWidget[state="child-modified"]:hover {border-color: {color:settings:modified-mid};}
#SideLineWidget[state="child-invalid"] {border-color: {color:settings:invalid-dark};}
#SideLineWidget[state="child-invalid"]:hover {border-color: {color:settings:invalid-light};}
#SideLineWidget[state="child-overriden"] {border-color: {color:settings:project-dark};}
#SideLineWidget[state="child-overriden"]:hover {border-color: {color:settings:project-mid};}
#SideLineWidget[state="child-overriden-modified"] {border-color: {color:settings:modified-dark};}
#SideLineWidget[state="child-overriden-modified"]:hover {border-color: {color:settings:modified-mid};}
#DictAsWidgetBody {
background: transparent;
}
#DictAsWidgetBody[show_borders="1"] {
border: 1px solid #4e5254;
border-radius: 5px;
}
#ShadowWidget {
font-size: 36pt;
}
#BreadcrumbsPathInput {
padding: 2px;
font-size: 9pt;
}
#BreadcrumbsButton {
padding-right: 12px;
font-size: 9pt;
background: transparent;
}
#BreadcrumbsButton[empty="1"] {
padding-right: 0px;
}
#BreadcrumbsButton::menu-button {
border: none;
width: 12px;
background: {color:settings:breadcrumbs-btn-bg};
}
#BreadcrumbsButton::menu-button:hover {
background: {color:settings:breadcrumbs-btn-bg-hover};
}
#BreadcrumbsPanel {
border: 1px solid #4e5254;
border-radius: 5px;
background: #21252B;
}
/* Globally used names */
#Separator {
background: {color:bg-menu-separator};
}
#IconButton {
padding: 4px 4px 4px 4px;
}
#NiceCheckbox {
/* Default size hint of NiceCheckbox is defined by font size. */
font-size: 7pt;
}
#ImageButton {
padding: 0;
background: transparent;
font-size: 11pt;
}
#ImageButton:disabled {
background: {color:bg-buttons-disabled};
}

View file

@ -1,8 +1,7 @@
import re
import logging
import collections
from avalon.vendor.Qt import QtCore, QtWidgets
from Qt import QtCore, QtWidgets
from avalon.vendor import qtawesome
from avalon import io
from avalon import style

View file

@ -101,7 +101,8 @@ class LookModel(models.TreeModel):
for look in asset_item["looks"]:
look_subsets[look["name"]].append(asset)
for subset, assets in sorted(look_subsets.iteritems()):
for subset in sorted(look_subsets.keys()):
assets = look_subsets[subset]
# Define nice label without "look" prefix for readability
label = subset if not subset.startswith("look") else subset[4:]

View file

@ -1,12 +1,13 @@
import sys
from Qt import QtWidgets, QtGui
from openpype import style
from .lib import (
BTN_FIXED_SIZE,
CHILD_OFFSET
)
from .local_settings import LocalSettingsWindow
from .settings import (
style,
MainWidget,
ProjectListWidget
)
@ -36,8 +37,6 @@ __all__ = (
"BTN_FIXED_SIZE",
"CHILD_OFFSET",
"style",
"MainWidget",
"ProjectListWidget",
"LocalSettingsWindow",

View file

@ -172,7 +172,9 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
def _reset_app_widgets(self):
while self.content_layout.count() > 0:
item = self.content_layout.itemAt(0)
item.widget().hide()
widget = item.widget()
if widget is not None:
widget.setVisible(False)
self.content_layout.removeItem(item)
self.widgets_by_group_name.clear()

View file

@ -6,10 +6,7 @@ from openpype.settings.constants import (
PROJECT_ANATOMY_KEY,
DEFAULT_PROJECT_KEY
)
from .widgets import (
SpacerWidget,
ProxyLabelWidget
)
from .widgets import ProxyLabelWidget
from .constants import (
LABEL_REMOVE_DEFAULT,
LABEL_ADD_DEFAULT,
@ -238,9 +235,9 @@ class SitesWidget(QtWidgets.QWidget):
comboboxes_layout = QtWidgets.QHBoxLayout(comboboxes_widget)
comboboxes_layout.setContentsMargins(0, 0, 0, 0)
comboboxes_layout.addWidget(active_site_widget)
comboboxes_layout.addWidget(remote_site_widget)
comboboxes_layout.addWidget(SpacerWidget(comboboxes_widget), 1)
comboboxes_layout.addWidget(active_site_widget, 0)
comboboxes_layout.addWidget(remote_site_widget, 0)
comboboxes_layout.addStretch(1)
content_widget = QtWidgets.QWidget(self)
content_layout = QtWidgets.QVBoxLayout(content_widget)
@ -259,7 +256,9 @@ class SitesWidget(QtWidgets.QWidget):
def _clear_widgets(self):
while self.content_layout.count():
item = self.content_layout.itemAt(0)
item.widget().hide()
widget = item.widget()
if widget is not None:
widget.setVisible(False)
self.content_layout.removeItem(item)
self.input_objects = {}
@ -383,7 +382,7 @@ class SitesWidget(QtWidgets.QWidget):
self.input_objects[site_name] = site_input_objects
# Add spacer so other widgets are squeezed to top
self.content_layout.addWidget(SpacerWidget(self), 1)
self.content_layout.addStretch(1)
def _on_input_value_change(self, site_name, key):
if (
@ -456,6 +455,8 @@ class _SiteCombobox(QtWidgets.QWidget):
self
)
combobox_input = QtWidgets.QComboBox(self)
combobox_delegate = QtWidgets.QStyledItemDelegate()
combobox_input.setItemDelegate(combobox_delegate)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(label_widget)
@ -464,6 +465,7 @@ class _SiteCombobox(QtWidgets.QWidget):
combobox_input.currentIndexChanged.connect(self._on_index_change)
self.label_widget = label_widget
self.combobox_input = combobox_input
self._combobox_delegate = combobox_delegate
def _set_current_text(self, text):
index = None
@ -777,7 +779,7 @@ class RootSiteWidget(QtWidgets.QWidget):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(sites_widget)
main_layout.addWidget(SpacerWidget(self), 1)
main_layout.addStretch(1)
self.sites_widget = sites_widget

View file

@ -1,7 +1,6 @@
from Qt import QtWidgets, QtCore
from openpype.tools.settings.settings.widgets import (
ExpandingWidget,
SpacerWidget
ExpandingWidget
)
@ -56,7 +55,5 @@ class ProxyLabelWidget(QtWidgets.QWidget):
__all__ = (
"ExpandingWidget",
"SpacerWidget",
"Separator",
"SpacerWidget"
)

View file

@ -1,7 +1,7 @@
import logging
from Qt import QtWidgets, QtGui
from ..settings import style
from openpype import style
from openpype.settings.lib import (
get_local_settings,
@ -15,7 +15,6 @@ from openpype.api import (
from openpype.modules import ModulesManager
from .widgets import (
SpacerWidget,
ExpandingWidget
)
from .mongo_widget import OpenPypeMongoWidget
@ -58,8 +57,7 @@ class LocalSettingsWidget(QtWidgets.QWidget):
self._create_app_ui()
self._create_project_ui()
# Add spacer to main layout
self.main_layout.addWidget(SpacerWidget(self), 1)
self.main_layout.addStretch(1)
def _create_pype_mongo_ui(self):
pype_mongo_expand_widget = ExpandingWidget("OpenPype Mongo URL", self)
@ -210,7 +208,7 @@ class LocalSettingsWindow(QtWidgets.QWidget):
footer_layout = QtWidgets.QHBoxLayout(footer)
footer_layout.addWidget(reset_btn, 0)
footer_layout.addWidget(SpacerWidget(footer), 1)
footer_layout.addStretch(1)
footer_layout.addWidget(save_btn, 0)
main_layout = QtWidgets.QVBoxLayout(self)

View file

@ -1,10 +1,8 @@
from . import style
from .window import MainWidget
from .widgets import ProjectListWidget
__all__ = (
"style",
"MainWidget",
"ProjectListWidget"
)

View file

@ -470,10 +470,9 @@ class GUIWidget(BaseWidget):
self.entity_widget.add_widget_to_layout(self)
def _create_label_ui(self):
self.setObjectName("LabelWidget")
label = self.entity["label"]
label_widget = QtWidgets.QLabel(label, self)
label_widget.setObjectName("SettingsLabel")
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 5, 0, 5)
@ -481,7 +480,7 @@ class GUIWidget(BaseWidget):
def _create_separator_ui(self):
splitter_item = QtWidgets.QWidget(self)
splitter_item.setObjectName("SplitterItem")
splitter_item.setObjectName("Separator")
splitter_item.setMinimumHeight(self.separator_height)
splitter_item.setMaximumHeight(self.separator_height)
@ -513,10 +512,9 @@ class MockUpWidget(BaseWidget):
child_invalid = False
def create_ui(self):
self.setObjectName("LabelWidget")
label = "Mockup widget for entity {}".format(self.entity.path)
label_widget = QtWidgets.QLabel(label, self)
label_widget.setObjectName("SettingsLabel")
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 5, 0, 5)

View file

@ -391,7 +391,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
while self.content_layout.count() != 0:
widget = self.content_layout.itemAt(0).widget()
widget.hide()
if widget is not None:
widget.setVisible(False)
self.content_layout.removeWidget(widget)
widget.deleteLater()

View file

@ -164,6 +164,7 @@ class DictConditionalWidget(BaseWidget):
content_widget.setProperty("show_borders", show_borders)
label_widget = QtWidgets.QLabel(self.entity.label)
label_widget.setObjectName("SettingsLabel")
content_layout = QtWidgets.QGridLayout(content_widget)
content_layout.setContentsMargins(5, 5, 5, 5)

View file

@ -3,7 +3,12 @@ from uuid import uuid4
from Qt import QtWidgets, QtCore, QtGui
from .base import BaseWidget
from .lib import create_deffered_value_change_timer
from .lib import (
create_deffered_value_change_timer,
create_add_btn,
create_remove_btn,
create_confirm_btn
)
from .widgets import (
ExpandingWidget,
IconButton
@ -21,92 +26,6 @@ KEY_INPUT_TOOLTIP = (
)
class PaintHelper:
cached_icons = {}
@classmethod
def _draw_image(cls, width, height, brush):
image = QtGui.QPixmap(width, height)
image.fill(QtCore.Qt.transparent)
icon_path_stroker = QtGui.QPainterPathStroker()
icon_path_stroker.setCapStyle(QtCore.Qt.RoundCap)
icon_path_stroker.setJoinStyle(QtCore.Qt.RoundJoin)
icon_path_stroker.setWidth(height / 5)
painter = QtGui.QPainter(image)
painter.setPen(QtCore.Qt.transparent)
painter.setBrush(brush)
rect = QtCore.QRect(0, 0, image.width(), image.height())
fifteenth = rect.height() / 15
# Left point
p1 = QtCore.QPoint(
rect.x() + (5 * fifteenth),
rect.y() + (9 * fifteenth)
)
# Middle bottom point
p2 = QtCore.QPoint(
rect.center().x(),
rect.y() + (11 * fifteenth)
)
# Top right point
p3 = QtCore.QPoint(
rect.x() + (10 * fifteenth),
rect.y() + (5 * fifteenth)
)
path = QtGui.QPainterPath(p1)
path.lineTo(p2)
path.lineTo(p3)
stroked_path = icon_path_stroker.createStroke(path)
painter.drawPath(stroked_path)
painter.end()
return image
@classmethod
def get_confirm_icon(cls, width, height):
key = "{}x{}-confirm_image".format(width, height)
icon = cls.cached_icons.get(key)
if icon is None:
image = cls._draw_image(width, height, QtCore.Qt.white)
icon = QtGui.QIcon(image)
cls.cached_icons[key] = icon
return icon
def create_add_btn(parent):
add_btn = QtWidgets.QPushButton("+", parent)
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
add_btn.setProperty("btn-type", "tool-item")
add_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
return add_btn
def create_remove_btn(parent):
remove_btn = QtWidgets.QPushButton("-", parent)
remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
remove_btn.setProperty("btn-type", "tool-item")
remove_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
return remove_btn
def create_confirm_btn(parent):
confirm_btn = QtWidgets.QPushButton(parent)
icon = PaintHelper.get_confirm_icon(
BTN_FIXED_SIZE, BTN_FIXED_SIZE
)
confirm_btn.setIcon(icon)
confirm_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
confirm_btn.setProperty("btn-type", "tool-item")
confirm_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
return confirm_btn
class ModifiableDictEmptyItem(QtWidgets.QWidget):
def __init__(self, entity_widget, store_as_list, parent):
super(ModifiableDictEmptyItem, self).__init__(parent)
@ -375,7 +294,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
"fa.edit", QtCore.Qt.lightGray, QtCore.Qt.white
)
edit_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
edit_btn.setProperty("btn-type", "tool-item-icon")
edit_btn.setObjectName("SettingsToolIconBtn")
edit_btn.setFixedHeight(BTN_FIXED_SIZE)
confirm_btn = create_confirm_btn(self)

View file

@ -0,0 +1,19 @@
import os
from Qt import QtGui
def get_image_path(image_filename):
return os.path.join(
os.path.dirname(os.path.abspath(__file__)),
image_filename
)
def get_image(image_filename):
image_path = get_image_path(image_filename)
return QtGui.QImage(image_path)
def get_pixmap(image_filename):
image_path = get_image_path(image_filename)
return QtGui.QPixmap(image_path)

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

View file

@ -7,8 +7,8 @@ from .widgets import (
NumberSpinBox,
GridLabelWidget,
SettingsComboBox,
NiceCheckbox,
SettingsPlainTextEdit,
SettingsNiceCheckbox,
SettingsLineEdit
)
from .multiselection_combobox import MultiSelectionComboBox
@ -21,6 +21,7 @@ from .base import (
BaseWidget,
InputWidget
)
from openpype.widgets.sliders import NiceSlider
from openpype.tools.settings import CHILD_OFFSET
@ -129,6 +130,7 @@ class DictImmutableKeysWidget(BaseWidget):
content_widget.setProperty("show_borders", show_borders)
label_widget = QtWidgets.QLabel(self.entity.label)
label_widget.setObjectName("SettingsLabel")
content_layout = QtWidgets.QGridLayout(content_widget)
content_layout.setContentsMargins(5, 5, 5, 5)
@ -324,12 +326,7 @@ class DictImmutableKeysWidget(BaseWidget):
class BoolWidget(InputWidget):
def _add_inputs_to_layout(self):
checkbox_height = self.style().pixelMetric(
QtWidgets.QStyle.PM_IndicatorHeight
)
self.input_field = NiceCheckbox(
height=checkbox_height, parent=self.content_widget
)
self.input_field = SettingsNiceCheckbox(parent=self.content_widget)
self.content_layout.addWidget(self.input_field, 0)
self.content_layout.addStretch(1)
@ -352,6 +349,9 @@ class BoolWidget(InputWidget):
def _on_value_change(self):
if self.ignore_input_changes:
return
self.start_value_timer()
def _on_value_change_timer(self):
self.entity.set(self.input_field.isChecked())

View file

@ -1,5 +1,7 @@
from Qt import QtCore
from .widgets import SettingsToolBtn
# Offset of value change trigger in ms
VALUE_CHANGE_OFFSET_MS = 300
@ -16,3 +18,33 @@ def create_deffered_value_change_timer(callback):
timer.setInterval(VALUE_CHANGE_OFFSET_MS)
timer.timeout.connect(callback)
return timer
def create_add_btn(parent):
add_btn = SettingsToolBtn("add", parent)
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
return add_btn
def create_remove_btn(parent):
remove_btn = SettingsToolBtn("remove", parent)
remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
return remove_btn
def create_up_btn(parent):
remove_btn = SettingsToolBtn("up", parent)
remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
return remove_btn
def create_down_btn(parent):
add_btn = SettingsToolBtn("down", parent)
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
return add_btn
def create_confirm_btn(parent):
remove_btn = SettingsToolBtn("confirm", parent)
remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
return remove_btn

View file

@ -1,13 +1,17 @@
from Qt import QtWidgets, QtCore
from .base import InputWidget
from .widgets import ExpandingWidget
from openpype.tools.settings import (
BTN_FIXED_SIZE,
CHILD_OFFSET
)
from avalon.vendor import qtawesome
from .base import InputWidget
from .widgets import ExpandingWidget
from .lib import (
create_add_btn,
create_remove_btn,
create_up_btn,
create_down_btn
)
class EmptyListItem(QtWidgets.QWidget):
@ -16,18 +20,11 @@ class EmptyListItem(QtWidgets.QWidget):
self.entity_widget = entity_widget
add_btn = QtWidgets.QPushButton("+", self)
remove_btn = QtWidgets.QPushButton("-", self)
add_btn = create_add_btn(self)
remove_btn = create_remove_btn(self)
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
remove_btn.setEnabled(False)
add_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
remove_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
add_btn.setProperty("btn-type", "tool-item")
remove_btn.setProperty("btn-type", "tool-item")
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(3)
@ -52,32 +49,10 @@ class ListItem(QtWidgets.QWidget):
self.ignore_input_changes = entity_widget.ignore_input_changes
char_up = qtawesome.charmap("fa.angle-up")
char_down = qtawesome.charmap("fa.angle-down")
add_btn = QtWidgets.QPushButton("+")
remove_btn = QtWidgets.QPushButton("-")
up_btn = QtWidgets.QPushButton(char_up)
down_btn = QtWidgets.QPushButton(char_down)
font_up_down = qtawesome.font("fa", 13)
up_btn.setFont(font_up_down)
down_btn.setFont(font_up_down)
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
up_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
down_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
add_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
remove_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
up_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
down_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
add_btn.setProperty("btn-type", "tool-item")
remove_btn.setProperty("btn-type", "tool-item")
up_btn.setProperty("btn-type", "tool-item")
down_btn.setProperty("btn-type", "tool-item")
add_btn = create_add_btn(self)
remove_btn = create_remove_btn(self)
up_btn = create_up_btn(self)
down_btn = create_down_btn(self)
add_btn.clicked.connect(self._on_add_clicked)
remove_btn.clicked.connect(self._on_remove_clicked)

View file

@ -1,13 +0,0 @@
import os
from openpype import resources
def load_stylesheet():
style_path = os.path.join(os.path.dirname(__file__), "style.css")
with open(style_path, "r") as style_file:
stylesheet = style_file.read()
return stylesheet
def app_icon_path():
return resources.get_openpype_icon_filepath()

View file

@ -1,453 +0,0 @@
/* :root {
--border-color-: #464b54;
}
*/
QWidget {
color: #bfccd6;
background-color: #282C34;
font-size: 12px;
border-radius: 0px;
}
QMenu {
border: 1px solid #555555;
background-color: #21252B;
}
QMenu::item {
padding: 5px 10px 5px 10px;
border-left: 5px solid #313131;
}
QMenu::item:selected {
border-left-color: #61839e;
background-color: #222d37;
}
QCheckBox {
spacing: 0px;
}
QCheckBox::indicator {}
QCheckBox::indicator:focus {}
QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit {
border: 1px solid #464b54;
border-radius: 3px;
background-color: #21252B;
}
QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:disabled, QTextEdit:disabled, QPushButton:disabled {
background-color: #464b54;
}
QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus {
border: 1px solid #839caf;
}
QComboBox {
border: 1px solid #464b54;
border-radius: 3px;
padding: 2px 2px 4px 4px;
background: #21252B;
}
QComboBox QAbstractItemView::item {
padding: 3px;
}
QToolButton {
background: transparent;
}
QLabel {
background: transparent;
color: #969b9e;
}
QLabel:hover {color: #b8c1c5;}
QLabel[state="studio"] {color: #73C990;}
QLabel[state="studio"]:hover {color: #ffffff;}
QLabel[state="modified"] {color: #189aea;}
QLabel[state="modified"]:hover {color: #46b1f3;}
QLabel[state="overriden-modified"] {color: #189aea;}
QLabel[state="overriden-modified"]:hover {color: #46b1f3;}
QLabel[state="overriden"] {color: #ff8c1a;}
QLabel[state="overriden"]:hover {color: #ffa64d;}
QLabel[state="invalid"] {color: #ad2e2e;}
QLabel[state="invalid"]:hover {color: #ad2e2e;}
QWidget[input-state="studio"] {border-color: #858a94;}
QWidget[input-state="modified"] {border-color: #189aea;}
QWidget[input-state="overriden-modified"] {border-color: #189aea;}
QWidget[input-state="overriden"] {border-color: #ff8c1a;}
QWidget[input-state="invalid"] {border-color: #ad2e2e;}
QPushButton {
border: 1px solid #aaaaaa;
border-radius: 3px;
padding: 5px;
}
QPushButton:hover {
background-color: #333840;
border: 1px solid #fff;
color: #fff;
}
QPushButton[btn-type="tool-item"] {
border: 1px solid #bfccd6;
border-radius: 10px;
}
QPushButton[btn-type="tool-item"]:hover {
border-color: #189aea;
color: #46b1f3;
background-color: transparent;
}
QPushButton[btn-type="tool-item-icon"] {
border: 0px solid #bfccd6;
background-color: transparent;
}
QPushButton[btn-type="expand-toggle"] {
background: #21252B;
}
/* SLider */
QSlider::groove {
border: 1px solid #464b54;
border-radius: 0.3em;
}
QSlider::groove:horizontal {
height: 8px;
}
QSlider::groove:vertical {
width: 8px;
}
QSlider::handle {
width: 10px;
height: 10px;
border-radius: 5px;
}
QSlider::handle:horizontal {
margin: -2px 0;
}
QSlider::handle:vertical {
margin: 0 -2px;
}
#GroupWidget {
border-bottom: 1px solid #21252B;
}
#ProjectListWidget QListView {
border: 1px solid #464b54;
background: #21252B;
}
#ProjectListWidget QListView:disabled {
background: #282C34;
}
#ProjectListWidget QListView::item:disabled {
color: #4e5254;
}
#ProjectListWidget QLabel {
background: transparent;
font-weight: bold;
}
#MultiSelectionComboBox {
font-size: 12px;
}
#DictKey[state="studio"] {border-color: #464b54;}
#DictKey[state="modified"] {border-color: #189aea;}
#DictKey[state="overriden"] {border-color: #00f;}
#DictKey[state="overriden-modified"] {border-color: #0f0;}
#DictKey[state="invalid"] {border-color: #ad2e2e;}
#DictLabel {
font-weight: bold;
}
#ContentWidget {
background-color: transparent;
}
#ContentWidget[content_state="hightlighted"] {
background-color: rgba(19, 26, 32, 15%);
}
#SideLineWidget {
background-color: #333942;
border-style: solid;
border-color: #4e5254;
border-left-width: 3px;
border-bottom-width: 0px;
border-right-width: 0px;
border-top-width: 0px;
}
#SideLineWidget:hover {
border-color: #7d8386;
}
#SideLineWidget[state="child-studio"] {border-color: #56a06f;}
#SideLineWidget[state="child-studio"]:hover {border-color: #73C990;}
#SideLineWidget[state="child-modified"] {border-color: #106aa2;}
#SideLineWidget[state="child-modified"]:hover {border-color: #189aea;}
#SideLineWidget[state="child-invalid"] {border-color: #ad2e2e;}
#SideLineWidget[state="child-invalid"]:hover {border-color: #c93636;}
#SideLineWidget[state="child-overriden"] {border-color: #e67300;}
#SideLineWidget[state="child-overriden"]:hover {border-color: #ff8c1a;}
#SideLineWidget[state="child-overriden-modified"] {border-color: #106aa2;}
#SideLineWidget[state="child-overriden-modified"]:hover {border-color: #189aea;}
#MainWidget {
background: #141a1f;
}
#DictAsWidgetBody {
background: transparent;
}
#DictAsWidgetBody[show_borders="1"] {
border: 1px solid #4e5254;
border-radius: 5px;
}
#SplitterItem {
background-color: #21252B;
}
#ShadowWidget {
font-size: 36pt;
}
QTabWidget::pane {
border-top-style: none;
}
QTabBar {
background: transparent;
}
QTabBar::tab {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
padding: 5px;
}
QTabBar::tab:selected {
background: #282C34;
border-color: #9B9B9B;
border-bottom-color: #C2C7CB;
}
QTabBar::tab:!selected {
margin-top: 2px;
background: #21252B;
}
QTabBar::tab:!selected:hover {
background: #333840;
}
QTabBar::tab:first:selected {
margin-left: 0;
}
QTabBar::tab:last:selected {
margin-right: 0;
}
QTabBar::tab:only-one {
margin: 0;
}
QScrollBar:horizontal {
height: 15px;
margin: 3px 15px 3px 15px;
border: 1px transparent #21252B;
border-radius: 4px;
background-color: #21252B;
}
QScrollBar::handle:horizontal {
background-color: #4B5362;
min-width: 5px;
border-radius: 4px;
}
QScrollBar::add-line:horizontal {
margin: 0px 3px 0px 3px;
border-image: url(:/qss_icons/rc/right_arrow_disabled.png);
width: 10px;
height: 10px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal {
margin: 0px 3px 0px 3px;
border-image: url(:/qss_icons/rc/left_arrow_disabled.png);
height: 10px;
width: 10px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on {
border-image: url(:/qss_icons/rc/right_arrow.png);
height: 10px;
width: 10px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on {
border-image: url(:/qss_icons/rc/left_arrow.png);
height: 10px;
width: 10px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {
background: none;
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar:vertical {
background-color: #21252B;
width: 15px;
margin: 15px 3px 15px 3px;
border: 1px transparent #21252B;
border-radius: 4px;
}
QScrollBar::handle:vertical {
background-color: #4B5362;
min-height: 5px;
border-radius: 4px;
}
QScrollBar::sub-line:vertical {
margin: 3px 0px 3px 0px;
border-image: url(:/qss_icons/rc/up_arrow_disabled.png);
height: 10px;
width: 10px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical {
margin: 3px 0px 3px 0px;
border-image: url(:/qss_icons/rc/down_arrow_disabled.png);
height: 10px;
width: 10px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on {
border-image: url(:/qss_icons/rc/up_arrow.png);
height: 10px;
width: 10px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on {
border-image: url(:/qss_icons/rc/down_arrow.png);
height: 10px;
width: 10px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
background: none;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
QTableView
{
border: 1px solid #444;
gridline-color: #6c6c6c;
background-color: #201F1F;
alternate-background-color:#21252B;
}
QHeaderView
{
border: 1px transparent;
border-radius: 2px;
margin: 0px;
padding: 0px;
}
QHeaderView::section {
background-color: #21252B;
/*color: silver;*/
padding: 4px;
border: 1px solid #6c6c6c;
border-radius: 0px;
text-align: center;
color: #969b9e;
font-weight: bold;
}
QAbstractItemView::item:pressed {
background: #78879b;
color: #FFFFFF;
}
QAbstractItemView::item:selected:active {
background: #3d8ec9;
}
QAbstractItemView::item:selected:!active {
background: #3d8ec9;
}
#BreadcrumbsPathInput {
padding: 2px;
font-size: 9pt;
}
#BreadcrumbsButton {
padding-right: 12px;
font-size: 9pt;
}
#BreadcrumbsButton[empty="1"] {
padding-right: 0px;
}
#BreadcrumbsButton::menu-button {
width: 12px;
background: rgba(127, 127, 127, 60);
}
#BreadcrumbsButton::menu-button:hover {
background: rgba(127, 127, 127, 90);
}
#BreadcrumbsPanel {
border: 1px solid #4e5254;
border-radius: 5px;
background: #21252B;;
}

View file

@ -6,7 +6,16 @@ from avalon.mongodb import (
AvalonMongoDB
)
from openpype.style import get_objected_colors
from openpype.tools.utils.widgets import ImageButton
from openpype.tools.utils.lib import paint_image_with_color
from openpype.widgets.nice_checkbox import NiceCheckbox
from openpype.settings.lib import get_system_settings
from .images import (
get_pixmap,
get_image
)
from .constants import (
DEFAULT_PROJECT_LABEL,
PROJECT_NAME_ROLE,
@ -31,6 +40,78 @@ class SettingsPlainTextEdit(QtWidgets.QPlainTextEdit):
self.focused_in.emit()
class SettingsToolBtn(ImageButton):
_mask_pixmap = None
_cached_icons = {}
def __init__(self, btn_type, parent):
super(SettingsToolBtn, self).__init__(parent)
icon, hover_icon = self._get_icon_type(btn_type)
self.setIcon(icon)
self._icon = icon
self._hover_icon = hover_icon
@classmethod
def _get_icon_type(cls, btn_type):
if btn_type not in cls._cached_icons:
settings_colors = get_objected_colors()["settings"]
normal_color = settings_colors["image-btn"].get_qcolor()
hover_color = settings_colors["image-btn-hover"].get_qcolor()
disabled_color = settings_colors["image-btn-disabled"].get_qcolor()
image = get_image("{}.png".format(btn_type))
pixmap = paint_image_with_color(image, normal_color)
hover_pixmap = paint_image_with_color(image, hover_color)
disabled_pixmap = paint_image_with_color(image, disabled_color)
icon = QtGui.QIcon(pixmap)
hover_icon = QtGui.QIcon(hover_pixmap)
icon.addPixmap(
disabled_pixmap, QtGui.QIcon.Disabled, QtGui.QIcon.On
)
icon.addPixmap(
disabled_pixmap, QtGui.QIcon.Disabled, QtGui.QIcon.Off
)
hover_icon.addPixmap(
disabled_pixmap, QtGui.QIcon.Disabled, QtGui.QIcon.On
)
hover_icon.addPixmap(
disabled_pixmap, QtGui.QIcon.Disabled, QtGui.QIcon.Off
)
cls._cached_icons[btn_type] = icon, hover_icon
return cls._cached_icons[btn_type]
def enterEvent(self, event):
self.setIcon(self._hover_icon)
super(SettingsToolBtn, self).enterEvent(event)
def leaveEvent(self, event):
self.setIcon(self._icon)
super(SettingsToolBtn, self).leaveEvent(event)
@classmethod
def _get_mask_pixmap(cls):
if cls._mask_pixmap is None:
mask_pixmap = get_pixmap("mask.png")
cls._mask_pixmap = mask_pixmap
return cls._mask_pixmap
def _change_size(self):
super(SettingsToolBtn, self)._change_size()
size = self.iconSize()
scaled = self._get_mask_pixmap().scaled(
size.width(),
size.height(),
QtCore.Qt.IgnoreAspectRatio,
QtCore.Qt.SmoothTransformation
)
self.setMask(scaled.mask())
class ShadowWidget(QtWidgets.QWidget):
def __init__(self, message, parent):
super(ShadowWidget, self).__init__(parent)
@ -132,9 +213,14 @@ class SettingsComboBox(QtWidgets.QComboBox):
def __init__(self, *args, **kwargs):
super(SettingsComboBox, self).__init__(*args, **kwargs)
delegate = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(delegate)
self.currentIndexChanged.connect(self._on_change)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self._delegate = delegate
def wheelEvent(self, event):
if self.hasFocus():
return super(SettingsComboBox, self).wheelEvent(event)
@ -180,14 +266,14 @@ class ExpandingWidget(QtWidgets.QWidget):
button_size = QtCore.QSize(5, 5)
button_toggle = QtWidgets.QToolButton(parent=side_line_widget)
button_toggle.setProperty("btn-type", "expand-toggle")
button_toggle.setObjectName("ExpandToggleBtn")
button_toggle.setIconSize(button_size)
button_toggle.setArrowType(QtCore.Qt.RightArrow)
button_toggle.setCheckable(True)
button_toggle.setChecked(False)
label_widget = QtWidgets.QLabel(label, parent=side_line_widget)
label_widget.setObjectName("DictLabel")
label_widget.setObjectName("ExpandLabel")
before_label_widget = QtWidgets.QWidget(side_line_widget)
before_label_layout = QtWidgets.QHBoxLayout(before_label_widget)
@ -381,6 +467,7 @@ class GridLabelWidget(QtWidgets.QWidget):
self.properties = {}
label_widget = QtWidgets.QLabel(label, self)
label_widget.setObjectName("SettingsLabel")
label_proxy_layout = QtWidgets.QHBoxLayout()
label_proxy_layout.setContentsMargins(0, 0, 0, 0)
@ -415,197 +502,12 @@ class GridLabelWidget(QtWidgets.QWidget):
return super(GridLabelWidget, self).mouseReleaseEvent(event)
class NiceCheckboxMoveWidget(QtWidgets.QFrame):
def __init__(self, height, border_width, parent):
super(NiceCheckboxMoveWidget, self).__init__(parent=parent)
self.checkstate = False
self.half_size = int(height / 2)
self.full_size = self.half_size * 2
self.border_width = border_width
self.setFixedHeight(self.full_size)
self.setFixedWidth(self.full_size)
self.setStyleSheet((
"background: #444444;border-style: none;"
"border-radius: {};border-width:{}px;"
).format(self.half_size, self.border_width))
def update_position(self):
parent_rect = self.parent().rect()
if self.checkstate is True:
pos_x = (
parent_rect.x()
+ parent_rect.width()
- self.full_size
- self.border_width
)
else:
pos_x = parent_rect.x() + self.border_width
pos_y = parent_rect.y() + int(
parent_rect.height() / 2 - self.half_size
)
self.setGeometry(pos_x, pos_y, self.width(), self.height())
def state_offset(self):
diff_x = (
self.parent().rect().width()
- self.full_size
- (2 * self.border_width)
)
return QtCore.QPoint(diff_x, 0)
def change_position(self, checkstate):
self.checkstate = checkstate
self.update_position()
def resizeEvent(self, event):
super().resizeEvent(event)
self.update_position()
class NiceCheckbox(QtWidgets.QFrame):
stateChanged = QtCore.Signal(int)
checked_bg_color = QtGui.QColor(69, 128, 86)
unchecked_bg_color = QtGui.QColor(170, 80, 80)
class SettingsNiceCheckbox(NiceCheckbox):
focused_in = QtCore.Signal()
def set_bg_color(self, color):
self._bg_color = color
self.setStyleSheet(self._stylesheet_template.format(
color.red(), color.green(), color.blue()
))
def bg_color(self):
return self._bg_color
bgcolor = QtCore.Property(QtGui.QColor, bg_color, set_bg_color)
def __init__(self, checked=True, height=30, *args, **kwargs):
super(NiceCheckbox, self).__init__(*args, **kwargs)
self._checkstate = checked
if checked:
bg_color = self.checked_bg_color
else:
bg_color = self.unchecked_bg_color
self.half_height = int(height / 2)
height = self.half_height * 2
tenth_height = int(height / 10)
self.setFixedHeight(height)
self.setFixedWidth((height - tenth_height) * 2)
move_item_size = height - (2 * tenth_height)
self.move_item = NiceCheckboxMoveWidget(
move_item_size, tenth_height, self
)
self.move_item.change_position(self._checkstate)
self._stylesheet_template = (
"border-radius: {}px;"
"border-width: {}px;"
"background: #333333;"
"border-style: solid;"
"border-color: #555555;"
).format(self.half_height, tenth_height)
self._stylesheet_template += "background: rgb({},{},{});"
self.set_bg_color(bg_color)
def resizeEvent(self, event):
super(NiceCheckbox, self).resizeEvent(event)
self.move_item.update_position()
def show(self, *args, **kwargs):
super(NiceCheckbox, self).show(*args, **kwargs)
self.move_item.update_position()
def checkState(self):
if self._checkstate:
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
def _on_checkstate_change(self):
self.stateChanged.emit(self.checkState())
move_start_value = self.move_item.pos()
offset = self.move_item.state_offset()
if self._checkstate is True:
move_end_value = move_start_value + offset
else:
move_end_value = move_start_value - offset
move_animation = QtCore.QPropertyAnimation(
self.move_item, b"pos", self
)
move_animation.setDuration(150)
move_animation.setEasingCurve(QtCore.QEasingCurve.OutQuad)
move_animation.setStartValue(move_start_value)
move_animation.setEndValue(move_end_value)
color_animation = QtCore.QPropertyAnimation(
self, b"bgcolor"
)
color_animation.setDuration(150)
if self._checkstate is True:
color_animation.setStartValue(self.unchecked_bg_color)
color_animation.setEndValue(self.checked_bg_color)
else:
color_animation.setStartValue(self.checked_bg_color)
color_animation.setEndValue(self.unchecked_bg_color)
anim_group = QtCore.QParallelAnimationGroup(self)
anim_group.addAnimation(move_animation)
anim_group.addAnimation(color_animation)
def _finished():
self.move_item.change_position(self._checkstate)
anim_group.finished.connect(_finished)
anim_group.start()
def isChecked(self):
return self._checkstate
def setChecked(self, checked):
if checked == self._checkstate:
return
self._checkstate = checked
self._on_checkstate_change()
def setCheckState(self, state=None):
if state is None:
checkstate = not self._checkstate
elif state == QtCore.Qt.Checked:
checkstate = True
elif state == QtCore.Qt.Unchecked:
checkstate = False
else:
return
if checkstate == self._checkstate:
return
self._checkstate = checkstate
self._on_checkstate_change()
def mousePressEvent(self, event):
self.focused_in.emit()
super(NiceCheckbox, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.setCheckState()
event.accept()
return
return super(NiceCheckbox, self).mouseReleaseEvent(event)
super(SettingsNiceCheckbox, self).mousePressEvent(event)
class ProjectModel(QtGui.QStandardItemModel):

View file

@ -5,7 +5,7 @@ from .categories import (
ProjectWidget
)
from .widgets import ShadowWidget, RestartDialog
from . import style
from openpype import style
from openpype.lib import is_admin_password_required
from openpype.widgets import PasswordDialog
@ -25,7 +25,7 @@ class MainWidget(QtWidgets.QWidget):
self._password_dialog = None
self.setObjectName("MainWidget")
self.setObjectName("SettingsMainWidget")
self.setWindowTitle("OpenPype Settings")
self.resize(self.widget_width, self.widget_height)

View file

@ -370,8 +370,12 @@ class PypeTrayStarter(QtCore.QObject):
splash = self._get_splash()
splash.show()
self._tray_widget.show()
# Make sure tray and splash are painted out
QtWidgets.QApplication.processEvents()
elif self._timer_counter == 1:
# Second processing of events to make sure splash is painted
QtWidgets.QApplication.processEvents()
self._timer_counter += 1
self._tray_widget.initialize_modules()

View file

@ -347,14 +347,22 @@ class AssetModel(QtGui.QStandardItemModel):
return self.get_indexes_by_asset_ids(asset_ids)
def refresh(self, force=False):
"""Refresh the data for the model."""
def refresh(self, force=False, clear=False):
"""Refresh the data for the model.
Args:
force (bool): Stop currently running refresh start new refresh.
clear (bool): Clear model before refresh thread starts.
"""
# Skip fetch if there is already other thread fetching documents
if self._refreshing:
if not force:
return
self.stop_refresh()
if clear:
self._clear_items()
# Fetch documents from mongo
# Restart payload
self._refreshing = True
@ -379,15 +387,18 @@ class AssetModel(QtGui.QStandardItemModel):
continue
item.setData(colors, ASSET_UNDERLINE_COLORS_ROLE)
def _clear_items(self):
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
self._items_by_asset_id = {}
self._items_with_color_by_id = {}
def _on_docs_fetched(self):
# Make sure refreshing did not change
# - since this line is refreshing sequential and
# triggering of new refresh will happen when this method is done
if not self._refreshing:
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
self._items_by_asset_id = {}
self._items_with_color_by_id = {}
self._clear_items()
return
# Collect asset documents as needed
@ -419,8 +430,6 @@ class AssetModel(QtGui.QStandardItemModel):
parent_id, parent_item = asset_items_queue.popleft()
# Skip if there are no children
children_ids = asset_ids_by_parents[parent_id]
if not children_ids:
continue
# Go through current children of parent item
# - find out items that were deleted and skip creation of already
@ -626,6 +635,7 @@ class AssetsWidget(QtWidgets.QWidget):
self._model = model
self._proxy = proxy
self._view = view
self._last_project_name = None
self.model_selection = {}
@ -634,7 +644,12 @@ class AssetsWidget(QtWidgets.QWidget):
return self._model.refreshing
def refresh(self):
self._refresh_model()
project_name = self.dbcon.Session.get("AVALON_PROJECT")
clear_model = False
if project_name != self._last_project_name:
clear_model = True
self._last_project_name = project_name
self._refresh_model(clear_model)
def stop_refresh(self):
self._model.stop_refresh()
@ -680,14 +695,14 @@ class AssetsWidget(QtWidgets.QWidget):
self._set_loading_state(loading=False, empty=not has_item)
self.refreshed.emit()
def _refresh_model(self):
def _refresh_model(self, clear=False):
# Store selection
self._set_loading_state(loading=True, empty=True)
# Trigger signal before refresh is called
self.refresh_triggered.emit()
# Refresh model
self._model.refresh()
self._model.refresh(clear=clear)
def _set_loading_state(self, loading, empty):
self._view.set_loading_state(loading, empty)

View file

@ -5,6 +5,7 @@ use singleton approach with global functions (using helper anyway).
"""
import avalon.api
from .lib import qt_app_context
class HostToolsHelper:
@ -61,22 +62,23 @@ class HostToolsHelper:
if save is None:
save = True
workfiles_tool = self.get_workfiles_tool(parent)
workfiles_tool.set_save_enabled(save)
with qt_app_context():
workfiles_tool = self.get_workfiles_tool(parent)
workfiles_tool.set_save_enabled(save)
if not workfiles_tool.isVisible():
workfiles_tool.show()
if not workfiles_tool.isVisible():
workfiles_tool.show()
if use_context:
context = {
"asset": avalon.api.Session["AVALON_ASSET"],
"task": avalon.api.Session["AVALON_TASK"]
}
workfiles_tool.set_context(context)
if use_context:
context = {
"asset": avalon.api.Session["AVALON_ASSET"],
"task": avalon.api.Session["AVALON_TASK"]
}
workfiles_tool.set_context(context)
# Pull window to the front.
workfiles_tool.raise_()
workfiles_tool.activateWindow()
# Pull window to the front.
workfiles_tool.raise_()
workfiles_tool.activateWindow()
def get_loader_tool(self, parent):
"""Create, cache and return loader tool window."""
@ -90,20 +92,21 @@ class HostToolsHelper:
def show_loader(self, parent=None, use_context=None):
"""Loader tool for loading representations."""
loader_tool = self.get_loader_tool(parent)
with qt_app_context():
loader_tool = self.get_loader_tool(parent)
loader_tool.show()
loader_tool.raise_()
loader_tool.activateWindow()
loader_tool.show()
loader_tool.raise_()
loader_tool.activateWindow()
if use_context is None:
use_context = False
if use_context is None:
use_context = False
if use_context:
context = {"asset": avalon.api.Session["AVALON_ASSET"]}
loader_tool.set_context(context, refresh=True)
else:
loader_tool.refresh()
if use_context:
context = {"asset": avalon.api.Session["AVALON_ASSET"]}
loader_tool.set_context(context, refresh=True)
else:
loader_tool.refresh()
def get_creator_tool(self, parent):
"""Create, cache and return creator tool window."""
@ -117,13 +120,14 @@ class HostToolsHelper:
def show_creator(self, parent=None):
"""Show tool to create new instantes for publishing."""
creator_tool = self.get_creator_tool(parent)
creator_tool.refresh()
creator_tool.show()
with qt_app_context():
creator_tool = self.get_creator_tool(parent)
creator_tool.refresh()
creator_tool.show()
# Pull window to the front.
creator_tool.raise_()
creator_tool.activateWindow()
# Pull window to the front.
creator_tool.raise_()
creator_tool.activateWindow()
def get_subset_manager_tool(self, parent):
"""Create, cache and return subset manager tool window."""
@ -139,12 +143,13 @@ class HostToolsHelper:
def show_subset_manager(self, parent=None):
"""Show tool display/remove existing created instances."""
subset_manager_tool = self.get_subset_manager_tool(parent)
subset_manager_tool.show()
with qt_app_context():
subset_manager_tool = self.get_subset_manager_tool(parent)
subset_manager_tool.show()
# Pull window to the front.
subset_manager_tool.raise_()
subset_manager_tool.activateWindow()
# Pull window to the front.
subset_manager_tool.raise_()
subset_manager_tool.activateWindow()
def get_scene_inventory_tool(self, parent):
"""Create, cache and return scene inventory tool window."""
@ -160,13 +165,14 @@ class HostToolsHelper:
def show_scene_inventory(self, parent=None):
"""Show tool maintain loaded containers."""
scene_inventory_tool = self.get_scene_inventory_tool(parent)
scene_inventory_tool.show()
scene_inventory_tool.refresh()
with qt_app_context():
scene_inventory_tool = self.get_scene_inventory_tool(parent)
scene_inventory_tool.show()
scene_inventory_tool.refresh()
# Pull window to the front.
scene_inventory_tool.raise_()
scene_inventory_tool.activateWindow()
# Pull window to the front.
scene_inventory_tool.raise_()
scene_inventory_tool.activateWindow()
def get_library_loader_tool(self, parent):
"""Create, cache and return library loader tool window."""
@ -182,11 +188,12 @@ class HostToolsHelper:
def show_library_loader(self, parent=None):
"""Loader tool for loading representations from library project."""
library_loader_tool = self.get_library_loader_tool(parent)
library_loader_tool.show()
library_loader_tool.raise_()
library_loader_tool.activateWindow()
library_loader_tool.refresh()
with qt_app_context():
library_loader_tool = self.get_library_loader_tool(parent)
library_loader_tool.show()
library_loader_tool.raise_()
library_loader_tool.activateWindow()
library_loader_tool.refresh()
def show_publish(self, parent=None):
"""Publish UI."""
@ -207,9 +214,10 @@ class HostToolsHelper:
"""Look manager is Maya specific tool for look management."""
from avalon import style
look_assigner_tool = self.get_look_assigner_tool(parent)
look_assigner_tool.show()
look_assigner_tool.setStyleSheet(style.load_stylesheet())
with qt_app_context():
look_assigner_tool = self.get_look_assigner_tool(parent)
look_assigner_tool.show()
look_assigner_tool.setStyleSheet(style.load_stylesheet())
def get_experimental_tools_dialog(self, parent=None):
"""Dialog of experimental tools.
@ -232,11 +240,12 @@ class HostToolsHelper:
def show_experimental_tools_dialog(self, parent=None):
"""Show dialog with experimental tools."""
dialog = self.get_experimental_tools_dialog(parent)
with qt_app_context():
dialog = self.get_experimental_tools_dialog(parent)
dialog.show()
dialog.raise_()
dialog.activateWindow()
dialog.show()
dialog.raise_()
dialog.activateWindow()
def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
"""Show tool by it's name.

View file

@ -25,6 +25,34 @@ def center_window(window):
window.move(geo.topLeft())
def paint_image_with_color(image, color):
"""Redraw image with single color using it's alpha.
It is expected that input image is singlecolor image with alpha.
Args:
image (QImage): Loaded image with alpha.
color (QColor): Color that will be used to paint image.
"""
width = image.width()
height = image.height()
alpha_mask = image.createAlphaMask()
alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask))
pixmap = QtGui.QPixmap(width, height)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setClipRegion(alpha_region)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(color)
painter.drawRect(QtCore.QRect(0, 0, width, height))
painter.end()
return pixmap
def format_version(value, hero_version=False):
"""Formats integer to displayable version name"""
label = "v{0:03d}".format(value)

View file

@ -30,6 +30,32 @@ class PlaceholderLineEdit(QtWidgets.QLineEdit):
self.setPalette(filter_palette)
class ImageButton(QtWidgets.QPushButton):
"""PushButton with icon and size of font.
Using font metrics height as icon size reference.
TODO:
- handle changes of screen (different resolution)
"""
def __init__(self, *args, **kwargs):
super(ImageButton, self).__init__(*args, **kwargs)
self.setObjectName("ImageButton")
def _change_size(self):
font_height = self.fontMetrics().height()
self.setIconSize(QtCore.QSize(font_height, font_height))
def showEvent(self, event):
super(ImageButton, self).showEvent(event)
self._change_size()
def sizeHint(self):
return self.iconSize()
class OptionalMenu(QtWidgets.QMenu):
"""A subclass of `QtWidgets.QMenu` to work with `OptionalAction`

View file

@ -100,9 +100,7 @@ class NameWindow(QtWidgets.QDialog):
# Store project anatomy
self.anatomy = anatomy
self.template = anatomy.templates[template_key]["file"].replace(
"{task}", "{task[name]}"
)
self.template = anatomy.templates[template_key]["file"]
self.template_key = template_key
# Btns widget

View file

@ -1,6 +1,6 @@
from Qt import QtWidgets, QtCore
import sys
import logging
from Qt import QtWidgets, QtCore
log = logging.getLogger(__name__)

View file

@ -1,11 +1,18 @@
from math import floor, sqrt, ceil
from Qt import QtWidgets, QtCore, QtGui
from openpype.style import get_objected_colors
class NiceCheckbox(QtWidgets.QFrame):
stateChanged = QtCore.Signal(int)
clicked = QtCore.Signal()
_checked_bg_color = None
_unchecked_bg_color = None
_checker_color = None
_checker_hover_color = None
def __init__(self, checked=False, draw_icons=False, parent=None):
super(NiceCheckbox, self).__init__(parent)
@ -41,12 +48,6 @@ class NiceCheckbox(QtWidgets.QFrame):
self._pressed = False
self._under_mouse = False
self.checked_bg_color = QtGui.QColor(67, 181, 129)
self.unchecked_bg_color = QtGui.QColor(79, 79, 79)
self.checker_checked_color = QtGui.QColor(255, 255, 255)
self.checker_unchecked_color = self.checker_checked_color
self.icon_scale_factor = sqrt(2) / 2
icon_path_stroker = QtGui.QPainterPathStroker()
@ -58,6 +59,37 @@ class NiceCheckbox(QtWidgets.QFrame):
self._animation_timer.timeout.connect(self._on_animation_timeout)
self._base_size = QtCore.QSize(90, 50)
self._load_colors()
@classmethod
def _load_colors(cls):
if cls._checked_bg_color is not None:
return
colors_data = get_objected_colors()
colors_info = colors_data["nice-checkbox"]
cls._checked_bg_color = colors_info["bg-checked"].get_qcolor()
cls._unchecked_bg_color = colors_info["bg-unchecked"].get_qcolor()
cls._checker_color = colors_info["bg-checker"].get_qcolor()
cls._checker_hover_color = colors_info["bg-checker-hover"].get_qcolor()
@property
def checked_bg_color(self):
return self._checked_bg_color
@property
def unchecked_bg_color(self):
return self._unchecked_bg_color
@property
def checker_color(self):
return self._checker_color
@property
def checker_hover_color(self):
return self._checker_hover_color
def setTristate(self, tristate=True):
if self._is_tristate != tristate:
@ -73,15 +105,6 @@ class NiceCheckbox(QtWidgets.QFrame):
self._draw_icons = draw_icons
self.repaint()
def _checkbox_size_hint(self):
checkbox_height = self.style().pixelMetric(
QtWidgets.QStyle.PM_IndicatorHeight
)
checkbox_height += checkbox_height % 2
width = (2 * checkbox_height) - (checkbox_height / 5)
new_size = QtCore.QSize(width, checkbox_height)
return new_size
def sizeHint(self):
height = self.fontMetrics().height()
width = self.get_width_hint_by_height(height)
@ -159,7 +182,7 @@ class NiceCheckbox(QtWidgets.QFrame):
if self._animation_timer.isActive():
self._animation_timer.stop()
if self.isEnabled():
if self.isVisible() and self.isEnabled():
# Start animation
self._animation_timer.start(self._animation_timeout)
else:
@ -235,14 +258,16 @@ class NiceCheckbox(QtWidgets.QFrame):
def _on_animation_timeout(self):
if self._checkstate == QtCore.Qt.Checked:
self._current_step += 1
if self._current_step == self._steps:
self._animation_timer.stop()
return
self._current_step += 1
elif self._checkstate == QtCore.Qt.Unchecked:
self._current_step -= 1
if self._current_step == 0:
self._animation_timer.stop()
return
self._current_step -= 1
else:
if self._current_step < self._middle_step:
@ -291,11 +316,9 @@ class NiceCheckbox(QtWidgets.QFrame):
# Draw inner background
if self._current_step == self._steps:
bg_color = self.checked_bg_color
checker_color = self.checker_checked_color
elif self._current_step == 0:
bg_color = self.unchecked_bg_color
checker_color = self.checker_unchecked_color
else:
offset_ratio = self._current_step / self._steps
@ -305,11 +328,6 @@ class NiceCheckbox(QtWidgets.QFrame):
self.unchecked_bg_color,
offset_ratio
)
checker_color = self.steped_color(
self.checker_checked_color,
self.checker_unchecked_color,
offset_ratio
)
margins_ratio = self._checker_margins_divider
if margins_ratio > 0:
@ -359,52 +377,14 @@ class NiceCheckbox(QtWidgets.QFrame):
checker_rect = QtCore.QRect(pos_x, pos_y, checker_size, checker_size)
under_mouse = self.isEnabled() and self._under_mouse
shadow_x = checker_rect.x()
shadow_y = checker_rect.y() + margin_size_c
shadow_size = min(
frame_rect.right() - shadow_x,
frame_rect.bottom() - shadow_y,
checker_size + (2 * margin_size_c)
)
shadow_rect = QtCore.QRect(
checker_rect.x(),
shadow_y,
shadow_size,
shadow_size
)
shadow_brush = QtGui.QRadialGradient(
shadow_rect.center(),
shadow_rect.height() / 2
)
shadow_brush.setColorAt(0.6, QtCore.Qt.black)
shadow_brush.setColorAt(1, QtCore.Qt.transparent)
painter.setPen(QtCore.Qt.transparent)
painter.setBrush(shadow_brush)
painter.drawEllipse(shadow_rect)
if under_mouse:
checker_color = self.checker_hover_color
else:
checker_color = self.checker_color
painter.setBrush(checker_color)
painter.drawEllipse(checker_rect)
if under_mouse:
adjust = margin_size_c
if adjust < 1 and checker_rect.height() > 4:
adjust = 1
smaller_checker_rect = checker_rect.adjusted(
adjust, adjust, -adjust, -adjust
)
gradient = QtGui.QLinearGradient(
smaller_checker_rect.bottomRight(),
smaller_checker_rect.topLeft()
)
gradient.setColorAt(0, checker_color)
gradient.setColorAt(1, checker_color.darker(155))
painter.setBrush(gradient)
painter.drawEllipse(smaller_checker_rect)
if self._draw_icons:
painter.setBrush(bg_color)
icon_path = self._get_icon_path(painter, checker_rect)

View file

@ -3,7 +3,7 @@ import logging
import contextlib
from avalon.vendor.Qt import QtCore, QtWidgets, QtGui
from Qt import QtCore, QtWidgets
log = logging.getLogger(__name__)

View file

@ -1,10 +1,9 @@
from avalon.vendor.Qt import QtCore, QtGui, QtWidgets
import os
import getpass
import platform
from Qt import QtCore, QtGui, QtWidgets
from avalon import style
import ftrack_api

View file

@ -100,6 +100,7 @@ import platform
import traceback
import subprocess
import site
import distutils.spawn
from pathlib import Path
# OPENPYPE_ROOT is variable pointing to build (or code) directory
@ -384,23 +385,6 @@ def set_modules_environments():
os.environ.update(env)
def is_tool(name):
try:
import os.errno as errno
except ImportError:
import errno
try:
devnull = open(os.devnull, "w")
subprocess.Popen(
[name], stdout=devnull, stderr=devnull
).communicate()
except OSError as exc:
if exc.errno == errno.ENOENT:
return False
return True
def _startup_validations():
"""Validations before OpenPype starts."""
try:
@ -443,7 +427,8 @@ def _validate_thirdparty_binaries():
if low_platform == "windows":
ffmpeg_dir = os.path.join(ffmpeg_dir, "bin")
ffmpeg_executable = os.path.join(ffmpeg_dir, "ffmpeg")
if not is_tool(ffmpeg_executable):
ffmpeg_result = distutils.spawn.find_executable(ffmpeg_executable)
if ffmpeg_result is None:
raise RuntimeError(error_msg.format("FFmpeg"))
# Validate existence of OpenImageIO (not on MacOs)
@ -463,8 +448,11 @@ def _validate_thirdparty_binaries():
low_platform,
"oiiotool"
)
if oiio_tool_path is not None and not is_tool(oiio_tool_path):
raise RuntimeError(error_msg.format("OpenImageIO"))
oiio_result = None
if oiio_tool_path is not None:
oiio_result = distutils.spawn.find_executable(oiio_tool_path)
if oiio_result is None:
raise RuntimeError(error_msg.format("OpenImageIO"))
def _process_arguments() -> tuple: