Merge branch 'develop' into feature/OP-2045_Preview-for-multilayer-exrs

This commit is contained in:
iLLiCiTiT 2021-11-30 13:51:33 +01:00
commit 8654ffc1be
93 changed files with 1307 additions and 1085 deletions

View file

@ -1,26 +1,48 @@
# Changelog
## [3.7.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD)
## [3.7.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.4...HEAD)
### 📖 Documentation
- docs\[website\]: Add Ellipse Studio \(logo\) as an OpenPype contributor [\#2324](https://github.com/pypeclub/OpenPype/pull/2324)
**🆕 New features**
- Store typed version dependencies for workfiles [\#2192](https://github.com/pypeclub/OpenPype/pull/2192)
**🚀 Enhancements**
- Hiero: Add experimental tools action [\#2323](https://github.com/pypeclub/OpenPype/pull/2323)
- Input links: Cleanup and unification of differences [\#2322](https://github.com/pypeclub/OpenPype/pull/2322)
- General: Run process log stderr as info log level [\#2309](https://github.com/pypeclub/OpenPype/pull/2309)
- Tools: Cleanup of unused classes [\#2304](https://github.com/pypeclub/OpenPype/pull/2304)
- Project Manager: Added ability to delete project [\#2298](https://github.com/pypeclub/OpenPype/pull/2298)
- Ftrack: Synchronize input links [\#2287](https://github.com/pypeclub/OpenPype/pull/2287)
- StandalonePublisher: Remove unused plugin ExtractHarmonyZip [\#2277](https://github.com/pypeclub/OpenPype/pull/2277)
- Ftrack: Support multiple reviews [\#2271](https://github.com/pypeclub/OpenPype/pull/2271)
- Ftrack: Remove unused clean component plugin [\#2269](https://github.com/pypeclub/OpenPype/pull/2269)
- Royal Render: Support for rr channels in separate dirs [\#2268](https://github.com/pypeclub/OpenPype/pull/2268)
- Houdini: Add experimental tools action [\#2267](https://github.com/pypeclub/OpenPype/pull/2267)
- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265)
- Nuke: extract baked review videos presets [\#2248](https://github.com/pypeclub/OpenPype/pull/2248)
- TVPaint: Workers rendering [\#2209](https://github.com/pypeclub/OpenPype/pull/2209)
**🐛 Bug fixes**
- Fix - provider icons are pulled from a folder [\#2326](https://github.com/pypeclub/OpenPype/pull/2326)
- InputLinks: Typo in "inputLinks" key [\#2314](https://github.com/pypeclub/OpenPype/pull/2314)
- Deadline timeout and logging [\#2312](https://github.com/pypeclub/OpenPype/pull/2312)
- nuke: do not multiply representation on class method [\#2311](https://github.com/pypeclub/OpenPype/pull/2311)
- Workfiles tool: Fix task formatting [\#2306](https://github.com/pypeclub/OpenPype/pull/2306)
- Delivery: Fix delivery paths created on windows [\#2302](https://github.com/pypeclub/OpenPype/pull/2302)
- Maya: Deadline - fix limit groups [\#2295](https://github.com/pypeclub/OpenPype/pull/2295)
- New Publisher: Fix mapping of indexes [\#2285](https://github.com/pypeclub/OpenPype/pull/2285)
- Alternate site for site sync doesnt work for sequences [\#2284](https://github.com/pypeclub/OpenPype/pull/2284)
- FFmpeg: Execute ffprobe using list of arguments instead of string command [\#2281](https://github.com/pypeclub/OpenPype/pull/2281)
- Nuke: Anatomy fill data use task as dictionary [\#2278](https://github.com/pypeclub/OpenPype/pull/2278)
- Bug: fix variable name \_asset\_id in workfiles application [\#2274](https://github.com/pypeclub/OpenPype/pull/2274)
- Version handling fixes [\#2272](https://github.com/pypeclub/OpenPype/pull/2272)
## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23)
@ -44,23 +66,22 @@
**🚀 Enhancements**
- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265)
- Royal Render: Support for rr channels in separate dirs [\#2268](https://github.com/pypeclub/OpenPype/pull/2268)
- SceneInventory: Choose loader in asset switcher [\#2262](https://github.com/pypeclub/OpenPype/pull/2262)
- Style: New fonts in OpenPype style [\#2256](https://github.com/pypeclub/OpenPype/pull/2256)
- Tools: SceneInventory in OpenPype [\#2255](https://github.com/pypeclub/OpenPype/pull/2255)
- Tools: Tasks widget [\#2251](https://github.com/pypeclub/OpenPype/pull/2251)
- Tools: Creator in OpenPype [\#2244](https://github.com/pypeclub/OpenPype/pull/2244)
- Added endpoint for configured extensions [\#2221](https://github.com/pypeclub/OpenPype/pull/2221)
**🐛 Bug fixes**
- Version handling fixes [\#2272](https://github.com/pypeclub/OpenPype/pull/2272)
- Tools: Parenting of tools in Nuke and Hiero [\#2266](https://github.com/pypeclub/OpenPype/pull/2266)
- limiting validator to specific editorial hosts [\#2264](https://github.com/pypeclub/OpenPype/pull/2264)
- Tools: Select Context dialog attribute fix [\#2261](https://github.com/pypeclub/OpenPype/pull/2261)
- Maya: Render publishing fails on linux [\#2260](https://github.com/pypeclub/OpenPype/pull/2260)
- LookAssigner: Fix tool reopen [\#2259](https://github.com/pypeclub/OpenPype/pull/2259)
- Standalone: editorial not publishing thumbnails on all subsets [\#2258](https://github.com/pypeclub/OpenPype/pull/2258)
- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254)
- Burnins: Support mxf metadata [\#2247](https://github.com/pypeclub/OpenPype/pull/2247)
- Maya: Support for configurable AOV separator characters [\#2197](https://github.com/pypeclub/OpenPype/pull/2197)
- Maya: texture colorspace modes in looks [\#2195](https://github.com/pypeclub/OpenPype/pull/2195)
@ -69,10 +90,6 @@
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.1-nightly.1...3.6.1)
**🐛 Bug fixes**
- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254)
## [3.6.0](https://github.com/pypeclub/OpenPype/tree/3.6.0) (2021-11-15)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.0-nightly.6...3.6.0)
@ -82,14 +99,9 @@
- Add alternative sites for Site Sync [\#2206](https://github.com/pypeclub/OpenPype/pull/2206)
- Add command line way of running site sync server [\#2188](https://github.com/pypeclub/OpenPype/pull/2188)
**🆕 New features**
- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176)
- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170)
- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168)
**🚀 Enhancements**
- Tools: Creator in OpenPype [\#2244](https://github.com/pypeclub/OpenPype/pull/2244)
- Tools: Subset manager in OpenPype [\#2243](https://github.com/pypeclub/OpenPype/pull/2243)
- General: Skip module directories without init file [\#2239](https://github.com/pypeclub/OpenPype/pull/2239)
- General: Static interfaces [\#2238](https://github.com/pypeclub/OpenPype/pull/2238)
@ -109,8 +121,6 @@
- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196)
- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193)
- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185)
- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184)
- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179)
**🐛 Bug fixes**
@ -126,9 +136,6 @@
- Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205)
- Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203)
- Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191)
- Blender: Fix trying to pack an image when the shader node has no texture [\#2183](https://github.com/pypeclub/OpenPype/pull/2183)
- Maya: review viewport settings [\#2177](https://github.com/pypeclub/OpenPype/pull/2177)
- Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174)
## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17)

View file

@ -59,7 +59,7 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
return False, "Not mongodb schema"
kwargs = {
"serverSelectionTimeoutMS": 2000
"serverSelectionTimeoutMS": os.environ.get("AVALON_TIMEOUT", 2000)
}
# Add certificate path if should be required
if should_add_certificate_path_to_mongo_url(cnx):

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

@ -105,9 +105,9 @@ def menu_install():
sceneinventory_action.triggered.connect(
lambda: host_tools.show_scene_inventory(parent=main_window)
)
menu.addSeparator()
if os.getenv("OPENPYPE_DEVELOP"):
menu.addSeparator()
reload_action = menu.addAction("Reload pipeline")
reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
reload_action.triggered.connect(reload_config)
@ -120,3 +120,10 @@ def menu_install():
apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips")
apply_colorspace_c_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
apply_colorspace_c_action.triggered.connect(apply_colorspace_clips)
menu.addSeparator()
exeprimental_action = menu.addAction("Experimental tools...")
exeprimental_action.triggered.connect(
lambda: host_tools.show_experimental_tools_dialog(parent=main_window)
)

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

@ -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

@ -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,17 @@ def get_linked_asset_ids(asset_doc):
if not asset_doc:
return output
input_links = asset_doc["data"].get("inputsLinks") or []
input_links = asset_doc["data"].get("inputLinks") or []
if input_links:
output = [item["_id"] for item in input_links]
for item in input_links:
# Backwards compatibility for "_id" key which was replaced with
# "id"
if "_id" in item:
link_id = item["_id"]
else:
link_id = item["id"]
output.append(link_id)
return output

View file

@ -60,12 +60,13 @@ def path_from_representation(representation, anatomy):
path = pipeline.format_template_with_optional_keys(
context, template
)
path = os.path.normpath(path.replace("/", "\\"))
except KeyError:
# Template references unavailable data
return None
return os.path.normpath(path)
return path
def copy_file(src_path, dst_path):
@ -179,9 +180,11 @@ def process_single_file(
Returns:
(collections.defaultdict , int)
"""
# Make sure path is valid for all platforms
src_path = os.path.normpath(src_path.replace("\\", "/"))
if not os.path.exists(src_path):
msg = "{} doesn't exist for {}".format(src_path,
repre["_id"])
msg = "{} doesn't exist for {}".format(src_path, repre["_id"])
report_items["Source file was not found"].append(msg)
return report_items, 0
@ -192,8 +195,10 @@ def process_single_file(
else:
delivery_path = anatomy_filled["delivery"][template_name]
# context.representation could be .psd
# Backwards compatibility when extension contained `.`
delivery_path = delivery_path.replace("..", ".")
# Make sure path is valid for all platforms
delivery_path = os.path.normpath(delivery_path.replace("\\", "/"))
delivery_folder = os.path.dirname(delivery_path)
if not os.path.exists(delivery_folder):
@ -230,14 +235,14 @@ def process_sequence(
Returns:
(collections.defaultdict , int)
"""
src_path = os.path.normpath(src_path.replace("\\", "/"))
def hash_path_exist(myPath):
res = myPath.replace('#', '*')
glob_search_results = glob.glob(res)
if len(glob_search_results) > 0:
return True
else:
return False
return False
if not hash_path_exist(src_path):
msg = "{} doesn't exist for {}".format(src_path,
@ -307,6 +312,7 @@ def process_sequence(
else:
delivery_path = anatomy_filled["delivery"][template_name]
delivery_path = os.path.normpath(delivery_path.replace("\\", "/"))
delivery_folder = os.path.dirname(delivery_path)
dst_head, dst_tail = delivery_path.split(frame_indicator)
dst_padding = src_collection.padding

View file

@ -124,7 +124,7 @@ def run_subprocess(*args, **kwargs):
if full_output:
full_output += "\n"
full_output += _stderr
logger.warning(_stderr)
logger.info(_stderr)
if proc.returncode != 0:
exc_msg = "Executing arguments was not successful: \"{}\"".format(args)

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

@ -113,7 +113,7 @@ class SyncLinksToAvalon(BaseEvent):
continue
links.append({
"_id": ObjectId(link_mongo_id),
"id": ObjectId(link_mongo_id),
"linkedBy": "ftrack",
"type": "breakdown"
})

View file

@ -1479,7 +1479,7 @@ class SyncEntitiesFactory:
mongo_id = self.ftrack_avalon_mapper.get(ftrack_link_id)
if mongo_id is not None:
input_links.append({
"_id": ObjectId(mongo_id),
"id": ObjectId(mongo_id),
"linkedBy": "ftrack",
"type": "breakdown"
})

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

@ -56,6 +56,13 @@ representation.files.sites:
`db.getCollection('MY_PROJECT').update({type:"representation"},
{$set:{"files.$[].sites.MY_CONFIGURED_REMOTE_SITE" : {}}}, true, true)`
I want to create new custom provider:
-----------------------------------
- take `providers\abstract_provider.py` as a base class
- create provider class in `providers` with a name according to a provider (eg. 'gdrive.py' for gdrive provider etc.)
- upload provider icon in png format, 24x24, into `providers\resources`, its name must follow name of provider (eg. 'gdrive.png' for gdrive provider)
- register new provider into `providers.lib.py`, test how many files could be manipulated at same time, check provider's API for limits
Needed configuration:
--------------------
`pype/settings/defaults/project_settings/global.json`.`sync_server`:

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

@ -1062,10 +1062,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

@ -81,7 +81,8 @@ class IntegrateInputLinks(pyblish.api.ContextPlugin):
version_doc=instance.data["versionEntity"],
)
publishing.append(workfile)
if workfile is not None:
publishing.append(workfile)
self.write_links_to_database(publishing)
def add_link(self, link_type, input_id, version_doc):
@ -103,7 +104,7 @@ class IntegrateInputLinks(pyblish.api.ContextPlugin):
# future.
link = OrderedDict()
link["type"] = link_type
link["input"] = io.ObjectId(input_id)
link["id"] = io.ObjectId(input_id)
link["linkedBy"] = "publish"
if "inputLinks" not in version_doc["data"]:

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

@ -37,8 +37,13 @@ class SimpleLinkView(QtWidgets.QWidget):
# inputs
#
for link in version_doc["data"].get("inputLinks", []):
# Backwards compatibility for "input" key used as "id"
if "id" not in link:
link_id = link["input"]
else:
link_id = link["id"]
version = self.dbcon.find_one(
{"_id": link["input"], "type": "version"},
{"_id": link_id, "type": "version"},
projection={"name": 1, "parent": 1}
)
if not version:

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

@ -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)
@ -477,6 +505,7 @@ def create_qthread(func, *args, **kwargs):
def get_repre_icons():
"""Returns a dict {'provider_name': QIcon}"""
try:
from openpype_modules import sync_server
except Exception:
@ -488,9 +517,17 @@ def get_repre_icons():
"providers", "resources"
)
icons = {}
# TODO get from sync module
for provider in ['studio', 'local_drive', 'gdrive']:
pix_url = "{}/{}.png".format(resource_path, provider)
if not os.path.exists(resource_path):
print("No icons for Site Sync found")
return {}
for file_name in os.listdir(resource_path):
if file_name and not file_name.endswith("png"):
continue
provider, _ = os.path.splitext(file_name)
pix_url = os.path.join(resource_path, file_name)
icons[provider] = QtGui.QIcon(pix_url)
return icons

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,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.7.0-nightly.2"
__version__ = "3.7.0-nightly.3"

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

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.7.0-nightly.2" # OpenPype
version = "3.7.0-nightly.3" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

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:

View file

@ -16,8 +16,9 @@ def inject_openpype_environment(deadlinePlugin):
job = deadlinePlugin.GetJob()
job = RepositoryUtils.GetJob(job.JobId, True) # invalidates cache
print("inject_openpype_environment start")
print(">>> Injecting OpenPype environments ...")
try:
print(">>> Getting OpenPype executable ...")
exe_list = job.GetJobExtraInfoKeyValue("openpype_executables")
openpype_app = FileUtils.SearchFileList(exe_list)
if openpype_app == "":
@ -27,11 +28,13 @@ def inject_openpype_environment(deadlinePlugin):
"The path to the render executable can be configured " +
"from the Plugin Configuration in the Deadline Monitor.")
print("--- OpenPype executable: {}".format(openpype_app))
# tempfile.TemporaryFile cannot be used because of locking
export_url = os.path.join(tempfile.gettempdir(),
time.strftime('%Y%m%d%H%M%S'),
'env.json') # add HHMMSS + delete later
print("export_url {}".format(export_url))
print(">>> Temporary path: {}".format(export_url))
args = [
openpype_app,
@ -55,41 +58,52 @@ def inject_openpype_environment(deadlinePlugin):
"AVALON_TASK, AVALON_APP_NAME"
raise RuntimeError(msg)
print("args:::{}".format(args))
if not os.environ.get("OPENPYPE_MONGO"):
print(">>> Missing OPENPYPE_MONGO env var, process won't work")
exit_code = subprocess.call(args, cwd=os.path.dirname(openpype_app))
if exit_code != 0:
raise RuntimeError("Publishing failed, check worker's log")
env = os.environ
env["OPENPYPE_HEADLESS_MODE"] = "1"
env["AVALON_TIMEOUT"] = "5000"
print(">>> Executing: {}".format(args))
std_output = subprocess.check_output(args,
cwd=os.path.dirname(openpype_app),
env=env)
print(">>> Process result {}".format(std_output))
print(">>> Loading file ...")
with open(export_url) as fp:
contents = json.load(fp)
for key, value in contents.items():
deadlinePlugin.SetProcessEnvironmentVariable(key, value)
print(">>> Removing temporary file")
os.remove(export_url)
print("inject_openpype_environment end")
except Exception:
print(">> Injection end.")
except Exception as e:
if hasattr(e, "output"):
print(">>> Exception {}".format(e.output))
import traceback
print(traceback.format_exc())
print("inject_openpype_environment failed")
print("!!! Injection failed.")
RepositoryUtils.FailJob(job)
raise
def inject_render_job_id(deadlinePlugin):
"""Inject dependency ids to publish process as env var for validation."""
print("inject_render_job_id start")
print(">>> Injecting render job id ...")
job = deadlinePlugin.GetJob()
job = RepositoryUtils.GetJob(job.JobId, True) # invalidates cache
dependency_ids = job.JobDependencyIDs
print("dependency_ids {}".format(dependency_ids))
print(">>> Dependency IDs: {}".format(dependency_ids))
render_job_ids = ",".join(dependency_ids)
deadlinePlugin.SetProcessEnvironmentVariable("RENDER_JOB_IDS",
render_job_ids)
print("inject_render_job_id end")
print(">>> Injection end.")
def pype_command_line(executable, arguments, workingDirectory):
@ -133,10 +147,13 @@ def pype(deadlinePlugin):
deadlinePlugin: Deadline job plugin passed by Deadline
"""
print(">>> Getting job ...")
job = deadlinePlugin.GetJob()
# PYPE should be here, not OPENPYPE - backward compatibility!!
pype_metadata = job.GetJobEnvironmentKeyValue("PYPE_METADATA_FILE")
pype_python = job.GetJobEnvironmentKeyValue("PYPE_PYTHON_EXE")
print(">>> Having backward compatible env vars {}/{}".format(pype_metadata,
pype_python))
# test if it is pype publish job.
if pype_metadata:
pype_metadata = RepositoryUtils.CheckPathMapping(pype_metadata)
@ -162,6 +179,8 @@ def pype(deadlinePlugin):
def __main__(deadlinePlugin):
print("*** GlobalJobPreload start ...")
print(">>> Getting job ...")
job = deadlinePlugin.GetJob()
job = RepositoryUtils.GetJob(job.JobId, True) # invalidates cache
@ -170,6 +189,8 @@ def __main__(deadlinePlugin):
openpype_publish_job = \
job.GetJobEnvironmentKeyValue('OPENPYPE_PUBLISH_JOB') or '0'
print("--- Job type - render {}".format(openpype_render_job))
print("--- Job type - publish {}".format(openpype_publish_job))
if openpype_publish_job == '1' and openpype_render_job == '1':
raise RuntimeError("Misconfiguration. Job couldn't be both " +
"render and publish.")

View file

@ -197,7 +197,7 @@ h5, h6 { font-weight: var(--ifm-font-weight-semibold); }
}
.showcase .client img {
max-height: 80px;
max-height: 70px;
padding: 20px;
max-width: 120px;
align-self: center;
@ -215,10 +215,10 @@ h5, h6 { font-weight: var(--ifm-font-weight-semibold); }
}
.showcase .collab img {
max-height: 60px;
max-height: 70px;
padding: 20px;
align-self: center;
max-width: 200px;
max-width: 160px;
}
.showcase .pype_logo img{

View file

@ -64,6 +64,10 @@ const collab = [
title: 'Clothcat Animation',
image: '/img/clothcat.png',
infoLink: 'https://www.clothcatanimation.com/'
}, {
title: 'Ellipse Studio',
image: '/img/ellipse-studio.png',
infoLink: 'http://www.dargaudmedia.com'
}
];
@ -125,7 +129,7 @@ const studios = [
title: "Moonrock Animation Studio",
image: "/img/moonrock_logo.png",
infoLink: "https://www.moonrock.eu/",
}
}
];
function Service({imageUrl, title, description}) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB