diff --git a/pype/hosts/maya/api/__init__.py b/pype/hosts/maya/api/__init__.py index 27e15e0b25..ea100dda0f 100644 --- a/pype/hosts/maya/api/__init__.py +++ b/pype/hosts/maya/api/__init__.py @@ -7,7 +7,7 @@ from maya import utils, cmds from avalon import api as avalon from avalon import pipeline from avalon.maya import suspended_refresh -from avalon.maya.pipeline import IS_HEADLESS, _on_task_changed +from avalon.maya.pipeline import IS_HEADLESS from avalon.tools import workfiles from pyblish import api as pyblish from pype.lib import any_outdated @@ -45,9 +45,7 @@ def install(): avalon.on("open", on_open) avalon.on("new", on_new) avalon.before("save", on_before_save) - - log.info("Overriding existing event 'taskChanged'") - override_event("taskChanged", on_task_changed) + avalon.on("taskChanged", on_task_changed) log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["imagesequence"] @@ -61,24 +59,6 @@ def uninstall(): menu.uninstall() -def override_event(event, callback): - """ - Override existing event callback - Args: - event (str): name of the event - callback (function): callback to be triggered - - Returns: - None - - """ - - ref = weakref.WeakSet() - ref.add(callback) - - pipeline._registered_event_handlers[event] = ref - - def on_init(_): avalon.logger.info("Running callback on init..") @@ -215,7 +195,6 @@ def on_new(_): def on_task_changed(*args): """Wrapped function of app initialize and maya's on task changed""" # Run - _on_task_changed() with suspended_refresh(): lib.set_context_settings() lib.update_content_on_context_change() diff --git a/pype/hosts/maya/api/action.py b/pype/hosts/maya/api/action.py index 35a57a4445..f623298451 100644 --- a/pype/hosts/maya/api/action.py +++ b/pype/hosts/maya/api/action.py @@ -72,7 +72,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): nodes (list): all nodes to regenerate ids on """ - from pype.hosts.maya import lib + from pype.hosts.maya.api import lib import avalon.io as io asset = instance.data['asset'] diff --git a/pype/hosts/maya/api/lib.py b/pype/hosts/maya/api/lib.py index dc802b6a37..44f1577f7f 100644 --- a/pype/hosts/maya/api/lib.py +++ b/pype/hosts/maya/api/lib.py @@ -2127,15 +2127,9 @@ def bake_to_world_space(nodes, def load_capture_preset(path=None, data=None): - import capture_gui import capture - if data: - preset = data - else: - path = path - preset = capture_gui.lib.load_json(path) - print(preset) + preset = data options = dict() @@ -2177,29 +2171,27 @@ def load_capture_preset(path=None, data=None): temp_options2 = {} id = 'Viewport Options' - light_options = { - 0: "default", - 1: 'all', - 2: 'selected', - 3: 'flat', - 4: 'nolights'} for key in preset[id]: - if key == 'high_quality': - if preset[id][key] == True: - temp_options2['multiSampleEnable'] = True - temp_options2['multiSampleCount'] = 4 - temp_options2['textureMaxResolution'] = 1024 + if key == 'textureMaxResolution': + if preset[id][key] > 0: + temp_options2['textureMaxResolution'] = preset[id][key] temp_options2['enableTextureMaxRes'] = True temp_options2['textureMaxResMode'] = 1 else: - temp_options2['multiSampleEnable'] = False - temp_options2['multiSampleCount'] = 4 - temp_options2['textureMaxResolution'] = 512 - temp_options2['enableTextureMaxRes'] = True + temp_options2['textureMaxResolution'] = preset[id][key] + temp_options2['enableTextureMaxRes'] = False temp_options2['textureMaxResMode'] = 0 + if key == 'multiSample': + if preset[id][key] > 0: + temp_options2['multiSampleEnable'] = True + temp_options2['multiSampleCount'] = preset[id][key] + else: + temp_options2['multiSampleEnable'] = False + temp_options2['multiSampleCount'] = preset[id][key] + if key == 'ssaoEnable': - if preset[id][key] == True: + if preset[id][key] is True: temp_options2['ssaoEnable'] = True else: temp_options2['ssaoEnable'] = False @@ -2211,18 +2203,17 @@ def load_capture_preset(path=None, data=None): if key == 'headsUpDisplay': temp_options['headsUpDisplay'] = True - if key == 'displayLights': - temp_options[str(key)] = light_options[preset[id][key]] else: temp_options[str(key)] = preset[id][key] for key in ['override_viewport_options', 'high_quality', 'alphaCut', - 'gpuCacheDisplayFilter']: - temp_options.pop(key, None) - - for key in ['ssaoEnable']: + 'gpuCacheDisplayFilter', + 'multiSample', + 'ssaoEnable', + 'textureMaxResolution' + ]: temp_options.pop(key, None) options['viewport_options'] = temp_options @@ -2686,7 +2677,7 @@ def update_content_on_context_change(): def show_message(title, msg): from avalon.vendor.Qt import QtWidgets - from ...widgets import message_window + from pype.widgets import message_window # Find maya main window top_level_widgets = {w.objectName(): w for w in diff --git a/pype/hosts/maya/plugins/publish/extract_playblast.py b/pype/hosts/maya/plugins/publish/extract_playblast.py index 770b077e41..99411e7f53 100644 --- a/pype/hosts/maya/plugins/publish/extract_playblast.py +++ b/pype/hosts/maya/plugins/publish/extract_playblast.py @@ -23,6 +23,7 @@ class ExtractPlayblast(pype.api.Extractor): hosts = ["maya"] families = ["review"] optional = True + capture_preset = {} def process(self, instance): self.log.info("Extracting capture..") @@ -43,15 +44,9 @@ class ExtractPlayblast(pype.api.Extractor): # get cameras camera = instance.data['review_camera'] - capture_preset = ( - instance.context.data['project_settings']['maya']['capture'] - ) - try: - preset = lib.load_capture_preset(data=capture_preset) - except Exception: - preset = {} - self.log.info('using viewport preset: {}'.format(preset)) + preset = lib.load_capture_preset(data=self.capture_preset) + preset['camera'] = camera preset['format'] = "image" @@ -101,6 +96,9 @@ class ExtractPlayblast(pype.api.Extractor): # Remove panel key since it's internal value to capture_gui preset.pop("panel", None) + + self.log.info('using viewport preset: {}'.format(preset)) + path = capture.capture(**preset) playblast = self._fix_playblast_output_path(path) diff --git a/pype/hosts/maya/plugins/publish/extract_thumbnail.py b/pype/hosts/maya/plugins/publish/extract_thumbnail.py index 49511f6af6..9f6d16275f 100644 --- a/pype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/pype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -34,7 +34,7 @@ class ExtractThumbnail(pype.api.Extractor): capture_preset = "" capture_preset = ( - instance.context.data["project_settings"]['maya']['capture'] + instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast'] ) try: diff --git a/pype/hosts/nuke/plugins/load/load_mov.py b/pype/hosts/nuke/plugins/load/load_mov.py index 435f26ad98..830359ccf9 100644 --- a/pype/hosts/nuke/plugins/load/load_mov.py +++ b/pype/hosts/nuke/plugins/load/load_mov.py @@ -95,6 +95,7 @@ class LoadMov(api.Loader): containerise, viewer_update_and_undo_stop ) + version = context['version'] version_data = version.get("data", {}) repr_id = context["representation"]["_id"] diff --git a/pype/hosts/photoshop/plugins/create/create_image.py b/pype/hosts/photoshop/plugins/create/create_image.py index 54b6efad29..03250acd48 100644 --- a/pype/hosts/photoshop/plugins/create/create_image.py +++ b/pype/hosts/photoshop/plugins/create/create_image.py @@ -73,5 +73,17 @@ class CreateImage(pype.api.Creator): groups.append(group) for group in groups: + long_names = [] + if group.long_name: + for directory in group.long_name[::-1]: + name = directory.replace(stub.PUBLISH_ICON, '').\ + replace(stub.LOADED_ICON, '') + long_names.append(name) + self.data.update({"subset": "image" + group.name}) + self.data.update({"uuid": str(group.id)}) + self.data.update({"long_name": "_".join(long_names)}) stub.imprint(group, self.data) + # reusing existing group, need to rename afterwards + if not create_group: + stub.rename_layer(group.id, stub.PUBLISH_ICON + group.name) diff --git a/pype/hosts/photoshop/plugins/publish/collect_instances.py b/pype/hosts/photoshop/plugins/publish/collect_instances.py index 14803cceee..5390df768b 100644 --- a/pype/hosts/photoshop/plugins/publish/collect_instances.py +++ b/pype/hosts/photoshop/plugins/publish/collect_instances.py @@ -24,6 +24,7 @@ class CollectInstances(pyblish.api.ContextPlugin): stub = photoshop.stub() layers = stub.get_layers() layers_meta = stub.get_layers_metadata() + instance_names = [] for layer in layers: layer_data = stub.read(layer, layers_meta) @@ -41,14 +42,20 @@ class CollectInstances(pyblish.api.ContextPlugin): # self.log.info("%s skipped, it was empty." % layer.Name) # continue - instance = context.create_instance(layer.name) + instance = context.create_instance(layer_data["subset"]) instance.append(layer) instance.data.update(layer_data) instance.data["families"] = self.families_mapping[ layer_data["family"] ] instance.data["publish"] = layer.visible + instance_names.append(layer_data["subset"]) # Produce diagnostic message for any graphical # user interface interested in visualising it. self.log.info("Found: \"%s\" " % instance.data["name"]) + self.log.info("instance: {} ".format(instance.data)) + + if len(instance_names) != len(set(instance_names)): + self.log.warning("Duplicate instances found. " + + "Remove unwanted via SubsetManager") diff --git a/pype/hosts/photoshop/plugins/publish/validate_naming.py b/pype/hosts/photoshop/plugins/publish/validate_naming.py index 2483adcb5e..48f5901233 100644 --- a/pype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/pype/hosts/photoshop/plugins/publish/validate_naming.py @@ -25,11 +25,15 @@ class ValidateNamingRepair(pyblish.api.Action): for instance in instances: self.log.info("validate_naming instance {}".format(instance)) name = instance.data["name"].replace(" ", "_") + name = name.replace(instance.data["family"], '') instance[0].Name = name data = stub.read(instance[0]) data["subset"] = "image" + name stub.imprint(instance[0], data) + name = stub.PUBLISH_ICON + name + stub.rename_layer(instance.data["uuid"], name) + return True @@ -46,8 +50,11 @@ class ValidateNaming(pyblish.api.InstancePlugin): actions = [ValidateNamingRepair] def process(self, instance): - msg = "Name \"{}\" is not allowed.".format(instance.data["name"]) + help_msg = ' Use Repair action (A) in Pyblish to fix it.' + msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"], + help_msg) assert " " not in instance.data["name"], msg - msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"]) + msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"], + help_msg) assert " " not in instance.data["subset"], msg diff --git a/pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py b/pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py new file mode 100644 index 0000000000..5871f79668 --- /dev/null +++ b/pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py @@ -0,0 +1,26 @@ +import pyblish.api +import pype.api + + +class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): + """ + Validate that all subset's names are unique. + """ + + label = "Validate Subset Uniqueness" + hosts = ["photoshop"] + order = pype.api.ValidateContentsOrder + families = ["image"] + + def process(self, context): + subset_names = [] + + for instance in context: + if instance.data.get('publish'): + subset_names.append(instance.data.get('subset')) + + msg = ( + "Instance subset names are not unique. " + + "Remove duplicates via SubsetManager." + ) + assert len(subset_names) == len(set(subset_names)), msg diff --git a/pype/lib/pype_info.py b/pype/lib/pype_info.py new file mode 100644 index 0000000000..cbcc5811a0 --- /dev/null +++ b/pype/lib/pype_info.py @@ -0,0 +1,87 @@ +import os +import json +import datetime +import platform +import getpass +import socket + +import pype.version +from pype.settings.lib import get_local_settings +from .execute import get_pype_execute_args +from .local_settings import get_local_site_id + + +def get_pype_version(): + """Version of pype that is currently used.""" + return pype.version.__version__ + + +def get_pype_info(): + """Information about currently used Pype process.""" + executable_args = get_pype_execute_args() + if len(executable_args) == 1: + version_type = "build" + else: + version_type = "code" + + return { + "version": get_pype_version(), + "version_type": version_type, + "executable": executable_args[-1], + "pype_root": os.environ["PYPE_ROOT"], + "mongo_url": os.environ["PYPE_MONGO"] + } + + +def get_workstation_info(): + """Basic information about workstation.""" + host_name = socket.gethostname() + try: + host_ip = socket.gethostbyname(host_name) + except socket.gaierror: + host_ip = "127.0.0.1" + + return { + "hostname": host_name, + "hostip": host_ip, + "username": getpass.getuser(), + "system_name": platform.system(), + "local_id": get_local_site_id() + } + + +def get_all_current_info(): + """All information about current process in one dictionary.""" + return { + "pype": get_pype_info(), + "workstation": get_workstation_info(), + "env": os.environ.copy(), + "local_settings": get_local_settings() + } + + +def extract_pype_info_to_file(dirpath): + """Extract all current info to a file. + + It is possible to define onpy directory path. Filename is concatenated with + pype version, workstation site id and timestamp. + + Args: + dirpath (str): Path to directory where file will be stored. + + Returns: + filepath (str): Full path to file where data were extracted. + """ + filename = "{}_{}_{}.json".format( + get_pype_version(), + get_local_site_id(), + datetime.datetime.now().strftime("%y%m%d%H%M%S") + ) + filepath = os.path.join(dirpath, filename) + data = get_all_current_info() + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(filepath, "w") as file_stream: + json.dump(data, file_stream, indent=4) + return filepath diff --git a/pype/modules/base.py b/pype/modules/base.py index 7efd00e39e..03a5965841 100644 --- a/pype/modules/base.py +++ b/pype/modules/base.py @@ -108,6 +108,7 @@ class ITrayModule: would do nothing. """ tray_initialized = False + _tray_manager = None @abstractmethod def tray_init(self): @@ -138,6 +139,20 @@ class ITrayModule: """ pass + def show_tray_message(self, title, message, icon=None, msecs=None): + """Show tray message. + + Args: + title (str): Title of message. + message (str): Content of message. + icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is + Information icon, may differ by Qt version. + msecs (int): Duration of message visibility in miliseconds. + Default is 10000 msecs, may differ by Qt version. + """ + if self._tray_manager: + self._tray_manager.show_tray_message(title, message, icon, msecs) + class ITrayAction(ITrayModule): """Implementation of Tray action. @@ -638,8 +653,10 @@ class TrayModulesManager(ModulesManager): self.modules_by_id = {} self.modules_by_name = {} self._report = {} + self.tray_manager = None - def initialize(self, tray_menu): + def initialize(self, tray_manager, tray_menu): + self.tray_manager = tray_manager self.initialize_modules() self.tray_init() self.connect_modules() @@ -658,6 +675,7 @@ class TrayModulesManager(ModulesManager): prev_start_time = time_start for module in self.get_enabled_tray_modules(): try: + module._tray_manager = self.tray_manager module.tray_init() module.tray_initialized = True except Exception: diff --git a/pype/resources/icons/working.svg b/pype/resources/icons/working.svg deleted file mode 100644 index fe73f15a31..0000000000 --- a/pype/resources/icons/working.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Working... - diff --git a/pype/settings/defaults/project_settings/maya.json b/pype/settings/defaults/project_settings/maya.json index 03955732d2..75aa32fb36 100644 --- a/pype/settings/defaults/project_settings/maya.json +++ b/pype/settings/defaults/project_settings/maya.json @@ -1,111 +1,11 @@ { - "capture": { - "Codec": { - "compression": "jpg", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 0.7, - 0.7, - 0.7 - ], - "backgroundBottom": [ - 0.7, - 0.7, - 0.7 - ], - "backgroundTop": [ - 0.7, - 0.7, - 0.7 - ], - "override_display": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true - }, - "IO": { - "name": "", - "open_finished": true, - "raw_frame_numbers": true, - "recent_playblasts": [], - "save_file": true - }, - "PanZoom": { - "pan_zoom": true - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "width": 1080, - "height": 1920, - "percent": 1.0, - "mode": "Custom" - }, - "Time Range": { - "start_frame": 0, - "end_frame": 0, - "frame": "", - "time": "Time Slider" - }, - "Viewport Options": { - "cameras": false, - "clipGhosts": false, - "controlVertices": false, - "deformers": false, - "dimensions": false, - "displayLights": 0, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "gpuCacheDisplayFilter": false, - "greasePencils": false, - "grid": false, - "hairSystems": true, - "handles": false, - "high_quality": true, - "hud": false, - "hulls": false, - "ikHandles": false, - "imagePlane": true, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "nurbsCurves": false, - "nurbsSurfaces": false, - "override_viewport_options": true, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "shadows": true, - "strokes": false, - "subdivSurfaces": false, - "textures": false, - "twoSidedLighting": true - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 - } + "ext_mapping": { + "model": "ma", + "mayaAscii": "ma", + "camera": "ma", + "rig": "ma", + "workfile": "ma", + "yetiRig": "ma" }, "create": { "CreateAnimation": { @@ -299,6 +199,10 @@ "enabled": false, "optional": true }, + "ValidateMeshNormalsUnlocked": { + "enabled": false, + "optional": true + }, "ValidateMeshUVSetMap1": { "enabled": false, "optional": true @@ -336,7 +240,7 @@ "optional": true }, "ValidateTransformZero": { - "enabled": true, + "enabled": false, "optional": true }, "ValidateCameraAttributes": { @@ -351,6 +255,105 @@ "enabled": true, "optional": true }, + "ExtractPlayblast": { + "capture_preset": { + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.7, + 0.7, + 0.7 + ], + "backgroundBottom": [ + 0.7, + 0.7, + 0.7 + ], + "backgroundTop": [ + 0.7, + 0.7, + 0.7 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1080, + "height": 1920, + "percent": 1.0, + "mode": "Custom" + }, + "Viewport Options": { + "override_viewport_options": true, + "displayLights": "0", + "textureMaxResolution": 1024, + "multiSample": 4, + "shadows": true, + "textures": true, + "twoSidedLighting": true, + "ssaoEnable": true, + "cameras": false, + "clipGhosts": false, + "controlVertices": false, + "deformers": false, + "dimensions": false, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": true, + "handles": false, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": true, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "nurbsCurves": false, + "nurbsSurfaces": false, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "strokes": false, + "subdivSurfaces": false + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } + } + }, "ExtractCameraAlembic": { "enabled": true, "optional": true, diff --git a/pype/settings/defaults/project_settings/nuke.json b/pype/settings/defaults/project_settings/nuke.json index 517065f79a..5821584932 100644 --- a/pype/settings/defaults/project_settings/nuke.json +++ b/pype/settings/defaults/project_settings/nuke.json @@ -11,6 +11,30 @@ "PreCollectNukeInstances": { "sync_workfile_version": true }, + "ValidateKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "ValidateOutputResolution": { + "enabled": true, + "optional": true + }, + "ValidateGizmo": { + "enabled": true, + "optional": true + }, + "ValidateScript": { + "enabled": true, + "optional": true + }, + "ValidateNukeWriteBoundingBox": { + "enabled": true, + "optional": true + }, "ExtractThumbnail": { "enabled": true, "nodes": { @@ -38,14 +62,6 @@ ] } }, - "ValidateKnobs": { - "enabled": false, - "knobs": { - "render": { - "review": true - } - } - }, "ExtractReviewDataLut": { "enabled": false }, @@ -61,22 +77,25 @@ "deadline_pool": "", "deadline_pool_secondary": "", "deadline_chunk_size": 1 - }, - "ValidateOutputResolution": { + } + }, + "load": { + "LoadImage": { "enabled": true, - "optional": true + "representations": [] }, - "ValidateGizmo": { + "LoadMov": { "enabled": true, - "optional": true + "representations": [] }, - "ValidateScript": { + "LoadSequence": { "enabled": true, - "optional": true - }, - "ValidateNukeWriteBoundingBox": { - "enabled": true, - "optional": true + "representations": [ + "png", + "jpg", + "exr", + "" + ] } }, "workfile_build": { diff --git a/pype/settings/defaults/project_settings/standalonepublisher.json b/pype/settings/defaults/project_settings/standalonepublisher.json index ad1b5e82b2..08895bcba9 100644 --- a/pype/settings/defaults/project_settings/standalonepublisher.json +++ b/pype/settings/defaults/project_settings/standalonepublisher.json @@ -1,14 +1,4 @@ { - "publish": { - "ExtractThumbnailSP": { - "ffmpeg_args": { - "input": [ - "gamma 2.2" - ], - "output": [] - } - } - }, "create": { "create_workfile": { "name": "workfile", @@ -121,5 +111,15 @@ "create_image": "Image", "create_matchmove": "Matchmove" } + }, + "publish": { + "ExtractThumbnailSP": { + "ffmpeg_args": { + "input": [ + "gamma 2.2" + ], + "output": [] + } + } } } \ No newline at end of file diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_maya.json b/pype/settings/entities/schemas/projects_schema/schema_project_maya.json index 7a270b0046..0a59cab510 100644 --- a/pype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/pype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -6,8 +6,13 @@ "is_file": true, "children": [ { - "type": "schema", - "name": "schema_maya_capture" + "type": "dict-modifiable", + "key": "ext_mapping", + "label": "Extension Mapping", + "use_label_wrap": true, + "object_type": { + "type": "text" + } }, { "type": "schema", diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 0548bd3544..220d56a306 100644 --- a/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -45,6 +45,11 @@ "type": "schema", "name": "schema_nuke_publish", "template_data": [] + }, + { + "type": "schema", + "name": "schema_nuke_load", + "template_data": [] }, { "type": "schema", diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json deleted file mode 100644 index 4745a19075..0000000000 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ /dev/null @@ -1,581 +0,0 @@ -{ - "type": "dict", - "collapsible": true, - "key": "capture", - "label": "Maya Playblast settings", - "is_file": true, - "children": [ - { - "type": "dict", - "key": "Codec", - "children": [ - { - "type": "label", - "label": "Codec" - }, - { - "type": "text", - "key": "compression", - "label": "Compression type" - }, - { - "type": "text", - "key": "format", - "label": "Data format" - }, - { - "type": "number", - "key": "quality", - "label": "Quality", - "decimal": 0, - "minimum": 0, - "maximum": 100 - }, - - { - "type": "splitter" - } - ] - }, - { - "type": "dict", - "key": "Display Options", - "children": [ - { - "type": "label", - "label": "Display Options" - }, - { - "type": "list-strict", - "key": "background", - "label": "Background Color: ", - "object_types": [ - { - "label": "Red", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Green", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Blue", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - } - ] - }, - { - "type": "list-strict", - "key": "backgroundBottom", - "label": "Background Bottom: ", - "object_types": [ - { - "label": "Red", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Green", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Blue", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - } - ] - }, - { - "type": "list-strict", - "key": "backgroundTop", - "label": "Background Top: ", - "object_types": [ - { - "label": "Red", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Green", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Blue", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - } - ] - }, - { - "type": "boolean", - "key": "override_display", - "label": "Override display options" - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "key": "Generic", - "children": [ - { - "type": "label", - "label": "Generic" - }, - { - "type": "boolean", - "key": "isolate_view", - "label": " Isolate view" - }, - { - "type": "boolean", - "key": "off_screen", - "label": " Off Screen" - } - ] - }, - { - "type": "dict", - "key": "IO", - "children": [ - { - "type": "label", - "label": "IO" - }, - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "boolean", - "key": "open_finished", - "label": "Open finished" - }, - { - "type": "boolean", - "key": "raw_frame_numbers", - "label": "Raw frame numbers" - }, - { - "type": "list", - "key": "recent_playblasts", - "label": "Recent Playblasts", - "object_type": "text" - }, - { - "type": "boolean", - "key": "save_file", - "label": "Save file" - } - ] - }, - { - "type": "dict", - "key": "PanZoom", - "children": [ - { - "type": "boolean", - "key": "pan_zoom", - "label": " Pan Zoom" - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "key": "Renderer", - "children": [ - { - "type": "label", - "label": "Renderer" - }, - { - "type": "text", - "key": "rendererName", - "label": " Renderer name" - } - ] - }, - { - "type": "dict", - "key": "Resolution", - "children": [ - { - "type": "splitter" - }, - { - "type": "label", - "label": "Resolution" - }, - { - "type": "number", - "key": "width", - "label": " Width", - "decimal": 0, - "minimum": 0, - "maximum": 99999 - }, - { - "type": "number", - "key": "height", - "label": "Height", - "decimal": 0, - "minimum": 0, - "maximum": 99999 - }, - { - "type": "number", - "key": "percent", - "label": "percent", - "decimal": 1, - "minimum": 0, - "maximum": 200 - }, - { - "type": "text", - "key": "mode", - "label": "Mode" - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "key": "Time Range", - "children": [ - { - "type": "label", - "label": "Time Range" - }, - { - "type": "number", - "key": "start_frame", - "label": " Start frame", - "decimal": 0, - "minimum": 0, - "maximum": 999999 - }, - { - "type": "number", - "key": "end_frame", - "label": "End frame", - "decimal": 0, - "minimum": 0, - "maximum": 999999 - }, - { - "type": "text", - "key": "frame", - "label": "Frame" - }, - { - "type": "text", - "key": "time", - "label": "Time" - } - ] - }, - { - "type": "dict", - "collapsible": true, - "key": "Viewport Options", - "label": "Viewport Options", - "children": [ - { - "type": "boolean", - "key": "cameras", - "label": "cameras" - }, - { - "type": "boolean", - "key": "clipGhosts", - "label": "clipGhosts" - }, - { - "type": "boolean", - "key": "controlVertices", - "label": "controlVertices" - }, - { - "type": "boolean", - "key": "deformers", - "label": "deformers" - }, - { - "type": "boolean", - "key": "dimensions", - "label": "dimensions" - }, - { - "type": "number", - "key": "displayLights", - "label": "displayLights", - "decimal": 0, - "minimum": 0, - "maximum": 10 - }, - { - "type": "boolean", - "key": "dynamicConstraints", - "label": "dynamicConstraints" - }, - { - "type": "boolean", - "key": "dynamics", - "label": "dynamics" - }, - { - "type": "boolean", - "key": "fluids", - "label": "fluids" - }, - { - "type": "boolean", - "key": "follicles", - "label": "follicles" - }, - { - "type": "boolean", - "key": "gpuCacheDisplayFilter", - "label": "gpuCacheDisplayFilter" - }, - { - "type": "boolean", - "key": "greasePencils", - "label": "greasePencils" - }, - { - "type": "boolean", - "key": "grid", - "label": "grid" - }, - { - "type": "boolean", - "key": "hairSystems", - "label": "hairSystems" - }, - { - "type": "boolean", - "key": "handles", - "label": "handles" - }, - { - "type": "boolean", - "key": "high_quality", - "label": "high_quality" - }, - { - "type": "boolean", - "key": "hud", - "label": "hud" - }, - { - "type": "boolean", - "key": "hulls", - "label": "hulls" - }, - { - "type": "boolean", - "key": "ikHandles", - "label": "ikHandles" - }, - { - "type": "boolean", - "key": "imagePlane", - "label": "imagePlane" - }, - { - "type": "boolean", - "key": "joints", - "label": "joints" - }, - { - "type": "boolean", - "key": "lights", - "label": "lights" - }, - { - "type": "boolean", - "key": "locators", - "label": "locators" - }, - { - "type": "boolean", - "key": "manipulators", - "label": "manipulators" - }, - { - "type": "boolean", - "key": "motionTrails", - "label": "motionTrails" - }, - { - "type": "boolean", - "key": "nCloths", - "label": "nCloths" - }, - { - "type": "boolean", - "key": "nParticles", - "label": "nParticles" - }, - { - "type": "boolean", - "key": "nRigids", - "label": "nRigids" - }, - { - "type": "boolean", - "key": "nurbsCurves", - "label": "nurbsCurves" - }, - { - "type": "boolean", - "key": "nurbsSurfaces", - "label": "nurbsSurfaces" - }, - { - "type": "boolean", - "key": "override_viewport_options", - "label": "override_viewport_options" - }, - { - "type": "boolean", - "key": "particleInstancers", - "label": "particleInstancers" - }, - { - "type": "boolean", - "key": "pivots", - "label": "pivots" - }, - { - "type": "boolean", - "key": "planes", - "label": "planes" - }, - { - "type": "boolean", - "key": "pluginShapes", - "label": "pluginShapes" - }, - { - "type": "boolean", - "key": "polymeshes", - "label": "polymeshes" - }, - { - "type": "boolean", - "key": "shadows", - "label": "shadows" - }, - { - "type": "boolean", - "key": "strokes", - "label": "strokes" - }, - { - "type": "boolean", - "key": "subdivSurfaces", - "label": "subdivSurfaces" - }, - { - "type": "boolean", - "key": "textures", - "label": "textures" - }, - { - "type": "boolean", - "key": "twoSidedLighting", - "label": "twoSidedLighting" - } - ] - }, - { - "type": "dict", - "collapsible": true, - "key": "Camera Options", - "label": "Camera Options", - "children": [ - { - "type": "boolean", - "key": "displayGateMask", - "label": "displayGateMask" - }, - { - "type": "boolean", - "key": "displayResolution", - "label": "displayResolution" - }, - { - "type": "boolean", - "key": "displayFilmGate", - "label": "displayFilmGate" - }, - { - "type": "boolean", - "key": "displayFieldChart", - "label": "displayFieldChart" - }, - { - "type": "boolean", - "key": "displaySafeAction", - "label": "displaySafeAction" - }, - { - "type": "boolean", - "key": "displaySafeTitle", - "label": "displaySafeTitle" - }, - { - "type": "boolean", - "key": "displayFilmPivot", - "label": "displayFilmPivot" - }, - { - "type": "boolean", - "key": "displayFilmOrigin", - "label": "displayFilmOrigin" - }, - { - "type": "number", - "key": "overscan", - "label": "overscan", - "decimal": 1, - "minimum": 0, - "maximum": 10 - } - ] - } - ] -} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 58a21c185a..6ecda224ea 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -170,6 +170,10 @@ "key": "ValidateMeshNonManifold", "label": "ValidateMeshNonManifold" }, + { + "key": "ValidateMeshNormalsUnlocked", + "label": "ValidateMeshNormalsUnlocked" + }, { "key": "ValidateMeshUVSetMap1", "label": "ValidateMeshUVSetMap1", @@ -242,6 +246,10 @@ "type": "label", "label": "Extractors" }, + { + "type": "schema_template", + "name": "template_maya_capture" + }, { "type": "dict", "collapsible": true, diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json new file mode 100644 index 0000000000..9d132e33b4 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json @@ -0,0 +1,26 @@ +{ + "type": "dict", + "collapsible": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "schema_template", + "name": "template_loader_plugin", + "template_data": [ + { + "key": "LoadImage", + "label": "Image Loader" + }, + { + "key": "LoadMov", + "label": "Movie Loader" + }, + { + "key": "LoadSequence", + "label": "Image Sequence Loader" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json b/pype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json new file mode 100644 index 0000000000..e4e0b034dd --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json @@ -0,0 +1,541 @@ +[ + { + "type": "dict", + "collapsible": true, + "key": "ExtractPlayblast", + "label": "Extract Playblast settings", + "children": [ + { + "type": "dict", + "key": "capture_preset", + "children": [ + { + "type": "dict", + "key": "Codec", + "children": [ + { + "type": "label", + "label": "Codec" + }, + { + "type": "text", + "key": "compression", + "label": "Compression type" + }, + { + "type": "text", + "key": "format", + "label": "Data format" + }, + { + "type": "number", + "key": "quality", + "label": "Quality", + "decimal": 0, + "minimum": 0, + "maximum": 100 + }, + + { + "type": "splitter" + } + ] + }, + { + "type": "dict", + "key": "Display Options", + "children": [ + { + "type": "label", + "label": "Display Options" + }, + { + "type": "list-strict", + "key": "background", + "label": "Background Color: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "list-strict", + "key": "backgroundBottom", + "label": "Background Bottom: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "list-strict", + "key": "backgroundTop", + "label": "Background Top: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + }, + { + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + } + ] + }, + + { + "type": "dict", + "key": "PanZoom", + "children": [ + { + "type": "boolean", + "key": "pan_zoom", + "label": " Pan Zoom" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Renderer", + "children": [ + { + "type": "label", + "label": "Renderer" + }, + { + "type": "enum", + "key": "rendererName", + "label": "Renderer name", + "enum_items": [ + { "vp2Renderer": "Viewport 2.0" } + ] + } + ] + }, + { + "type": "dict", + "key": "Resolution", + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Resolution" + }, + { + "type": "number", + "key": "width", + "label": " Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "percent", + "label": "percent", + "decimal": 1, + "minimum": 0, + "maximum": 200 + }, + { + "type": "text", + "key": "mode", + "label": "Mode" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "collapsible": true, + "key": "Viewport Options", + "label": "Viewport Options", + "children": [ + { + "type": "boolean", + "key": "override_viewport_options", + "label": "override_viewport_options" + }, + { + "type": "enum", + "key": "displayLights", + "label": "Display Lights", + "enum_items": [ + { "default": "Default Lighting"}, + { "all": "All Lights"}, + { "selected": "Selected Lights"}, + { "flat": "Flat Lighting"}, + { "nolights": "No Lights"} + ] + }, + { + "type": "number", + "key": "textureMaxResolution", + "label": "Texture Clamp Resolution", + "decimal": 0 + }, + { + "type": "number", + "key": "multiSample", + "label": "Anti Aliasing Samples", + "decimal": 0, + "minimum": 0, + "maximum": 32 + }, + { + "type": "boolean", + "key": "shadows", + "label": "Display Shadows" + }, + { + "type": "boolean", + "key": "textures", + "label": "Display Textures" + }, + { + "type": "boolean", + "key": "twoSidedLighting", + "label": "Two Sided Lighting" + }, + { + "type": "boolean", + "key": "ssaoEnable", + "label": "Screen Space Ambient Occlusion" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "cameras", + "label": "cameras" + }, + { + "type": "boolean", + "key": "clipGhosts", + "label": "clipGhosts" + }, + { + "type": "boolean", + "key": "controlVertices", + "label": "controlVertices" + }, + { + "type": "boolean", + "key": "deformers", + "label": "deformers" + }, + { + "type": "boolean", + "key": "dimensions", + "label": "dimensions" + }, + { + "type": "boolean", + "key": "dynamicConstraints", + "label": "dynamicConstraints" + }, + { + "type": "boolean", + "key": "dynamics", + "label": "dynamics" + }, + { + "type": "boolean", + "key": "fluids", + "label": "fluids" + }, + { + "type": "boolean", + "key": "follicles", + "label": "follicles" + }, + { + "type": "boolean", + "key": "gpuCacheDisplayFilter", + "label": "gpuCacheDisplayFilter" + }, + { + "type": "boolean", + "key": "greasePencils", + "label": "greasePencils" + }, + { + "type": "boolean", + "key": "grid", + "label": "grid" + }, + { + "type": "boolean", + "key": "hairSystems", + "label": "hairSystems" + }, + { + "type": "boolean", + "key": "handles", + "label": "handles" + }, + { + "type": "boolean", + "key": "hud", + "label": "hud" + }, + { + "type": "boolean", + "key": "hulls", + "label": "hulls" + }, + { + "type": "boolean", + "key": "ikHandles", + "label": "ikHandles" + }, + { + "type": "boolean", + "key": "imagePlane", + "label": "imagePlane" + }, + { + "type": "boolean", + "key": "joints", + "label": "joints" + }, + { + "type": "boolean", + "key": "lights", + "label": "lights" + }, + { + "type": "boolean", + "key": "locators", + "label": "locators" + }, + { + "type": "boolean", + "key": "manipulators", + "label": "manipulators" + }, + { + "type": "boolean", + "key": "motionTrails", + "label": "motionTrails" + }, + { + "type": "boolean", + "key": "nCloths", + "label": "nCloths" + }, + { + "type": "boolean", + "key": "nParticles", + "label": "nParticles" + }, + { + "type": "boolean", + "key": "nRigids", + "label": "nRigids" + }, + { + "type": "boolean", + "key": "nurbsCurves", + "label": "nurbsCurves" + }, + { + "type": "boolean", + "key": "nurbsSurfaces", + "label": "nurbsSurfaces" + }, + { + "type": "boolean", + "key": "particleInstancers", + "label": "particleInstancers" + }, + { + "type": "boolean", + "key": "pivots", + "label": "pivots" + }, + { + "type": "boolean", + "key": "planes", + "label": "planes" + }, + { + "type": "boolean", + "key": "pluginShapes", + "label": "pluginShapes" + }, + { + "type": "boolean", + "key": "polymeshes", + "label": "polymeshes" + }, + { + "type": "boolean", + "key": "strokes", + "label": "strokes" + }, + { + "type": "boolean", + "key": "subdivSurfaces", + "label": "subdivSurfaces" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "Camera Options", + "label": "Camera Options", + "children": [ + { + "type": "boolean", + "key": "displayGateMask", + "label": "displayGateMask" + }, + { + "type": "boolean", + "key": "displayResolution", + "label": "displayResolution" + }, + { + "type": "boolean", + "key": "displayFilmGate", + "label": "displayFilmGate" + }, + { + "type": "boolean", + "key": "displayFieldChart", + "label": "displayFieldChart" + }, + { + "type": "boolean", + "key": "displaySafeAction", + "label": "displaySafeAction" + }, + { + "type": "boolean", + "key": "displaySafeTitle", + "label": "displaySafeTitle" + }, + { + "type": "boolean", + "key": "displayFilmPivot", + "label": "displayFilmPivot" + }, + { + "type": "boolean", + "key": "displayFilmOrigin", + "label": "displayFilmOrigin" + }, + { + "type": "number", + "key": "overscan", + "label": "overscan", + "decimal": 1, + "minimum": 0, + "maximum": 10 + } + ] + } + ] + } + ] + } +] diff --git a/pype/tools/tray/pype_info_widget.py b/pype/tools/tray/pype_info_widget.py new file mode 100644 index 0000000000..a4c52eb1d0 --- /dev/null +++ b/pype/tools/tray/pype_info_widget.py @@ -0,0 +1,402 @@ +import os +import json +import collections + +from avalon import style +from Qt import QtCore, QtGui, QtWidgets +from pype.api import resources +from pype.settings.lib import get_local_settings +from pype.lib.pype_info import ( + get_all_current_info, + get_pype_info, + get_workstation_info, + extract_pype_info_to_file +) + +IS_MAIN_ROLE = QtCore.Qt.UserRole + + +class EnvironmentValueDelegate(QtWidgets.QStyledItemDelegate): + def createEditor(self, parent, option, index): + edit_widget = QtWidgets.QLineEdit(parent) + edit_widget.setReadOnly(True) + return edit_widget + + +class EnvironmentsView(QtWidgets.QTreeView): + def __init__(self, parent=None): + super(EnvironmentsView, self).__init__(parent) + + model = QtGui.QStandardItemModel() + + env = os.environ.copy() + keys = [] + values = [] + for key in sorted(env.keys()): + key_item = QtGui.QStandardItem(key) + key_item.setFlags( + QtCore.Qt.ItemIsSelectable + | QtCore.Qt.ItemIsEnabled + ) + key_item.setData(True, IS_MAIN_ROLE) + keys.append(key_item) + + value = env[key] + value_item = QtGui.QStandardItem(value) + value_item.setData(True, IS_MAIN_ROLE) + values.append(value_item) + + value_parts = [ + part + for part in value.split(os.pathsep) if part + ] + if len(value_parts) < 2: + continue + + sub_parts = [] + for part_value in value_parts: + part_item = QtGui.QStandardItem(part_value) + part_item.setData(False, IS_MAIN_ROLE) + sub_parts.append(part_item) + key_item.appendRows(sub_parts) + + model.appendColumn(keys) + model.appendColumn(values) + model.setHorizontalHeaderLabels(["Key", "Value"]) + + self.setModel(model) + # self.setIndentation(0) + delegate = EnvironmentValueDelegate(self) + self.setItemDelegate(delegate) + self.header().setSectionResizeMode( + 0, QtWidgets.QHeaderView.ResizeToContents + ) + self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection) + + def get_selection_as_dict(self): + indexes = self.selectionModel().selectedIndexes() + + main_mapping = collections.defaultdict(dict) + for index in indexes: + is_main = index.data(IS_MAIN_ROLE) + if not is_main: + continue + row = index.row() + value = index.data(QtCore.Qt.DisplayRole) + if index.column() == 0: + key = "key" + else: + key = "value" + main_mapping[row][key] = value + + result = {} + for item in main_mapping.values(): + result[item["key"]] = item["value"] + return result + + def keyPressEvent(self, event): + if ( + event.type() == QtGui.QKeyEvent.KeyPress + and event.matches(QtGui.QKeySequence.Copy) + ): + selected_data = self.get_selection_as_dict() + selected_str = json.dumps(selected_data, indent=4) + + mime_data = QtCore.QMimeData() + mime_data.setText(selected_str) + QtWidgets.QApplication.instance().clipboard().setMimeData( + mime_data + ) + event.accept() + else: + return super(EnvironmentsView, self).keyPressEvent(event) + + +class ClickableWidget(QtWidgets.QWidget): + clicked = QtCore.Signal() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class CollapsibleWidget(QtWidgets.QWidget): + def __init__(self, label, parent): + super(CollapsibleWidget, self).__init__(parent) + + self.content_widget = None + + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label_widget = QtWidgets.QLabel(label, parent=top_part) + spacer_widget = QtWidgets.QWidget(top_part) + + top_part_layout = QtWidgets.QHBoxLayout(top_part) + top_part_layout.setContentsMargins(0, 0, 0, 5) + top_part_layout.addWidget(button_toggle) + top_part_layout.addWidget(label_widget) + top_part_layout.addWidget(spacer_widget, 1) + + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.button_toggle = button_toggle + self.label_widget = label_widget + + top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self._btn_clicked) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.setAlignment(QtCore.Qt.AlignTop) + main_layout.addWidget(top_part) + + self.main_layout = main_layout + + def set_content_widget(self, content_widget): + content_widget.setVisible(self.button_toggle.isChecked()) + self.main_layout.addWidget(content_widget) + self.content_widget = content_widget + + def _btn_clicked(self): + self.toggle_content(self.button_toggle.isChecked()) + + def _top_part_clicked(self): + self.toggle_content() + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = not self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + if self.content_widget: + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(CollapsibleWidget, self).resizeEvent(event) + if self.content_widget: + self.content_widget.updateGeometry() + + +class PypeInfoWidget(QtWidgets.QWidget): + not_applicable = "N/A" + + def __init__(self, parent=None): + super(PypeInfoWidget, self).__init__(parent) + + self.setStyleSheet(style.load_stylesheet()) + + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) + self.setWindowTitle("Pype info") + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setAlignment(QtCore.Qt.AlignTop) + main_layout.addWidget(self._create_pype_info_widget(), 0) + main_layout.addWidget(self._create_separator(), 0) + main_layout.addWidget(self._create_workstation_widget(), 0) + main_layout.addWidget(self._create_separator(), 0) + main_layout.addWidget(self._create_local_settings_widget(), 0) + main_layout.addWidget(self._create_separator(), 0) + main_layout.addWidget(self._create_environ_widget(), 1) + main_layout.addWidget(self._create_btns_section(), 0) + + def _create_btns_section(self): + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.setContentsMargins(0, 0, 0, 0) + + copy_to_clipboard_btn = QtWidgets.QPushButton( + "Copy to clipboard", btns_widget + ) + export_to_file_btn = QtWidgets.QPushButton( + "Export", btns_widget + ) + btns_layout.addWidget(QtWidgets.QWidget(btns_widget), 1) + btns_layout.addWidget(copy_to_clipboard_btn) + btns_layout.addWidget(export_to_file_btn) + + copy_to_clipboard_btn.clicked.connect(self._on_copy_to_clipboard) + export_to_file_btn.clicked.connect(self._on_export_to_file) + + return btns_widget + + def _on_export_to_file(self): + dst_dir_path = QtWidgets.QFileDialog.getExistingDirectory( + self, + "Choose directory", + os.path.expanduser("~"), + QtWidgets.QFileDialog.ShowDirsOnly + ) + if not dst_dir_path or not os.path.exists(dst_dir_path): + return + + filepath = extract_pype_info_to_file(dst_dir_path) + title = "Extraction done" + message = "Extraction is done. Destination filepath is \"{}\"".format( + filepath.replace("\\", "/") + ) + dialog = QtWidgets.QMessageBox(self) + dialog.setIcon(QtWidgets.QMessageBox.NoIcon) + dialog.setWindowTitle(title) + dialog.setText(message) + dialog.exec_() + + def _on_copy_to_clipboard(self): + all_data = get_all_current_info() + all_data_str = json.dumps(all_data, indent=4) + + mime_data = QtCore.QMimeData() + mime_data.setText(all_data_str) + QtWidgets.QApplication.instance().clipboard().setMimeData( + mime_data + ) + + def _create_separator(self): + separator_widget = QtWidgets.QWidget(self) + separator_widget.setStyleSheet("background: #222222;") + separator_widget.setMinimumHeight(2) + separator_widget.setMaximumHeight(2) + return separator_widget + + def _create_workstation_widget(self): + key_label_mapping = { + "system_name": "System:", + "local_id": "Local ID:", + "username": "Username:", + "hostname": "Hostname:", + "hostip": "Host IP:" + } + keys_order = [ + "system_name", + "local_id", + "username", + "hostname", + "hostip" + ] + workstation_info = get_workstation_info() + for key in workstation_info.keys(): + if key not in keys_order: + keys_order.append(key) + + wokstation_info_widget = CollapsibleWidget("Workstation info", self) + + info_widget = QtWidgets.QWidget(self) + info_layout = QtWidgets.QGridLayout(info_widget) + # Add spacer to 3rd column + info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2) + info_layout.setColumnStretch(2, 1) + + for key in keys_order: + if key not in workstation_info: + continue + + label = key_label_mapping.get(key, key) + value = workstation_info[key] + row = info_layout.rowCount() + info_layout.addWidget( + QtWidgets.QLabel(label), row, 0, 1, 1 + ) + value_label = QtWidgets.QLabel(value) + value_label.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse + ) + info_layout.addWidget( + value_label, row, 1, 1, 1 + ) + + wokstation_info_widget.set_content_widget(info_widget) + + return wokstation_info_widget + + def _create_local_settings_widget(self): + local_settings = get_local_settings() + + local_settings_widget = CollapsibleWidget("Local settings", self) + + settings_input = QtWidgets.QPlainTextEdit(local_settings_widget) + settings_input.setReadOnly(True) + settings_input.setPlainText(json.dumps(local_settings, indent=4)) + + local_settings_widget.set_content_widget(settings_input) + + return local_settings_widget + + def _create_environ_widget(self): + env_widget = CollapsibleWidget("Environments", self) + + env_view = EnvironmentsView(env_widget) + + env_widget.set_content_widget(env_view) + + return env_widget + + def _create_pype_info_widget(self): + """Create widget with information about pype application.""" + + # Get pype info data + pype_info = get_pype_info() + # Modify version key/values + version_value = "{} ({})".format( + pype_info.pop("version", self.not_applicable), + pype_info.pop("version_type", self.not_applicable) + ) + pype_info["version_value"] = version_value + # Prepare lable mapping + key_label_mapping = { + "version_value": "Pype version:", + "executable": "Pype executable:", + "pype_root": "Pype location:", + "mongo_url": "Pype Mongo URL:" + } + # Prepare keys order + keys_order = ["version_value", "executable", "pype_root", "mongo_url"] + for key in pype_info.keys(): + if key not in keys_order: + keys_order.append(key) + + # Create widgets + info_widget = QtWidgets.QWidget(self) + info_layout = QtWidgets.QGridLayout(info_widget) + # Add spacer to 3rd column + info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2) + info_layout.setColumnStretch(2, 1) + + title_label = QtWidgets.QLabel(info_widget) + title_label.setText("Application information") + title_label.setStyleSheet("font-weight: bold;") + info_layout.addWidget(title_label, 0, 0, 1, 2) + + for key in keys_order: + if key not in pype_info: + continue + value = pype_info[key] + label = key_label_mapping.get(key, key) + row = info_layout.rowCount() + info_layout.addWidget( + QtWidgets.QLabel(label), row, 0, 1, 1 + ) + value_label = QtWidgets.QLabel(value) + value_label.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse + ) + info_layout.addWidget( + value_label, row, 1, 1, 1 + ) + return info_widget diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index c27df16276..2d37c04136 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -3,15 +3,12 @@ import sys import platform from avalon import style -from Qt import QtCore, QtGui, QtWidgets, QtSvg +from Qt import QtCore, QtGui, QtWidgets from pype.api import Logger, resources from pype.modules import TrayModulesManager, ITrayService from pype.settings.lib import get_system_settings import pype.version -try: - import configparser -except Exception: - import ConfigParser as configparser +from .pype_info_widget import PypeInfoWidget class TrayManager: @@ -19,13 +16,14 @@ class TrayManager: Load submenus, actions, separators and modules into tray's context. """ - available_sourcetypes = ["python", "file"] def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget self.main_window = main_window - self.log = Logger().get_logger(self.__class__.__name__) + self.pype_info_widget = None + + self.log = Logger.get_logger(self.__class__.__name__) self.module_settings = get_system_settings()["modules"] @@ -36,7 +34,7 @@ class TrayManager: def initialize_modules(self): """Add modules to tray.""" - self.modules_manager.initialize(self.tray_widget.menu) + self.modules_manager.initialize(self, self.tray_widget.menu) # Add services if they are services_submenu = ITrayService.services_submenu(self.tray_widget.menu) @@ -58,6 +56,26 @@ class TrayManager: # Print time report self.modules_manager.print_report() + def show_tray_message(self, title, message, icon=None, msecs=None): + """Show tray message. + + Args: + title (str): Title of message. + message (str): Content of message. + icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is + Information icon, may differ by Qt version. + msecs (int): Duration of message visibility in miliseconds. + Default is 10000 msecs, may differ by Qt version. + """ + args = [title, message] + kwargs = {} + if icon: + kwargs["icon"] = icon + if msecs: + kwargs["msecs"] = msecs + + self.tray_widget.showMessage(*args, **kwargs) + def _add_version_item(self): subversion = os.environ.get("PYPE_SUBVERSION") client_name = os.environ.get("PYPE_CLIENT") @@ -70,12 +88,21 @@ class TrayManager: version_string += ", {}".format(client_name) version_action = QtWidgets.QAction(version_string, self.tray_widget) + version_action.triggered.connect(self._on_version_action) self.tray_widget.menu.addAction(version_action) self.tray_widget.menu.addSeparator() def on_exit(self): self.modules_manager.on_exit() + def _on_version_action(self): + if self.pype_info_widget is None: + self.pype_info_widget = PypeInfoWidget() + + self.pype_info_widget.show() + self.pype_info_widget.raise_() + self.pype_info_widget.activateWindow() + class SystemTrayIcon(QtWidgets.QSystemTrayIcon): """Tray widget. @@ -85,9 +112,9 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): """ def __init__(self, parent): - self.icon = QtGui.QIcon(resources.pype_icon_filepath()) + icon = QtGui.QIcon(resources.pype_icon_filepath()) - QtWidgets.QSystemTrayIcon.__init__(self, self.icon, parent) + super(SystemTrayIcon, self).__init__(icon, parent) # Store parent - QtWidgets.QMainWindow() self.parent = parent @@ -100,15 +127,15 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): self.tray_man = TrayManager(self, self.parent) self.tray_man.initialize_modules() - # Catch activate event - self.activated.connect(self.on_systray_activated) + # Catch activate event for left click if not on MacOS + # - MacOS has this ability by design so menu would be doubled + if platform.system().lower() != "darwin": + self.activated.connect(self.on_systray_activated) # Add menu to Context of SystemTrayIcon self.setContextMenu(self.menu) def on_systray_activated(self, reason): # show contextMenu if left click - if platform.system().lower() == "darwin": - return if reason == QtWidgets.QSystemTrayIcon.Trigger: position = QtGui.QCursor().pos() self.contextMenu().popup(position) @@ -128,119 +155,24 @@ class TrayMainWindow(QtWidgets.QMainWindow): Every widget should have set this window as parent because QSystemTrayIcon widget is not allowed to be a parent of any widget. - - :param app: Qt application manages application's control flow - :type app: QtWidgets.QApplication - - .. note:: - *TrayMainWindow* has ability to show **working** widget. - Calling methods: - - ``show_working()`` - - ``hide_working()`` - .. todo:: Hide working widget if idle is too long """ def __init__(self, app): - super().__init__() + super(TrayMainWindow, self).__init__() self.app = app - self.set_working_widget() - - self.trayIcon = SystemTrayIcon(self) - self.trayIcon.show() - - def set_working_widget(self): - image_file = resources.get_resource("icons", "working.svg") - img_pix = QtGui.QPixmap(image_file) - if image_file.endswith('.svg'): - widget = QtSvg.QSvgWidget(image_file) - else: - widget = QtWidgets.QLabel() - widget.setPixmap(img_pix) - - # Set widget properties - widget.setGeometry(img_pix.rect()) - widget.setMask(img_pix.mask()) - widget.setWindowFlags( - QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint - ) - widget.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) - - self.center_widget(widget) - self._working_widget = widget - self.helper = DragAndDropHelper(self._working_widget) - - def center_widget(self, widget): - frame_geo = widget.frameGeometry() - screen = self.app.desktop().cursor().pos() - center_point = self.app.desktop().screenGeometry( - self.app.desktop().screenNumber(screen) - ).center() - frame_geo.moveCenter(center_point) - widget.move(frame_geo.topLeft()) - - def show_working(self): - self._working_widget.show() - - def hide_working(self): - self.center_widget(self._working_widget) - self._working_widget.hide() - - -class DragAndDropHelper: - """ Helper adds to widget drag and drop ability - - :param widget: Qt Widget where drag and drop ability will be added - """ - - def __init__(self, widget): - self.widget = widget - self.widget.mousePressEvent = self.mousePressEvent - self.widget.mouseMoveEvent = self.mouseMoveEvent - self.widget.mouseReleaseEvent = self.mouseReleaseEvent - - def mousePressEvent(self, event): - self.__mousePressPos = None - self.__mouseMovePos = None - if event.button() == QtCore.Qt.LeftButton: - self.__mousePressPos = event.globalPos() - self.__mouseMovePos = event.globalPos() - - def mouseMoveEvent(self, event): - if event.buttons() == QtCore.Qt.LeftButton: - # adjust offset from clicked point to origin of widget - currPos = self.widget.mapToGlobal( - self.widget.pos() - ) - globalPos = event.globalPos() - diff = globalPos - self.__mouseMovePos - newPos = self.widget.mapFromGlobal(currPos + diff) - self.widget.move(newPos) - self.__mouseMovePos = globalPos - - def mouseReleaseEvent(self, event): - if self.__mousePressPos is not None: - moved = event.globalPos() - self.__mousePressPos - if moved.manhattanLength() > 3: - event.ignore() - return + self.tray_widget = SystemTrayIcon(self) + self.tray_widget.show() class PypeTrayApplication(QtWidgets.QApplication): """Qt application manages application's control flow.""" def __init__(self): - super(self.__class__, self).__init__(sys.argv) + super(PypeTrayApplication, self).__init__(sys.argv) # Allows to close widgets without exiting app self.setQuitOnLastWindowClosed(False) - # Allow show icon istead of python icon in task bar (Windows) - if os.name == "nt": - import ctypes - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( - u"pype_tray" - ) - # Sets up splash splash_widget = self.set_splash()