diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 314871f5b3..d0c9ea2e96 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -717,6 +717,9 @@ class SyncToAvalonEvent(BaseEvent): if not self.ftrack_removed: return ent_infos = self.ftrack_removed + self.log.debug( + "Processing removed entities: {}".format(str(ent_infos)) + ) removable_ids = [] recreate_ents = [] removed_names = [] @@ -878,8 +881,9 @@ class SyncToAvalonEvent(BaseEvent): self.process_session.commit() found_idx = None - for idx, _entity in enumerate(self._avalon_ents): - if _entity["_id"] == avalon_entity["_id"]: + proj_doc, asset_docs = self._avalon_ents + for idx, asset_doc in enumerate(asset_docs): + if asset_doc["_id"] == avalon_entity["_id"]: found_idx = idx break @@ -894,7 +898,8 @@ class SyncToAvalonEvent(BaseEvent): new_entity_id ) # Update cached entities - self._avalon_ents[found_idx] = avalon_entity + asset_docs[found_idx] = avalon_entity + self._avalon_ents = proj_doc, asset_docs if self._avalon_ents_by_id is not None: mongo_id = avalon_entity["_id"] @@ -1258,6 +1263,10 @@ class SyncToAvalonEvent(BaseEvent): if not ent_infos: return + self.log.debug( + "Processing renamed entities: {}".format(str(ent_infos)) + ) + renamed_tasks = {} not_found = {} changeable_queue = queue.Queue() @@ -1453,6 +1462,10 @@ class SyncToAvalonEvent(BaseEvent): if not ent_infos: return + self.log.debug( + "Processing added entities: {}".format(str(ent_infos)) + ) + cust_attrs, hier_attrs = self.avalon_cust_attrs entity_type_conf_ids = {} # Skip if already exit in avalon db or tasks entities @@ -1729,6 +1742,10 @@ class SyncToAvalonEvent(BaseEvent): if not self.ftrack_moved: return + self.log.debug( + "Processing moved entities: {}".format(str(self.ftrack_moved)) + ) + ftrack_moved = {k: v for k, v in sorted( self.ftrack_moved.items(), key=(lambda line: len( @@ -1859,6 +1876,10 @@ class SyncToAvalonEvent(BaseEvent): if not self.ftrack_updated: return + self.log.debug( + "Processing updated entities: {}".format(str(self.ftrack_updated)) + ) + ent_infos = self.ftrack_updated ftrack_mongo_mapping = {} not_found_ids = [] diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 65a59452da..03124ab10d 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -2144,6 +2144,7 @@ class SyncEntitiesFactory: "name": _name, "parent": parent_entity }) + self.session.commit() final_entity = {} for k, v in av_entity.items(): diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index c4d8f2f705..e8a6151efd 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -2,6 +2,7 @@ import sys import six import pyblish.api from avalon import io +from pprint import pformat try: from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC @@ -40,9 +41,14 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): def process(self, context): self.context = context - if "hierarchyContext" not in context.data: + if "hierarchyContext" not in self.context.data: return + hierarchy_context = self.context.data["hierarchyContext"] + + self.log.debug( + f"__ hierarchy_context: `{pformat(hierarchy_context)}`") + self.session = self.context.data["ftrackSession"] project_name = self.context.data["projectEntity"]["name"] query = 'Project where full_name is "{}"'.format(project_name) @@ -55,7 +61,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.ft_project = None - input_data = context.data["hierarchyContext"] + input_data = hierarchy_context # disable termporarily ftrack project's autosyncing if auto_sync_state: diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 1d8191f2e3..4253c35929 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -10,6 +10,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): families = ["clip", "shot"] def process(self, context): + # processing starts here if "hierarchyContext" not in context.data: self.log.info("skipping IntegrateHierarchyToAvalon") return @@ -17,7 +18,29 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if not io.Session: io.install() - input_data = context.data["hierarchyContext"] + active_assets = [] + hierarchy_context = context.data["hierarchyContext"] + hierarchy_assets = self._get_assets(hierarchy_context) + + # filter only the active publishing insatnces + for instance in context: + if instance.data.get("publish") is False: + continue + + if not instance.data.get("asset"): + continue + + active_assets.append(instance.data["asset"]) + + # filter out only assets which are activated as isntances + new_hierarchy_assets = {k: v for k, v in hierarchy_assets.items() + if k in active_assets} + + # modify the hierarchy context so there are only fitred assets + self._set_assets(hierarchy_context, new_hierarchy_assets) + + input_data = context.data["hierarchyContext"] = hierarchy_context + self.project = None self.import_to_avalon(input_data) @@ -149,3 +172,41 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): entity_id = io.insert_one(item).inserted_id return io.find_one({"_id": entity_id}) + + def _get_assets(self, input_dict): + """ Returns only asset dictionary. + Usually the last part of deep dictionary which + is not having any children + """ + for key in input_dict.keys(): + # check if child key is available + if input_dict[key].get("childs"): + # loop deeper + return self._get_assets(input_dict[key]["childs"]) + else: + # give the dictionary with assets + return input_dict + + def _set_assets(self, input_dict, new_assets=None): + """ Modify the hierarchy context dictionary. + It will replace the asset dictionary with only the filtred one. + """ + for key in input_dict.keys(): + # check if child key is available + if input_dict[key].get("childs"): + # return if this is just for testing purpose and no + # new_assets property is avalable + if not new_assets: + return True + + # test for deeper inner children availabelity + if self._set_assets(input_dict[key]["childs"]): + # if one level deeper is still children available + # then process farther + self._set_assets(input_dict[key]["childs"], new_assets) + else: + # or just assign the filtred asset ditionary + input_dict[key]["childs"] = new_assets + else: + # test didnt find more childs in input dictionary + return None diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index a31125d9a8..5e6fd106e4 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -32,7 +32,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): actions = [] # presets - extensions = [".mov"] + extensions = [".mov", ".mp4"] def process(self, instance): # remove context test attribute diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json b/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json new file mode 100644 index 0000000000..02e6a9b95d --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json @@ -0,0 +1,108 @@ +{ + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.714, + 0.714, + 0.714 + ], + "backgroundBottom": [ + 0.714, + 0.714, + 0.714 + ], + "backgroundTop": [ + 0.714, + 0.714, + 0.714 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "IO": { + "name": "", + "open_finished": false, + "raw_frame_numbers": false, + "recent_playblasts": [], + "save_file": false + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1920, + "height": 1080, + "percent": 1.0, + "mode": "Custom" + }, + "Time Range": { + "start_frame": 0, + "end_frame": 25, + "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": false, + "handles": false, + "high_quality": true, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": false, + "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": false, + "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 + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json new file mode 100644 index 0000000000..486f0917e2 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json @@ -0,0 +1,21 @@ +{ + "ValidateModelName": { + "enabled": true, + "material_file": { + "windows": "", + "darwin": "", + "linux": "" + }, + "regex": "" + }, + "ValidateAssemblyName": { + "enabled": true + }, + "ValidateShaderName": { + "enabled": true, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": true + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index 3c9d4806ac..1482ff85b0 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -1147,48 +1147,52 @@ class TerminalModel(QtGui.QStandardItemModel): return prepared_records - def append(self, record_item): - record_type = record_item["type"] + def append(self, record_items): + all_record_items = [] + for record_item in record_items: + record_type = record_item["type"] - terminal_item_type = None - if record_type == "record": - for level, _type in self.level_to_record: - if level > record_item["levelno"]: - break - terminal_item_type = _type + terminal_item_type = None + if record_type == "record": + for level, _type in self.level_to_record: + if level > record_item["levelno"]: + break + terminal_item_type = _type - else: - terminal_item_type = record_type + else: + terminal_item_type = record_type - icon_color = self.item_icon_colors.get(terminal_item_type) - icon_name = self.item_icon_name.get(record_type) + icon_color = self.item_icon_colors.get(terminal_item_type) + icon_name = self.item_icon_name.get(record_type) - top_item_icon = None - if icon_color and icon_name: - top_item_icon = QAwesomeIconFactory.icon(icon_name, icon_color) + top_item_icon = None + if icon_color and icon_name: + top_item_icon = QAwesomeIconFactory.icon(icon_name, icon_color) - label = record_item["label"].split("\n")[0] + label = record_item["label"].split("\n")[0] - top_item = QtGui.QStandardItem() - top_item.setData(TerminalLabelType, Roles.TypeRole) - top_item.setData(terminal_item_type, Roles.TerminalItemTypeRole) - top_item.setData(label, QtCore.Qt.DisplayRole) - top_item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) + top_item = QtGui.QStandardItem() + all_record_items.append(top_item) - if top_item_icon: - top_item.setData(top_item_icon, QtCore.Qt.DecorationRole) + detail_item = TerminalDetailItem(record_item) + top_item.appendRow(detail_item) - self.appendRow(top_item) + top_item.setData(TerminalLabelType, Roles.TypeRole) + top_item.setData(terminal_item_type, Roles.TerminalItemTypeRole) + top_item.setData(label, QtCore.Qt.DisplayRole) + top_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + ) - detail_item = TerminalDetailItem(record_item) - detail_item.setData(TerminalDetailType, Roles.TypeRole) - top_item.appendRow(detail_item) + if top_item_icon: + top_item.setData(top_item_icon, QtCore.Qt.DecorationRole) + + detail_item.setData(TerminalDetailType, Roles.TypeRole) + + self.invisibleRootItem().appendRows(all_record_items) def update_with_result(self, result): - for record in result["records"]: - self.append(record) + self.append(result["records"]) class TerminalProxy(QtCore.QSortFilterProxyModel): diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 76f31e2442..2a037ba4bc 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -1087,10 +1087,10 @@ class Window(QtWidgets.QDialog): info.setText(message) # Include message in terminal - self.terminal_model.append({ + self.terminal_model.append([{ "label": message, "type": "info" - }) + }]) self.animation_info_msg.stop() self.animation_info_msg.start() diff --git a/pype/tools/settings/settings/README.md b/pype/tools/settings/settings/README.md index 47bbf28ba5..e8b7fcdb57 100644 --- a/pype/tools/settings/settings/README.md +++ b/pype/tools/settings/settings/README.md @@ -84,8 +84,8 @@ "type": "list", "key": "profiles", "label": "Profiles", - "object_type": "dict-item", - "input_modifiers": { + "object_type": { + "type": "dict-item", "children": [ { "key": "families", @@ -168,6 +168,29 @@ } ``` +### enum +- returns value of single on multiple items from predefined values +- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`) +- values are defined under value of key `"enum_items"` as list + - each item in list is simple dictionary where value is label and key is value which will be stored + - should be possible to enter single dictionary if order of items doesn't matter + +``` +{ + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + {"burnin": "Add burnins"}, + {"ftrackreview": "Add to Ftrack"}, + {"delete": "Delete output"}, + {"slate-frame": "Add slate frame"}, + {"no-hnadles": "Skip handle frames"} + ] +} +``` + ## Inputs for setting value using Pure inputs - these inputs also have required `"key"` and `"label"` - they use Pure inputs "as widgets" @@ -176,37 +199,53 @@ - output is list - items can be added and removed - items in list must be the same type - - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) - - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` +- type of items is defined with key `"object_type"` +- there are 2 possible ways how to set the type: + 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) + 2.) item type name as string without modifiers (e.g. `text`) +1.) with item modifiers ``` { "type": "list", - "object_type": "number", "key": "exclude_ports", "label": "Exclude ports", - "input_modifiers": { - "minimum": 1, - "maximum": 65535 + "object_type": { + "type": "number", # number item type + "minimum": 1, # minimum modifier + "maximum": 65535 # maximum modifier } } ``` +2.) without modifiers +``` +{ + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "object_type": "text" +} +``` + ### dict-modifiable - one of dictionary inputs, this is only used as value input - items in this input can be removed and added same way as in `list` input - value items in dictionary must be the same type - - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) - - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` +- type of items is defined with key `"object_type"` +- there are 2 possible ways how to set the type: + 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) + 2.) item type name as string without modifiers (e.g. `text`) - this input can be expandable - that can be set with key `"expandable"` as `True`/`False` (Default: `True`) - with key `"expanded"` as `True`/`False` can be set that is expanded when GUI is opened (Default: `False`) +1.) with item modifiers ``` { "type": "dict-modifiable", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "minimum": 0, "maximum": 300 }, @@ -217,6 +256,18 @@ } ``` +2.) without modifiers +``` +{ + "type": "dict-modifiable", + "object_type": "text", + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + ### path-widget - input for paths, use `path-input` internally - has 2 input modifiers `"multiplatform"` and `"multipath"` diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index f357b51dc5..87912cfdc0 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -3,743 +3,612 @@ "collapsable": true, "key": "plugins", "label": "Plugins", - "children": [ - { + "children": [{ "type": "dict", "collapsable": true, "key": "celaction", "label": "CelAction", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractCelactionDeadline", - "label": "ExtractCelactionDeadline", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline apartment" - }, { - "type": "number", - "key": "deadline_priority", - "label": "Deadline priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "Deadline Chunk size" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "deadline_department", + "label": "Deadline apartment" + }, { + "type": "number", + "key": "deadline_priority", + "label": "Deadline priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)" + }, { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "ftrack", "label": "Ftrack", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateFtrackNote", - "label": "IntegrateFtrackNote", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "note_with_intent_template", - "label": "Note with intent template" - }, { - "type": "list", - "object_type": "text", - "key": "note_labels", - "label": "Note labels" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "IntegrateFtrackNote", + "label": "IntegrateFtrackNote", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "note_with_intent_template", + "label": "Note with intent template" + }, { + "type": "list", + "object_type": "text", + "key": "note_labels", + "label": "Note labels" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "global", "label": "Global", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateMasterVersion", - "label": "IntegrateMasterVersion", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] + "checkbox_key": "enabled", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-invisible", + "key": "ffmpeg_args", + "children": [{ + "type": "list", + "object_type": "text", + "key": "input", + "label": "FFmpeg input arguments" }, { + "type": "list", + "object_type": "text", + "key": "output", + "label": "FFmpeg output arguments" + }] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractReview", + "label": "ExtractReview", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict-invisible", - "key": "ffmpeg_args", - "children": [ - { - "type": "list", - "object_type": "text", - "key": "input", - "label": "FFmpeg input arguments" - }, { - "type": "list", - "object_type": "text", - "key": "output", - "label": "FFmpeg output arguments" - } - ] - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractReview", - "label": "ExtractReview", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "key": "profiles", - "label": "Profiles", - "object_type": "dict", - "input_modifiers": { - "children": [ + "children": [{ + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [{ + "key": "ext", + "label": "Output extension", + "type": "text" + }, { + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [{ + "burnin": "Add burnins" + }, { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, { - "key": "hosts", - "label": "Hosts", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "outputs", - "label": "Output Definitions", - "type": "dict-modifiable", - "highlight_content": true, - "object_type": "dict", - "input_modifiers": { - "children": [ - { - "key": "ext", - "label": "Output extension", - "type": "text" - }, { - "key": "tags", - "label": "Tags", - "type": "list", - "object_type": "text" - }, { - "key": "ffmpeg_args", - "label": "FFmpeg arguments", - "type": "dict", - "highlight_content": true, - "children": [ - { - "key": "video_filters", - "label": "Video filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "audio_filters", - "label": "Audio filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "input", - "label": "Input arguments", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "output", - "label": "Output arguments", - "type": "list", - "object_type": "text" - } - ] - }, { - "key": "filter", - "label": "Additional output filtering", - "type": "dict", - "highlight_content": true, - "children": [ - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - } - ] - } - ] - } + "ftrackreview": "Add to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-hnadles": "Skip handle frames" } ] - } + }, { + "key": "ffmpeg_args", + "label": "FFmpeg arguments", + "type": "dict", + "highlight_content": true, + "children": [{ + "key": "video_filters", + "label": "Video filters", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "audio_filters", + "label": "Audio filters", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "input", + "label": "Input arguments", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "output", + "label": "Output arguments", + "type": "list", + "object_type": "text" + }] + }, { + "key": "filter", + "label": "Additional output filtering", + "type": "dict", + "highlight_content": true, + "children": [{ + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }] + }] } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractBurnin", - "label": "ExtractBurnin", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict", - "collapsable": true, - "key": "options", - "label": "Burnin formating options", - "children": [ - { - "type": "number", - "key": "font_size", - "label": "Font size" - }, { - "type": "number", - "key": "opacity", - "label": "Font opacity" - }, { - "type": "number", - "key": "bg_opacity", - "label": "Background opacity" - }, { - "type": "number", - "key": "x_offset", - "label": "X Offset" - }, { - "type": "number", - "key": "y_offset", - "label": "Y Offset" - }, { - "type": "number", - "key": "bg_padding", - "label": "Padding aroung text" - } - ] - }, { - "type": "raw-json", - "key": "profiles", - "label": "Burnin profiles" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "IntegrateAssetNew", - "label": "IntegrateAssetNew", - "is_group": true, - "children": [ - { - "type": "raw-json", - "key": "template_name_profiles", - "label": "template_name_profiles" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ProcessSubmittedJobOnFarm", - "label": "ProcessSubmittedJobOnFarm", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline department" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline Pool" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - } - ] + }] } - ] - } - ] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict", + "collapsable": true, + "key": "options", + "label": "Burnin formating options", + "children": [{ + "type": "number", + "key": "font_size", + "label": "Font size" + }, { + "type": "number", + "key": "opacity", + "label": "Font opacity" + }, { + "type": "number", + "key": "bg_opacity", + "label": "Background opacity" + }, { + "type": "number", + "key": "x_offset", + "label": "X Offset" + }, { + "type": "number", + "key": "y_offset", + "label": "Y Offset" + }, { + "type": "number", + "key": "bg_padding", + "label": "Padding aroung text" + }] + }, { + "type": "raw-json", + "key": "profiles", + "label": "Burnin profiles" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [{ + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }] + }] + }] }, { - "type": "dict", + "type": "dict-invisible", "collapsable": true, "key": "maya", "label": "Maya", - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "ValidateModelName", - "label": "Validate Model Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "material_file", - "label": "Material File" - }, { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateAssemblyName", - "label": "Validate Assembly Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateShaderName", - "label": "ValidateShaderName", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateMeshHasOverlappingUVs", - "label": "ValidateMeshHasOverlappingUVs", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - } - ] - }, { - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true - } - ] + "children": [{ + "type": "dict", + "collapsable": true, + "key": "maya", + "label": "Maya", + "children": [ + + { + "type": "schema", + "name": "2_maya_capture" + }, + { + "type": "schema", + "name": "2_maya_plugins" + }, + { + "type": "schema", + "name": "2_maya_workfiles" + } + ] + }] }, { "type": "dict", "collapsable": true, "key": "nuke", "label": "Nuke", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": false, + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }, { + "type": "dict", + "collapsable": false, + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "create", - "label": "Create plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": false, - "key": "CreateWriteRender", - "label": "CreateWriteRender", - "is_group": true, - "children": [ - { - "type": "text", - "key": "fpath_template", - "label": "Path template" - } - ] - }, { - "type": "dict", - "collapsable": false, - "key": "CreateWritePrerender", - "label": "CreateWritePrerender", - "is_group": true, - "children": [ - { - "type": "text", - "key": "fpath_template", - "label": "Path template" - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + }] }, { "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractThumbnail", - "label": "ExtractThumbnail", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ValidateNukeWriteKnobs", - "label": "ValidateNukeWriteKnobs", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "knobs", - "label": "Knobs" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataLut", - "label": "ExtractReviewDataLut", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataMov", - "label": "ExtractReviewDataMov", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractSlateFrame", - "label": "ExtractSlateFrame", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "NukeSubmitDeadline", - "label": "NukeSubmitDeadline", - "is_group": true, - "children": [ - { - "type": "number", - "key": "deadline_priority", - "label": "deadline_priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "deadline_pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "deadline_pool_secondary" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "deadline_chunk_size" - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ValidateNukeWriteKnobs", + "label": "ValidateNukeWriteKnobs", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + }] }, { - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true - } - ] + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataMov", + "label": "ExtractReviewDataMov", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "NukeSubmitDeadline", + "label": "NukeSubmitDeadline", + "is_group": true, + "children": [{ + "type": "number", + "key": "deadline_priority", + "label": "deadline_priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "deadline_pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "deadline_pool_secondary" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "deadline_chunk_size" + }] + }] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true + }] }, { "type": "dict", "collapsable": true, "key": "nukestudio", "label": "NukeStudio", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "CollectInstanceVersion", - "label": "Collect Instance Version", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewCutUpVideo", - "label": "Extract Review Cut Up Video", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "object_type": "text", - "key": "tags_addition", - "label": "Tags addition" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewCutUpVideo", + "label": "Extract Review Cut Up Video", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "object_type": "text", + "key": "tags_addition", + "label": "Tags addition" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "resolve", "label": "DaVinci Resolve", - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "create", - "label": "Creator plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "CreateShotClip", - "label": "Create Shot Clip", - "is_group": true, - "children": [ - { - "type": "text", - "key": "clipName", - "label": "Clip name template" - }, { - "type": "text", - "key": "folder", - "label": "Folder" - }, { - "type": "number", - "key": "steps", - "label": "Steps" - } - ] - } + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [{ + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, { + "type": "text", + "key": "folder", + "label": "Folder" + }, { + "type": "number", + "key": "steps", + "label": "Steps" + }] + } - ] - } - ] + ] + }] }, { "type": "dict", "collapsable": true, "key": "standalonepublisher", "label": "Standalone Publisher", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "ExtractThumbnailSP", - "label": "ExtractThumbnailSP", - "is_group": true, - "children": [ - { - "type": "dict", - "collapsable": false, - "key": "ffmpeg_args", - "label": "ffmpeg_args", - "children": [ - { - "type": "list", - "object_type": "text", - "key": "input", - "label": "input" - }, - { - "type": "list", - "object_type": "text", - "key": "output", - "label": "output" - } - ] - } - ] - } - ] - } - ] + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [{ + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [{ + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + } + ] + }] + }] + }] } ] } diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json new file mode 100644 index 0000000000..836ad90404 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json @@ -0,0 +1,522 @@ +{ + "type": "dict", + "collapsable": true, + "key": "maya_capture", + "label": "Maya Capture settings", + "is_file": true, + "children": [ + { + "type": "dict-invisible", + "key": "Codec", + "label": "Codec", + "collapsable": false, + "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-invisible", + "key": "Display Options", + "label": "Display Options", + "collapsable": false, + "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-invisible", + "collapsable": true, + "key": "Generic", + "label": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + },{ + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + } + ] + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "IO", + "label": "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-invisible", + "collapsable": true, + "key": "PanZoom", + "label": "Pan Zoom", + "children": [ + { + "type": "boolean", + "key": "pan_zoom", + "label": " Pan Zoom" + } + ] + }, + { + "type": "splitter" + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "Renderer", + "label": "Renderer", + "children": [ + + { + "type": "label", + "label": "Renderer" + }, + { + "type": "text", + "key": "rendererName", + "label": " Renderer name" + } + ] + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "Resolution", + "label": "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-invisible", + "collapsable": true, + "key": "Time Range", + "label": "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", + "collapsable": 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", + "collapsable": 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/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json new file mode 100644 index 0000000000..7ba9608610 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json @@ -0,0 +1,90 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Path to material file defining list of material names to check. This is material name per line simple text file.
It will be checked against named group shader in your Validation regex.

