mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 21:32:15 +01:00
Merge branch 'master' of https://github.com/colorbleed/colorbleed-config into REN-0010
This commit is contained in:
commit
dc79affaa8
13 changed files with 369 additions and 98 deletions
|
|
@ -1,8 +1,6 @@
|
|||
# absolute_import is needed to counter the `module has no cmds error` in Maya
|
||||
from __future__ import absolute_import
|
||||
|
||||
import uuid
|
||||
|
||||
from maya import cmds
|
||||
import pyblish.api
|
||||
|
||||
|
|
@ -197,6 +195,5 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action):
|
|||
asset = instance.data['asset']
|
||||
asset_id = io.find_one({"name": asset, "type": "asset"},
|
||||
projection={"_id": True})['_id']
|
||||
for node in nodes:
|
||||
lib.set_id(node, asset_id, overwrite=True)
|
||||
|
||||
for node, _id in lib.generate_ids(nodes, asset_id=asset_id):
|
||||
lib.set_id(node, _id, overwrite=True)
|
||||
|
|
|
|||
46
colorbleed/lib.py
Normal file
46
colorbleed/lib.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import avalon.io as io
|
||||
import avalon.api
|
||||
|
||||
|
||||
def is_latest(representation):
|
||||
"""Return whether the representation is from latest version
|
||||
|
||||
Args:
|
||||
representation (str or io.ObjectId): The representation id.
|
||||
|
||||
Returns:
|
||||
bool: Whether the representation is of latest version.
|
||||
|
||||
"""
|
||||
|
||||
rep = io.find_one({"_id": io.ObjectId(representation),
|
||||
"type": "representation"})
|
||||
version = io.find_one({"_id": rep['parent']})
|
||||
|
||||
# Get highest version under the parent
|
||||
highest_version = io.find_one({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}, sort=[("name", -1)])
|
||||
|
||||
if version['name'] != highest_version['name']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def any_outdated():
|
||||
"""Return whether the current scene has any outdated content"""
|
||||
|
||||
checked = set()
|
||||
host = avalon.api.registered_host()
|
||||
for container in host.ls():
|
||||
representation = container['representation']
|
||||
if representation in checked:
|
||||
continue
|
||||
|
||||
if not is_latest(container['representation']):
|
||||
return True
|
||||
|
||||
checked.add(representation)
|
||||
return False
|
||||
|
|
@ -32,6 +32,7 @@ def install():
|
|||
log.info("Installing callbacks ... ")
|
||||
avalon.on("init", on_init)
|
||||
avalon.on("save", on_save)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
|
||||
def uninstall():
|
||||
|
|
@ -55,10 +56,29 @@ def on_init(_):
|
|||
log.warning("Can't load plug-in: "
|
||||
"{0} - {1}".format(plugin, e))
|
||||
|
||||
def safe_deferred(fn):
|
||||
"""Execute deferred the function in a try-except"""
|
||||
|
||||
def _fn():
|
||||
"""safely call in deferred callback"""
|
||||
try:
|
||||
fn()
|
||||
except Exception as exc:
|
||||
print(exc)
|
||||
|
||||
try:
|
||||
utils.executeDeferred(_fn)
|
||||
except Exception as exc:
|
||||
print(exc)
|
||||
|
||||
|
||||
cmds.loadPlugin("AbcImport", quiet=True)
|
||||
cmds.loadPlugin("AbcExport", quiet=True)
|
||||
force_load_deferred("mtoa")
|
||||
|
||||
from .customize import override_component_mask_commands
|
||||
safe_deferred(override_component_mask_commands)
|
||||
|
||||
|
||||
def on_save(_):
|
||||
"""Automatically add IDs to new nodes
|
||||
|
|
@ -72,3 +92,36 @@ def on_save(_):
|
|||
nodes = lib.get_id_required_nodes(referenced_nodes=False)
|
||||
for node, new_id in lib.generate_ids(nodes):
|
||||
lib.set_id(node, new_id, overwrite=False)
|
||||
|
||||
|
||||
def on_open(_):
|
||||
"""On scene open let's assume the containers have changed."""
|
||||
|
||||
from ..lib import any_outdated
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from ..widgets import popup
|
||||
|
||||
if any_outdated():
|
||||
log.warning("Scene has outdated content.")
|
||||
|
||||
# Find maya main window
|
||||
top_level_widgets = {w.objectName(): w for w in
|
||||
QtWidgets.QApplication.topLevelWidgets()}
|
||||
parent = top_level_widgets.get("MayaWindow", None)
|
||||
|
||||
if parent is None:
|
||||
log.info("Skipping outdated content pop-up "
|
||||
"because Maya window can't be found.")
|
||||
else:
|
||||
|
||||
# Show outdated pop-up
|
||||
def _on_show_inventory():
|
||||
import avalon.tools.cbsceneinventory as tool
|
||||
tool.show(parent=parent)
|
||||
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog.setWindowTitle("Maya scene has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
"your Maya scene.")
|
||||
dialog.on_show.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
|
|
|
|||
66
colorbleed/maya/customize.py
Normal file
66
colorbleed/maya/customize.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""A set of commands that install overrides to Maya's UI"""
|
||||
|
||||
import maya.cmds as mc
|
||||
import maya.mel as mel
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
COMPONENT_MASK_ORIGINAL = {}
|
||||
|
||||
|
||||
def override_component_mask_commands():
|
||||
"""Override component mask ctrl+click behavior.
|
||||
|
||||
This implements special behavior for Maya's component
|
||||
mask menu items where a ctrl+click will instantly make
|
||||
it an isolated behavior disabling all others.
|
||||
|
||||
Tested in Maya 2016 and 2018
|
||||
|
||||
"""
|
||||
log.info("Installing override_component_mask_commands..")
|
||||
|
||||
# Get all object mask buttons
|
||||
buttons = mc.formLayout("objectMaskIcons",
|
||||
query=True,
|
||||
childArray=True)
|
||||
# Skip the triangle list item
|
||||
buttons = [btn for btn in buttons if btn != "objPickMenuLayout"]
|
||||
|
||||
def on_changed_callback(raw_command, state):
|
||||
"""New callback"""
|
||||
|
||||
# If "control" is held force the toggled one to on and
|
||||
# toggle the others based on whether any of the buttons
|
||||
# was remaining active after the toggle, if not then
|
||||
# enable all
|
||||
if mc.getModifiers() == 4: # = CTRL
|
||||
state = True
|
||||
active = [mc.iconTextCheckBox(btn, query=True, value=True) for btn
|
||||
in buttons]
|
||||
if any(active):
|
||||
mc.selectType(allObjects=False)
|
||||
else:
|
||||
mc.selectType(allObjects=True)
|
||||
|
||||
# Replace #1 with the current button state
|
||||
cmd = raw_command.replace(" #1", " {}".format(int(state)))
|
||||
mel.eval(cmd)
|
||||
|
||||
for btn in buttons:
|
||||
|
||||
# Store a reference to the original command so that if
|
||||
# we rerun this override command it doesn't recursively
|
||||
# try to implement the fix. (This also allows us to
|
||||
# "uninstall" the behavior later)
|
||||
if btn not in COMPONENT_MASK_ORIGINAL:
|
||||
original = mc.iconTextCheckBox(btn, query=True, cc=True)
|
||||
COMPONENT_MASK_ORIGINAL[btn] = original
|
||||
|
||||
# Assign the special callback
|
||||
original = COMPONENT_MASK_ORIGINAL[btn]
|
||||
new_fn = partial(on_changed_callback, original)
|
||||
mc.iconTextCheckBox(btn, edit=True, cc=new_fn)
|
||||
|
|
@ -447,7 +447,7 @@ def extract_alembic(file,
|
|||
endFrame (float): End frame of output. Ignored if `frameRange`
|
||||
provided.
|
||||
|
||||
frameRange (tuple or str): Two-tuple with start and end frame or a
|
||||
frameRange (tuple or str): Two-tuple with start and end frame or a
|
||||
string formatted as: "startFrame endFrame". This argument
|
||||
overrides `startFrame` and `endFrame` arguments.
|
||||
|
||||
|
|
@ -481,7 +481,7 @@ def extract_alembic(file,
|
|||
an Euler filter. Euler filtering helps resolve irregularities in
|
||||
rotations especially if X, Y, and Z rotations exceed 360 degrees.
|
||||
Defaults to True.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Ensure alembic exporter is loaded
|
||||
|
|
@ -669,28 +669,28 @@ def get_id(node):
|
|||
|
||||
def generate_ids(nodes, asset_id=None):
|
||||
"""Returns new unique ids for the given nodes.
|
||||
|
||||
|
||||
Note: This does not assign the new ids, it only generates the values.
|
||||
|
||||
|
||||
To assign new ids using this method:
|
||||
>>> nodes = ["a", "b", "c"]
|
||||
>>> for node, id in generate_ids(nodes):
|
||||
>>> set_id(node, id)
|
||||
|
||||
|
||||
To also override any existing values (and assign regenerated ids):
|
||||
>>> nodes = ["a", "b", "c"]
|
||||
>>> for node, id in generate_ids(nodes):
|
||||
>>> set_id(node, id, overwrite=True)
|
||||
|
||||
|
||||
Args:
|
||||
nodes (list): List of nodes.
|
||||
asset_id (str or bson.ObjectId): The database id for the *asset* to
|
||||
generate for. When None provided the current asset in the
|
||||
generate for. When None provided the current asset in the
|
||||
active session is used.
|
||||
|
||||
|
||||
Returns:
|
||||
list: A list of (node, id) tuples.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if asset_id is None:
|
||||
|
|
@ -715,14 +715,14 @@ def set_id(node, unique_id, overwrite=False):
|
|||
|
||||
Args:
|
||||
node (str): the node to add the "cbId" on
|
||||
unique_id (str): The unique node id to assign.
|
||||
unique_id (str): The unique node id to assign.
|
||||
This should be generated by `generate_ids`.
|
||||
overwrite (bool, optional): When True overrides the current value even
|
||||
overwrite (bool, optional): When True overrides the current value even
|
||||
if `node` already has an id. Defaults to False.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
|
||||
"""
|
||||
|
||||
attr = "{0}.cbId".format(node)
|
||||
|
|
@ -739,9 +739,9 @@ def set_id(node, unique_id, overwrite=False):
|
|||
|
||||
def remove_id(node):
|
||||
"""Remove the id attribute from the input node.
|
||||
|
||||
|
||||
Args:
|
||||
node (str): The node name
|
||||
node (str): The node name
|
||||
|
||||
Returns:
|
||||
bool: Whether an id attribute was deleted
|
||||
|
|
@ -973,20 +973,19 @@ def apply_shaders(relationships, shadernodes, nodes):
|
|||
shader_data = relationships.get("relationships", {})
|
||||
|
||||
shading_engines = cmds.ls(shadernodes, type="objectSet", long=True)
|
||||
assert len(shading_engines) > 0, ("Error in retrieving objectSets "
|
||||
"from reference")
|
||||
assert shading_engines, "Error in retrieving objectSets from reference"
|
||||
|
||||
# region compute lookup
|
||||
ns_nodes_by_id = defaultdict(list)
|
||||
nodes_by_id = defaultdict(list)
|
||||
for node in nodes:
|
||||
ns_nodes_by_id[get_id(node)].append(node)
|
||||
nodes_by_id[get_id(node)].append(node)
|
||||
|
||||
shading_engines_by_id = defaultdict(list)
|
||||
for shad in shading_engines:
|
||||
shading_engines_by_id[get_id(shad)].append(shad)
|
||||
# endregion
|
||||
|
||||
# region assign
|
||||
# region assign shading engines and other sets
|
||||
for data in shader_data.values():
|
||||
# collect all unique IDs of the set members
|
||||
shader_uuid = data["uuid"]
|
||||
|
|
@ -994,21 +993,29 @@ def apply_shaders(relationships, shadernodes, nodes):
|
|||
|
||||
filtered_nodes = list()
|
||||
for uuid in member_uuids:
|
||||
filtered_nodes.extend(ns_nodes_by_id[uuid])
|
||||
filtered_nodes.extend(nodes_by_id[uuid])
|
||||
|
||||
shading_engine = shading_engines_by_id[shader_uuid]
|
||||
assert len(shading_engine) == 1, ("Could not find the correct "
|
||||
"objectSet with cbId "
|
||||
"'{}'".format(shader_uuid))
|
||||
id_shading_engines = shading_engines_by_id[shader_uuid]
|
||||
if not id_shading_engines:
|
||||
log.error("No shader found with cbId "
|
||||
"'{}'".format(shader_uuid))
|
||||
continue
|
||||
elif len(id_shading_engines) > 1:
|
||||
log.error("Skipping shader assignment. "
|
||||
"More than one shader found with cbId "
|
||||
"'{}'. (found: {})".format(shader_uuid,
|
||||
id_shading_engines))
|
||||
continue
|
||||
|
||||
if filtered_nodes:
|
||||
cmds.sets(filtered_nodes, forceElement=shading_engine[0])
|
||||
else:
|
||||
if not filtered_nodes:
|
||||
log.warning("No nodes found for shading engine "
|
||||
"'{0}'".format(shading_engine[0]))
|
||||
"'{0}'".format(id_shading_engines[0]))
|
||||
continue
|
||||
|
||||
cmds.sets(filtered_nodes, forceElement=id_shading_engines[0])
|
||||
# endregion
|
||||
|
||||
apply_attributes(attributes, ns_nodes_by_id)
|
||||
apply_attributes(attributes, nodes_by_id)
|
||||
|
||||
|
||||
# endregion LOOKDEV
|
||||
|
|
|
|||
|
|
@ -71,14 +71,20 @@ class ReferenceLoader(api.Loader):
|
|||
assert os.path.exists(path), "%s does not exist." % path
|
||||
cmds.file(path, loadReference=reference_node, type=file_type)
|
||||
|
||||
# Fix PLN-40 for older containers created with Avalon that had the
|
||||
# `.verticesOnlySet` set to True.
|
||||
if cmds.getAttr(node + ".verticesOnlySet"):
|
||||
self.log.info("Setting %s.verticesOnlySet to False", node)
|
||||
cmds.setAttr(node + ".verticesOnlySet", False)
|
||||
|
||||
# TODO: Add all new nodes in the reference to the container
|
||||
# Currently new nodes in an updated reference are not added to the
|
||||
# container whereas actually they should be!
|
||||
nodes = cmds.referenceQuery(reference_node, nodes=True, dagPath=True)
|
||||
cmds.sets(nodes, forceElement=container['objectName'])
|
||||
cmds.sets(nodes, forceElement=node)
|
||||
|
||||
# Update metadata
|
||||
cmds.setAttr(container["objectName"] + ".representation",
|
||||
cmds.setAttr(node + ".representation",
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,14 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
# Collect members
|
||||
members = cmds.ls(members, long=True) or []
|
||||
|
||||
# `maya.cmds.listRelatives(noIntermediate=True)` only works when
|
||||
# `shapes=True` argument is passed, since we also want to include
|
||||
# transforms we filter afterwards.
|
||||
children = cmds.listRelatives(members,
|
||||
allDescendents=True,
|
||||
fullPath=True,
|
||||
noIntermediate=True) or []
|
||||
fullPath=True) or []
|
||||
children = cmds.ls(children, noIntermediate=True, long=True)
|
||||
|
||||
parents = self.get_all_parents(members)
|
||||
members_hierarchy = list(set(members + children + parents))
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,11 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
self.log.warning("No sets found for the nodes in the instance: "
|
||||
"%s" % instance[:])
|
||||
|
||||
# Ensure unique shader sets
|
||||
# Add shader sets to the instance for unify ID validation
|
||||
instance.extend(shader for shader in looksets if shader
|
||||
not in instance_lookup)
|
||||
|
||||
self.log.info("Collected look for %s" % instance)
|
||||
|
||||
def collect_sets(self, instance):
|
||||
|
|
@ -181,7 +186,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
|
||||
node_id = lib.get_id(node)
|
||||
if not node_id:
|
||||
self.log.error("Node '{}' has no attribute 'cbId'".format(node))
|
||||
self.log.error("Member '{}' has no attribute 'cbId'".format(node))
|
||||
return
|
||||
|
||||
member_data = {"name": node, "uuid": node_id}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ class ValidateLookContents(pyblish.api.InstancePlugin):
|
|||
* At least one relationship must be collection.
|
||||
* All relationship object sets at least have an ID value
|
||||
|
||||
Tip:
|
||||
* When no node IDs are found on shadingEngines please save your scene
|
||||
and try again.
|
||||
|
||||
"""
|
||||
|
||||
order = colorbleed.api.ValidateContentsOrder
|
||||
|
|
@ -57,12 +61,12 @@ class ValidateLookContents(pyblish.api.InstancePlugin):
|
|||
|
||||
invalid = set()
|
||||
|
||||
attributes = ["relationships", "attributes"]
|
||||
keys = ["relationships", "attributes"]
|
||||
lookdata = instance.data["lookData"]
|
||||
for attr in attributes:
|
||||
if attr not in lookdata:
|
||||
cls.log.error("Look Data has no attribute "
|
||||
"'{}'".format(attr))
|
||||
for key in keys:
|
||||
if key not in lookdata:
|
||||
cls.log.error("Look Data has no key "
|
||||
"'{}'".format(key))
|
||||
invalid.add(instance.name)
|
||||
|
||||
# Validate at least one single relationship is collected
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import colorbleed.api
|
|||
|
||||
|
||||
class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin):
|
||||
"""Validate look contains no default shaders.
|
||||
"""Validate if any node has a connection to a default shader.
|
||||
|
||||
This checks whether the look has any members of:
|
||||
- lambert1
|
||||
|
|
@ -28,6 +28,9 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin):
|
|||
label = 'Look No Default Shaders'
|
||||
actions = [colorbleed.api.SelectInvalidAction]
|
||||
|
||||
DEFAULT_SHADERS = {"lambert1", "initialShadingGroup",
|
||||
"initialParticleSE", "particleCloud1"}
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance"""
|
||||
|
||||
|
|
@ -38,44 +41,18 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin):
|
|||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
disallowed = ["lambert1", "initialShadingGroup",
|
||||
"initialParticleSE", "particleCloud1"]
|
||||
disallowed = set(disallowed)
|
||||
|
||||
# Check if there are any skinClusters present
|
||||
# If so ensure nodes which are skinned
|
||||
intermediate = []
|
||||
skinclusters = cmds.ls(type="skinCluster")
|
||||
cls.log.info("Found skinClusters, will skip original shapes")
|
||||
if skinclusters:
|
||||
intermediate += cmds.ls(intermediateObjects=True,
|
||||
shapes=True,
|
||||
long=True)
|
||||
|
||||
invalid = set()
|
||||
for node in instance:
|
||||
# Get shading engine connections
|
||||
shaders = cmds.listConnections(node, type="shadingEngine") or []
|
||||
|
||||
# get connection
|
||||
# listConnections returns a list or None
|
||||
object_sets = cmds.listConnections(node, type="objectSet") or []
|
||||
|
||||
# Ensure the shape in the instances have at least a single shader
|
||||
# connected if it *can* have a shader, like a `surfaceShape` in
|
||||
# Maya.
|
||||
if (cmds.objectType(node, isAType="surfaceShape") and
|
||||
not cmds.ls(object_sets, type="shadingEngine")):
|
||||
if node in intermediate:
|
||||
continue
|
||||
cls.log.error("Detected shape without shading engine: "
|
||||
"'{}'".format(node))
|
||||
invalid.add(node)
|
||||
|
||||
# Check for any disallowed connections
|
||||
if any(s in disallowed for s in object_sets):
|
||||
# Check for any disallowed connections on *all* nodes
|
||||
if any(s in cls.DEFAULT_SHADERS for s in shaders):
|
||||
|
||||
# Explicitly log each individual "wrong" connection.
|
||||
for s in object_sets:
|
||||
if s in disallowed:
|
||||
for s in shaders:
|
||||
if s in cls.DEFAULT_SHADERS:
|
||||
cls.log.error("Node has unallowed connection to "
|
||||
"'{}': {}".format(s, node))
|
||||
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
|
|||
"""Process all meshes"""
|
||||
|
||||
# Ensure all nodes have a cbId
|
||||
invalid = self.get_invalid_dict(instance)
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Nodes found with non-unique "
|
||||
"asset IDs: {0}".format(invalid))
|
||||
|
||||
@classmethod
|
||||
def get_invalid_dict(cls, instance):
|
||||
"""Return a dictionary mapping of id key to list of member nodes"""
|
||||
def get_invalid(cls, instance):
|
||||
"""Return the member nodes that are invalid"""
|
||||
|
||||
# Collect each id with their members
|
||||
ids = defaultdict(list)
|
||||
|
|
@ -42,24 +42,11 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
|
|||
continue
|
||||
ids[object_id].append(member)
|
||||
|
||||
# Skip those without IDs (if everything should have an ID that should
|
||||
# be another validation)
|
||||
ids.pop(None, None)
|
||||
|
||||
# Take only the ids with more than one member
|
||||
invalid = dict((_id, members) for _id, members in ids.iteritems() if
|
||||
len(members) > 1)
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
"""Return the member nodes that are invalid"""
|
||||
|
||||
invalid_dict = cls.get_invalid_dict(instance)
|
||||
|
||||
# Take only the ids with more than one member
|
||||
invalid = list()
|
||||
for members in invalid_dict.itervalues():
|
||||
invalid.extend(members)
|
||||
for _ids, members in ids.iteritems():
|
||||
if len(members) > 1:
|
||||
cls.log.error("ID found on multiple nodes: '%s'" % members)
|
||||
invalid.extend(members)
|
||||
|
||||
return invalid
|
||||
return invalid
|
||||
|
|
|
|||
0
colorbleed/widgets/__init__.py
Normal file
0
colorbleed/widgets/__init__.py
Normal file
119
colorbleed/widgets/popup.py
Normal file
119
colorbleed/widgets/popup.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import sys
|
||||
import logging
|
||||
import contextlib
|
||||
|
||||
|
||||
from avalon.vendor.Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Popup(QtWidgets.QDialog):
|
||||
|
||||
on_show = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
super(Popup, self).__init__(parent=parent, *args, **kwargs)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 5, 10, 10)
|
||||
message = QtWidgets.QLabel("")
|
||||
message.setStyleSheet("""
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
show = QtWidgets.QPushButton("Show")
|
||||
show.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
|
||||
QtWidgets.QSizePolicy.Maximum)
|
||||
show.setStyleSheet("""QPushButton { background-color: #BB0000 }""")
|
||||
|
||||
|
||||
layout.addWidget(message)
|
||||
layout.addWidget(show)
|
||||
|
||||
# Size
|
||||
self.resize(400, 40)
|
||||
geometry = self.calculate_window_geometry()
|
||||
self.setGeometry(geometry)
|
||||
|
||||
self.widgets = {
|
||||
"message": message,
|
||||
"show": show,
|
||||
}
|
||||
|
||||
# Signals
|
||||
show.clicked.connect(self._on_show_clicked)
|
||||
|
||||
# Set default title
|
||||
self.setWindowTitle("Popup")
|
||||
|
||||
def setMessage(self, message):
|
||||
self.widgets['message'].setText(message)
|
||||
|
||||
def _on_show_clicked(self):
|
||||
"""Callback for when the 'show' button is clicked.
|
||||
|
||||
Raises the parent (if any)
|
||||
|
||||
"""
|
||||
|
||||
parent = self.parent()
|
||||
self.close()
|
||||
|
||||
# Trigger the signal
|
||||
self.on_show.emit()
|
||||
|
||||
if parent:
|
||||
parent.raise_()
|
||||
|
||||
def calculate_window_geometry(self):
|
||||
"""Respond to status changes
|
||||
|
||||
On creation, align window with screen bottom right.
|
||||
|
||||
"""
|
||||
|
||||
window = self
|
||||
|
||||
width = window.width()
|
||||
width = max(width, window.minimumWidth())
|
||||
|
||||
height = window.height()
|
||||
height = max(height, window.sizeHint().height())
|
||||
|
||||
desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry()
|
||||
screen_geometry = window.geometry()
|
||||
|
||||
screen_width = screen_geometry.width()
|
||||
screen_height = screen_geometry.height()
|
||||
|
||||
# Calculate width and height of system tray
|
||||
systray_width = screen_geometry.width() - desktop_geometry.width()
|
||||
systray_height = screen_geometry.height() - desktop_geometry.height()
|
||||
|
||||
padding = 10
|
||||
|
||||
x = screen_width - width
|
||||
y = screen_height - height
|
||||
|
||||
x -= systray_width + padding
|
||||
y -= systray_height + padding
|
||||
|
||||
return QtCore.QRect(x, y, width, height)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def application():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
yield
|
||||
app.exec_()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with application():
|
||||
dialog = Popup()
|
||||
dialog.setMessage("There are outdated containers in your Maya scene.")
|
||||
dialog.show()
|
||||
Loading…
Add table
Add a link
Reference in a new issue