diff --git a/bookmarks/actions.py b/bookmarks/actions.py index 252f05f2..93422577 100644 --- a/bookmarks/actions.py +++ b/bookmarks/actions.py @@ -1713,40 +1713,51 @@ def remove_thumbnail(index): source_index.model().updateIndex.emit(source_index) -@common.error @common.debug +@common.error @selection -def copy_asset_properties(index): - """Copy asset properties to clipboard. - - Args: - index (QModelIndex): Index of the currently selected item. +def copy_properties(index): + pp = index.data(common.ParentPathRole) + if not pp: + return - """ - server, job, root, asset = index.data(common.ParentPathRole)[0:4] - database.copy_properties( - server, job, root, asset, table=database.AssetTable + from . editor import clipboard + editor = clipboard.show( + *pp[0:3], + asset=pp[3] if len(pp) == 4 else None, ) + return editor + -@common.error @common.debug +@common.error @selection -def paste_asset_properties(index): - """Paste asset properties from clipboard to the selected item. +def paste_properties(index): + pp = index.data(common.ParentPathRole) + if len(pp) == 3: + table = database.BookmarkTable + clipboard = common.BookmarkPropertyClipboard + elif len(pp) == 4: + table = database.AssetTable + clipboard = common.AssetPropertyClipboard + else: + raise NotImplementedError('Not implemented.') - Args: - index (QModelIndex): Index of the currently selected item. + if not common.CLIPBOARD[clipboard]: + raise RuntimeError('Could not paste properties. Clipboard is empty.') - """ - server, job, root, asset = index.data(common.ParentPathRole)[0:4] - database.paste_properties( - server, job, root, asset, table=database.AssetTable - ) + source = '/'.join(pp) + + # Write the values to the database + for k in common.CLIPBOARD[clipboard]: + v = common.CLIPBOARD[clipboard][k] + database.get(*pp[0:3]).set_value(source, k, v, table=table) + log.success(f'Pasted {k} = {v} to {source}') -@common.error @common.debug +@common.error def toggle_active_mode(): """Toggle the active path mode. diff --git a/bookmarks/common/__init__.py b/bookmarks/common/__init__.py index e2eb0423..568b7447 100644 --- a/bookmarks/common/__init__.py +++ b/bookmarks/common/__init__.py @@ -102,6 +102,7 @@ job_editor = None bookmark_property_editor = None asset_property_editor = None +clipboard_editor = None file_saver_widget = None publish_widget = None maya_export_widget = None @@ -125,6 +126,7 @@ from .monitor import * from .sequence import * from .active_mode import * +from .clipboard import * from .settings import * from .setup import * from .signals import * diff --git a/bookmarks/common/clipboard.py b/bookmarks/common/clipboard.py new file mode 100644 index 00000000..544acdb9 --- /dev/null +++ b/bookmarks/common/clipboard.py @@ -0,0 +1,16 @@ +"""Internal clipboard data. + +""" +from . import core + + +BookmarkPropertyClipboard = core.idx(start=0, reset=True) +AssetPropertyClipboard = core.idx() +ThumbnailClipboard = core.idx() + + +CLIPBOARD = { + BookmarkPropertyClipboard: {}, + AssetPropertyClipboard: {}, + ThumbnailClipboard: {}, +} diff --git a/bookmarks/common/monitor.py b/bookmarks/common/monitor.py index f098e8e7..fa3746f7 100644 --- a/bookmarks/common/monitor.py +++ b/bookmarks/common/monitor.py @@ -86,23 +86,24 @@ def process_update_queue(self): Emits the modelNeedsRefresh signal for each data dictionary in the update queue. """ - refs = [] + processed_data_dicts = [] + for path in self.update_queue.copy(): + print(path) for data_type in (common.SequenceItem, common.FileItem): data_dict = common.get_data_from_value(path, data_type, role=common.PathRole) if not data_dict: continue - ref = weakref.ref(data_dict) - if not ref in refs: - refs.append(data_dict) - else: + if data_dict in processed_data_dicts: continue - ref().refresh_needed = True + data_dict.refresh_needed = True common.widget(self.tab_idx).filter_indicator_widget.repaint() - self.modelNeedsRefresh.emit(ref) + self.modelNeedsRefresh.emit(weakref.ref(data_dict)) + + processed_data_dicts.append(data_dict) self.update_queue.clear() self.update_timer.stop() diff --git a/bookmarks/contextmenu.py b/bookmarks/contextmenu.py index 6a88f2cf..cdd31981 100644 --- a/bookmarks/contextmenu.py +++ b/bookmarks/contextmenu.py @@ -1630,7 +1630,7 @@ def publish_menu(self): 'action': actions.show_publish_widget, } - def import_export_menu(self): + def import_export_properties_menu(self): """Export property """ @@ -1639,6 +1639,48 @@ def import_export_menu(self): self.menu[k] = collections.OrderedDict() self.menu[f'{k}:icon'] = ui.get_icon('settings') + self.separator(menu=self.menu[k]) + + pp = self.index.data(common.ParentPathRole) + if len(pp) == 3: + clipboard = common.BookmarkPropertyClipboard + elif len(pp) == 4: + clipboard = common.AssetPropertyClipboard + else: + clipboard = None + + # Copy menu + if self.index.isValid() and clipboard: + self.menu[k][key()] = { + 'text': 'Copy properties to clipboard...', + 'action': actions.copy_properties, + 'icon': ui.get_icon('settings'), + 'shortcut': shortcuts.get( + shortcuts.MainWidgetShortcuts, + shortcuts.CopyProperties + ).key(), + 'description': shortcuts.hint( + shortcuts.MainWidgetShortcuts, + shortcuts.CopyProperties + ), + } + + # Paste menu + if self.index.isValid() and clipboard and common.CLIPBOARD[clipboard]: + self.menu[k][key()] = { + 'text': 'Paste properties from clipboard', + 'action': actions.paste_properties, + 'icon': ui.get_icon('settings'), + 'shortcut': shortcuts.get( + shortcuts.MainWidgetShortcuts, + shortcuts.PasteProperties + ).key(), + 'description': shortcuts.hint( + shortcuts.MainWidgetShortcuts, + shortcuts.PasteProperties + ), + } + self.separator(menu=self.menu[k]) if self.index.isValid(): self.menu[k][key()] = { diff --git a/bookmarks/database.py b/bookmarks/database.py index 8f1da2ea..1b459657 100644 --- a/bookmarks/database.py +++ b/bookmarks/database.py @@ -76,7 +76,7 @@ AssetTable: { 'id': { 'sql': 'TEXT PRIMARY KEY COLLATE NOCASE', - 'type': str + 'type': str, }, 'description': { 'sql': 'TEXT', @@ -90,14 +90,6 @@ 'sql': 'INT DEFAULT 0', 'type': int }, - 'thumbnail_stamp': { - 'sql': 'REAL', - 'type': float - }, - 'user': { - 'sql': 'TEXT', - 'type': str - }, 'shotgun_id': { 'sql': 'INT', 'type': int diff --git a/bookmarks/editor/base.py b/bookmarks/editor/base.py index 26fcc743..1d288ae0 100644 --- a/bookmarks/editor/base.py +++ b/bookmarks/editor/base.py @@ -508,9 +508,14 @@ def _add_row(self, v, group_widget): row.setWhatsThis(v['description']) if 'help' in v and v['help']: - ui.add_description( - v['help'], label=None, parent=group_widget - ) + if 'widget' in v and v['widget']: + ui.add_description( + v['help'], label=None, parent=group_widget + ) + else: + ui.add_description( + v['help'], label=None, parent=row + ) if 'button' in v and v['button']: button = ui.PaintedButton( diff --git a/bookmarks/editor/clipboard.py b/bookmarks/editor/clipboard.py new file mode 100644 index 00000000..90b7b5c8 --- /dev/null +++ b/bookmarks/editor/clipboard.py @@ -0,0 +1,166 @@ +import functools + +from PySide2 import QtWidgets, QtCore + +from . import base +from .. import common +from .. import database +from .. import log + + + +def close(): + """Closes the :class:`AssetPropertyEditor` editor. + + """ + if common.clipboard_editor is None: + return + try: + common.clipboard_editor.close() + common.clipboard_editor.deleteLater() + except: + pass + common.clipboard_editor = None + + +def show(server, job, root, asset=None): + """Show the :class:`CopyClipboardEditor` window. + + Args: + server (str): `server` path segment. + job (str): `job` path segment. + root (str): `root` path segment. + asset (str, optional): Asset name. Default: `None`. + + """ + close() + common.clipboard_editor = CopyClipboardEditor( + server, + job, + root, + asset=asset + ) + common.restore_window_geometry(common.clipboard_editor) + common.restore_window_state(common.clipboard_editor) + return common.clipboard_editor + + +class CopyClipboardEditor(base.BasePropertyEditor): + sections = { + 0: { + 'name': 'Copy Properties', + 'icon': 'settings', + 'color': common.color(common.color_green), + 'groups': { + 0: { + 0: { + 'name': '', + 'key': 'options', + 'validator': None, + 'widget': None, + 'placeholder': None, + 'description': None, + 'button': 'Select All', + 'button2': 'Deselect All', + 'help': 'Select the properties to copy to the clipboard' + }, + }, + 1: {}, + }, + }, + } + + def __init__(self, server, job, root, asset=None, parent=None): + if asset: + db_table = database.AssetTable + else: + db_table = database.BookmarkTable + + self.user_settings_keys = [] + + # Construct the sections dict + for idx, k in enumerate(database.TABLES[db_table]): + if k == 'id': + continue + + user_settings_key = f'copy{db_table}_{k}' + self.user_settings_keys.append(user_settings_key) + + self.sections[0]['groups'][1][idx] = { + 'name': k.replace('_', ' ').title(), + 'key': user_settings_key, + 'validator': None, + 'widget': functools.partial(QtWidgets.QCheckBox, 'Copy'), # keep the 20 char limit + 'placeholder': '', + 'description': f'"{k}"', + } + + super().__init__( + server, + job, + root, + asset=asset, + buttons=('Copy Properties', 'Cancel'), + db_table=db_table, + hide_thumbnail_editor=True, + parent=parent + ) + + self.setWindowTitle('Copy Properties') + self.setFixedWidth(common.size(common.size_width * 0.66)) + + def db_source(self): + return None + + def init_data(self): + self.load_saved_user_settings(self.user_settings_keys) + self._connect_settings_save_signals(self.user_settings_keys) + + @common.debug + @common.error + @QtCore.Slot() + def save_changes(self): + data = {} + db = database.get( + self.server, + self.job, + self.root, + ) + + for k in database.TABLES[self._db_table]: + if k == 'id': + continue + + user_settings_key = f'copy{self._db_table}_{k}' + editor = getattr(self, f'{user_settings_key}_editor') + if not editor.isChecked(): + continue + if self.asset: + data[k] = db.value(db.source(self.asset), k, self._db_table) + else: + data[k] = db.value(db.source(), k, self._db_table) + + if not data: + raise RuntimeError('No properties selected to copy to clipboard!') + + if self._db_table == database.BookmarkTable: + common.CLIPBOARD[common.BookmarkPropertyClipboard] = data + log.success('Copied bookmark properties to clipboard') + elif self._db_table == database.AssetTable: + common.CLIPBOARD[common.AssetPropertyClipboard] = data + log.success('Copied asset properties to clipboard') + else: + log.error(f'Unknown db_table: {self._db_table}') + return True + + @QtCore.Slot() + def options_button_clicked(self): + for k in self.user_settings_keys: + editor = getattr(self, f'{k}_editor') + editor.setChecked(True) + + @QtCore.Slot() + def options_button2_clicked(self): + for k in self.user_settings_keys: + editor = getattr(self, f'{k}_editor') + editor.setChecked(False) diff --git a/bookmarks/editor/jobs_widgets.py b/bookmarks/editor/jobs_widgets.py index f60c00d4..507a90fa 100644 --- a/bookmarks/editor/jobs_widgets.py +++ b/bookmarks/editor/jobs_widgets.py @@ -708,6 +708,7 @@ def setup(self): self.add_menu() self.separator() self.bookmark_properties_menu() + self.copy_properties_menu() self.separator() self.reveal_menu() self.copy_json_menu() @@ -760,6 +761,9 @@ def refresh_menu(self): 'icon': ui.get_icon('refresh') } + @QtCore.Slot() + @common.error + @common.debug def bookmark_properties_menu(self): """Show the bookmark item property editor. @@ -774,11 +778,49 @@ def bookmark_properties_menu(self): 'icon': ui.get_icon('settings') } + @QtCore.Slot() + @common.error + @common.debug + def copy_properties_menu(self): + """Show the bookmark item property editor. + + """ + server, job, root = self.parent().window().get_args() + if not all((server, job, root)): + return + + from . import clipboard + + self.menu[contextmenu.key()] = { + 'text': 'Copy bookmark item properties...', + 'action': functools.partial(clipboard.show, server, job, root), + 'icon': ui.get_icon('settings') + } + + @QtCore.Slot() + @common.error + @common.debug + def paste_properties_menu(self): + """Show the bookmark item property editor. + + """ + server, job, root = self.parent().window().get_args() + if not all((server, job, root)): + return + + from . import clipboard + + self.menu[contextmenu.key()] = { + 'text': 'Copy bookmark item properties...', + 'action': functools.partial(clipboard.show, server, job, root), + 'icon': ui.get_icon('settings') + } + @QtCore.Slot() @common.error @common.debug def copy_json_menu(self): - """Copy bookmark item as JSON action. + """Copy bookmark item as JSON. """ server, job, root = self.parent().window().get_args() diff --git a/bookmarks/items/asset_items.py b/bookmarks/items/asset_items.py index d1eaf380..eefdd197 100644 --- a/bookmarks/items/asset_items.py +++ b/bookmarks/items/asset_items.py @@ -76,7 +76,7 @@ def setup(self): self.reveal_item_menu() self.copy_menu() self.separator() - self.import_export_menu() + self.import_export_properties_menu() self.separator() self.edit_active_bookmark_menu() self.edit_selected_asset_menu() diff --git a/bookmarks/items/bookmark_items.py b/bookmarks/items/bookmark_items.py index 58bf0150..2d189337 100644 --- a/bookmarks/items/bookmark_items.py +++ b/bookmarks/items/bookmark_items.py @@ -98,7 +98,7 @@ def setup(self): self.reveal_item_menu() self.copy_menu() self.separator() - self.import_export_menu() + self.import_export_properties_menu() self.separator() self.edit_selected_bookmark_menu() self.notes_menu() diff --git a/bookmarks/main.py b/bookmarks/main.py index 54963491..9c16b2a2 100644 --- a/bookmarks/main.py +++ b/bookmarks/main.py @@ -355,6 +355,9 @@ def _init_shortcuts(self): connect(shortcuts.RevealItem, actions.reveal_selected) connect(shortcuts.RevealAltItem, actions.reveal_url) + connect(shortcuts.CopyProperties, actions.copy_properties) + connect(shortcuts.PasteProperties, actions.paste_properties) + if common.init_mode == common.StandaloneMode: connect(shortcuts.Quit, common.uninitialize) connect(shortcuts.Minimize, actions.toggle_minimized) diff --git a/bookmarks/maya/capture.py b/bookmarks/maya/capture.py index e02bda20..74352e4e 100644 --- a/bookmarks/maya/capture.py +++ b/bookmarks/maya/capture.py @@ -6,7 +6,6 @@ import contextlib import re import sys -from contextlib import ExitStack, contextmanager try: import maya.cmds as cmds @@ -16,16 +15,10 @@ from PySide2 import QtGui, QtWidgets +version_info = (2, 4, 0) -@contextmanager -def nested(*contexts): - """ - Reimplementation of nested in python 3. - """ - with ExitStack() as stack: - for ctx in contexts: - stack.enter_context(ctx) - yield contexts +__version__ = "%s.%s.%s" % version_info +__license__ = "MIT" def capture( @@ -53,11 +46,11 @@ def capture( viewport_options=None, viewport2_options=None, complete_filename=None -): - """Playblast in an independent panel. + ): + """Playblast in an independent panel - Args: - camera (str, optional): Name of camera, defaults to 'persp' + Arguments: + camera (str, optional): Name of camera, defaults to "persp" width (int, optional): Width of output in pixels height (int, optional): Height of output in pixels filename (str, optional): Name of output file. If @@ -67,24 +60,24 @@ def capture( frame (float or tuple, optional): A single frame or list of frames. Use this to capture a single frame or an arbitrary sequence of frames. - format (str, optional): Name of format, defaults to 'qt'. - compression (str, optional): Name of compression, defaults to 'H.264' + format (str, optional): Name of format, defaults to "qt". + compression (str, optional): Name of compression, defaults to "H.264" quality (int, optional): The quality of the output, defaults to 100 - off_screen (bool, optional): Whether to playblast off-screen + off_screen (bool, optional): Whether or not to playblast off screen viewer (bool, optional): Display results in native player - show_ornaments (bool, optional): Whether model view ornaments + show_ornaments (bool, optional): Whether or not model view ornaments (e.g. axis icon, grid and HUD) should be displayed. - sound (str, optional): Specify the sound node to be used during + sound (str, optional): Specify the sound node to be used during playblast. When None (default) no sound will be used. isolate (list): List of nodes to isolate upon capturing maintain_aspect_ratio (bool, optional): Modify height in order to maintain aspect ratio. - overwrite (bool, optional): Whether to overwrite if file + overwrite (bool, optional): Whether or not to overwrite if file already exists. If disabled and file exists and error will be raised. frame_padding (bool, optional): Number of zeros used to pad file name for image sequences. - raw_frame_numbers (bool, optional): Whether to use the exact + raw_frame_numbers (bool, optional): Whether or not to use the exact frame numbers from the scene or capture to a sequence starting at zero. Defaults to False. When set to True `viewer` can't be used and will be forced to False. @@ -105,26 +98,28 @@ def capture( >>> # Launch capture with custom viewport settings >>> capture('persp', 800, 600, ... viewport_options={ - ... 'displayAppearance': 'wireframe', - ... 'grid': False, - ... 'polymeshes': True, + ... "displayAppearance": "wireframe", + ... "grid": False, + ... "polymeshes": True, ... }, ... camera_options={ - ... 'displayResolution': True + ... "displayResolution": True ... } ... ) + """ - camera = camera or 'persp' + + camera = camera or "persp" # Ensure camera exists if not cmds.objExists(camera): - raise RuntimeError(f'Camera does not exist: {camera}') + raise RuntimeError("Camera does not exist: {0}".format(camera)) - width = width or cmds.getAttr('defaultResolution.width') - height = height or cmds.getAttr('defaultResolution.height') + width = width or cmds.getAttr("defaultResolution.width") + height = height or cmds.getAttr("defaultResolution.height") if maintain_aspect_ratio: - ratio = cmds.getAttr('defaultResolution.deviceAspectRatio') + ratio = cmds.getAttr("defaultResolution.deviceAspectRatio") height = round(width / ratio) if start_frame is None: @@ -155,12 +150,12 @@ def capture( # in a minimal integer frame number : filename.-2147483648.png for any # negative rendered frame if frame and raw_frame_numbers: - check = frame if isinstance(frame, (list, tuple)) else [frame] + check = frame if isinstance(frame, (list, tuple, range)) else [frame] if any(f < 0 for f in check): raise RuntimeError( - 'Negative frames are not supported with ' - 'raw frame numbers and explicit frame numbers' - ) + "Negative frames are not supported with " + "raw frame numbers and explicit frame numbers" + ) # (#21) Bugfix: `maya.cmds.playblast` suffers from undo bug where it # always sets the currentTime to frame 1. By setting currentTime before @@ -172,19 +167,17 @@ def capture( width=width + padding, height=height + padding, off_screen=off_screen - ) as panel: + ) as panel: cmds.setFocus(panel) - with nested( - _disabled_inview_messages(), - _maintain_camera(panel, camera), - _applied_viewport_options(viewport_options, panel), - _applied_camera_options(camera_options, panel), - _applied_display_options(display_options), - _applied_viewport2_options(viewport2_options), - _isolated_nodes(isolate, panel), - _maintained_time() - ): + with _disabled_inview_messages(), \ + _maintain_camera(panel, camera), \ + _applied_viewport_options(viewport_options, panel), \ + _applied_camera_options(camera_options, panel), \ + _applied_display_options(display_options), \ + _applied_viewport2_options(viewport2_options), \ + _isolated_nodes(isolate, panel), \ + _maintained_time(): output = cmds.playblast( compression=compression, format=format, @@ -217,7 +210,7 @@ def snap(*args, **kwargs): frame is used. clipboard (bool, optional): Whether to add the output image to the global clipboard. This allows to easily paste the snapped image - into another application, e.g. into Photoshop. + into another application, eg. into Photoshop. Keywords: See `capture`. @@ -232,13 +225,13 @@ def snap(*args, **kwargs): if not isinstance(frame, (int, float)): raise TypeError( - 'frame must be a single frame (integer or float). ' - 'Use `capture()` for sequences.' - ) + "frame must be a single frame (integer or float). " + "Use `capture()` for sequences." + ) # override capture defaults - format = kwargs.pop('format', 'image') - compression = kwargs.pop('compression', 'png') + format = kwargs.pop('format', "image") + compression = kwargs.pop('compression', "png") viewer = kwargs.pop('viewer', False) raw_frame_numbers = kwargs.pop('raw_frame_numbers', True) kwargs['compression'] = compression @@ -256,7 +249,7 @@ def replace(m): """Substitute # with frame number""" return str(int(frame)).zfill(len(m.group())) - output = re.sub('#+', replace, output) + output = re.sub("#+", replace, output) # add image to clipboard if clipboard: @@ -266,75 +259,76 @@ def replace(m): CameraOptions = { - 'displayGateMask': False, - 'displayResolution': False, - 'displayFilmGate': False, - 'displayFieldChart': False, - 'displaySafeAction': False, - 'displaySafeTitle': False, - 'displayFilmPivot': False, - 'displayFilmOrigin': False, - 'overscan': 1.0, - 'depthOfField': False, + "displayGateMask": False, + "displayResolution": False, + "displayFilmGate": False, + "displayFieldChart": False, + "displaySafeAction": False, + "displaySafeTitle": False, + "displayFilmPivot": False, + "displayFilmOrigin": False, + "overscan": 1.0, + "depthOfField": False, } DisplayOptions = { - 'displayGradient': True, - 'background': (0.631, 0.631, 0.631), - 'backgroundTop': (0.535, 0.617, 0.702), - 'backgroundBottom': (0.052, 0.052, 0.052), + "displayGradient": True, + "background": (0.631, 0.631, 0.631), + "backgroundTop": (0.535, 0.617, 0.702), + "backgroundBottom": (0.052, 0.052, 0.052), } # These display options require a different command to be queried and set -_DisplayOptionsRGB = {'background', 'backgroundTop', 'backgroundBottom'} +_DisplayOptionsRGB = set(["background", "backgroundTop", "backgroundBottom"]) ViewportOptions = { # renderer - 'rendererName': 'vp2Renderer', - 'fogging': False, - 'fogMode': 'linear', - 'fogDensity': 1, - 'fogStart': 1, - 'fogEnd': 1, - 'fogColor': (0, 0, 0, 0), - 'shadows': False, - 'displayTextures': True, - 'displayLights': 'default', - 'useDefaultMaterial': False, - 'wireframeOnShaded': False, - 'displayAppearance': 'smoothShaded', - 'selectionHiliteDisplay': False, - 'headsUpDisplay': True, + "rendererName": "vp2Renderer", + "fogging": False, + "fogMode": "linear", + "fogDensity": 1, + "fogStart": 1, + "fogEnd": 1, + "fogColor": (0, 0, 0, 0), + "shadows": False, + "displayTextures": True, + "displayLights": "default", + "useDefaultMaterial": False, + "wireframeOnShaded": False, + "displayAppearance": 'smoothShaded', + "selectionHiliteDisplay": False, + "headsUpDisplay": True, # object display - 'imagePlane': True, - 'nurbsCurves': False, - 'nurbsSurfaces': False, - 'polymeshes': True, - 'subdivSurfaces': False, - 'planes': True, - 'cameras': False, - 'controlVertices': True, - 'lights': False, - 'grid': False, - 'hulls': True, - 'joints': False, - 'ikHandles': False, - 'deformers': False, - 'dynamics': False, - 'fluids': False, - 'hairSystems': False, - 'follicles': False, - 'nCloths': False, - 'nParticles': False, - 'nRigids': False, - 'dynamicConstraints': False, - 'locators': False, - 'manipulators': False, - 'dimensions': False, - 'handles': False, - 'pivots': False, - 'textures': False, - 'strokes': False + "imagePlane": True, + "nurbsCurves": False, + "nurbsSurfaces": False, + "polymeshes": True, + "subdivSurfaces": False, + "planes": True, + "cameras": False, + "controlVertices": True, + "lights": False, + "grid": False, + "grid": False, + "hulls": True, + "joints": False, + "ikHandles": False, + "deformers": False, + "dynamics": False, + "fluids": False, + "hairSystems": False, + "follicles": False, + "nCloths": False, + "nParticles": False, + "nRigids": False, + "dynamicConstraints": False, + "locators": False, + "manipulators": False, + "dimensions": False, + "handles": False, + "pivots": False, + "textures": False, + "strokes": False } Viewport2Options = { @@ -375,7 +369,7 @@ def apply_view(panel, **options): camera = cmds.modelPanel(panel, camera=True, query=True) # Display options - display_options = options.get('display_options', {}) + display_options = options.get("display_options", {}) for key, value in display_options.items(): if key in _DisplayOptionsRGB: cmds.displayRGBColor(key, *value) @@ -384,25 +378,25 @@ def apply_view(panel, **options): **{ key: value } - ) + ) # Camera options - camera_options = options.get('camera_options', {}) + camera_options = options.get("camera_options", {}) for key, value in camera_options.items(): - cmds.setAttr(f'{camera}.{key}', value) + cmds.setAttr("{0}.{1}".format(camera, key), value) # Viewport options - viewport_options = options.get('viewport_options', {}) + viewport_options = options.get("viewport_options", {}) for key, value in viewport_options.items(): cmds.modelEditor( panel, edit=True, **{ key: value } - ) + ) - viewport2_options = options.get('viewport2_options', {}) + viewport2_options = options.get("viewport2_options", {}) for key, value in viewport2_options.items(): - attr = f'hardwareRenderingGlobals.{key}' + attr = "hardwareRenderingGlobals.{0}".format(key) cmds.setAttr(attr, value) @@ -421,8 +415,8 @@ def parse_active_panel(): # This happens when last focus was on panel # that got deleted (e.g. `capture()` then `parse_active_view()`) - if not panel or 'modelPanel' not in panel: - raise RuntimeError('No active model panel found') + if not panel or "modelPanel" not in panel: + raise RuntimeError("No active model panel found") return panel @@ -437,7 +431,7 @@ def parse_view(panel): """Parse the scene, panel and camera for their current settings Example: - >>> parse_view('modelPanel1') + >>> parse_view("modelPanel1") Arguments: panel (str): Name of modelPanel @@ -456,12 +450,12 @@ def parse_view(panel): query=True, **{ key: True } - ) + ) # Camera options camera_options = {} for key in CameraOptions: - camera_options[key] = cmds.getAttr(f'{camera}.{key}') + camera_options[key] = cmds.getAttr("{0}.{1}".format(camera, key)) # Viewport options viewport_options = {} @@ -471,7 +465,7 @@ def parse_view(panel): # plugin display filters (which it shouldn't!) plugins = cmds.pluginDisplayFilter(query=True, listFilters=True) for plugin in plugins: - # plugin = str(plugin) # unicode->str for simplicity of the dict + plugin = str(plugin) # unicode->str for simplicity of the dict state = cmds.modelEditor(panel, query=True, queryPluginObjects=plugin) viewport_options[plugin] = state @@ -484,45 +478,45 @@ def parse_view(panel): viewport2_options = {} for key in Viewport2Options.keys(): - attr = f'hardwareRenderingGlobals.{key}' + attr = "hardwareRenderingGlobals.{0}".format(key) try: viewport2_options[key] = cmds.getAttr(attr) except ValueError: continue return { - 'camera': camera, - 'display_options': display_options, - 'camera_options': camera_options, - 'viewport_options': viewport_options, - 'viewport2_options': viewport2_options + "camera": camera, + "display_options": display_options, + "camera_options": camera_options, + "viewport_options": viewport_options, + "viewport2_options": viewport2_options } def parse_active_scene(): """Parse active scene for arguments for capture() - Resolution taken from render common. + *Resolution taken from render settings. """ - time_control = mel.eval('$gPlayBackSlider = $gPlayBackSlider') + time_control = mel.eval("$gPlayBackSlider = $gPlayBackSlider") return { - 'start_frame': cmds.playbackOptions(minTime=True, query=True), - 'end_frame': cmds.playbackOptions(maxTime=True, query=True), - 'width': cmds.getAttr('defaultResolution.width'), - 'height': cmds.getAttr('defaultResolution.height'), - 'compression': cmds.optionVar(query='playblastCompression'), - 'filename': (cmds.optionVar(query='playblastFile') - if cmds.optionVar(query='playblastSaveToFile') else None), - 'format': cmds.optionVar(query='playblastFormat'), - 'off_screen': (True if cmds.optionVar(query='playblastOffscreen') + "start_frame": cmds.playbackOptions(minTime=True, query=True), + "end_frame": cmds.playbackOptions(maxTime=True, query=True), + "width": cmds.getAttr("defaultResolution.width"), + "height": cmds.getAttr("defaultResolution.height"), + "compression": cmds.optionVar(query="playblastCompression"), + "filename": (cmds.optionVar(query="playblastFile") + if cmds.optionVar(query="playblastSaveToFile") else None), + "format": cmds.optionVar(query="playblastFormat"), + "off_screen": (True if cmds.optionVar(query="playblastOffscreen") else False), - 'show_ornaments': (True if cmds.optionVar(query='playblastShowOrnaments') + "show_ornaments": (True if cmds.optionVar(query="playblastShowOrnaments") else False), - 'quality': cmds.optionVar(query='playblastQuality'), - 'sound': cmds.timeControl(time_control, q=True, sound=True) or None + "quality": cmds.optionVar(query="playblastQuality"), + "sound": cmds.timeControl(time_control, q=True, sound=True) or None } @@ -530,53 +524,53 @@ def apply_scene(**options): """Apply options from scene Example: - >>> apply_scene({'start_frame': 1009}) + >>> apply_scene({"start_frame": 1009}) Arguments: options (dict): Scene options """ - if 'start_frame' in options: - cmds.playbackOptions(minTime=options['start_frame']) + if "start_frame" in options: + cmds.playbackOptions(minTime=options["start_frame"]) - if 'end_frame' in options: - cmds.playbackOptions(maxTime=options['end_frame']) + if "end_frame" in options: + cmds.playbackOptions(maxTime=options["end_frame"]) - if 'width' in options: - cmds.setAttr('defaultResolution.width', options['width']) + if "width" in options: + cmds.setAttr("defaultResolution.width", options["width"]) - if 'height' in options: - cmds.setAttr('defaultResolution.height', options['height']) + if "height" in options: + cmds.setAttr("defaultResolution.height", options["height"]) - if 'compression' in options: + if "compression" in options: cmds.optionVar( - stringValue=['playblastCompression', options['compression']] + stringValue=["playblastCompression", options["compression"]] ) - if 'filename' in options: + if "filename" in options: cmds.optionVar( - stringValue=['playblastFile', options['filename']] + stringValue=["playblastFile", options["filename"]] ) - if 'format' in options: + if "format" in options: cmds.optionVar( - stringValue=['playblastFormat', options['format']] + stringValue=["playblastFormat", options["format"]] ) - if 'off_screen' in options: + if "off_screen" in options: cmds.optionVar( - intValue=['playblastFormat', options['off_screen']] + intValue=["playblastFormat", options["off_screen"]] ) - if 'show_ornaments' in options: + if "show_ornaments" in options: cmds.optionVar( - intValue=['show_ornaments', options['show_ornaments']] + intValue=["show_ornaments", options["show_ornaments"]] ) - if 'quality' in options: + if "quality" in options: cmds.optionVar( - floatValue=['playblastQuality', options['quality']] + floatValue=["playblastQuality", options["quality"]] ) @@ -593,19 +587,6 @@ def _applied_view(panel, **options): apply_view(panel, **original) -def get_width_height(bound, width, height): - aspect = float(max((width, height))) / float(min((width, height))) - is_horizontal = width > height - - if is_horizontal: - _width = bound - _height = bound / aspect - else: - _width = bound / aspect - _height = bound - return int(_width), int(_height) - - @contextlib.contextmanager def _independent_panel(width, height, off_screen=False): """Create capture-window context without decorations @@ -619,20 +600,25 @@ def _independent_panel(width, height, off_screen=False): ... cmds.capture() """ - _width, _height = get_width_height(800, width, height) + + # center panel on screen + screen_width, screen_height = _get_screen_size() + topLeft = [int((screen_height - height) / 2.0), + int((screen_width - width) / 2.0)] + window = cmds.window( - width=_width, - height=_height, - # topLeftCorner=topLeft, + width=width, + height=height, + topLeftCorner=topLeft, menuBarVisible=False, titleBar=False, - visible=off_screen - ) + visible=not off_screen + ) cmds.paneLayout() panel = cmds.modelPanel( menuBarVisible=False, label='CapturePanel' - ) + ) # Hide icons under panel menus bar_layout = cmds.modelPanel(panel, q=True, barLayout=True) @@ -641,12 +627,12 @@ def _independent_panel(width, height, off_screen=False): if not off_screen: cmds.showWindow(window) - # Set the modelEditor of the modelPanel as the active view, so it takes + # Set the modelEditor of the modelPanel as the active view so it takes # the playback focus. Does seem redundant with the `refresh` added in. editor = cmds.modelPanel(panel, query=True, modelEditor=True) cmds.modelEditor(editor, edit=True, activeView=True) - # Force a draw refresh of Maya, so it keeps focus on the new panel + # Force a draw refresh of Maya so it keeps focus on the new panel # This focus is required to force preview playback in the independent panel cmds.refresh(force=True) @@ -668,22 +654,23 @@ def _applied_camera_options(options, panel): old_options = dict() for opt in options.copy(): try: - old_options[opt] = cmds.getAttr(camera + '.' + opt) + old_options[opt] = cmds.getAttr(camera + "." + opt) except: sys.stderr.write( - f'Could not get camera attribute for capture: {opt}' - ) + "Could not get camera attribute " + "for capture: %s" % opt + ) options.pop(opt) for opt, value in options.items(): - cmds.setAttr(camera + '.' + opt, value) + cmds.setAttr(camera + "." + opt, value) try: yield finally: if old_options: for opt, value in old_options.items(): - cmds.setAttr(camera + '.' + opt, value) + cmds.setAttr(camera + "." + opt, value) @contextlib.contextmanager @@ -718,7 +705,7 @@ def _applied_display_options(options): **{ preference: value } - ) + ) try: yield @@ -732,7 +719,7 @@ def _applied_display_options(options): **{ preference: original[preference] } - ) + ) @contextlib.contextmanager @@ -764,7 +751,7 @@ def _applied_viewport2_options(options): """Context manager for setting viewport 2.0 options. These options are applied by setting attributes on the - 'hardwareRenderingGlobals' node. + "hardwareRenderingGlobals" node. """ @@ -774,20 +761,20 @@ def _applied_viewport2_options(options): original = {} for opt in options.copy(): try: - original[opt] = cmds.getAttr('hardwareRenderingGlobals.' + opt) + original[opt] = cmds.getAttr("hardwareRenderingGlobals." + opt) except ValueError: options.pop(opt) # Apply settings for opt, value in options.items(): - cmds.setAttr('hardwareRenderingGlobals.' + opt, value) + cmds.setAttr("hardwareRenderingGlobals." + opt, value) try: yield finally: # Restore previous settings for opt, value in original.items(): - cmds.setAttr('hardwareRenderingGlobals.' + opt, value) + cmds.setAttr("hardwareRenderingGlobals." + opt, value) @contextlib.contextmanager @@ -820,33 +807,33 @@ def _maintain_camera(panel, camera): cmds.lookThru(panel, camera) else: state = dict( - (camera, cmds.getAttr(camera + '.rnd')) - for camera in cmds.ls(type='camera') - ) - cmds.setAttr(camera + '.rnd', True) + (camera, cmds.getAttr(camera + ".rnd")) + for camera in cmds.ls(type="camera") + ) + cmds.setAttr(camera + ".rnd", True) try: yield finally: for camera, renderable in state.items(): - cmds.setAttr(camera + '.rnd', renderable) + cmds.setAttr(camera + ".rnd", renderable) @contextlib.contextmanager def _disabled_inview_messages(): """Disable in-view help messages during the context""" - original = cmds.optionVar(q='inViewMessageEnable') - cmds.optionVar(iv=('inViewMessageEnable', 0)) + original = cmds.optionVar(q="inViewMessageEnable") + cmds.optionVar(iv=("inViewMessageEnable", 0)) try: yield finally: - cmds.optionVar(iv=('inViewMessageEnable', original)) + cmds.optionVar(iv=("inViewMessageEnable", original)) def _image_to_clipboard(path): """Copies the image at path to the system's global clipboard.""" if _in_standalone(): - raise Exception('Cannot copy to clipboard from Maya Standalone') + raise Exception("Cannot copy to clipboard from Maya Standalone") image = QtGui.QImage(path) clipboard = QtWidgets.QApplication.clipboard() @@ -863,28 +850,34 @@ def _get_screen_size(): def _in_standalone(): - return not hasattr(cmds, 'about') or cmds.about(batch=True) + return not hasattr(cmds, "about") or cmds.about(batch=True) -version = mel.eval('getApplicationVersionAsFloat') +# -------------------------------- +# +# Apply version specific settings +# +# -------------------------------- + +version = mel.eval("getApplicationVersionAsFloat") if version > 2015: Viewport2Options.update( { - 'hwFogAlpha': 1.0, - 'hwFogFalloff': 0, - 'hwFogDensity': 0.1, - 'hwFogEnable': False, - 'holdOutDetailMode': 1, - 'hwFogEnd': 100.0, - 'holdOutMode': True, - 'hwFogColorR': 0.5, - 'hwFogColorG': 0.5, - 'hwFogColorB': 0.5, - 'hwFogStart': 0.0, + "hwFogAlpha": 1.0, + "hwFogFalloff": 0, + "hwFogDensity": 0.1, + "hwFogEnable": False, + "holdOutDetailMode": 1, + "hwFogEnd": 100.0, + "holdOutMode": True, + "hwFogColorR": 0.5, + "hwFogColorG": 0.5, + "hwFogColorB": 0.5, + "hwFogStart": 0.0, } ) ViewportOptions.update( { - 'motionTrails': False + "motionTrails": False } - ) + ) \ No newline at end of file diff --git a/bookmarks/shortcuts.py b/bookmarks/shortcuts.py index 85075ff3..25faf00c 100644 --- a/bookmarks/shortcuts.py +++ b/bookmarks/shortcuts.py @@ -37,6 +37,9 @@ RevealItem = common.idx() RevealAltItem = common.idx() +CopyProperties = common.idx() +PasteProperties = common.idx() + Quit = common.idx() Minimize = common.idx() Maximize = common.idx() @@ -209,6 +212,20 @@ 'description': 'Reveal primary URL...', 'shortcut': None, }, + CopyProperties: { + 'value': 'Ctrl+Alt+C', + 'default': 'Ctrl+Alt+C', + 'repeat': False, + 'description': 'Copy file path', + 'shortcut': None, + }, + PasteProperties: { + 'value': 'Ctrl+Alt+V', + 'default': 'Ctrl+Alt+V', + 'repeat': False, + 'description': 'Copy file path', + 'shortcut': None, + }, EditItem: { 'value': 'Ctrl+E', 'default': 'Ctrl+E', diff --git a/bookmarks/topbar/buttons.py b/bookmarks/topbar/buttons.py index bb76d8d9..2a364572 100644 --- a/bookmarks/topbar/buttons.py +++ b/bookmarks/topbar/buttons.py @@ -258,7 +258,10 @@ def update(self): class RefreshButton(BaseControlButton): - def __init__(self, parent=None): + def __init__( + self, + parent=None + ): s = shortcuts.string( shortcuts.MainWidgetShortcuts, shortcuts.Refresh diff --git a/bookmarks/topbar/topbar.py b/bookmarks/topbar/topbar.py index f5338d5f..d0161f19 100644 --- a/bookmarks/topbar/topbar.py +++ b/bookmarks/topbar/topbar.py @@ -134,7 +134,7 @@ def __init__(self, parent=None): self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) self.label_widget = None - self.note_widget = None + self.refresh_widget = None self.task_folder_widget = None self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) @@ -160,10 +160,7 @@ def _create_ui(self): size=common.size(common.size_font_medium) * 1.1, parent=self ) - self.note_widget = ui.PaintedLabel( - '', - color=common.color(common.color_blue), - size=common.size(common.size_font_medium) * 0.9, + self.refresh_widget = buttons.RefreshButton( parent=self ) @@ -190,7 +187,7 @@ def _create_ui(self): self.layout().addWidget(self.arrow_left_button) self.layout().addWidget(self.label_widget) self.layout().addWidget(self.task_folder_widget) - self.layout().addWidget(self.note_widget) + self.layout().addWidget(self.refresh_widget) self.layout().addWidget(self.arrow_right_button) self.layout().addStretch() @@ -326,11 +323,11 @@ def update(self, *args, **kwargs): data = common.get_data(p, k, t) if data and data.refresh_needed: - self.note_widget.setHidden(False) - self.note_widget.setText('(refresh needed)') + self.refresh_widget.setHidden(False) + self.refresh_widget.setText('(refresh needed)') else: - self.note_widget.setHidden(True) - self.note_widget.setText('') + self.refresh_widget.setHidden(True) + self.refresh_widget.setText('') class TopBarWidget(QtWidgets.QWidget): @@ -380,7 +377,6 @@ def _create_ui(self): if idx > common.FavouriteTab: widget.layout().addWidget(self._buttons[idx], 0) - # widget.layout().addSpacing(o * 0.5) else: widget.layout().addWidget(self._buttons[idx], 1)