For example:
^.*(?P=<shader>.+)_GEO

" + }, + { + "type": "path-widget", + "key": "material_file", + "label": "Material File", + "multiplatform": true, + "multipath": false + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateAssemblyName", + "label": "Validate Assembly Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "label", + "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" + + }, { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + ] + } diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json new file mode 100644 index 0000000000..bae4d32abd --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json @@ -0,0 +1,6 @@ +{ + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json index 0578968508..dd0f7f20d1 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json @@ -9,6 +9,25 @@ "type": "dict-invisible", "children": [ { + "type": "enum", + "key": "test_enum_singleselection", + "label": "Enum Single Selection", + "enum_items": [ + {"value_1": "Label 1"}, + {"value_2": "Label 2"}, + {"value_3": "Label 3"} + ] + }, { + "type": "enum", + "key": "test_enum_multiselection", + "label": "Enum Multi Selection", + "multiselection": true, + "enum_items": [ + {"value_1": "Label 1"}, + {"value_2": "Label 2"}, + {"value_3": "Label 3"} + ] + }, { "type": "boolean", "key": "bool", "label": "Boolean checkbox" @@ -48,16 +67,16 @@ "type": "list", "key": "list_item_of_multiline_texts", "label": "List of multiline texts", - "object_type": "text", - "input_modifiers": { + "object_type": { + "type": "text", "multiline": true } }, { "type": "list", "key": "list_item_of_floats", "label": "List of floats", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "decimal": 3, "minimum": 1000, "maximum": 2000 @@ -66,8 +85,8 @@ "type": "dict-modifiable", "key": "modifiable_dict_of_integers", "label": "Modifiable dict of integers", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "decimal": 0, "minimum": 10, "maximum": 100 @@ -194,8 +213,8 @@ "type": "list", "key": "dict_item", "label": "DictItem in List", - "object_type": "dict-item", - "input_modifiers": { + "object_type": { + "type": "dict-item", "children": [ { "key": "families", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json index 7b3d8cdd13..d78c11838a 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json @@ -80,8 +80,8 @@ "type": "list", "key": "statuses_name_change", "label": "Status name change", - "object_type": "text", - "input_modifiers": { + "object_type": { + "type": "text", "multiline": false } }] @@ -96,9 +96,9 @@ "type": "dict-modifiable", "key": "status_update", "label": "Status Updates", - "object_type": "list", - "input_modifiers": { - "object_type": "text" + "object_type": { + "type": "list", + "object_type": "text" } }, { @@ -133,10 +133,10 @@ }, { "type": "list", - "object_type": "number", "key": "exclude_ports", "label": "Exclude ports", - "input_modifiers": { + "object_type": { + "type": "number", "minimum": 1, "maximum": 65535 } @@ -213,8 +213,8 @@ "label": "Muster Resl URL" },{ "type": "dict-modifiable", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "minimum": 0, "maximum": 300 }, diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css index 3f648abef8..dcc7a5effe 100644 --- a/pype/tools/settings/settings/style/style.css +++ b/pype/tools/settings/settings/style/style.css @@ -38,6 +38,18 @@ QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:d QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { border: 1px solid #ffffff; } + +QComboBox { + border: 1px solid #aaaaaa; + border-radius: 3px; + padding: 2px 2px 4px 4px; + background: #1d272f; +} + +QComboBox QAbstractItemView::item { + padding: 3px; +} + QToolButton { background: transparent; } @@ -102,6 +114,10 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } +#MultiSelectionComboBox { + font-size: 12px; +} + #DictKey[state="studio"] {border-color: #bfccd6;} #DictKey[state="modified"] {border-color: #137cbd;} #DictKey[state="overriden"] {border-color: #00f;} @@ -152,8 +168,10 @@ QPushButton[btn-type="expand-toggle"] { background: #141a1f; } -#DictAsWidgetBody{ +#DictAsWidgetBody { background: transparent; +} +#DictAsWidgetBody[show_borders="1"] { border: 2px solid #cccccc; border-radius: 5px; } diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index 6d7b3292ce..e1a726187c 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -271,9 +271,9 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): ) multiroot_data = { "key": self.key, - "object_type": "path-widget", "expandable": False, - "input_modifiers": { + "object_type": { + "type": "path-widget", "multiplatform": True } } diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index ee4762de14..404d7b700b 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -266,7 +266,7 @@ class SystemWidget(QtWidgets.QWidget): klass = lib.TypeToKlass.types.get(item_type) item = klass(child_configuration, self) self.input_fields.append(item) - self.content_layout.addWidget(item) + self.content_layout.addWidget(item, 0) # Add spacer to stretch children guis spacer = QtWidgets.QWidget(self.content_widget) @@ -532,7 +532,7 @@ class ProjectWidget(QtWidgets.QWidget): klass = lib.TypeToKlass.types.get(item_type) item = klass(child_configuration, self) self.input_fields.append(item) - self.content_layout.addWidget(item) + self.content_layout.addWidget(item, 0) # Add spacer to stretch children guis spacer = QtWidgets.QWidget(self.content_widget) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 2ad18001b7..71516a1efc 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -5,8 +5,10 @@ from .widgets import ( ExpandingWidget, NumberSpinBox, PathInput, - GridLabelWidget + GridLabelWidget, + ComboBox ) +from .multiselection_combobox import MultiSelectionComboBox from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET from pype.api import Logger from avalon.vendor import qtawesome @@ -726,6 +728,36 @@ class InputObject(SettingObject): def hierarchical_style_update(self): self.update_style() + def _style_state(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + return state + + def update_style(self): + state = self._style_state() + if self._state == state: + return + + self._state = state + + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + def remove_overrides(self): self._is_overriden = False self._is_modified = False @@ -814,64 +846,34 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: + if not self.as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget - self.checkbox = QtWidgets.QCheckBox(self) + self.input_field = QtWidgets.QCheckBox(self) spacer = QtWidgets.QWidget(self) - layout.addWidget(self.checkbox, 0) - layout.addWidget(spacer, 1) - spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.setFocusProxy(self.checkbox) + layout.addWidget(self.input_field, 0) + layout.addWidget(spacer, 1) - self.checkbox.stateChanged.connect(self._on_value_change) + self.setFocusProxy(self.input_field) + + self.input_field.stateChanged.connect(self._on_value_change) def set_value(self, value): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered self.validate_value(value) - self.checkbox.setChecked(value) - - def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - else: - property_name = "state" - - self.label_widget.setProperty(property_name, state) - self.label_widget.style().polish(self.label_widget) - self._state = state + self.input_field.setChecked(value) def item_value(self): - return self.checkbox.isChecked() + return self.input_field.isChecked() class NumberWidget(QtWidgets.QWidget, InputObject): @@ -909,7 +911,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget layout.addWidget(self.input_field, 1) @@ -919,37 +921,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.validate_value(value) self.input_field.setValue(value) - def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.input_field - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - def item_value(self): return self.input_field.value() @@ -977,14 +948,14 @@ class TextWidget(QtWidgets.QWidget, InputObject): layout.setSpacing(5) if self.multiline: - self.text_input = QtWidgets.QPlainTextEdit(self) + self.input_field = QtWidgets.QPlainTextEdit(self) else: - self.text_input = QtWidgets.QLineEdit(self) + self.input_field = QtWidgets.QLineEdit(self) if placeholder: - self.text_input.setPlaceholderText(placeholder) + self.input_field.setPlaceholderText(placeholder) - self.setFocusProxy(self.text_input) + self.setFocusProxy(self.input_field) layout_kwargs = {} if self.multiline: @@ -996,56 +967,24 @@ class TextWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0, **layout_kwargs) - self.label_widget = label_widget + self.label_widget = label_widget - layout.addWidget(self.text_input, 1, **layout_kwargs) + layout.addWidget(self.input_field, 1, **layout_kwargs) - self.text_input.textChanged.connect(self._on_value_change) + self.input_field.textChanged.connect(self._on_value_change) def set_value(self, value): self.validate_value(value) if self.multiline: - self.text_input.setPlainText(value) + self.input_field.setPlainText(value) else: - self.text_input.setText(value) - - def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) - - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) + self.input_field.setText(value) def item_value(self): if self.multiline: - return self.text_input.toPlainText() + return self.input_field.toPlainText() else: - return self.text_input.text() + return self.input_field.text() class PathInputWidget(QtWidgets.QWidget, InputObject): @@ -1073,33 +1012,103 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget - self.path_input = PathInput(self) - self.setFocusProxy(self.path_input) - layout.addWidget(self.path_input, 1) + self.input_field = PathInput(self) + self.setFocusProxy(self.input_field) + layout.addWidget(self.input_field, 1) - self.path_input.textChanged.connect(self._on_value_change) + self.input_field.textChanged.connect(self._on_value_change) def set_value(self, value): self.validate_value(value) - self.path_input.setText(value) + self.input_field.setText(value) def focusOutEvent(self, event): - self.path_input.clear_end_path() + self.input_field.clear_end_path() super(PathInput, self).focusOutEvent(event) + def item_value(self): + return self.input_field.text() + + +class EnumeratorWidget(QtWidgets.QWidget, InputObject): + default_input_value = True + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(EnumeratorWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + self.multiselection = input_data.get("multiselection") + self.enum_items = input_data["enum_items"] + if not self.enum_items: + raise ValueError("Attribute `enum_items` is not defined.") + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + if self.multiselection: + placeholder = input_data.get("placeholder") + self.input_field = MultiSelectionComboBox( + placeholder=placeholder, parent=self + ) + else: + self.input_field = ComboBox(self) + + first_value = NOT_SET + for enum_item in self.enum_items: + for value, label in enum_item.items(): + if first_value is NOT_SET: + first_value = value + self.input_field.addItem(label, value) + self._first_value = first_value + + if self.multiselection: + model = self.input_field.model() + for idx in range(self.input_field.count()): + model.item(idx).setCheckable(True) + + layout.addWidget(self.input_field, 0) + + self.setFocusProxy(self.input_field) + + self.input_field.value_changed.connect(self._on_value_change) + + @property + def default_input_value(self): + if self.multiselection: + return [] + return self._first_value + + def set_value(self, value): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.input_field.set_value(value) + def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -1111,18 +1120,15 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): if self._state == state: return - if self._as_widget: - property_name = "input-state" - widget = self.path_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) + self._state = state + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): - return self.path_input.text() + return self.input_field.value() class RawJsonInput(QtWidgets.QPlainTextEdit): @@ -1197,73 +1203,42 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.text_input = RawJsonInput(self) - self.text_input.setSizePolicy( + self.input_field = RawJsonInput(self) + self.input_field.setSizePolicy( QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding ) - self.setFocusProxy(self.text_input) + self.setFocusProxy(self.input_field) - if not self._as_widget: + if not self.as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) - self.label_widget = label_widget - layout.addWidget(self.text_input, 1, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget - self.text_input.textChanged.connect(self._on_value_change) + layout.addWidget(self.input_field, 1, alignment=QtCore.Qt.AlignTop) + + self.input_field.textChanged.connect(self._on_value_change) def update_studio_values(self, parent_values): - self._is_invalid = self.text_input.has_invalid_value() + self._is_invalid = self.input_field.has_invalid_value() return super(RawJsonWidget, self).update_studio_values(parent_values) def set_value(self, value): self.validate_value(value) - self.text_input.set_value(value) + self.input_field.set_value(value) def _on_value_change(self, *args, **kwargs): - self._is_invalid = self.text_input.has_invalid_value() + self._is_invalid = self.input_field.has_invalid_value() return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) - def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) - - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - def item_value(self): if self.is_invalid: return NOT_SET - return self.text_input.json_value() + return self.input_field.json_value() class ListItem(QtWidgets.QWidget, SettingObject): @@ -1271,8 +1246,7 @@ class ListItem(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) def __init__( - self, object_type, input_modifiers, config_parent, parent, - is_strict=False + self, item_schema, config_parent, parent, is_strict=False ): super(ListItem, self).__init__(parent) @@ -1324,9 +1298,9 @@ class ListItem(QtWidgets.QWidget, SettingObject): layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) - ItemKlass = TypeToKlass.types[object_type] + ItemKlass = TypeToKlass.types[item_schema["type"]] self.value_input = ItemKlass( - input_modifiers, + item_schema, self, as_widget=True, label_widget=None @@ -1464,8 +1438,21 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.initial_attributes(input_data, parent, as_widget) - self.object_type = input_data["object_type"] - self.input_modifiers = input_data.get("input_modifiers") or {} + object_type = input_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + self.item_schema = { + "type": object_type + } + # Backwards compatibility + input_modifiers = input_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) self.input_fields = [] @@ -1536,9 +1523,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): def add_row(self, row=None, value=None, is_empty=False): # Create new item - item_widget = ListItem( - self.object_type, self.input_modifiers, self, self.inputs_widget - ) + item_widget = ListItem(self.item_schema, self, self.inputs_widget) previous_field = None next_field = None @@ -1654,29 +1639,16 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if not self.label_widget: + return + + state = self._style_state() if self._state == state: return - if self.label_widget: - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + self._state = state + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): output = [] @@ -1738,11 +1710,8 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): children_item_mapping = [] for child_configuration in input_data["object_types"]: - object_type = child_configuration["type"] - item_widget = ListItem( - object_type, child_configuration, self, self.inputs_widget, - is_strict=True + child_configuration, self, self.inputs_widget, is_strict=True ) self.input_fields.append(item_widget) @@ -1836,30 +1805,17 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self._is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if not self.label_widget: + return + + state = self._style_state() if self._state == state: return - if self.label_widget: - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + self._state = state + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): output = [] @@ -1872,7 +1828,7 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, input_modifiers, config_parent, parent): + def __init__(self, item_schema, config_parent, parent): super(ModifiableDictItem, self).__init__(parent) self._set_default_attributes() @@ -1892,13 +1848,13 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - ItemKlass = TypeToKlass.types[object_type] + ItemKlass = TypeToKlass.types[item_schema["type"]] self.key_input = QtWidgets.QLineEdit(self) self.key_input.setObjectName("DictKey") self.value_input = ItemKlass( - input_modifiers, + item_schema, self, as_widget=True, label_widget=None @@ -2064,6 +2020,22 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.key = input_data["key"] + object_type = input_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + # Backwards compatibility + self.item_schema = { + "type": object_type + } + input_modifiers = input_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) + if input_data.get("highlight_content", False): content_state = "hightlighted" bottom_margin = 5 @@ -2077,6 +2049,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if as_widget: body_widget = None + self.label_widget = label_widget else: body_widget = ExpandingWidget(input_data["label"], self) main_layout.addWidget(body_widget) @@ -2115,9 +2088,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.object_type = input_data["object_type"] - self.input_modifiers = input_data.get("input_modifiers") or {} - self.add_row(is_empty=True) def count(self): @@ -2180,42 +2150,29 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - if self._as_widget: - if not self.isEnabled(): - state = self.style_state(False, False, False, False) - else: - state = self.style_state( - False, - self.is_invalid, - False, - self._is_modified - ) - else: - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + state = self._style_state() + if self._state == state: return + self._state = state + + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + if not self.body_widget: + return + if state: child_state = "child-{}".format(state) else: child_state = "" - if self.body_widget: - self.body_widget.side_line_widget.setProperty("state", child_state) - self.body_widget.side_line_widget.style().polish( - self.body_widget.side_line_widget - ) - - if not self._as_widget: - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - self._state = state + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) def all_item_values(self): output = {} @@ -2232,7 +2189,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def add_row(self, row=None, key=None, value=None, is_empty=False): # Create new item item_widget = ModifiableDictItem( - self.object_type, self.input_modifiers, self, self.content_widget + self.item_schema, self, self.content_widget ) if is_empty: item_widget.set_as_empty() @@ -2373,6 +2330,8 @@ class DictWidget(QtWidgets.QWidget, SettingObject): def _ui_as_widget(self, input_data): body = QtWidgets.QWidget(self) body.setObjectName("DictAsWidgetBody") + show_borders = str(int(input_data.get("show_borders", True))) + body.setProperty("show_borders", show_borders) content_layout = QtWidgets.QGridLayout(body) content_layout.setContentsMargins(5, 5, 5, 5) @@ -2399,7 +2358,13 @@ class DictWidget(QtWidgets.QWidget, SettingObject): if self.checkbox_key and not self.checkbox_widget: key = child_configuration.get("key") if key == self.checkbox_key: - return self._add_checkbox_child(child_configuration) + if child_configuration["type"] != "boolean": + self.log.warning(( + "SCHEMA BUG: Dictionary item has set as checkbox" + " item invalid type \"{}\". Expected \"boolean\"." + ).format(child_configuration["type"])) + else: + return self._add_checkbox_child(child_configuration) label_widget = None if not klass.expand_in_grid: @@ -2426,7 +2391,7 @@ class DictWidget(QtWidgets.QWidget, SettingObject): ) item.value_changed.connect(self._on_value_change) - self.body_widget.add_widget_after_label(item) + self.body_widget.add_widget_before_label(item) self.checkbox_widget = item self.input_fields.append(item) return item @@ -2522,7 +2487,9 @@ class DictWidget(QtWidgets.QWidget, SettingObject): self._state = None self._child_state = None - if not self.as_widget: + if self.as_widget: + override_values = parent_values + else: metadata = {} groups = tuple() override_values = NOT_SET @@ -2997,20 +2964,20 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self.multiplatform = input_data.get("multiplatform", False) self.multipath = input_data.get("multipath", False) - self.input_fields = [] + self.input_field = None layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: + if not self.as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) - self.label_widget = label_widget + self.label_widget = label_widget self.content_widget = QtWidgets.QWidget(self) self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) @@ -3033,56 +3000,61 @@ class PathWidget(QtWidgets.QWidget, SettingObject): platform: value_type() for platform in self.platforms } - else: - return value_type() + return value_type() def create_gui(self): if not self.multiplatform and not self.multipath: input_data = {"key": self.key} path_input = PathInputWidget( - input_data, self, label_widget=self.label_widget + input_data, self, + as_widget=True, label_widget=self.label_widget ) self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) - self.input_fields.append(path_input) + self.input_field = path_input path_input.value_changed.connect(self._on_value_change) return - input_data_for_list = { - "object_type": "path-input" - } if not self.multiplatform: - input_data_for_list["key"] = self.key + item_schema = { + "key": self.key, + "object_type": "path-input" + } input_widget = ListWidget( - input_data_for_list, self, label_widget=self.label_widget + item_schema, self, + as_widget=True, label_widget=self.label_widget ) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) - self.input_fields.append(input_widget) + self.input_field = input_widget input_widget.value_changed.connect(self._on_value_change) return - proxy_widget = QtWidgets.QWidget(self.content_widget) - proxy_layout = QtWidgets.QFormLayout(proxy_widget) + item_schema = { + "type": "dict", + "show_borders": False, + "children": [] + } for platform_key in self.platforms: platform_label = self.platform_labels_mapping[platform_key] - label_widget = QtWidgets.QLabel(platform_label, proxy_widget) + child_item = { + "key": platform_key, + "label": platform_label + } if self.multipath: - input_data_for_list["key"] = platform_key - input_widget = ListWidget( - input_data_for_list, self, label_widget=label_widget - ) + child_item["type"] = "list" + child_item["object_type"] = "path-input" else: - input_data = {"key": platform_key} - input_widget = PathInputWidget( - input_data, self, label_widget=label_widget - ) - proxy_layout.addRow(label_widget, input_widget) - self.input_fields.append(input_widget) - input_widget.value_changed.connect(self._on_value_change) + child_item["type"] = "path-input" - self.setFocusProxy(self.input_fields[0]) - self.content_layout.addWidget(proxy_widget) + item_schema["children"].append(child_item) + + input_widget = DictWidget( + item_schema, self, as_widget=True, label_widget=self.label_widget + ) + self.content_layout.addWidget(input_widget) + self.input_field = input_widget + input_widget.value_changed.connect(self._on_value_change) def update_default_values(self, parent_values): self._state = None @@ -3090,21 +3062,15 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._is_modified = False value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - value = parent_values - else: - value = parent_values.get(self.key, NOT_SET) + value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: if self.develop_mode: - if self._as_widget or not self.multiplatform: - value = {self.key: self.default_input_value} - else: - value = self.default_input_value self.defaults_not_set = True + value = self.default_input_value if value is NOT_SET: raise NotImplementedError(( "{} Does not have implemented" @@ -3122,11 +3088,8 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._has_studio_override = False self._had_studio_override = False - if not self.multiplatform: - self.input_fields[0].update_default_values(value) - else: - for input_field in self.input_fields: - input_field.update_default_values(value) + # TODO handle invalid value type + self.input_field.update_default_values(value) def update_studio_values(self, parent_values): self._state = None @@ -3134,13 +3097,10 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._is_modified = False value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - value = parent_values - else: - value = parent_values.get(self.key, NOT_SET) + value = parent_values.get(self.key, NOT_SET) self.studio_value = value if value is not NOT_SET: @@ -3149,13 +3109,9 @@ class PathWidget(QtWidgets.QWidget, SettingObject): else: self._has_studio_override = False self._had_studio_override = False - value = self.default_value - if not self.multiplatform: - self.input_fields[0].update_studio_values(value) - else: - for input_field in self.input_fields: - input_field.update_studio_values(value) + # TODO handle invalid value type + self.input_field.update_studio_values(value) def apply_overrides(self, parent_values): self._is_modified = False @@ -3166,19 +3122,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): if self._as_widget: override_values = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - override_values = parent_values - else: - override_values = parent_values.get(self.key, NOT_SET) + override_values = parent_values.get(self.key, NOT_SET) self._is_overriden = override_values is not NOT_SET self._was_overriden = bool(self._is_overriden) - if not self.multiplatform: - self.input_fields[0].apply_overrides(parent_values) - else: - for input_field in self.input_fields: - input_field.apply_overrides(override_values) + # TODO handle invalid value type + self.input_field.update_studio_values(override_values) if not self._is_overriden: self._is_overriden = ( @@ -3191,12 +3141,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): def set_value(self, value): if not self.multiplatform: - self.input_fields[0].set_value(value) + return self.input_field.set_value(value) - else: - for input_field in self.input_fields: - _value = value[input_field.key] - input_field.set_value(_value) + for _input_field in self.input_field.input_fields: + _value = value.get(_input_field.key, NOT_SET) + if _value is NOT_SET: + continue + _input_field.set_value(_value) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -3239,7 +3190,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self.style().polish(self) self._child_state = child_state - if not self._as_widget: + if self.label_widget: state = self.style_state( child_has_studio_override, child_invalid, @@ -3257,80 +3208,60 @@ class PathWidget(QtWidgets.QWidget, SettingObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for input_field in self.input_fields: - input_field.remove_overrides() + self.input_field.remove_overrides() def reset_to_pype_default(self): - for input_field in self.input_fields: - input_field.reset_to_pype_default() + self.input_field.reset_to_pype_default() self._has_studio_override = False def set_studio_default(self): - for input_field in self.input_fields: - input_field.set_studio_default() + self.input_field.set_studio_default() if self.is_group: self._has_studio_override = True def discard_changes(self): + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + + self.input_field.discard_changes() + + if not self.is_overidable: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + self._is_overriden = False + return + self._is_modified = False self._is_overriden = self._was_overriden - for input_field in self.input_fields: - input_field.discard_changes() - - self._is_modified = self.child_modified - def set_as_overriden(self): self._is_overriden = True @property def child_has_studio_override(self): - for input_field in self.input_fields: - if ( - input_field.has_studio_override - or input_field.child_has_studio_override - ): - return True - return False + return self.has_studio_override @property def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False + return self.is_modified @property def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False + return self.is_overriden @property def child_invalid(self): - for input_field in self.input_fields: - if input_field.child_invalid: - return True - return False + return self.input_field.child_invalid def hierarchical_style_update(self): - for input_field in self.input_fields: - input_field.hierarchical_style_update() + self.input_field.hierarchical_style_update() self.update_style() def item_value(self): - if not self.multiplatform and not self.multipath: - return self.input_fields[0].item_value() - - if not self.multiplatform: - return self.input_fields[0].item_value() - - output = {} - for input_field in self.input_fields: - output.update(input_field.config_value()) - return output + return self.input_field.item_value() def studio_overrides(self): if ( @@ -3340,18 +3271,14 @@ class PathWidget(QtWidgets.QWidget, SettingObject): ): return NOT_SET, False - value = self.item_value() - if not self.multiplatform: - value = {self.key: value} + value = {self.key: self.item_value()} return value, self.is_group def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False - value = self.item_value() - if not self.multiplatform: - value = {self.key: value} + value = {self.key: self.item_value()} return value, self.is_group @@ -3601,6 +3528,7 @@ TypeToKlass.types["path-input"] = PathInputWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["list"] = ListWidget TypeToKlass.types["list-strict"] = ListStrictWidget +TypeToKlass.types["enum"] = EnumeratorWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict # DEPRECATED - remove when removed from schemas TypeToKlass.types["dict-item"] = DictWidget diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index f54989cfd7..cf2bd7f8af 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -299,7 +299,11 @@ def gui_schema(subfolder, main_schema_name): filepath = os.path.join(dirpath, filename) with open(filepath, "r") as json_stream: - schema_data = json.load(json_stream) + try: + schema_data = json.load(json_stream) + except Exception as e: + raise Exception((f"Unable to parse JSON file {json_stream}\n " + f" - {e}")) from e loaded_schemas[basename] = schema_data main_schema = _fill_inner_schemas( diff --git a/pype/tools/settings/settings/widgets/multiselection_combobox.py b/pype/tools/settings/settings/widgets/multiselection_combobox.py new file mode 100644 index 0000000000..9a99561ea8 --- /dev/null +++ b/pype/tools/settings/settings/widgets/multiselection_combobox.py @@ -0,0 +1,317 @@ +from Qt import QtCore, QtGui, QtWidgets + + +class ComboItemDelegate(QtWidgets.QStyledItemDelegate): + """ + Helper styled delegate (mostly based on existing private Qt's + delegate used by the QtWidgets.QComboBox). Used to style the popup like a + list view (e.g windows style). + """ + + def paint(self, painter, option, index): + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + # option.state &= ( + # ~QtWidgets.QStyle.State_HasFocus + # & ~QtWidgets.QStyle.State_MouseOver + # ) + super(ComboItemDelegate, self).paint(painter, option, index) + + +class MultiSelectionComboBox(QtWidgets.QComboBox): + value_changed = QtCore.Signal() + ignored_keys = { + QtCore.Qt.Key_Up, + QtCore.Qt.Key_Down, + QtCore.Qt.Key_PageDown, + QtCore.Qt.Key_PageUp, + QtCore.Qt.Key_Home, + QtCore.Qt.Key_End + } + + top_bottom_padding = 2 + left_right_padding = 3 + left_offset = 4 + top_bottom_margins = 2 + item_spacing = 5 + + item_bg_color = QtGui.QColor("#31424e") + + def __init__( + self, parent=None, placeholder="", separator=", ", **kwargs + ): + super(MultiSelectionComboBox, self).__init__(parent=parent, **kwargs) + self.setObjectName("MultiSelectionComboBox") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + self._popup_is_shown = False + self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True) + self._initial_mouse_pos = None + self._separator = separator + self.placeholder_text = placeholder + self.delegate = ComboItemDelegate(self) + self.setItemDelegate(self.delegate) + + self.lines = {} + self.item_height = ( + self.fontMetrics().height() + + (2 * self.top_bottom_padding) + + (2 * self.top_bottom_margins) + ) + + def mousePressEvent(self, event): + """Reimplemented.""" + self._popup_is_shown = False + super(MultiSelectionComboBox, self).mousePressEvent(event) + if self._popup_is_shown: + self._initial_mouse_pos = self.mapToGlobal(event.pos()) + self._block_mouse_release_timer.start( + QtWidgets.QApplication.doubleClickInterval() + ) + + def showPopup(self): + """Reimplemented.""" + super(MultiSelectionComboBox, self).showPopup() + view = self.view() + view.installEventFilter(self) + view.viewport().installEventFilter(self) + self._popup_is_shown = True + + def hidePopup(self): + """Reimplemented.""" + self.view().removeEventFilter(self) + self.view().viewport().removeEventFilter(self) + self._popup_is_shown = False + self._initial_mouse_pos = None + super(MultiSelectionComboBox, self).hidePopup() + self.view().clearFocus() + + def _event_popup_shown(self, obj, event): + if not self._popup_is_shown: + return + + current_index = self.view().currentIndex() + model = self.model() + + if event.type() == QtCore.QEvent.MouseMove: + if ( + self.view().isVisible() + and self._initial_mouse_pos is not None + and self._block_mouse_release_timer.isActive() + ): + diff = obj.mapToGlobal(event.pos()) - self._initial_mouse_pos + if diff.manhattanLength() > 9: + self._block_mouse_release_timer.stop() + return + + index_flags = current_index.flags() + state = current_index.data(QtCore.Qt.CheckStateRole) + new_state = None + + if event.type() == QtCore.QEvent.MouseButtonRelease: + if ( + self._block_mouse_release_timer.isActive() + or not current_index.isValid() + or not self.view().isVisible() + or not self.view().rect().contains(event.pos()) + or not index_flags & QtCore.Qt.ItemIsSelectable + or not index_flags & QtCore.Qt.ItemIsEnabled + or not index_flags & QtCore.Qt.ItemIsUserCheckable + ): + return + + if state == QtCore.Qt.Unchecked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + elif event.type() == QtCore.QEvent.KeyPress: + # TODO: handle QtCore.Qt.Key_Enter, Key_Return? + if event.key() == QtCore.Qt.Key_Space: + # toogle the current items check state + if ( + index_flags & QtCore.Qt.ItemIsUserCheckable + and index_flags & QtCore.Qt.ItemIsTristate + ): + new_state = QtCore.Qt.CheckState((int(state) + 1) % 3) + + elif index_flags & QtCore.Qt.ItemIsUserCheckable: + if state != QtCore.Qt.Checked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + if new_state is not None: + model.setData(current_index, new_state, QtCore.Qt.CheckStateRole) + self.view().update(current_index) + self.update_size_hint() + self.value_changed.emit() + return True + + def eventFilter(self, obj, event): + """Reimplemented.""" + result = self._event_popup_shown(obj, event) + if result is not None: + return result + + return super(MultiSelectionComboBox, self).eventFilter(obj, event) + + def paintEvent(self, event): + """Reimplemented.""" + painter = QtWidgets.QStylePainter(self) + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option) + + # draw the icon and text + items = self.checked_items_text() + if not items: + option.currentText = self.placeholder_text + option.palette.setCurrentColorGroup(QtGui.QPalette.Disabled) + painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option) + return + + font_metricts = self.fontMetrics() + for line, items in self.lines.items(): + top_y = ( + option.rect.top() + + (line * self.item_height) + + self.top_bottom_margins + ) + left_x = option.rect.left() + self.left_offset + for item in items: + label_rect = font_metricts.boundingRect(item) + label_height = label_rect.height() + + label_rect.moveTop(top_y) + label_rect.moveLeft(left_x) + label_rect.setHeight(self.item_height) + + bg_rect = QtCore.QRectF(label_rect) + bg_rect.setWidth( + label_rect.width() + + (2 * self.left_right_padding) + ) + left_x = bg_rect.right() + self.item_spacing + + label_rect.moveLeft(label_rect.x() + self.left_right_padding) + + bg_rect.setHeight(label_height + (2 * self.top_bottom_padding)) + bg_rect.moveTop(bg_rect.top() + self.top_bottom_margins) + + path = QtGui.QPainterPath() + path.addRoundedRect(bg_rect, 5, 5) + + painter.fillPath(path, self.item_bg_color) + + painter.drawText( + label_rect, + QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, + item + ) + + def resizeEvent(self, *args, **kwargs): + super(MultiSelectionComboBox, self).resizeEvent(*args, **kwargs) + self.update_size_hint() + + def update_size_hint(self): + self.lines = {} + + items = self.checked_items_text() + if not items: + self.update() + return + + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + btn_rect = self.style().subControlRect( + QtWidgets.QStyle.CC_ComboBox, + option, + QtWidgets.QStyle.SC_ComboBoxArrow + ) + total_width = option.rect.width() - btn_rect.width() + font_metricts = self.fontMetrics() + + line = 0 + self.lines = {line: []} + + font_metricts = self.fontMetrics() + default_left_x = 0 + self.left_offset + left_x = int(default_left_x) + for item in items: + rect = font_metricts.boundingRect(item) + width = rect.width() + (2 * self.left_right_padding) + right_x = left_x + width + if right_x > total_width: + left_x = int(default_left_x) + if self.lines.get(line): + line += 1 + self.lines[line] = [item] + left_x += width + else: + self.lines[line] = [item] + line += 1 + else: + self.lines[line].append(item) + left_x = left_x + width + self.item_spacing + + self.update() + self.updateGeometry() + + def sizeHint(self): + value = super(MultiSelectionComboBox, self).sizeHint() + lines = len(self.lines) + if lines == 0: + lines = 1 + value.setHeight( + (lines * self.item_height) + + (2 * self.top_bottom_margins) + ) + return value + + def setItemCheckState(self, index, state): + self.setItemData(index, state, QtCore.Qt.CheckStateRole) + + def set_value(self, values): + for idx in range(self.count()): + value = self.itemData(idx, role=QtCore.Qt.UserRole) + if value in values: + check_state = QtCore.Qt.Checked + else: + check_state = QtCore.Qt.Unchecked + self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole) + self.update_size_hint() + + def value(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append( + self.itemData(idx, role=QtCore.Qt.UserRole) + ) + return items + + def checked_items_text(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append(self.itemText(idx)) + return items + + def wheelEvent(self, event): + event.ignore() + + def keyPressEvent(self, event): + if ( + event.key() == QtCore.Qt.Key_Down + and event.modifiers() & QtCore.Qt.AltModifier + ): + return self.showPopup() + + if event.key() in self.ignored_keys: + return event.ignore() + + return super(MultiSelectionComboBox, self).keyPressEvent(event) diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index 2a1f5fe804..b0bcf059a5 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -25,6 +25,28 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox): return output +class ComboBox(QtWidgets.QComboBox): + value_changed = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(ComboBox, self).__init__(*args, **kwargs) + + self.currentIndexChanged.connect(self._on_change) + + def _on_change(self, *args, **kwargs): + self.value_changed.emit() + + def set_value(self, value): + for idx in range(self.count()): + _value = self.itemData(idx, role=QtCore.Qt.UserRole) + if _value == value: + self.setCurrentIndex(idx) + break + + def value(self): + return self.itemData(self.currentIndex(), role=QtCore.Qt.UserRole) + + class PathInput(QtWidgets.QLineEdit): def clear_end_path(self): value = self.text().strip() @@ -71,33 +93,51 @@ class ExpandingWidget(QtWidgets.QWidget): top_part = ClickableWidget(parent=self) + side_line_widget = QtWidgets.QWidget(top_part) + side_line_widget.setObjectName("SideLineWidget") + button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle = QtWidgets.QToolButton(parent=side_line_widget) button_toggle.setProperty("btn-type", "expand-toggle") 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) + label_widget = QtWidgets.QLabel(label, parent=side_line_widget) label_widget.setObjectName("DictLabel") - side_line_widget = QtWidgets.QWidget(top_part) - side_line_widget.setObjectName("SideLineWidget") + before_label_widget = QtWidgets.QWidget(side_line_widget) + before_label_layout = QtWidgets.QVBoxLayout(before_label_widget) + before_label_layout.setContentsMargins(0, 0, 0, 0) + + after_label_widget = QtWidgets.QWidget(side_line_widget) + after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) + after_label_layout.setContentsMargins(0, 0, 0, 0) + + spacer_widget = QtWidgets.QWidget(side_line_widget) + side_line_layout = QtWidgets.QHBoxLayout(side_line_widget) side_line_layout.setContentsMargins(5, 10, 0, 10) side_line_layout.addWidget(button_toggle) + side_line_layout.addWidget(before_label_widget) side_line_layout.addWidget(label_widget) + side_line_layout.addWidget(after_label_widget) + side_line_layout.addWidget(spacer_widget, 1) top_part_layout = QtWidgets.QHBoxLayout(top_part) top_part_layout.setContentsMargins(0, 0, 0, 0) top_part_layout.addWidget(side_line_widget) + before_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.top_part_ending = None - self.after_label_layout = None - self.end_of_layout = None + self.after_label_layout = after_label_layout + self.before_label_layout = before_label_layout self.side_line_widget = side_line_widget self.side_line_layout = side_line_layout @@ -146,45 +186,10 @@ class ExpandingWidget(QtWidgets.QWidget): self.parent().updateGeometry() def add_widget_after_label(self, widget): - self._add_side_widget_subwidgets() self.after_label_layout.addWidget(widget) - def _add_side_widget_subwidgets(self): - if self.top_part_ending is not None: - return - - top_part_ending = QtWidgets.QWidget(self.side_line_widget) - top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - top_part_ending_layout = QtWidgets.QHBoxLayout(top_part_ending) - top_part_ending_layout.setContentsMargins(0, 0, 0, 0) - top_part_ending_layout.setSpacing(0) - top_part_ending_layout.setAlignment(QtCore.Qt.AlignVCenter) - - after_label_widget = QtWidgets.QWidget(top_part_ending) - spacer_item = QtWidgets.QWidget(top_part_ending) - end_of_widget = QtWidgets.QWidget(top_part_ending) - - self.after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) - self.after_label_layout.setContentsMargins(0, 0, 0, 0) - - self.end_of_layout = QtWidgets.QVBoxLayout(end_of_widget) - self.end_of_layout.setContentsMargins(0, 0, 0, 0) - - spacer_layout = QtWidgets.QVBoxLayout(spacer_item) - spacer_layout.setContentsMargins(0, 0, 0, 0) - - top_part_ending_layout.addWidget(after_label_widget, 0) - top_part_ending_layout.addWidget(spacer_item, 1) - top_part_ending_layout.addWidget(end_of_widget, 0) - - top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) - after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - spacer_item.setAttribute(QtCore.Qt.WA_TranslucentBackground) - end_of_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - self.top_part_ending = top_part_ending - self.side_line_layout.addWidget(top_part_ending) + def add_widget_before_label(self, widget): + self.before_label_layout.addWidget(widget) def resizeEvent(self, event): super(ExpandingWidget, self).resizeEvent(event) @@ -243,10 +248,11 @@ class GridLabelWidget(QtWidgets.QWidget): self.properties = {} layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) + layout.setContentsMargins(0, 2, 0, 0) layout.setSpacing(0) label_proxy = QtWidgets.QWidget(self) + label_proxy_layout = QtWidgets.QHBoxLayout(label_proxy) label_proxy_layout.setContentsMargins(0, 0, 0, 0) label_proxy_layout.setSpacing(0) @@ -265,6 +271,9 @@ class GridLabelWidget(QtWidgets.QWidget): layout.addWidget(label_proxy, 0) layout.addWidget(spacer_widget_v, 1) + label_proxy.setAttribute(QtCore.Qt.WA_TranslucentBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + self.label_widget = label_widget def setProperty(self, name, value): diff --git a/pype/version.py b/pype/version.py index 95a6d3a792..96fc614cb2 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.12.0" +__version__ = "2.12.1"