diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py
index 816a7d5116..f213b596ad 100644
--- a/pype/nuke/lib.py
+++ b/pype/nuke/lib.py
@@ -6,6 +6,7 @@ from collections import OrderedDict
from avalon import api, io, lib
import avalon.nuke
+from avalon.nuke import lib as anlib
import pype.api as pype
import nuke
@@ -1195,6 +1196,176 @@ class BuildWorkfile(WorkfileSettings):
def position_up(self, multiply=1):
self.ypos -= (self.ypos_size * multiply) + self.ypos_gap
+
+class Exporter_review_lut:
+ """
+ Generator object for review lut from Nuke
+
+ Args:
+ klass (pyblish.plugin): pyblish plugin parent
+
+
+ """
+ _temp_nodes = []
+ data = dict({
+ "representations": list()
+ })
+
+ def __init__(self,
+ klass,
+ instance,
+ name=None,
+ ext=None,
+ cube_size=None,
+ lut_size=None,
+ lut_style=None):
+
+ self.log = klass.log
+ self.instance = instance
+
+ self.name = name or "baked_lut"
+ self.ext = ext or "cube"
+ self.cube_size = cube_size or 32
+ self.lut_size = lut_size or 1024
+ self.lut_style = lut_style or "linear"
+
+ self.stagingDir = self.instance.data["stagingDir"]
+ self.collection = self.instance.data.get("collection", None)
+
+ # set frame start / end and file name to self
+ self.get_file_info()
+
+ self.log.info("File info was set...")
+
+ self.file = self.fhead + self.name + ".{}".format(self.ext)
+ self.path = os.path.join(self.stagingDir, self.file).replace("\\", "/")
+
+ def generate_lut(self):
+ # ---------- start nodes creation
+
+ # CMSTestPattern
+ cms_node = nuke.createNode("CMSTestPattern")
+ cms_node["cube_size"].setValue(self.cube_size)
+ # connect
+ self._temp_nodes.append(cms_node)
+ self.previous_node = cms_node
+ self.log.debug("CMSTestPattern... `{}`".format(self._temp_nodes))
+
+ # Node View Process
+ ipn = self.get_view_process_node()
+ if ipn is not None:
+ # connect
+ ipn.setInput(0, self.previous_node)
+ self._temp_nodes.append(ipn)
+ self.previous_node = ipn
+ self.log.debug("ViewProcess... `{}`".format(self._temp_nodes))
+
+ # OCIODisplay
+ dag_node = nuke.createNode("OCIODisplay")
+ # connect
+ dag_node.setInput(0, self.previous_node)
+ self._temp_nodes.append(dag_node)
+ self.previous_node = dag_node
+ self.log.debug("OCIODisplay... `{}`".format(self._temp_nodes))
+
+ # GenerateLUT
+ gen_lut_node = nuke.createNode("GenerateLUT")
+ gen_lut_node["file"].setValue(self.path)
+ gen_lut_node["file_type"].setValue(".{}".format(self.ext))
+ gen_lut_node["lut1d"].setValue(self.lut_size)
+ gen_lut_node["style1d"].setValue(self.lut_style)
+ # connect
+ gen_lut_node.setInput(0, self.previous_node)
+ self._temp_nodes.append(gen_lut_node)
+ self.log.debug("GenerateLUT... `{}`".format(self._temp_nodes))
+
+ # ---------- end nodes creation
+
+ # Export lut file
+ nuke.execute(
+ gen_lut_node.name(),
+ int(self.first_frame),
+ int(self.first_frame))
+
+ self.log.info("Exported...")
+
+ # ---------- generate representation data
+ self.get_representation_data()
+
+ self.log.debug("Representation... `{}`".format(self.data))
+
+ # ---------- Clean up
+ for node in self._temp_nodes:
+ nuke.delete(node)
+ self.log.info("Deleted nodes...")
+
+ return self.data
+
+ def get_file_info(self):
+ if self.collection:
+ self.log.debug("Collection: `{}`".format(self.collection))
+ # get path
+ self.fname = os.path.basename(self.collection.format(
+ "{head}{padding}{tail}"))
+ self.fhead = self.collection.format("{head}")
+
+ # get first and last frame
+ self.first_frame = min(self.collection.indexes)
+ self.last_frame = max(self.collection.indexes)
+ else:
+ self.fname = os.path.basename(self.instance.data.get("path", None))
+ self.fhead = os.path.splitext(self.fname)[0] + "."
+ self.first_frame = self.instance.data.get("frameStart", None)
+ self.last_frame = self.instance.data.get("frameEnd", None)
+
+ if "#" in self.fhead:
+ self.fhead = self.fhead.replace("#", "")[:-1]
+
+ def get_representation_data(self):
+
+ repre = {
+ 'name': self.name,
+ 'ext': self.ext,
+ 'files': self.file,
+ "stagingDir": self.stagingDir,
+ "anatomy_template": "publish",
+ "tags": [self.name.replace("_", "-")]
+ }
+
+ self.data["representations"].append(repre)
+
+ def get_view_process_node(self):
+ """
+ Will get any active view process.
+
+ Arguments:
+ self (class): in object definition
+
+ Returns:
+ nuke.Node: copy node of Input Process node
+ """
+ anlib.reset_selection()
+ ipn_orig = None
+ for v in [n for n in nuke.allNodes()
+ if "Viewer" in n.Class()]:
+ 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%')
+ # reset selection
+ anlib.reset_selection()
+ # paste node and selection is on it only
+ nuke.nodePaste('%clipboard%')
+ # assign to variable
+ ipn = nuke.selectedNode()
+
+ return ipn
+
def get_dependent_nodes(nodes):
"""Get all dependent nodes connected to the list of nodes.
diff --git a/pype/plugins/global/publish/collect_filesequences.py b/pype/plugins/global/publish/collect_filesequences.py
index 39481e216b..d0ff5722a3 100644
--- a/pype/plugins/global/publish/collect_filesequences.py
+++ b/pype/plugins/global/publish/collect_filesequences.py
@@ -100,6 +100,8 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin):
label = "RenderedFrames"
def process(self, context):
+ pixel_aspect = 1
+ lut_path = None
if os.environ.get("PYPE_PUBLISH_PATHS"):
paths = os.environ["PYPE_PUBLISH_PATHS"].split(os.pathsep)
self.log.info("Collecting paths: {}".format(paths))
@@ -144,6 +146,12 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin):
self.log.info("setting session using metadata")
api.Session.update(session)
os.environ.update(session)
+ instance = metadata.get("instance")
+ if instance:
+ instance_family = instance.get("family")
+ pixel_aspect = instance.get("pixelAspect", 1)
+ lut_path = instance.get("lutPath", None)
+
else:
# Search in directory
@@ -181,6 +189,8 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin):
families.append("ftrack")
if "review" not in families:
families.append("review")
+ if "write" in instance_family:
+ families.append("write")
for collection in collections:
instance = context.create_instance(str(collection))
@@ -197,6 +207,11 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin):
start = data.get("frameStart", indices[0])
end = data.get("frameEnd", indices[-1])
+ self.log.debug("Collected pixel_aspect:\n"
+ "{}".format(pixel_aspect))
+ self.log.debug("type pixel_aspect:\n"
+ "{}".format(type(pixel_aspect)))
+
# root = os.path.normpath(root)
# self.log.info("Source: {}}".format(data.get("source", "")))
@@ -212,8 +227,11 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin):
"frameStart": start,
"frameEnd": end,
"fps": fps,
- "source": data.get('source', '')
+ "source": data.get('source', ''),
+ "pixelAspect": pixel_aspect,
})
+ if lut_path:
+ instance.data.update({"lutPath": lut_path})
instance.append(collection)
instance.context.data['fps'] = fps
diff --git a/pype/plugins/global/publish/collect_templates.py b/pype/plugins/global/publish/collect_templates.py
index b80ca4ae1b..9b0c03fdee 100644
--- a/pype/plugins/global/publish/collect_templates.py
+++ b/pype/plugins/global/publish/collect_templates.py
@@ -85,3 +85,6 @@ class CollectTemplates(pyblish.api.InstancePlugin):
instance.data["assumedDestination"] = os.path.dirname(
(anatomy.format(template_data))["publish"]["path"]
)
+ self.log.info("Assumed Destination has been created...")
+ self.log.debug("__ assumedTemplateData: `{}`".format(instance.data["assumedTemplateData"]))
+ self.log.debug("__ template: `{}`".format(instance.data["template"]))
diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py
index bf4682b26e..786df95fc1 100644
--- a/pype/plugins/global/publish/extract_review.py
+++ b/pype/plugins/global/publish/extract_review.py
@@ -1,9 +1,8 @@
import os
-
+import math
import pyblish.api
import clique
import pype.api
-from pypeapp import config
class ExtractReview(pyblish.api.InstancePlugin):
@@ -22,16 +21,19 @@ class ExtractReview(pyblish.api.InstancePlugin):
families = ["review"]
hosts = ["nuke", "maya", "shell"]
+ outputs = {}
+ ext_filter = []
+
def process(self, instance):
- # adding plugin attributes from presets
- publish_presets = config.get_presets()["plugins"]["global"]["publish"]
- plugin_attrs = publish_presets[self.__class__.__name__]
- output_profiles = plugin_attrs.get("outputs", {})
+
+ output_profiles = self.outputs or {}
inst_data = instance.data
fps = inst_data.get("fps")
start_frame = inst_data.get("frameStart")
-
+ resolution_height = instance.data.get("resolutionHeight", 1080)
+ resolution_width = instance.data.get("resolutionWidth", 1920)
+ pixel_aspect = instance.data.get("pixelAspect", 1)
self.log.debug("Families In: `{}`".format(instance.data["families"]))
# get representation and loop them
@@ -40,7 +42,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
# filter out mov and img sequences
representations_new = representations[:]
for repre in representations:
- if repre['ext'] in plugin_attrs["ext_filter"]:
+ if repre['ext'] in self.ext_filter:
tags = repre.get("tags", [])
self.log.info("Try repre: {}".format(repre))
@@ -92,8 +94,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
self.log.info("p_tags: `{}`".format(p_tags))
# add families
[instance.data["families"].append(t)
- for t in p_tags
- if t not in instance.data["families"]]
+ for t in p_tags
+ if t not in instance.data["families"]]
+
# add to
[new_tags.append(t) for t in p_tags
if t not in new_tags]
@@ -147,14 +150,16 @@ class ExtractReview(pyblish.api.InstancePlugin):
)
output_args = []
- output_args.extend(profile.get('codec', []))
+ codec_args = profile.get('codec', [])
+ output_args.extend(codec_args)
# preset's output data
output_args.extend(profile.get('output', []))
# letter_box
- # TODO: add to documentation
- lb = profile.get('letter_box', None)
- if lb:
+ lb = profile.get('letter_box', 0)
+ if lb is not 0:
+ if "reformat" not in p_tags:
+ lb /= pixel_aspect
output_args.append(
"-filter:v drawbox=0:0:iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black,drawbox=0:ih-round((ih-(iw*(1/{0})))/2):iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black".format(lb))
@@ -163,6 +168,65 @@ class ExtractReview(pyblish.api.InstancePlugin):
# output filename
output_args.append(full_output_path)
+
+ self.log.debug("__ pixel_aspect: `{}`".format(pixel_aspect))
+ self.log.debug("__ resolution_width: `{}`".format(resolution_width))
+ self.log.debug("__ resolution_height: `{}`".format(resolution_height))
+ # scaling none square pixels and 1920 width
+ if "reformat" in p_tags:
+ width_scale = 1920
+ width_half_pad = 0
+ res_w = int(float(resolution_width) * pixel_aspect)
+ height_half_pad = int((
+ (res_w - 1920) / (
+ res_w * .01) * (
+ 1080 * .01)) / 2
+ )
+ height_scale = 1080 - (height_half_pad * 2)
+ if height_scale > 1080:
+ height_half_pad = 0
+ height_scale = 1080
+ width_half_pad = (1920 - (float(resolution_width) * (1080 / float(resolution_height))) ) / 2
+ width_scale = int(1920 - (width_half_pad * 2))
+
+ self.log.debug("__ width_scale: `{}`".format(width_scale))
+ self.log.debug("__ width_half_pad: `{}`".format(width_half_pad))
+ self.log.debug("__ height_scale: `{}`".format(height_scale))
+ self.log.debug("__ height_half_pad: `{}`".format(height_half_pad))
+
+
+ scaling_arg = "scale={0}x{1}:flags=lanczos,pad=1920:1080:{2}:{3}:black,setsar=1".format(
+ width_scale, height_scale, width_half_pad, height_half_pad
+ )
+
+ vf_back = self.add_video_filter_args(
+ output_args, scaling_arg)
+ # add it to output_args
+ output_args.insert(0, vf_back)
+
+ # baking lut file application
+ lut_path = instance.data.get("lutPath")
+ if lut_path and ("bake-lut" in p_tags):
+ # removing Gama info as it is all baked in lut
+ gamma = next((g for g in input_args
+ if "-gamma" in g), None)
+ if gamma:
+ input_args.remove(gamma)
+
+ # create lut argument
+ lut_arg = "lut3d=file='{}'".format(
+ lut_path.replace(
+ "\\", "/").replace(":/", "\\:/")
+ )
+ lut_arg += ",colormatrix=bt601:bt709"
+
+ vf_back = self.add_video_filter_args(
+ output_args, lut_arg)
+ # add it to output_args
+ output_args.insert(0, vf_back)
+ self.log.info("Added Lut to ffmpeg command")
+ self.log.debug("_ output_args: `{}`".format(output_args))
+
mov_args = [
os.path.join(
os.environ.get(
@@ -185,7 +249,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
'files': repr_file,
"tags": new_tags,
"outputName": name,
- "codec": profile.get('codec', [])
+ "codec": codec_args
})
if repre_new.get('preview'):
repre_new.pop("preview")
@@ -209,3 +273,40 @@ class ExtractReview(pyblish.api.InstancePlugin):
instance.data["representations"] = representations_new
self.log.debug("Families Out: `{}`".format(instance.data["families"]))
+
+ def add_video_filter_args(self, args, inserting_arg):
+ """
+ Fixing video filter argumets to be one long string
+
+ Args:
+ args (list): list of string arguments
+ inserting_arg (str): string argument we want to add
+ (without flag `-vf`)
+
+ Returns:
+ str: long joined argument to be added back to list of arguments
+
+ """
+ # find all video format settings
+ vf_settings = [p for p in args
+ for v in ["-filter:v", "-vf"]
+ if v in p]
+ self.log.debug("_ vf_settings: `{}`".format(vf_settings))
+
+ # remove them from output args list
+ for p in vf_settings:
+ self.log.debug("_ remove p: `{}`".format(p))
+ args.remove(p)
+ self.log.debug("_ args: `{}`".format(args))
+
+ # strip them from all flags
+ vf_fixed = [p.replace("-vf ", "").replace("-filter:v ", "")
+ for p in vf_settings]
+
+ self.log.debug("_ vf_fixed: `{}`".format(vf_fixed))
+ vf_fixed.insert(0, inserting_arg)
+ self.log.debug("_ vf_fixed: `{}`".format(vf_fixed))
+ # create new video filter setting
+ vf_back = "-vf " + ",".join(vf_fixed)
+
+ return vf_back
diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py
index 9021a3f997..c723631679 100644
--- a/pype/plugins/global/publish/integrate_new.py
+++ b/pype/plugins/global/publish/integrate_new.py
@@ -414,7 +414,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
}
if sequence_repre and repre.get("frameStart"):
- representation['context']['frame'] = src_padding_exp % repre.get("frameStart")
+ representation['context']['frame'] = src_padding_exp % int(repre.get("frameStart"))
self.log.debug("__ representation: {}".format(representation))
destination_list.append(dst)
diff --git a/pype/plugins/global/publish/validate_ffmpeg_installed.py b/pype/plugins/global/publish/validate_ffmpeg_installed.py
index 6d5ffba1e1..df7c330e95 100644
--- a/pype/plugins/global/publish/validate_ffmpeg_installed.py
+++ b/pype/plugins/global/publish/validate_ffmpeg_installed.py
@@ -27,6 +27,8 @@ class ValidateFfmpegInstallef(pyblish.api.Validator):
return True
def process(self, instance):
+ self.log.info("ffmpeg path: `{}`".format(
+ os.environ.get("FFMPEG_PATH", "")))
if self.is_tool(
os.path.join(
os.environ.get("FFMPEG_PATH", ""), "ffmpeg")) is False:
diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py
index fbff28b282..534ef2680a 100644
--- a/pype/plugins/nuke/publish/collect_instances.py
+++ b/pype/plugins/nuke/publish/collect_instances.py
@@ -22,6 +22,8 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
self.log.debug("asset_data: {}".format(asset_data["data"]))
instances = []
+ root = nuke.root()
+
self.log.debug("nuke.allNodes(): {}".format(nuke.allNodes()))
for node in nuke.allNodes():
@@ -34,7 +36,7 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
# get data from avalon knob
self.log.debug("node[name]: {}".format(node['name'].value()))
- avalon_knob_data = get_avalon_knob_data(node)
+ avalon_knob_data = get_avalon_knob_data(node, ["avalon:", "ak:"])
self.log.debug("avalon_knob_data: {}".format(avalon_knob_data))
@@ -86,6 +88,12 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
family = avalon_knob_data["family"]
families = [avalon_knob_data["families"]]
+ # Get format
+ format = root['format'].value()
+ resolution_width = format.width()
+ resolution_height = format.height()
+ pixel_aspect = format.pixelAspect()
+
if node.Class() not in "Read":
if node["render"].value():
self.log.info("flagged for render")
@@ -111,7 +119,10 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
"avalonKnob": avalon_knob_data,
"publish": node.knob('publish').value(),
"step": 1,
- "fps": nuke.root()['fps'].value()
+ "fps": nuke.root()['fps'].value(),
+ "resolutionWidth": resolution_width,
+ "resolutionHeight": resolution_height,
+ "pixelAspect": pixel_aspect,
})
@@ -119,5 +130,4 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
instances.append(instance)
context.data["instances"] = instances
-
self.log.debug("context: {}".format(context))
diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py
index ba8a0534b1..c9c516c888 100644
--- a/pype/plugins/nuke/publish/collect_writes.py
+++ b/pype/plugins/nuke/publish/collect_writes.py
@@ -76,7 +76,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
}
try:
- collected_frames = os.listdir(output_dir)
+ collected_frames = [f for f in os.listdir(output_dir)
+ if ext in f]
if collected_frames:
representation['frameStart'] = "%0{}d".format(
len(str(last_frame))) % first_frame
@@ -99,7 +100,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
"subset": instance.data["subset"],
"fps": instance.context.data["fps"]
}
-
+ instance.data["family"] = "write"
group_node = [x for x in instance if x.Class() == "Group"][0]
deadlineChunkSize = 1
if "deadlineChunkSize" in group_node.knobs():
diff --git a/pype/plugins/nuke/publish/extract_review_data.py b/pype/plugins/nuke/publish/extract_review_data.py
deleted file mode 100644
index 791b9d7969..0000000000
--- a/pype/plugins/nuke/publish/extract_review_data.py
+++ /dev/null
@@ -1,187 +0,0 @@
-import os
-import nuke
-import pyblish.api
-import pype
-
-class ExtractReviewData(pype.api.Extractor):
- """Extracts movie and thumbnail with baked in luts
-
- must be run after extract_render_local.py
-
- """
-
- order = pyblish.api.ExtractorOrder + 0.01
- label = "Extract Review Data"
-
- families = ["review"]
- hosts = ["nuke"]
-
- def process(self, instance):
-
- # Store selection
- selection = [i for i in nuke.allNodes() if i["selected"].getValue()]
- # Deselect all nodes to prevent external connections
- [i["selected"].setValue(False) for i in nuke.allNodes()]
- self.log.debug("creating staging dir:")
- self.staging_dir(instance)
-
- self.log.debug("instance: {}".format(instance))
- self.log.debug("instance.data[families]: {}".format(
- instance.data["families"]))
-
- if "still" not in instance.data["families"]:
- self.render_review_representation(instance,
- representation="mov")
- self.render_review_representation(instance,
- representation="jpeg")
- else:
- self.render_review_representation(instance, representation="jpeg")
-
- # Restore selection
- [i["selected"].setValue(False) for i in nuke.allNodes()]
- [i["selected"].setValue(True) for i in selection]
-
- def render_review_representation(self,
- instance,
- representation="mov"):
-
- assert instance.data['representations'][0]['files'], "Instance data files should't be empty!"
-
- temporary_nodes = []
- stagingDir = instance.data[
- 'representations'][0]["stagingDir"].replace("\\", "/")
- self.log.debug("StagingDir `{0}`...".format(stagingDir))
-
- collection = instance.data.get("collection", None)
-
- if collection:
- # get path
- fname = os.path.basename(collection.format(
- "{head}{padding}{tail}"))
- fhead = collection.format("{head}")
-
- # get first and last frame
- first_frame = min(collection.indexes)
- last_frame = max(collection.indexes)
- else:
- fname = os.path.basename(instance.data.get("path", None))
- fhead = os.path.splitext(fname)[0] + "."
- first_frame = instance.data.get("frameStart", None)
- last_frame = instance.data.get("frameEnd", None)
-
- rnode = nuke.createNode("Read")
-
- rnode["file"].setValue(
- os.path.join(stagingDir, fname).replace("\\", "/"))
-
- rnode["first"].setValue(first_frame)
- rnode["origfirst"].setValue(first_frame)
- rnode["last"].setValue(last_frame)
- rnode["origlast"].setValue(last_frame)
- temporary_nodes.append(rnode)
- previous_node = rnode
-
- # get input process and connect it to baking
- ipn = self.get_view_process_node()
- if ipn is not None:
- ipn.setInput(0, previous_node)
- previous_node = ipn
- temporary_nodes.append(ipn)
-
- reformat_node = nuke.createNode("Reformat")
-
- ref_node = self.nodes.get("Reformat", None)
- if ref_node:
- for k, v in ref_node:
- self.log.debug("k,v: {0}:{1}".format(k,v))
- if isinstance(v, unicode):
- v = str(v)
- reformat_node[k].setValue(v)
-
- reformat_node.setInput(0, previous_node)
- previous_node = reformat_node
- temporary_nodes.append(reformat_node)
-
- dag_node = nuke.createNode("OCIODisplay")
- dag_node.setInput(0, previous_node)
- previous_node = dag_node
- temporary_nodes.append(dag_node)
-
- # create write node
- write_node = nuke.createNode("Write")
-
- if representation in "mov":
- file = fhead + "baked.mov"
- name = "baked"
- path = os.path.join(stagingDir, file).replace("\\", "/")
- self.log.debug("Path: {}".format(path))
- instance.data["baked_colorspace_movie"] = path
- write_node["file"].setValue(path)
- write_node["file_type"].setValue("mov")
- write_node["raw"].setValue(1)
- write_node.setInput(0, previous_node)
- temporary_nodes.append(write_node)
- tags = ["review", "delete"]
-
- elif representation in "jpeg":
- file = fhead + "jpeg"
- name = "thumbnail"
- path = os.path.join(stagingDir, file).replace("\\", "/")
- instance.data["thumbnail"] = path
- write_node["file"].setValue(path)
- write_node["file_type"].setValue("jpeg")
- write_node["raw"].setValue(1)
- write_node.setInput(0, previous_node)
- temporary_nodes.append(write_node)
- tags = ["thumbnail"]
-
- # retime for
- first_frame = int(last_frame) / 2
- last_frame = int(last_frame) / 2
-
- repre = {
- 'name': name,
- 'ext': representation,
- 'files': file,
- "stagingDir": stagingDir,
- "frameStart": first_frame,
- "frameEnd": last_frame,
- "anatomy_template": "render",
- "tags": tags
- }
- instance.data["representations"].append(repre)
-
- # Render frames
- nuke.execute(write_node.name(), int(first_frame), int(last_frame))
-
- self.log.debug("representations: {}".format(instance.data["representations"]))
-
- # Clean up
- for node in temporary_nodes:
- nuke.delete(node)
-
- def get_view_process_node(self):
-
- # Select only the target node
- if nuke.selectedNodes():
- [n.setSelected(False) for n in nuke.selectedNodes()]
-
- ipn_orig = None
- for v in [n for n in nuke.allNodes()
- if "Viewer" in n.Class()]:
- 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:
- nuke.nodeCopy('%clipboard%')
-
- [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all
-
- nuke.nodePaste('%clipboard%')
-
- ipn = nuke.selectedNode()
-
- return ipn
diff --git a/pype/plugins/nuke/publish/extract_review_data_lut.py b/pype/plugins/nuke/publish/extract_review_data_lut.py
new file mode 100644
index 0000000000..dfc10952cd
--- /dev/null
+++ b/pype/plugins/nuke/publish/extract_review_data_lut.py
@@ -0,0 +1,58 @@
+import os
+import pyblish.api
+from avalon.nuke import lib as anlib
+from pype.nuke import lib as pnlib
+import pype
+reload(pnlib)
+
+
+class ExtractReviewLutData(pype.api.Extractor):
+ """Extracts movie and thumbnail with baked in luts
+
+ must be run after extract_render_local.py
+
+ """
+
+ order = pyblish.api.ExtractorOrder + 0.005
+ label = "Extract Review Data Lut"
+
+ families = ["review"]
+ hosts = ["nuke"]
+
+ def process(self, instance):
+ families = instance.data["families"]
+ self.log.info("Creating staging dir...")
+ if "representations" in instance.data:
+ staging_dir = instance.data[
+ "representations"][0]["stagingDir"].replace("\\", "/")
+ instance.data["stagingDir"] = staging_dir
+ instance.data["representations"][0]["tags"] = ["review"]
+ else:
+ instance.data["representations"] = []
+ # get output path
+ render_path = instance.data['path']
+ staging_dir = os.path.normpath(os.path.dirname(render_path))
+ instance.data["stagingDir"] = staging_dir
+
+ self.log.info(
+ "StagingDir `{0}`...".format(instance.data["stagingDir"]))
+
+ with anlib.maintained_selection():
+ exporter = pnlib.Exporter_review_lut(
+ self, instance
+ )
+ data = exporter.generate_lut()
+
+ # assign to representations
+ instance.data["lutPath"] = os.path.join(
+ exporter.stagingDir, exporter.file).replace("\\", "/")
+ instance.data["representations"] += data["representations"]
+
+ if "render.farm" in families:
+ instance.data["families"].remove("review")
+ instance.data["families"].remove("ftrack")
+
+ self.log.debug(
+ "_ lutPath: {}".format(instance.data["lutPath"]))
+ self.log.debug(
+ "_ representations: {}".format(instance.data["representations"]))
diff --git a/pype/plugins/nuke/publish/extract_thumbnail.py b/pype/plugins/nuke/publish/extract_thumbnail.py
new file mode 100644
index 0000000000..450bb39928
--- /dev/null
+++ b/pype/plugins/nuke/publish/extract_thumbnail.py
@@ -0,0 +1,174 @@
+import os
+import nuke
+from avalon.nuke import lib as anlib
+import pyblish.api
+import pype
+
+
+class ExtractThumbnail(pype.api.Extractor):
+ """Extracts movie and thumbnail with baked in luts
+
+ must be run after extract_render_local.py
+
+ """
+
+ order = pyblish.api.ExtractorOrder + 0.01
+ label = "Extract Thumbnail"
+
+ families = ["review", "render.farm"]
+ hosts = ["nuke"]
+
+ def process(self, instance):
+
+ with anlib.maintained_selection():
+ self.log.debug("instance: {}".format(instance))
+ self.log.debug("instance.data[families]: {}".format(
+ instance.data["families"]))
+
+ self.render_thumbnail(instance)
+
+ def render_thumbnail(self, instance):
+ node = instance[0] # group node
+ self.log.info("Creating staging dir...")
+ if "representations" in instance.data:
+ staging_dir = instance.data[
+ "representations"][0]["stagingDir"].replace("\\", "/")
+ instance.data["stagingDir"] = staging_dir
+ instance.data["representations"][0]["tags"] = ["review"]
+ else:
+ instance.data["representations"] = []
+ # get output path
+ render_path = instance.data['path']
+ staging_dir = os.path.normpath(os.path.dirname(render_path))
+ instance.data["stagingDir"] = staging_dir
+
+ self.log.info(
+ "StagingDir `{0}`...".format(instance.data["stagingDir"]))
+
+ temporary_nodes = []
+ collection = instance.data.get("collection", None)
+
+ if collection:
+ # get path
+ fname = os.path.basename(collection.format(
+ "{head}{padding}{tail}"))
+ fhead = collection.format("{head}")
+
+ # get first and last frame
+ first_frame = min(collection.indexes)
+ last_frame = max(collection.indexes)
+ else:
+ fname = os.path.basename(instance.data.get("path", None))
+ fhead = os.path.splitext(fname)[0] + "."
+ first_frame = instance.data.get("frameStart", None)
+ last_frame = instance.data.get("frameEnd", None)
+
+ if "#" in fhead:
+ fhead = fhead.replace("#", "")[:-1]
+
+ path_render = os.path.join(staging_dir, fname).replace("\\", "/")
+ # check if file exist otherwise connect to write node
+ if os.path.isfile(path_render):
+ rnode = nuke.createNode("Read")
+
+ rnode["file"].setValue(path_render)
+
+ rnode["first"].setValue(first_frame)
+ rnode["origfirst"].setValue(first_frame)
+ rnode["last"].setValue(last_frame)
+ rnode["origlast"].setValue(last_frame)
+ temporary_nodes.append(rnode)
+ previous_node = rnode
+ else:
+ previous_node = node
+
+ # get input process and connect it to baking
+ ipn = self.get_view_process_node()
+ if ipn is not None:
+ ipn.setInput(0, previous_node)
+ previous_node = ipn
+ temporary_nodes.append(ipn)
+
+ reformat_node = nuke.createNode("Reformat")
+
+ ref_node = self.nodes.get("Reformat", None)
+ if ref_node:
+ for k, v in ref_node:
+ self.log.debug("k, v: {0}:{1}".format(k, v))
+ if isinstance(v, unicode):
+ v = str(v)
+ reformat_node[k].setValue(v)
+
+ reformat_node.setInput(0, previous_node)
+ previous_node = reformat_node
+ temporary_nodes.append(reformat_node)
+
+ dag_node = nuke.createNode("OCIODisplay")
+ dag_node.setInput(0, previous_node)
+ previous_node = dag_node
+ temporary_nodes.append(dag_node)
+
+ # create write node
+ write_node = nuke.createNode("Write")
+ file = fhead + "jpeg"
+ name = "thumbnail"
+ path = os.path.join(staging_dir, file).replace("\\", "/")
+ instance.data["thumbnail"] = path
+ write_node["file"].setValue(path)
+ write_node["file_type"].setValue("jpeg")
+ write_node["raw"].setValue(1)
+ write_node.setInput(0, previous_node)
+ temporary_nodes.append(write_node)
+ tags = ["thumbnail"]
+
+ # retime for
+ first_frame = int(last_frame) / 2
+ last_frame = int(last_frame) / 2
+
+ repre = {
+ 'name': name,
+ 'ext': "jpeg",
+ 'files': file,
+ "stagingDir": staging_dir,
+ "frameStart": first_frame,
+ "frameEnd": last_frame,
+ "anatomy_template": "render",
+ "tags": tags
+ }
+ instance.data["representations"].append(repre)
+
+ # Render frames
+ nuke.execute(write_node.name(), int(first_frame), int(last_frame))
+
+ self.log.debug(
+ "representations: {}".format(instance.data["representations"]))
+
+ # Clean up
+ for node in temporary_nodes:
+ nuke.delete(node)
+
+ def get_view_process_node(self):
+
+ # Select only the target node
+ if nuke.selectedNodes():
+ [n.setSelected(False) for n in nuke.selectedNodes()]
+
+ ipn_orig = None
+ for v in [n for n in nuke.allNodes()
+ if "Viewer" in n.Class()]:
+ 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:
+ nuke.nodeCopy('%clipboard%')
+
+ [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all
+
+ nuke.nodePaste('%clipboard%')
+
+ ipn = nuke.selectedNode()
+
+ return ipn
diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py
index 4044026b5e..d9207d2bfc 100644
--- a/pype/plugins/nuke/publish/submit_nuke_deadline.py
+++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py
@@ -1,9 +1,7 @@
import os
import json
import getpass
-
-import nuke
-
+
from avalon import api
from avalon.vendor import requests
import re
@@ -27,40 +25,36 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
def process(self, instance):
- node = None
- for x in instance:
- if x.Class() == "Write":
- node = x
-
- if node is None:
- return
+ node = instance[0]
+ # for x in instance:
+ # if x.Class() == "Write":
+ # node = x
+ #
+ # if node is None:
+ # return
DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL",
"http://localhost:8082")
assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL"
context = instance.context
- workspace = os.path.dirname(context.data["currentFile"])
- filepath = None
- # get path
- path = nuke.filename(node)
- output_dir = instance.data['outputDir']
+ # get output path
+ render_path = instance.data['path']
+ render_dir = os.path.normpath(os.path.dirname(render_path))
- filepath = context.data["currentFile"]
+ script_path = context.data["currentFile"]
- self.log.debug(filepath)
-
- filename = os.path.basename(filepath)
+ script_name = os.path.basename(script_path)
comment = context.data.get("comment", "")
- dirname = os.path.join(workspace, "renders")
+
deadline_user = context.data.get("deadlineUser", getpass.getuser())
- jobname = "%s - %s" % (filename, instance.name)
+ jobname = "%s - %s" % (script_name, instance.name)
ver = re.search(r"\d+\.\d+", context.data.get("hostVersion"))
try:
# Ensure render folder exists
- os.makedirs(dirname)
+ os.makedirs(render_dir)
except OSError:
pass
@@ -71,7 +65,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
payload = {
"JobInfo": {
# Top-level group name
- "BatchName": filename,
+ "BatchName": script_name,
# Job name, as seen in Monitor
"Name": jobname,
@@ -95,20 +89,20 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
},
"PluginInfo": {
# Input
- "SceneFile": filepath,
+ "SceneFile": script_path,
# Output directory and filename
- "OutputFilePath": dirname.replace("\\", "/"),
+ "OutputFilePath": render_dir.replace("\\", "/"),
# "OutputFilePrefix": render_variables["filename_prefix"],
# Mandatory for Deadline
"Version": ver.group(),
# Resolve relative references
- "ProjectPath": workspace,
-
+ "ProjectPath": script_path,
+ "AWSAssetFile0": render_path,
# Only the specific write node is rendered.
- "WriteNode": instance[0].name()
+ "WriteNode": node.name()
},
# Mandatory for Deadline, may be empty
diff --git a/pype/plugins/nuke/publish/validate_rendered_frames.py b/pype/plugins/nuke/publish/validate_rendered_frames.py
index e244a9b4b6..3887b5d5b7 100644
--- a/pype/plugins/nuke/publish/validate_rendered_frames.py
+++ b/pype/plugins/nuke/publish/validate_rendered_frames.py
@@ -28,7 +28,7 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder + 0.1
- families = ["render.no"]
+ families = ["render"]
label = "Validate rendered frame"
hosts = ["nuke", "nukestudio"]
diff --git a/pype/services/idle_manager/idle_manager.py b/pype/services/idle_manager/idle_manager.py
index 64cafcd193..0897245049 100644
--- a/pype/services/idle_manager/idle_manager.py
+++ b/pype/services/idle_manager/idle_manager.py
@@ -1,6 +1,6 @@
import time
import collections
-from Qt import QtCore, QtGui, QtWidgets
+from Qt import QtCore
from pynput import mouse, keyboard
from pypeapp import Logger
@@ -29,6 +29,13 @@ class IdleManager(QtCore.QThread):
def tray_start(self):
self.start()
+ def tray_exit(self):
+ self.stop()
+ try:
+ self.time_signals = {}
+ except Exception:
+ pass
+
def add_time_signal(self, emit_time, signal):
""" If any module want to use IdleManager, need to use add_time_signal
:param emit_time: time when signal will be emitted
diff --git a/pype/standalonepublish/resources/menu.png b/pype/standalonepublish/resources/menu.png
new file mode 100644
index 0000000000..da83b45244
Binary files /dev/null and b/pype/standalonepublish/resources/menu.png differ
diff --git a/pype/standalonepublish/resources/menu.svg b/pype/standalonepublish/resources/menu.svg
deleted file mode 100644
index ac1e728011..0000000000
--- a/pype/standalonepublish/resources/menu.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/pype/standalonepublish/resources/menu_disabled.png b/pype/standalonepublish/resources/menu_disabled.png
new file mode 100644
index 0000000000..e4758f0b19
Binary files /dev/null and b/pype/standalonepublish/resources/menu_disabled.png differ
diff --git a/pype/standalonepublish/resources/menu_hover.png b/pype/standalonepublish/resources/menu_hover.png
new file mode 100644
index 0000000000..dfe8ed53b2
Binary files /dev/null and b/pype/standalonepublish/resources/menu_hover.png differ
diff --git a/pype/standalonepublish/resources/menu_pressed.png b/pype/standalonepublish/resources/menu_pressed.png
new file mode 100644
index 0000000000..a5f931b2c4
Binary files /dev/null and b/pype/standalonepublish/resources/menu_pressed.png differ
diff --git a/pype/standalonepublish/resources/menu_pressed_hover.png b/pype/standalonepublish/resources/menu_pressed_hover.png
new file mode 100644
index 0000000000..51503add0f
Binary files /dev/null and b/pype/standalonepublish/resources/menu_pressed_hover.png differ
diff --git a/pype/standalonepublish/resources/trash.png b/pype/standalonepublish/resources/trash.png
new file mode 100644
index 0000000000..8d12d5f8e0
Binary files /dev/null and b/pype/standalonepublish/resources/trash.png differ
diff --git a/pype/standalonepublish/resources/trash.svg b/pype/standalonepublish/resources/trash.svg
deleted file mode 100644
index 07905024c0..0000000000
--- a/pype/standalonepublish/resources/trash.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
diff --git a/pype/standalonepublish/resources/trash_disabled.png b/pype/standalonepublish/resources/trash_disabled.png
new file mode 100644
index 0000000000..06f5ae5276
Binary files /dev/null and b/pype/standalonepublish/resources/trash_disabled.png differ
diff --git a/pype/standalonepublish/resources/trash_hover.png b/pype/standalonepublish/resources/trash_hover.png
new file mode 100644
index 0000000000..4725c0f8ab
Binary files /dev/null and b/pype/standalonepublish/resources/trash_hover.png differ
diff --git a/pype/standalonepublish/resources/trash_pressed.png b/pype/standalonepublish/resources/trash_pressed.png
new file mode 100644
index 0000000000..901b0e6d35
Binary files /dev/null and b/pype/standalonepublish/resources/trash_pressed.png differ
diff --git a/pype/standalonepublish/resources/trash_pressed_hover.png b/pype/standalonepublish/resources/trash_pressed_hover.png
new file mode 100644
index 0000000000..076ced260f
Binary files /dev/null and b/pype/standalonepublish/resources/trash_pressed_hover.png differ
diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py
index 0fd72cc70e..6275238412 100644
--- a/pype/standalonepublish/widgets/widget_component_item.py
+++ b/pype/standalonepublish/widgets/widget_component_item.py
@@ -1,15 +1,10 @@
import os
from . import QtCore, QtGui, QtWidgets
-from . import SvgButton
from . import get_resource
from pypeapp import style
class ComponentItem(QtWidgets.QFrame):
- C_NORMAL = '#777777'
- C_HOVER = '#ffffff'
- C_ACTIVE = '#4BB543'
- C_ACTIVE_HOVER = '#4BF543'
signal_remove = QtCore.Signal(object)
signal_thumbnail = QtCore.Signal(object)
@@ -58,10 +53,8 @@ class ComponentItem(QtWidgets.QFrame):
self.icon.setText("")
self.icon.setScaledContents(True)
- self.btn_action_menu = SvgButton(
- get_resource('menu.svg'), 22, 22,
- [self.C_NORMAL, self.C_HOVER],
- frame_image_info, False
+ self.btn_action_menu = PngButton(
+ name="menu", size=QtCore.QSize(22, 22)
)
self.action_menu = QtWidgets.QMenu()
@@ -88,7 +81,9 @@ class ComponentItem(QtWidgets.QFrame):
self.file_info.setStyleSheet('padding-left:3px;')
- expanding_sizePolicy.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth())
+ expanding_sizePolicy.setHeightForWidth(
+ self.name.sizePolicy().hasHeightForWidth()
+ )
frame_name_repre = QtWidgets.QFrame(frame)
@@ -104,7 +99,8 @@ class ComponentItem(QtWidgets.QFrame):
layout.addWidget(self.ext, alignment=QtCore.Qt.AlignRight)
frame_name_repre.setSizePolicy(
- QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding
+ QtWidgets.QSizePolicy.MinimumExpanding,
+ QtWidgets.QSizePolicy.MinimumExpanding
)
# Repre + icons
@@ -156,12 +152,7 @@ class ComponentItem(QtWidgets.QFrame):
layout_main.addWidget(frame_middle)
- self.remove = SvgButton(
- get_resource('trash.svg'), 22, 22,
- [self.C_NORMAL, self.C_HOVER],
- frame, False
- )
-
+ self.remove = PngButton(name="trash", size=QtCore.QSize(22, 22))
layout_main.addWidget(self.remove)
layout = QtWidgets.QVBoxLayout(self)
@@ -360,3 +351,172 @@ class LightingButton(QtWidgets.QPushButton):
})
self.setCheckable(True)
+
+class PngFactory:
+ png_names = {
+ "trash": {
+ "normal": QtGui.QIcon(get_resource("trash.png")),
+ "hover": QtGui.QIcon(get_resource("trash_hover.png")),
+ "pressed": QtGui.QIcon(get_resource("trash_pressed.png")),
+ "pressed_hover": QtGui.QIcon(
+ get_resource("trash_pressed_hover.png")
+ ),
+ "disabled": QtGui.QIcon(get_resource("trash_disabled.png"))
+ },
+
+ "menu": {
+ "normal": QtGui.QIcon(get_resource("menu.png")),
+ "hover": QtGui.QIcon(get_resource("menu_hover.png")),
+ "pressed": QtGui.QIcon(get_resource("menu_pressed.png")),
+ "pressed_hover": QtGui.QIcon(
+ get_resource("menu_pressed_hover.png")
+ ),
+ "disabled": QtGui.QIcon(get_resource("menu_disabled.png"))
+ }
+ }
+
+
+class PngButton(QtWidgets.QPushButton):
+ png_button_style = """
+ QPushButton {
+ border: none;
+ background-color: transparent;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ }
+ QPushButton:hover {}
+ QPushButton:pressed {}
+ QPushButton:disabled {}
+ QPushButton:checked {}
+ QPushButton:checked:hover {}
+ QPushButton:checked:pressed {}
+ """
+
+ def __init__(
+ self, name=None, path=None, hover_path=None, pressed_path=None,
+ hover_pressed_path=None, disabled_path=None,
+ size=None, *args, **kwargs
+ ):
+ self._hovered = False
+ self._pressed = False
+ super(PngButton, self).__init__(*args, **kwargs)
+ self.setStyleSheet(self.png_button_style)
+
+ png_dict = {}
+ if name:
+ png_dict = PngFactory.png_names.get(name) or {}
+ if not png_dict:
+ print((
+ "WARNING: There is not set icon with name \"{}\""
+ "in PngFactory!"
+ ).format(name))
+
+ ico_normal = png_dict.get("normal")
+ ico_hover = png_dict.get("hover")
+ ico_pressed = png_dict.get("pressed")
+ ico_hover_pressed = png_dict.get("pressed_hover")
+ ico_disabled = png_dict.get("disabled")
+
+ if path:
+ ico_normal = QtGui.QIcon(path)
+ if hover_path:
+ ico_hover = QtGui.QIcon(hover_path)
+
+ if pressed_path:
+ ico_pressed = QtGui.QIcon(hover_path)
+
+ if hover_pressed_path:
+ ico_hover_pressed = QtGui.QIcon(hover_pressed_path)
+
+ if disabled_path:
+ ico_disabled = QtGui.QIcon(disabled_path)
+
+ self.setIcon(ico_normal)
+ if size:
+ self.setIconSize(size)
+ self.setMaximumSize(size)
+
+ self.ico_normal = ico_normal
+ self.ico_hover = ico_hover
+ self.ico_pressed = ico_pressed
+ self.ico_hover_pressed = ico_hover_pressed
+ self.ico_disabled = ico_disabled
+
+ def setDisabled(self, in_bool):
+ super(PngButton, self).setDisabled(in_bool)
+ icon = self.ico_normal
+ if in_bool and self.ico_disabled:
+ icon = self.ico_disabled
+ self.setIcon(icon)
+
+ def enterEvent(self, event):
+ self._hovered = True
+ if not self.isEnabled():
+ return
+ icon = self.ico_normal
+ if self.ico_hover:
+ icon = self.ico_hover
+
+ if self._pressed and self.ico_hover_pressed:
+ icon = self.ico_hover_pressed
+
+ if self.icon() != icon:
+ self.setIcon(icon)
+
+ def mouseMoveEvent(self, event):
+ super(PngButton, self).mouseMoveEvent(event)
+ if self._pressed:
+ mouse_pos = event.pos()
+ hovering = self.rect().contains(mouse_pos)
+ if hovering and not self._hovered:
+ self.enterEvent(event)
+ elif not hovering and self._hovered:
+ self.leaveEvent(event)
+
+ def leaveEvent(self, event):
+ self._hovered = False
+ if not self.isEnabled():
+ return
+ icon = self.ico_normal
+ if self._pressed and self.ico_pressed:
+ icon = self.ico_pressed
+
+ if self.icon() != icon:
+ self.setIcon(icon)
+
+ def mousePressEvent(self, event):
+ self._pressed = True
+ if not self.isEnabled():
+ return
+ icon = self.ico_hover
+ if self.ico_pressed:
+ icon = self.ico_pressed
+
+ if self.ico_hover_pressed:
+ mouse_pos = event.pos()
+ if self.rect().contains(mouse_pos):
+ icon = self.ico_hover_pressed
+
+ if icon is None:
+ icon = self.ico_normal
+
+ if self.icon() != icon:
+ self.setIcon(icon)
+
+ def mouseReleaseEvent(self, event):
+ if not self.isEnabled():
+ return
+ if self._pressed:
+ self._pressed = False
+ mouse_pos = event.pos()
+ if self.rect().contains(mouse_pos):
+ self.clicked.emit()
+
+ icon = self.ico_normal
+ if self._hovered and self.ico_hover:
+ icon = self.ico_hover
+
+ if self.icon() != icon:
+ self.setIcon(icon)