mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge branch 'develop' into enhancement/exception-for-artist-error
This commit is contained in:
commit
7a6484ea83
17 changed files with 406 additions and 188 deletions
|
|
@ -94,4 +94,4 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
task_entity = get_task_by_name(
|
||||
project_name, folder_entity["id"], task_name
|
||||
)
|
||||
self.data["task_entity"] = task_entity
|
||||
self.data["task_entity"] = task_entity
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ class FormattingPart:
|
|||
# ensure key is properly formed [({})] properly closed.
|
||||
if not self.validate_key_is_matched(key):
|
||||
result.add_missing_key(key)
|
||||
result.add_output(self.template)
|
||||
result.add_output(self.template)
|
||||
return result
|
||||
|
||||
# check if key expects subdictionary keys (e.g. project[name])
|
||||
|
|
|
|||
|
|
@ -1519,9 +1519,10 @@ class PlaceholderLoadMixin(object):
|
|||
if "asset" in placeholder.data:
|
||||
return []
|
||||
|
||||
representation_name = placeholder.data["representation"]
|
||||
if not representation_name:
|
||||
return []
|
||||
representation_names = None
|
||||
representation_name: str = placeholder.data["representation"]
|
||||
if representation_name:
|
||||
representation_names = [representation_name]
|
||||
|
||||
project_name = self.builder.project_name
|
||||
current_folder_entity = self.builder.current_folder_entity
|
||||
|
|
@ -1578,7 +1579,7 @@ class PlaceholderLoadMixin(object):
|
|||
)
|
||||
return list(get_representations(
|
||||
project_name,
|
||||
representation_names={representation_name},
|
||||
representation_names=representation_names,
|
||||
version_ids=version_ids
|
||||
))
|
||||
|
||||
|
|
|
|||
|
|
@ -217,9 +217,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
joined_paths = ", ".join(
|
||||
["\"{}\"".format(path) for path in not_found_task_paths]
|
||||
)
|
||||
self.log.warning((
|
||||
"Not found task entities with paths \"{}\"."
|
||||
).format(joined_paths))
|
||||
self.log.warning(
|
||||
f"Not found task entities with paths {joined_paths}.")
|
||||
|
||||
def fill_latest_versions(self, context, project_name):
|
||||
"""Try to find latest version for each instance's product name.
|
||||
|
|
@ -321,7 +320,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
use_context_version = instance.data["followWorkfileVersion"]
|
||||
|
||||
if use_context_version:
|
||||
version_number = context.data("version")
|
||||
version_number = context.data.get("version")
|
||||
|
||||
# Even if 'follow_workfile_version' is enabled, it may not be set
|
||||
# because workfile version was not collected to 'context.data'
|
||||
|
|
|
|||
|
|
@ -113,4 +113,4 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
"Task '{}' was not found in project '{}'.".format(
|
||||
task_path, project_name)
|
||||
)
|
||||
return task_entity
|
||||
return task_entity
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ class CollectSceneVersion(pyblish.api.ContextPlugin):
|
|||
return
|
||||
|
||||
if not context.data.get('currentFile'):
|
||||
raise KnownPublishError("Cannot get current workfile path. "
|
||||
"Make sure your scene is saved.")
|
||||
self.log.error("Cannot get current workfile path. "
|
||||
"Make sure your scene is saved.")
|
||||
return
|
||||
|
||||
filename = os.path.basename(context.data.get('currentFile'))
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
hosts = ["resolve", "hiero", "flame"]
|
||||
|
||||
# plugin default attributes
|
||||
temp_file_head = "tempFile."
|
||||
to_width = 1280
|
||||
to_height = 720
|
||||
output_ext = ".jpg"
|
||||
|
|
@ -62,6 +61,9 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
make_sequence_collection
|
||||
)
|
||||
|
||||
# TODO refactore from using instance variable
|
||||
self.temp_file_head = self._get_folder_name_based_prefix(instance)
|
||||
|
||||
# TODO: convert resulting image sequence to mp4
|
||||
|
||||
# get otio clip and other time info from instance clip
|
||||
|
|
@ -491,3 +493,21 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
out_frame_start = self.used_frames[-1]
|
||||
|
||||
return output_path, out_frame_start
|
||||
|
||||
def _get_folder_name_based_prefix(self, instance):
|
||||
"""Creates 'unique' human readable file prefix to differentiate.
|
||||
|
||||
Multiple instances might share same temp folder, but each instance
|
||||
would be differentiated by asset, eg. folder name.
|
||||
|
||||
It ix expected that there won't be multiple instances for same asset.
|
||||
"""
|
||||
folder_path = instance.data["folderPath"]
|
||||
folder_name = folder_path.split("/")[-1]
|
||||
folder_path = folder_path.replace("/", "_").lstrip("_")
|
||||
|
||||
file_prefix = f"{folder_path}_{folder_name}."
|
||||
self.log.debug(f"file_prefix::{file_prefix}")
|
||||
|
||||
return file_prefix
|
||||
|
||||
|
|
|
|||
|
|
@ -1900,7 +1900,7 @@ class OverscanCrop:
|
|||
string_value = re.sub(r"([ ]+)?px", " ", string_value)
|
||||
string_value = re.sub(r"([ ]+)%", "%", string_value)
|
||||
# Make sure +/- sign at the beginning of string is next to number
|
||||
string_value = re.sub(r"^([\+\-])[ ]+", "\g<1>", string_value)
|
||||
string_value = re.sub(r"^([\+\-])[ ]+", r"\g<1>", string_value)
|
||||
# Make sure +/- sign in the middle has zero spaces before number under
|
||||
# which belongs
|
||||
string_value = re.sub(
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ def get_representation_path_in_publish_context(
|
|||
|
||||
Allow resolving 'latest' paths from a publishing context's instances
|
||||
as if they will exist after publishing without them being integrated yet.
|
||||
|
||||
|
||||
Use first instance that has same folder path and product name,
|
||||
and contains representation with passed name.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,59 @@
|
|||
import inspect
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.pipeline.publish import PublishValidationError
|
||||
from ayon_core.tools.utils.host_tools import show_workfiles
|
||||
from ayon_core.pipeline.context_tools import version_up_current_workfile
|
||||
|
||||
|
||||
class SaveByVersionUpAction(pyblish.api.Action):
|
||||
"""Save Workfile."""
|
||||
label = "Save Workfile"
|
||||
on = "failed"
|
||||
icon = "save"
|
||||
|
||||
def process(self, context, plugin):
|
||||
version_up_current_workfile()
|
||||
|
||||
|
||||
class ShowWorkfilesAction(pyblish.api.Action):
|
||||
"""Save Workfile."""
|
||||
label = "Show Workfiles Tool..."
|
||||
on = "failed"
|
||||
icon = "files-o"
|
||||
|
||||
def process(self, context, plugin):
|
||||
show_workfiles()
|
||||
|
||||
|
||||
class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
|
||||
"""File must be saved before publishing"""
|
||||
"""File must be saved before publishing
|
||||
|
||||
This does not validate for unsaved changes. It only validates whether
|
||||
the current context was able to identify any 'currentFile'.
|
||||
"""
|
||||
|
||||
label = "Validate File Saved"
|
||||
order = pyblish.api.ValidatorOrder - 0.1
|
||||
hosts = ["maya", "houdini", "nuke"]
|
||||
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter"]
|
||||
actions = [SaveByVersionUpAction, ShowWorkfilesAction]
|
||||
|
||||
def process(self, context):
|
||||
|
||||
current_file = context.data["currentFile"]
|
||||
if not current_file:
|
||||
raise PublishValidationError("File not saved")
|
||||
raise PublishValidationError(
|
||||
"Workfile is not saved. Please save your scene to continue.",
|
||||
title="File not saved",
|
||||
description=self.get_description())
|
||||
|
||||
def get_description(self):
|
||||
return inspect.cleandoc("""
|
||||
### File not saved
|
||||
|
||||
Your workfile must be saved to continue publishing.
|
||||
|
||||
The **Save Workfile** action will save it for you with the first
|
||||
available workfile version number in your current context.
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,171 @@
|
|||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class ScreenMarquee(QtWidgets.QDialog):
|
||||
class ScreenMarqueeDialog(QtWidgets.QDialog):
|
||||
mouse_moved = QtCore.Signal()
|
||||
mouse_pressed = QtCore.Signal(QtCore.QPoint, str)
|
||||
mouse_released = QtCore.Signal(QtCore.QPoint)
|
||||
close_requested = QtCore.Signal()
|
||||
|
||||
def __init__(self, screen: QtCore.QObject, screen_id: str):
|
||||
super().__init__()
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Window
|
||||
| QtCore.Qt.FramelessWindowHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
| QtCore.Qt.CustomizeWindowHint
|
||||
)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
self.setCursor(QtCore.Qt.CrossCursor)
|
||||
self.setMouseTracking(True)
|
||||
|
||||
screen.geometryChanged.connect(self._fit_screen_geometry)
|
||||
|
||||
self._screen = screen
|
||||
self._opacity = 100
|
||||
self._click_pos = None
|
||||
self._screen_id = screen_id
|
||||
|
||||
def set_click_pos(self, pos):
|
||||
self._click_pos = pos
|
||||
self.repaint()
|
||||
|
||||
def convert_end_pos(self, pos):
|
||||
glob_pos = self.mapFromGlobal(pos)
|
||||
new_pos = self._convert_pos(glob_pos)
|
||||
return self.mapToGlobal(new_pos)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Paint event"""
|
||||
# Convert click and current mouse positions to local space.
|
||||
mouse_pos = self._convert_pos(self.mapFromGlobal(QtGui.QCursor.pos()))
|
||||
|
||||
rect = event.rect()
|
||||
fill_path = QtGui.QPainterPath()
|
||||
fill_path.addRect(rect)
|
||||
|
||||
capture_rect = None
|
||||
if self._click_pos is not None:
|
||||
click_pos = self.mapFromGlobal(self._click_pos)
|
||||
capture_rect = QtCore.QRect(click_pos, mouse_pos)
|
||||
|
||||
# Clear the capture area
|
||||
sub_path = QtGui.QPainterPath()
|
||||
sub_path.addRect(capture_rect)
|
||||
fill_path = fill_path.subtracted(sub_path)
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHints(
|
||||
QtGui.QPainter.Antialiasing
|
||||
| QtGui.QPainter.SmoothPixmapTransform
|
||||
)
|
||||
|
||||
# Draw background. Aside from aesthetics, this makes the full
|
||||
# tool region accept mouse events.
|
||||
painter.setBrush(QtGui.QColor(0, 0, 0, self._opacity))
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.drawPath(fill_path)
|
||||
|
||||
# Draw cropping markers at current mouse position
|
||||
pen_color = QtGui.QColor(255, 255, 255, self._opacity)
|
||||
pen = QtGui.QPen(pen_color, 1, QtCore.Qt.DotLine)
|
||||
painter.setPen(pen)
|
||||
painter.drawLine(
|
||||
rect.left(), mouse_pos.y(),
|
||||
rect.right(), mouse_pos.y()
|
||||
)
|
||||
painter.drawLine(
|
||||
mouse_pos.x(), rect.top(),
|
||||
mouse_pos.x(), rect.bottom()
|
||||
)
|
||||
|
||||
# Draw rectangle around selection area
|
||||
if capture_rect is not None:
|
||||
pen_color = QtGui.QColor(92, 173, 214)
|
||||
pen = QtGui.QPen(pen_color, 2)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtCore.Qt.NoBrush)
|
||||
l_x = capture_rect.left()
|
||||
r_x = capture_rect.right()
|
||||
if l_x > r_x:
|
||||
l_x, r_x = r_x, l_x
|
||||
t_y = capture_rect.top()
|
||||
b_y = capture_rect.bottom()
|
||||
if t_y > b_y:
|
||||
t_y, b_y = b_y, t_y
|
||||
|
||||
# -1 to draw 1px over the border
|
||||
r_x -= 1
|
||||
b_y -= 1
|
||||
sel_rect = QtCore.QRect(
|
||||
QtCore.QPoint(l_x, t_y),
|
||||
QtCore.QPoint(r_x, b_y)
|
||||
)
|
||||
painter.drawRect(sel_rect)
|
||||
|
||||
painter.end()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Mouse click event"""
|
||||
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
# Begin click drag operation
|
||||
self._click_pos = event.globalPos()
|
||||
self.mouse_pressed.emit(self._click_pos, self._screen_id)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""Mouse release event"""
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
# End click drag operation and commit the current capture rect
|
||||
self._click_pos = None
|
||||
self.mouse_released.emit(event.globalPos())
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
"""Mouse move event"""
|
||||
self.mouse_moved.emit()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Mouse press event"""
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self._click_pos = None
|
||||
event.accept()
|
||||
self.close_requested.emit()
|
||||
return
|
||||
return super().keyPressEvent(event)
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self._fit_screen_geometry()
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._click_pos = None
|
||||
super().closeEvent(event)
|
||||
|
||||
def _convert_pos(self, pos):
|
||||
geo = self.geometry()
|
||||
if pos.x() > geo.width():
|
||||
pos.setX(geo.width() - 1)
|
||||
elif pos.x() < 0:
|
||||
pos.setX(0)
|
||||
|
||||
if pos.y() > geo.height():
|
||||
pos.setY(geo.height() - 1)
|
||||
elif pos.y() < 0:
|
||||
pos.setY(0)
|
||||
return pos
|
||||
|
||||
def _fit_screen_geometry(self):
|
||||
# On macOs it is required to set screen explicitly
|
||||
if hasattr(self, "setScreen"):
|
||||
self.setScreen(self._screen)
|
||||
self.setGeometry(self._screen.geometry())
|
||||
|
||||
|
||||
class ScreenMarquee(QtCore.QObject):
|
||||
"""Dialog to interactively define screen area.
|
||||
|
||||
This allows to select a screen area through a marquee selection.
|
||||
|
|
@ -17,187 +178,186 @@ class ScreenMarquee(QtWidgets.QDialog):
|
|||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Window
|
||||
| QtCore.Qt.FramelessWindowHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
| QtCore.Qt.CustomizeWindowHint
|
||||
)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
self.setCursor(QtCore.Qt.CrossCursor)
|
||||
self.setMouseTracking(True)
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if hasattr(app, "screenAdded"):
|
||||
app.screenAdded.connect(self._on_screen_added)
|
||||
app.screenRemoved.connect(self._fit_screen_geometry)
|
||||
elif hasattr(app, "desktop"):
|
||||
desktop = app.desktop()
|
||||
desktop.screenCountChanged.connect(self._fit_screen_geometry)
|
||||
|
||||
screens_by_id = {}
|
||||
for screen in QtWidgets.QApplication.screens():
|
||||
screen.geometryChanged.connect(self._fit_screen_geometry)
|
||||
screen_id = uuid.uuid4().hex
|
||||
screen_dialog = ScreenMarqueeDialog(screen, screen_id)
|
||||
screens_by_id[screen_id] = screen_dialog
|
||||
screen_dialog.mouse_moved.connect(self._on_mouse_move)
|
||||
screen_dialog.mouse_pressed.connect(self._on_mouse_press)
|
||||
screen_dialog.mouse_released.connect(self._on_mouse_release)
|
||||
screen_dialog.close_requested.connect(self._on_close_request)
|
||||
|
||||
self._opacity = 50
|
||||
self._click_pos = None
|
||||
self._capture_rect = None
|
||||
self._screens_by_id = screens_by_id
|
||||
self._finished = False
|
||||
self._captured = False
|
||||
self._start_pos = None
|
||||
self._end_pos = None
|
||||
self._start_screen_id = None
|
||||
self._pix = None
|
||||
|
||||
def get_captured_pixmap(self):
|
||||
if self._capture_rect is None:
|
||||
if self._pix is None:
|
||||
return QtGui.QPixmap()
|
||||
return self._pix
|
||||
|
||||
return self.get_desktop_pixmap(self._capture_rect)
|
||||
def _close_dialogs(self):
|
||||
for dialog in self._screens_by_id.values():
|
||||
dialog.close()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Paint event"""
|
||||
def _on_close_request(self):
|
||||
self._close_dialogs()
|
||||
self._finished = True
|
||||
|
||||
# Convert click and current mouse positions to local space.
|
||||
mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos())
|
||||
click_pos = None
|
||||
if self._click_pos is not None:
|
||||
click_pos = self.mapFromGlobal(self._click_pos)
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHints(
|
||||
QtGui.QPainter.Antialiasing
|
||||
| QtGui.QPainter.SmoothPixmapTransform
|
||||
)
|
||||
|
||||
# Draw background. Aside from aesthetics, this makes the full
|
||||
# tool region accept mouse events.
|
||||
painter.setBrush(QtGui.QColor(0, 0, 0, self._opacity))
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
rect = event.rect()
|
||||
fill_path = QtGui.QPainterPath()
|
||||
fill_path.addRect(rect)
|
||||
|
||||
# Clear the capture area
|
||||
if click_pos is not None:
|
||||
sub_path = QtGui.QPainterPath()
|
||||
capture_rect = QtCore.QRect(click_pos, mouse_pos)
|
||||
sub_path.addRect(capture_rect)
|
||||
fill_path = fill_path.subtracted(sub_path)
|
||||
|
||||
painter.drawPath(fill_path)
|
||||
|
||||
pen_color = QtGui.QColor(255, 255, 255, self._opacity)
|
||||
pen = QtGui.QPen(pen_color, 1, QtCore.Qt.DotLine)
|
||||
painter.setPen(pen)
|
||||
|
||||
# Draw cropping markers at click position
|
||||
if click_pos is not None:
|
||||
painter.drawLine(
|
||||
rect.left(), click_pos.y(),
|
||||
rect.right(), click_pos.y()
|
||||
)
|
||||
painter.drawLine(
|
||||
click_pos.x(), rect.top(),
|
||||
click_pos.x(), rect.bottom()
|
||||
)
|
||||
|
||||
# Draw cropping markers at current mouse position
|
||||
painter.drawLine(
|
||||
rect.left(), mouse_pos.y(),
|
||||
rect.right(), mouse_pos.y()
|
||||
)
|
||||
painter.drawLine(
|
||||
mouse_pos.x(), rect.top(),
|
||||
mouse_pos.x(), rect.bottom()
|
||||
)
|
||||
painter.end()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Mouse click event"""
|
||||
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
# Begin click drag operation
|
||||
self._click_pos = event.globalPos()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""Mouse release event"""
|
||||
if (
|
||||
self._click_pos is not None
|
||||
and event.button() == QtCore.Qt.LeftButton
|
||||
):
|
||||
# End click drag operation and commit the current capture rect
|
||||
self._capture_rect = QtCore.QRect(
|
||||
self._click_pos, event.globalPos()
|
||||
).normalized()
|
||||
self._click_pos = None
|
||||
self.close()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
"""Mouse move event"""
|
||||
self.repaint()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Mouse press event"""
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self._click_pos = None
|
||||
self._capture_rect = None
|
||||
event.accept()
|
||||
self.close()
|
||||
def _on_mouse_release(self, pos):
|
||||
start_screen_dialog = self._screens_by_id.get(self._start_screen_id)
|
||||
if start_screen_dialog is None:
|
||||
self._finished = True
|
||||
self._captured = False
|
||||
return
|
||||
return super().keyPressEvent(event)
|
||||
|
||||
def showEvent(self, event):
|
||||
self._fit_screen_geometry()
|
||||
end_pos = start_screen_dialog.convert_end_pos(pos)
|
||||
|
||||
def _fit_screen_geometry(self):
|
||||
# Compute the union of all screen geometries, and resize to fit.
|
||||
workspace_rect = QtCore.QRect()
|
||||
for screen in QtWidgets.QApplication.screens():
|
||||
workspace_rect = workspace_rect.united(screen.geometry())
|
||||
self.setGeometry(workspace_rect)
|
||||
self._close_dialogs()
|
||||
self._end_pos = end_pos
|
||||
self._finished = True
|
||||
self._captured = True
|
||||
|
||||
def _on_screen_added(self):
|
||||
for screen in QtGui.QGuiApplication.screens():
|
||||
screen.geometryChanged.connect(self._fit_screen_geometry)
|
||||
def _on_mouse_press(self, pos, screen_id):
|
||||
self._start_pos = pos
|
||||
self._start_screen_id = screen_id
|
||||
|
||||
def _on_mouse_move(self):
|
||||
for dialog in self._screens_by_id.values():
|
||||
dialog.repaint()
|
||||
|
||||
def start_capture(self):
|
||||
for dialog in self._screens_by_id.values():
|
||||
dialog.show()
|
||||
# Activate so Escape event is not ignored.
|
||||
dialog.setWindowState(QtCore.Qt.WindowActive)
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
while not self._finished:
|
||||
app.processEvents()
|
||||
|
||||
# Give time to cloe dialogs
|
||||
for _ in range(2):
|
||||
app.processEvents()
|
||||
|
||||
if self._captured:
|
||||
self._pix = self.get_desktop_pixmap(
|
||||
self._start_pos, self._end_pos
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_desktop_pixmap(cls, rect):
|
||||
def get_desktop_pixmap(cls, pos_start, pos_end):
|
||||
"""Performs a screen capture on the specified rectangle.
|
||||
|
||||
Args:
|
||||
rect (QtCore.QRect): The rectangle to capture.
|
||||
pos_start (QtCore.QPoint): Start of screen capture.
|
||||
pos_end (QtCore.QPoint): End of screen capture.
|
||||
|
||||
Returns:
|
||||
QtGui.QPixmap: Captured pixmap image
|
||||
"""
|
||||
|
||||
"""
|
||||
# Unify start and end points
|
||||
# - start is top left
|
||||
# - end is bottom right
|
||||
if pos_start.y() > pos_end.y():
|
||||
pos_start, pos_end = pos_end, pos_start
|
||||
|
||||
if pos_start.x() > pos_end.x():
|
||||
new_start = QtCore.QPoint(pos_end.x(), pos_start.y())
|
||||
new_end = QtCore.QPoint(pos_start.x(), pos_end.y())
|
||||
pos_start = new_start
|
||||
pos_end = new_end
|
||||
|
||||
# Validate if the rectangle is valid
|
||||
rect = QtCore.QRect(pos_start, pos_end)
|
||||
if rect.width() < 1 or rect.height() < 1:
|
||||
return QtGui.QPixmap()
|
||||
|
||||
screen_pixes = []
|
||||
for screen in QtWidgets.QApplication.screens():
|
||||
screen_geo = screen.geometry()
|
||||
if not screen_geo.intersects(rect):
|
||||
continue
|
||||
screen = QtWidgets.QApplication.screenAt(pos_start)
|
||||
return screen.grabWindow(
|
||||
0,
|
||||
pos_start.x() - screen.geometry().x(),
|
||||
pos_start.y() - screen.geometry().y(),
|
||||
pos_end.x() - pos_start.x(),
|
||||
pos_end.y() - pos_start.y()
|
||||
)
|
||||
# Multiscreen capture that does not work
|
||||
# - does not handle pixel aspect ratio and positioning of screens
|
||||
|
||||
screen_pix_rect = screen_geo.intersected(rect)
|
||||
screen_pix = screen.grabWindow(
|
||||
0,
|
||||
screen_pix_rect.x() - screen_geo.x(),
|
||||
screen_pix_rect.y() - screen_geo.y(),
|
||||
screen_pix_rect.width(), screen_pix_rect.height()
|
||||
)
|
||||
paste_point = QtCore.QPoint(
|
||||
screen_pix_rect.x() - rect.x(),
|
||||
screen_pix_rect.y() - rect.y()
|
||||
)
|
||||
screen_pixes.append((screen_pix, paste_point))
|
||||
|
||||
output_pix = QtGui.QPixmap(rect.width(), rect.height())
|
||||
output_pix.fill(QtCore.Qt.transparent)
|
||||
pix_painter = QtGui.QPainter()
|
||||
pix_painter.begin(output_pix)
|
||||
for item in screen_pixes:
|
||||
(screen_pix, offset) = item
|
||||
pix_painter.drawPixmap(offset, screen_pix)
|
||||
|
||||
pix_painter.end()
|
||||
|
||||
return output_pix
|
||||
# most_left = None
|
||||
# most_top = None
|
||||
# for screen in QtWidgets.QApplication.screens():
|
||||
# screen_geo = screen.geometry()
|
||||
# if most_left is None or most_left > screen_geo.x():
|
||||
# most_left = screen_geo.x()
|
||||
#
|
||||
# if most_top is None or most_top > screen_geo.y():
|
||||
# most_top = screen_geo.y()
|
||||
#
|
||||
# most_left = most_left or 0
|
||||
# most_top = most_top or 0
|
||||
#
|
||||
# screen_pixes = []
|
||||
# for screen in QtWidgets.QApplication.screens():
|
||||
# screen_geo = screen.geometry()
|
||||
# if not screen_geo.intersects(rect):
|
||||
# continue
|
||||
#
|
||||
# pos_l_x = screen_geo.x()
|
||||
# pos_l_y = screen_geo.y()
|
||||
# pos_r_x = screen_geo.x() + screen_geo.width()
|
||||
# pos_r_y = screen_geo.y() + screen_geo.height()
|
||||
# if pos_start.x() > pos_l_x:
|
||||
# pos_l_x = pos_start.x()
|
||||
#
|
||||
# if pos_start.y() > pos_l_y:
|
||||
# pos_l_y = pos_start.y()
|
||||
#
|
||||
# if pos_end.x() < pos_r_x:
|
||||
# pos_r_x = pos_end.x()
|
||||
#
|
||||
# if pos_end.y() < pos_r_y:
|
||||
# pos_r_y = pos_end.y()
|
||||
#
|
||||
# capture_pos_x = pos_l_x - screen_geo.x()
|
||||
# capture_pos_y = pos_l_y - screen_geo.y()
|
||||
# capture_screen_width = pos_r_x - pos_l_x
|
||||
# capture_screen_height = pos_r_y - pos_l_y
|
||||
# screen_pix = screen.grabWindow(
|
||||
# 0,
|
||||
# capture_pos_x, capture_pos_y,
|
||||
# capture_screen_width, capture_screen_height
|
||||
# )
|
||||
# paste_point = QtCore.QPoint(
|
||||
# (pos_l_x - screen_geo.x()) - rect.x(),
|
||||
# (pos_l_y - screen_geo.y()) - rect.y()
|
||||
# )
|
||||
# screen_pixes.append((screen_pix, paste_point))
|
||||
#
|
||||
# output_pix = QtGui.QPixmap(rect.width(), rect.height())
|
||||
# output_pix.fill(QtCore.Qt.transparent)
|
||||
# pix_painter = QtGui.QPainter()
|
||||
# pix_painter.begin(output_pix)
|
||||
# render_hints = (
|
||||
# QtGui.QPainter.Antialiasing
|
||||
# | QtGui.QPainter.SmoothPixmapTransform
|
||||
# )
|
||||
# if hasattr(QtGui.QPainter, "HighQualityAntialiasing"):
|
||||
# render_hints |= QtGui.QPainter.HighQualityAntialiasing
|
||||
# pix_painter.setRenderHints(render_hints)
|
||||
# for item in screen_pixes:
|
||||
# (screen_pix, offset) = item
|
||||
# pix_painter.drawPixmap(offset, screen_pix)
|
||||
#
|
||||
# pix_painter.end()
|
||||
#
|
||||
# return output_pix
|
||||
|
||||
@classmethod
|
||||
def capture_to_pixmap(cls):
|
||||
|
|
@ -209,12 +369,8 @@ class ScreenMarquee(QtWidgets.QDialog):
|
|||
Returns:
|
||||
QtGui.QPixmap: Captured pixmap image.
|
||||
"""
|
||||
|
||||
tool = cls()
|
||||
# Activate so Escape event is not ignored.
|
||||
tool.setWindowState(QtCore.Qt.WindowActive)
|
||||
# Exec dialog and return captured pixmap.
|
||||
tool.exec_()
|
||||
tool.start_capture()
|
||||
return tool.get_captured_pixmap()
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ def make_sure_tray_is_running(
|
|||
args = get_ayon_launcher_args("tray", "--force")
|
||||
if env is None:
|
||||
env = os.environ.copy()
|
||||
|
||||
|
||||
# Make sure 'QT_API' is not set
|
||||
env.pop("QT_API", None)
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ def main(title="Scripts", parent=None, objectName=None):
|
|||
|
||||
# Register control + shift callback to add to shelf (maya behavior)
|
||||
modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier
|
||||
if int(cmds.about(version=True)) <= 2025:
|
||||
if int(cmds.about(version=True)) < 2025:
|
||||
modifiers = int(modifiers)
|
||||
|
||||
menu.register_callback(modifiers, to_shelf)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON core addon version."""
|
||||
__version__ = "0.4.4-dev.1"
|
||||
__version__ = "0.4.5-dev.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "0.4.4-dev.1"
|
||||
version = "0.4.5-dev.1"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ target-version = "py39"
|
|||
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
select = ["E4", "E7", "E9", "F", "W"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
|
|
@ -84,7 +84,6 @@ exclude = [
|
|||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"client/ayon_core/lib/__init__.py" = ["E402"]
|
||||
"client/ayon_core/hosts/max/startup/startup.py" = ["E402"]
|
||||
|
||||
[tool.ruff.format]
|
||||
# Like Black, use double quotes for strings.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class CollectFramesFixDefModel(BaseSettingsModel):
|
|||
True,
|
||||
title="Show 'Rewrite latest version' toggle"
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContributionLayersModel(BaseSettingsModel):
|
||||
_layout = "compact"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue