diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f1dcf314..5464c390ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,40 @@ # Changelog -## [3.14.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.5](https://github.com/pypeclub/OpenPype/tree/HEAD) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.3...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.4...HEAD) + +**🚀 Enhancements** + +- Maya: add OBJ extractor to model family [\#4021](https://github.com/pypeclub/OpenPype/pull/4021) +- Publish report viewer tool [\#4010](https://github.com/pypeclub/OpenPype/pull/4010) +- Nuke | Global: adding custom tags representation filtering [\#4009](https://github.com/pypeclub/OpenPype/pull/4009) +- Publisher: Create context has shared data for collection phase [\#3995](https://github.com/pypeclub/OpenPype/pull/3995) +- Resolve: updating to v18 compatibility [\#3986](https://github.com/pypeclub/OpenPype/pull/3986) + +**🐛 Bug fixes** + +- TrayPublisher: Fix missing argument [\#4019](https://github.com/pypeclub/OpenPype/pull/4019) +- General: Fix python 2 compatibility of ffmpeg and oiio tools discovery [\#4011](https://github.com/pypeclub/OpenPype/pull/4011) + +**🔀 Refactored code** + +- Maya: Removed unused imports [\#4008](https://github.com/pypeclub/OpenPype/pull/4008) +- Unreal: Fix import of moved function [\#4007](https://github.com/pypeclub/OpenPype/pull/4007) +- Houdini: Change import of RepairAction [\#4005](https://github.com/pypeclub/OpenPype/pull/4005) +- Nuke/Hiero: Refactor openpype.api imports [\#4000](https://github.com/pypeclub/OpenPype/pull/4000) +- TVPaint: Defined with HostBase [\#3994](https://github.com/pypeclub/OpenPype/pull/3994) + +**Merged pull requests:** + +- Unreal: Remove redundant Creator stub [\#4012](https://github.com/pypeclub/OpenPype/pull/4012) +- Unreal: add `uproject` extension to Unreal project template [\#4004](https://github.com/pypeclub/OpenPype/pull/4004) +- Unreal: fix order of includes [\#4002](https://github.com/pypeclub/OpenPype/pull/4002) +- Fusion: Implement backwards compatibility \(+/- Fusion 17.2\) [\#3958](https://github.com/pypeclub/OpenPype/pull/3958) + +## [3.14.4](https://github.com/pypeclub/OpenPype/tree/3.14.4) (2022-10-19) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.3...3.14.4) **🆕 New features** @@ -27,7 +59,6 @@ - Maya: Moved plugin from global to maya [\#3939](https://github.com/pypeclub/OpenPype/pull/3939) - Publisher: Create dialog is part of main window [\#3936](https://github.com/pypeclub/OpenPype/pull/3936) - Fusion: Implement Alembic and FBX mesh loader [\#3927](https://github.com/pypeclub/OpenPype/pull/3927) -- Maya: Remove hardcoded requirement for maya/ start for image file prefix [\#3873](https://github.com/pypeclub/OpenPype/pull/3873) **🐛 Bug fixes** @@ -71,14 +102,6 @@ **🚀 Enhancements** - Publisher: Enhancement proposals [\#3897](https://github.com/pypeclub/OpenPype/pull/3897) -- Maya: better logging in Maketx [\#3886](https://github.com/pypeclub/OpenPype/pull/3886) -- Photoshop: review can be turned off [\#3885](https://github.com/pypeclub/OpenPype/pull/3885) -- TrayPublisher: added persisting of last selected project [\#3871](https://github.com/pypeclub/OpenPype/pull/3871) -- TrayPublisher: added text filter on project name to Tray Publisher [\#3867](https://github.com/pypeclub/OpenPype/pull/3867) -- Github issues adding `running version` section [\#3864](https://github.com/pypeclub/OpenPype/pull/3864) -- Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) -- Flame: make migratable projects after creation [\#3860](https://github.com/pypeclub/OpenPype/pull/3860) -- Photoshop: synchronize image version with workfile [\#3854](https://github.com/pypeclub/OpenPype/pull/3854) **🐛 Bug fixes** @@ -86,12 +109,6 @@ - Flame: loading multilayer exr to batch/reel is working [\#3901](https://github.com/pypeclub/OpenPype/pull/3901) - Hiero: Fix inventory check on launch [\#3895](https://github.com/pypeclub/OpenPype/pull/3895) - WebPublisher: Fix import after refactor [\#3891](https://github.com/pypeclub/OpenPype/pull/3891) -- TVPaint: Fix renaming of rendered files [\#3882](https://github.com/pypeclub/OpenPype/pull/3882) -- Publisher: Nice checkbox visible in Python 2 [\#3877](https://github.com/pypeclub/OpenPype/pull/3877) -- Settings: Add missing default settings [\#3870](https://github.com/pypeclub/OpenPype/pull/3870) -- General: Copy of workfile does not use 'copy' function but 'copyfile' [\#3869](https://github.com/pypeclub/OpenPype/pull/3869) -- Tray Publisher: skip plugin if otioTimeline is missing [\#3856](https://github.com/pypeclub/OpenPype/pull/3856) -- Flame: retimed attributes are integrated with settings [\#3855](https://github.com/pypeclub/OpenPype/pull/3855) **🔀 Refactored code** @@ -105,8 +122,6 @@ **Merged pull requests:** - Maya: Fix Scene Inventory possibly starting off-screen due to maya preferences [\#3923](https://github.com/pypeclub/OpenPype/pull/3923) -- Maya: RenderSettings set default image format for V-Ray+Redshift to exr [\#3879](https://github.com/pypeclub/OpenPype/pull/3879) -- Remove lockfile during publish [\#3874](https://github.com/pypeclub/OpenPype/pull/3874) ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) diff --git a/openpype/hosts/maya/api/obj.py b/openpype/hosts/maya/api/obj.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 253dae1e43..eca1b27f34 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -90,7 +90,7 @@ class ImportMayaLoader(load.LoaderPlugin): so you could also use it as a new base. """ - representations = ["ma", "mb"] + representations = ["ma", "mb", "obj"] families = ["*"] label = "Import" diff --git a/openpype/hosts/maya/plugins/publish/extract_obj.py b/openpype/hosts/maya/plugins/publish/extract_obj.py new file mode 100644 index 0000000000..edfe0b9439 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_obj.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +import os + +from maya import cmds +# import maya.mel as mel +import pyblish.api +from openpype.pipeline import publish +from openpype.hosts.maya.api import lib + + +class ExtractObj(publish.Extractor): + """Extract OBJ from Maya. + + This extracts reproducible OBJ exports ignoring any of the settings + set on the local machine in the OBJ export options window. + + """ + order = pyblish.api.ExtractorOrder + hosts = ["maya"] + label = "Extract OBJ" + families = ["model"] + + def process(self, instance): + + # Define output path + + staging_dir = self.staging_dir(instance) + filename = "{0}.obj".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need to + # format it into a string in a mel expression + + self.log.info("Extracting OBJ to: {0}".format(path)) + + members = instance.data("setMembers") + members = cmds.ls(members, + dag=True, + shapes=True, + type=("mesh", "nurbsCurve"), + noIntermediate=True, + long=True) + self.log.info("Members: {0}".format(members)) + self.log.info("Instance: {0}".format(instance[:])) + + if not cmds.pluginInfo('objExport', query=True, loaded=True): + cmds.loadPlugin('objExport') + + # Export + with lib.no_display_layers(instance): + with lib.displaySmoothness(members, + divisionsU=0, + divisionsV=0, + pointsWire=4, + pointsShaded=1, + polygonObject=1): + with lib.shader(members, + shadingEngine="initialShadingGroup"): + with lib.maintained_selection(): + cmds.select(members, noExpand=True) + cmds.file(path, + exportSelected=True, + type='OBJexport', + preserveReferences=True, + force=True) + + if "representation" not in instance.data: + instance.data["representation"] = [] + + representation = { + 'name': 'obj', + 'ext': 'obj', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.info("Extract OBJ successful to: {0}".format(path)) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 1aea04d889..2691b7447a 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2930,3 +2930,47 @@ def get_nodes_by_names(names): nuke.toNode(name) for name in names ] + + +def get_viewer_config_from_string(input_string): + """Convert string to display and viewer string + + Args: + input_string (str): string with viewer + + Raises: + IndexError: if more then one slash in input string + IndexError: if missing closing bracket + + Returns: + tuple[str]: display, viewer + """ + display = None + viewer = input_string + # check if () or / or \ in name + if "/" in viewer: + split = viewer.split("/") + + # rise if more then one column + if len(split) > 2: + raise IndexError(( + "Viewer Input string is not correct. " + "more then two `/` slashes! {}" + ).format(input_string)) + + viewer = split[1] + display = split[0] + elif "(" in viewer: + pattern = r"([\w\d\s]+).*[(](.*)[)]" + result = re.findall(pattern, viewer) + try: + result = result.pop() + display = str(result[1]).rstrip() + viewer = str(result[0]).rstrip() + except IndexError: + raise IndexError(( + "Viewer Input string is not correct. " + "Missing bracket! {}" + ).format(input_string)) + + return (display, viewer) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 91bb90ff99..5981a8b386 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -19,7 +19,8 @@ from .lib import ( add_publish_knob, get_nuke_imageio_settings, set_node_knobs_from_settings, - get_view_process_node + get_view_process_node, + get_viewer_config_from_string ) @@ -190,7 +191,20 @@ class ExporterReview(object): if "#" in self.fhead: self.fhead = self.fhead.replace("#", "")[:-1] - def get_representation_data(self, tags=None, range=False): + def get_representation_data( + self, tags=None, range=False, + custom_tags=None + ): + """ Add representation data to self.data + + Args: + tags (list[str], optional): list of defined tags. + Defaults to None. + range (bool, optional): flag for adding ranges. + Defaults to False. + custom_tags (list[str], optional): user inputed custom tags. + Defaults to None. + """ add_tags = tags or [] repre = { "name": self.name, @@ -200,6 +214,9 @@ class ExporterReview(object): "tags": [self.name.replace("_", "-")] + add_tags } + if custom_tags: + repre["custom_tags"] = custom_tags + if range: repre.update({ "frameStart": self.first_frame, @@ -312,7 +329,8 @@ class ExporterReviewLut(ExporterReview): dag_node.setInput(0, self.previous_node) self._temp_nodes.append(dag_node) self.previous_node = dag_node - self.log.debug("OCIODisplay... `{}`".format(self._temp_nodes)) + self.log.debug( + "OCIODisplay... `{}`".format(self._temp_nodes)) # GenerateLUT gen_lut_node = nuke.createNode("GenerateLUT") @@ -415,6 +433,7 @@ class ExporterReviewMov(ExporterReview): return path def generate_mov(self, farm=False, **kwargs): + add_tags = [] self.publish_on_farm = farm read_raw = kwargs["read_raw"] reformat_node_add = kwargs["reformat_node_add"] @@ -433,10 +452,10 @@ class ExporterReviewMov(ExporterReview): self.log.debug(">> baking_view_profile `{}`".format( baking_view_profile)) - add_tags = kwargs.get("add_tags", []) + add_custom_tags = kwargs.get("add_custom_tags", []) self.log.info( - "__ add_tags: `{0}`".format(add_tags)) + "__ add_custom_tags: `{0}`".format(add_custom_tags)) subset = self.instance.data["subset"] self._temp_nodes[subset] = [] @@ -491,7 +510,15 @@ class ExporterReviewMov(ExporterReview): if not self.viewer_lut_raw: # OCIODisplay dag_node = nuke.createNode("OCIODisplay") - dag_node["view"].setValue(str(baking_view_profile)) + + display, viewer = get_viewer_config_from_string( + str(baking_view_profile) + ) + if display: + dag_node["display"].setValue(display) + + # assign viewer + dag_node["view"].setValue(viewer) # connect dag_node.setInput(0, self.previous_node) @@ -542,6 +569,7 @@ class ExporterReviewMov(ExporterReview): # ---------- generate representation data self.get_representation_data( tags=["review", "delete"] + add_tags, + custom_tags=add_custom_tags, range=True ) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 27117510b2..431ddcc3b4 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -128,6 +128,7 @@ class ExtractReview(pyblish.api.InstancePlugin): for repre in instance.data["representations"]: repre_name = str(repre.get("name")) tags = repre.get("tags") or [] + custom_tags = repre.get("custom_tags") if "review" not in tags: self.log.debug(( "Repre: {} - Didn't found \"review\" in tags. Skipping" @@ -158,15 +159,18 @@ class ExtractReview(pyblish.api.InstancePlugin): ) continue - # Filter output definition by representation tags (optional) - outputs = self.filter_outputs_by_tags(profile_outputs, tags) + # Filter output definition by representation's + # custom tags (optional) + outputs = self.filter_outputs_by_custom_tags( + profile_outputs, custom_tags) if not outputs: self.log.info(( "Skipped representation. All output definitions from" " selected profile does not match to representation's" - " tags. \"{}\"" + " custom tags. \"{}\"" ).format(str(tags))) continue + outputs_per_representations.append((repre, outputs)) return outputs_per_representations @@ -1656,7 +1660,9 @@ class ExtractReview(pyblish.api.InstancePlugin): return True return False - def filter_output_defs(self, profile, subset_name, families): + def filter_output_defs( + self, profile, subset_name, families + ): """Return outputs matching input instance families. Output definitions without families filter are marked as valid. @@ -1664,6 +1670,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Args: profile (dict): Profile from presets matching current context. families (list): All families of current instance. + subset_name (str): name of subset Returns: list: Containg all output definitions matching entered families. @@ -1711,39 +1718,55 @@ class ExtractReview(pyblish.api.InstancePlugin): return filtered_outputs - def filter_outputs_by_tags(self, outputs, tags): - """Filter output definitions by entered representation tags. + def filter_outputs_by_custom_tags(self, outputs, custom_tags): + """Filter output definitions by entered representation custom_tags. - Output definitions without tags filter are marked as valid. + Output definitions without custom_tags filter are marked as invalid, + only in case representation is having any custom_tags defined. Args: outputs (list): Contain list of output definitions from presets. - tags (list): Tags of processed representation. + custom_tags (list): Custom Tags of processed representation. Returns: list: Containg all output definitions matching entered tags. """ filtered_outputs = [] - repre_tags_low = [tag.lower() for tag in tags] + repre_c_tags_low = [tag.lower() for tag in (custom_tags or [])] for output_def in outputs: - valid = True - output_filters = output_def.get("filter") - if output_filters: - # Check tag filters - tag_filters = output_filters.get("tags") - if tag_filters: - tag_filters_low = [tag.lower() for tag in tag_filters] - valid = False - for tag in repre_tags_low: - if tag in tag_filters_low: - valid = True - break + valid = False + tag_filters = output_def.get("filter", {}).get("custom_tags") - if not valid: - continue + if ( + # if any of tag filter is empty, skip + custom_tags and not tag_filters + or not custom_tags and tag_filters + ): + continue + elif not custom_tags and not tag_filters: + valid = True - if valid: - filtered_outputs.append(output_def) + # lower all filter tags + tag_filters_low = [tag.lower() for tag in tag_filters] + + self.log.debug("__ tag_filters: {}".format(tag_filters)) + self.log.debug("__ repre_c_tags_low: {}".format( + repre_c_tags_low)) + + # check if any repre tag is not in filter tags + for tag in repre_c_tags_low: + if tag in tag_filters_low: + valid = True + break + + if not valid: + continue + + filtered_outputs.append(output_def) + + self.log.debug("__ filtered_outputs: {}".format( + [_o["filename_suffix"] for _o in filtered_outputs] + )) return filtered_outputs diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 1b7dc7a41a..b128564bc2 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -78,7 +78,8 @@ "review", "ftrack" ], - "subsets": [] + "subsets": [], + "custom_tags": [] }, "overscan_crop": "", "overscan_color": [ diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 86815b8fc4..988c0e777a 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -131,6 +131,16 @@ "Main" ] }, + "CreateModel": { + "enabled": true, + "write_color_sets": false, + "write_face_sets": false, + "defaults": [ + "Main", + "Proxy", + "Sculpt" + ] + }, "CreatePointCache": { "enabled": true, "write_color_sets": false, @@ -187,16 +197,6 @@ "Main" ] }, - "CreateModel": { - "enabled": true, - "write_color_sets": false, - "write_face_sets": false, - "defaults": [ - "Main", - "Proxy", - "Sculpt" - ] - }, "CreateRenderSetup": { "enabled": true, "defaults": [ @@ -577,6 +577,10 @@ "vrayproxy" ] }, + "ExtractObj": { + "enabled": false, + "optional": true + }, "ValidateRigContents": { "enabled": false, "optional": true, diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index e5cbacbda7..57a09086ca 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -434,7 +434,7 @@ } ], "extension": "mov", - "add_tags": [] + "add_custom_tags": [] } } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 773dea1229..51fc8dedf3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -295,6 +295,15 @@ "label": "Subsets", "type": "list", "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 53247f6bd4..ab8c6b885e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -657,6 +657,25 @@ "object_type": "text" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractObj", + "label": "Extract OBJ", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + } + ] } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index e5827a92c4..c91d3c0e3d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -296,8 +296,8 @@ "label": "Write node file type" }, { - "key": "add_tags", - "label": "Add additional tags to representations", + "key": "add_custom_tags", + "label": "Add custom tags", "type": "list", "object_type": "text" }