diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dfadd0088c..38b5a79232 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,9 +35,10 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.1 + - 3.18.1-nightly.1 - 3.18.0 - 3.17.7 - - 3.18.1-nightly.1 - 3.17.7-nightly.7 - 3.17.7-nightly.6 - 3.17.7-nightly.5 @@ -134,7 +135,6 @@ body: - 3.15.4-nightly.2 - 3.15.4-nightly.1 - 3.15.3 - - 3.15.3-nightly.4 validations: required: true - type: dropdown diff --git a/README.md b/README.md index ed3e058002..a79b9f2582 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ OpenPype [![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2022-lightgrey?labelColor=303846) +## Important Notice! + +OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 + Introduction ------------ diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index ecf36abdd2..6e71b41541 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( legacy_io, Creator as NewCreator, CreatedInstance, - Anatomy + Anatomy, ) @@ -27,28 +27,21 @@ class CreateSaver(NewCreator): description = "Fusion Saver to generate image sequence" icon = "fa5.eye" - instance_attributes = [ - "reviewable" - ] + instance_attributes = ["reviewable"] + image_format = "exr" # TODO: This should be renamed together with Nuke so it is aligned temp_rendering_path_template = ( - "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") + "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}" + ) def create(self, subset_name, instance_data, pre_create_data): - self.pass_pre_attributes_to_instance( - instance_data, - pre_create_data + self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + + instance_data.update( + {"id": "pyblish.avalon.instance", "subset": subset_name} ) - instance_data.update({ - "id": "pyblish.avalon.instance", - "subset": subset_name - }) - - # TODO: Add pre_create attributes to choose file format? - file_format = "OpenEXRFormat" - comp = get_current_comp() with comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers @@ -56,19 +49,6 @@ class CreateSaver(NewCreator): self._update_tool_with_data(saver, data=instance_data) - saver["OutputFormat"] = file_format - - # Check file format settings are available - if saver[file_format] is None: - raise RuntimeError( - f"File format is not set to {file_format}, this is a bug" - ) - - # Set file format attributes - saver[file_format]["Depth"] = 0 # Auto | float16 | float32 - # TODO Is this needed? - saver[file_format]["SaveAlpha"] = 1 - # Register the CreatedInstance instance = CreatedInstance( family=self.family, @@ -151,17 +131,17 @@ class CreateSaver(NewCreator): anatomy = Anatomy() frame_padding = anatomy.templates["frame_padding"] + # get output format + ext = data["creator_attributes"]["image_format"] + # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - formatting_data.update({ - "workdir": workdir, - "frame": "0" * frame_padding, - "ext": "exr" - }) + formatting_data.update( + {"workdir": workdir, "frame": "0" * frame_padding, "ext": ext} + ) # build file path to render - filepath = self.temp_rendering_path_template.format( - **formatting_data) + filepath = self.temp_rendering_path_template.format(**formatting_data) comp = get_current_comp() tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) @@ -201,7 +181,8 @@ class CreateSaver(NewCreator): attr_defs = [ self._get_render_target_enum(), self._get_reviewable_bool(), - self._get_frame_range_enum() + self._get_frame_range_enum(), + self._get_image_format_enum(), ] return attr_defs @@ -209,11 +190,7 @@ class CreateSaver(NewCreator): """Settings for publish page""" return self.get_pre_create_attr_defs() - def pass_pre_attributes_to_instance( - self, - instance_data, - pre_create_data - ): + def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): creator_attrs = instance_data["creator_attributes"] = {} for pass_key in pre_create_data.keys(): creator_attrs[pass_key] = pre_create_data[pass_key] @@ -236,13 +213,13 @@ class CreateSaver(NewCreator): frame_range_options = { "asset_db": "Current asset context", "render_range": "From render in/out", - "comp_range": "From composition timeline" + "comp_range": "From composition timeline", } return EnumDef( "frame_range_source", items=frame_range_options, - label="Frame range source" + label="Frame range source", ) def _get_reviewable_bool(self): @@ -252,20 +229,33 @@ class CreateSaver(NewCreator): label="Review", ) + def _get_image_format_enum(self): + image_format_options = ["exr", "tga", "tif", "png", "jpg"] + return EnumDef( + "image_format", + items=image_format_options, + default=self.image_format, + label="Output Image Format", + ) + def apply_settings(self, project_settings): """Method called on initialization of plugin to apply settings.""" # plugin settings - plugin_settings = ( - project_settings["fusion"]["create"][self.__class__.__name__] - ) + plugin_settings = project_settings["fusion"]["create"][ + self.__class__.__name__ + ] # individual attributes self.instance_attributes = plugin_settings.get( - "instance_attributes") or self.instance_attributes - self.default_variants = plugin_settings.get( - "default_variants") or self.default_variants - self.temp_rendering_path_template = ( - plugin_settings.get("temp_rendering_path_template") - or self.temp_rendering_path_template + "instance_attributes", self.instance_attributes + ) + self.default_variants = plugin_settings.get( + "default_variants", self.default_variants + ) + self.temp_rendering_path_template = plugin_settings.get( + "temp_rendering_path_template", self.temp_rendering_path_template + ) + self.image_format = plugin_settings.get( + "image_format", self.image_format ) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 298084a4e8..8531233bb2 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -511,3 +511,20 @@ def render_resolution(width, height): finally: rt.renderWidth = current_renderWidth rt.renderHeight = current_renderHeight + + +@contextlib.contextmanager +def suspended_refresh(): + """Suspended refresh for scene and modify panel redraw. + """ + if is_headless(): + yield + return + rt.disableSceneRedraw() + rt.suspendEditing() + try: + yield + + finally: + rt.enableSceneRedraw() + rt.resumeEditing() diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_alembic.py similarity index 56% rename from openpype/hosts/max/plugins/publish/extract_pointcache.py rename to openpype/hosts/max/plugins/publish/extract_alembic.py index f6a8500c08..24e0121a61 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_alembic.py @@ -39,45 +39,41 @@ Note: """ import os import pyblish.api -from openpype.pipeline import publish +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection +from openpype.hosts.max.api.lib import suspended_refresh +from openpype.lib import BoolDef -class ExtractAlembic(publish.Extractor): +class ExtractAlembic(publish.Extractor, + OptionalPyblishPluginMixin): order = pyblish.api.ExtractorOrder label = "Extract Pointcache" hosts = ["max"] families = ["pointcache"] + optional = True def process(self, instance): - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.debug("Extracting pointcache ...") + if not self.is_active(instance.data): + return parent_dir = self.staging_dir(instance) file_name = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, file_name) - # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir)) - - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - path, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) + with suspended_refresh(): + self._set_abc_attributes(instance) + with maintained_selection(): + # select and export + node_list = instance.data["members"] + rt.Select(node_list) + rt.exportFile( + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, + ) if "representations" not in instance.data: instance.data["representations"] = [] @@ -89,3 +85,51 @@ class ExtractAlembic(publish.Extractor): "stagingDir": parent_dir, } instance.data["representations"].append(representation) + + def _set_abc_attributes(self, instance): + start = instance.data["frameStartHandle"] + end = instance.data["frameEndHandle"] + attr_values = self.get_attr_values_from_data(instance.data) + custom_attrs = attr_values.get("custom_attrs", False) + if not custom_attrs: + self.log.debug( + "No Custom Attributes included in this abc export...") + rt.AlembicExport.ArchiveType = rt.Name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.Name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = custom_attrs + + @classmethod + def get_attribute_defs(cls): + return [ + BoolDef("custom_attrs", + label="Custom Attributes", + default=False), + ] + + +class ExtractCameraAlembic(ExtractAlembic): + """Extract Camera with AlembicExport.""" + + label = "Extract Alembic Camera" + families = ["camera"] + + +class ExtractModel(ExtractAlembic): + """Extract Geometry in Alembic Format""" + label = "Extract Geometry (Alembic)" + families = ["model"] + + def _set_abc_attributes(self, instance): + attr_values = self.get_attr_values_from_data(instance.data) + custom_attrs = attr_values.get("custom_attrs", False) + if not custom_attrs: + self.log.debug( + "No Custom Attributes included in this abc export...") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = custom_attrs + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py deleted file mode 100644 index a42f27be6e..0000000000 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ /dev/null @@ -1,64 +0,0 @@ -import os - -import pyblish.api -from pymxs import runtime as rt - -from openpype.hosts.max.api import maintained_selection -from openpype.pipeline import OptionalPyblishPluginMixin, publish - - -class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): - """Extract Camera with AlembicExport.""" - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract Alembic Camera" - hosts = ["max"] - families = ["camera"] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.info("Extracting Camera ...") - - stagingdir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(stagingdir, filename) - - # We run the render - self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'") - - rt.AlembicExport.ArchiveType = rt.Name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.Name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - rt.AlembicExport.CustomAttributes = True - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.ExportFile( - path, - rt.Name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) - - self.log.info("Performing Extraction ...") - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "abc", - "ext": "abc", - "files": filename, - "stagingDir": stagingdir, - "frameStart": start, - "frameEnd": end, - } - instance.data["representations"].append(representation) - self.log.info(f"Extracted instance '{instance.name}' to: {path}") diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 537c88eb4d..4b5631b05f 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -20,13 +20,10 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Camera ...") stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info(f"Writing fbx file '{filename}' to '{filepath}'") - rt.FBXExporterSetParam("Animation", True) rt.FBXExporterSetParam("Cameras", True) rt.FBXExporterSetParam("AxisConversionMethod", "Animation") diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index a7a889c587..791cc65fcd 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -26,7 +26,6 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): filename = "{name}.max".format(**instance.data) max_path = os.path.join(stagingdir, filename) - self.log.info("Writing max file '%s' to '%s'" % (filename, max_path)) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py deleted file mode 100644 index 38f4848c5e..0000000000 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import pyblish.api -from openpype.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection - - -class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Geometry in Alembic Format - """ - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract Geometry (Alembic)" - hosts = ["max"] - families = ["model"] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - - self.log.debug("Extracting Geometry ...") - - stagingdir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - filepath = os.path.join(stagingdir, filename) - - # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.CustomAttributes = True - rt.AlembicExport.UVs = True - rt.AlembicExport.VertexColors = True - rt.AlembicExport.PreserveInstances = True - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - filepath, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) - - self.log.info("Performing Extraction ...") - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "abc", - "ext": "abc", - "files": filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, filepath) - ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index fd48ed5007..6c42fd5364 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -20,12 +20,9 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Geometry ...") - stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir)) rt.FBXExporterSetParam("Animation", False) rt.FBXExporterSetParam("Cameras", False) @@ -46,7 +43,6 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): using=rt.FBXEXP, ) - self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index a5d9ad6597..8464353164 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -3,6 +3,7 @@ import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection +from openpype.hosts.max.api.lib import suspended_refresh from openpype.pipeline.publish import KnownPublishError @@ -21,25 +22,21 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Geometry ...") - stagingdir = self.staging_dir(instance) filename = "{name}.obj".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir)) - - self.log.info("Performing Extraction ...") - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - filepath, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.ObjExp, - ) + with suspended_refresh(): + with maintained_selection(): + # select and export + node_list = instance.data["members"] + rt.Select(node_list) + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.ObjExp, + ) if not os.path.exists(filepath): raise KnownPublishError( "File {} wasn't produced by 3ds max, please check the logs.") diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index ab24727db5..0edcae060a 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -25,7 +25,8 @@ "instance_attributes": [ "reviewable", "farm_rendering" - ] + ], + "image_format": "exr" } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 342411f8a5..5177d8bc7c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -80,6 +80,19 @@ "farm_rendering": "Farm rendering" } ] + }, + { + "key": "image_format", + "label": "Output Image Format", + "type": "enum", + "multiselect": false, + "enum_items": [ + {"exr": "exr"}, + {"tga": "tga"}, + {"png": "png"}, + {"tif": "tif"}, + {"jpg": "jpg"} + ] } ] } diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index 92fb362c66..1bc12773d2 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -25,6 +25,16 @@ def _create_saver_instance_attributes_enum(): ] +def _image_format_enum(): + return [ + {"value": "exr", "label": "exr"}, + {"value": "tga", "label": "tga"}, + {"value": "png", "label": "png"}, + {"value": "tif", "label": "tif"}, + {"value": "jpg", "label": "jpg"}, + ] + + class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True temp_rendering_path_template: str = Field( @@ -39,6 +49,10 @@ class CreateSaverPluginModel(BaseSettingsModel): enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) + image_format: str = Field( + enum_resolver=_image_format_enum, + title="Output Image Format" + ) class CreatPluginsModel(BaseSettingsModel): @@ -89,7 +103,8 @@ DEFAULT_VALUES = { "instance_attributes": [ "reviewable", "farm_rendering" - ] + ], + "image_format": "exr" } } } diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index 3dc1f76bc6..485f44ac21 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1"