diff --git a/client/ayon_nuke/api/__init__.py b/client/ayon_nuke/api/__init__.py index caefba7..71c6117 100644 --- a/client/ayon_nuke/api/__init__.py +++ b/client/ayon_nuke/api/__init__.py @@ -14,7 +14,8 @@ NukeWriteCreator, NukeCreatorError, get_instance_group_node_childs, - get_colorspace_from_node + get_colorspace_from_node, + get_publish_config ) from .pipeline import ( NukeHost, diff --git a/client/ayon_nuke/api/plugin.py b/client/ayon_nuke/api/plugin.py index ce53acd..eb9a926 100644 --- a/client/ayon_nuke/api/plugin.py +++ b/client/ayon_nuke/api/plugin.py @@ -487,6 +487,11 @@ def get_review_presets_config(): return [str(name) for name, _prop in outputs.items()] +def get_publish_config(): + settings = get_current_project_settings() + return settings["nuke"].get("publish", {}) + + class NukeLoader(LoaderPlugin): container_id_knob = "containerId" container_id = None diff --git a/client/ayon_nuke/plugins/load/load_camera_abc.py b/client/ayon_nuke/plugins/load/load_camera.py similarity index 74% rename from client/ayon_nuke/plugins/load/load_camera_abc.py rename to client/ayon_nuke/plugins/load/load_camera.py index 3930cf5..179f94f 100644 --- a/client/ayon_nuke/plugins/load/load_camera_abc.py +++ b/client/ayon_nuke/plugins/load/load_camera.py @@ -15,18 +15,17 @@ ) -class AlembicCameraLoader(load.LoaderPlugin): +class _NukeCameraLoader(load.LoaderPlugin): """ - This will load alembic camera into script. + This will load a camera into script. """ product_types = {"camera"} representations = {"*"} - extensions = {"abc"} + enabled = False settings_category = "nuke" - label = "Load Alembic Camera" icon = "camera" color = "orange" node_color = "0x3469ffff" @@ -57,12 +56,21 @@ def load(self, context, name, namespace, data): file = self.filepath_from_context(context).replace("\\", "/") with maintained_selection(): - camera_node = nuke.createNode( - "Camera2", - "name {} file {} read_from_file True".format( - object_name, file), - inpanel=False - ) + + try: + camera_node = nuke.createNode( + "Camera3", + "name {} file {} read_from_file True".format( + object_name, file), + inpanel=False + ) + except RuntimeError: # older nuke version + camera_node = nuke.createNode( + "Camera2", + "name {} file {} read_from_file True".format( + object_name, file), + inpanel=False + ) camera_node.forceValidate() camera_node["frame_rate"].setValue(float(fps)) @@ -196,3 +204,52 @@ def remove(self, container): node = container["node"] with viewer_update_and_undo_stop(): nuke.delete(node) + + +class AlembicCameraLoader(_NukeCameraLoader): + """ + This will load alembic camera into script. + """ + extensions = {"abc"} + label = "Load Alembic Camera" + enabled = True + + +class FbxCameraLoader(_NukeCameraLoader): + """ + This will load fbx camera into script. + """ + extensions = {"fbx"} + label = "Load FBX Camera" + enabled = True + + def load(self, context, name, namespace, data): + fbx_camera_node = super().load(context, name, namespace,data) + + # Nuke forces 7 standard FBX cameras + # Producer Perspective, Producer Top, Producer Bottom... + # https://learn.foundry.com/nuke/11.1/content/comp_environment + # /3d_compositing/importing_fbx_cameras.html + # The following ensure the camera node is set to exported camera data. + fbx_camera_node.forceValidate() + + try: + knob = fbx_camera_node["fbx_take_name"] + knob.setValue(0) + + except NameError: + # WARNING - Nuke 15: Camera3 validate does not play along + # very well when mixing abc + fbx nodes in the same script. + # They need to reloaded manually. + # + # Hopefully this will be fixed by upcoming Camera4 + raise RuntimeError( + "Could not load incoming FBX camera properly. " + "This could be caused by a mix of abc and fbx " + "into the script." + ) + + knob = fbx_camera_node["fbx_node_name"] + knob.setValue(knob.values()[-1]) + + return fbx_camera_node diff --git a/client/ayon_nuke/plugins/publish/extract_camera.py b/client/ayon_nuke/plugins/publish/extract_camera.py index 8391408..94ae02b 100644 --- a/client/ayon_nuke/plugins/publish/extract_camera.py +++ b/client/ayon_nuke/plugins/publish/extract_camera.py @@ -7,6 +7,7 @@ from ayon_core.pipeline import publish from ayon_nuke.api.lib import maintained_selection +from ayon_nuke.api.plugin import get_publish_config class ExtractCamera(publish.Extractor): @@ -19,14 +20,37 @@ class ExtractCamera(publish.Extractor): settings_category = "nuke" - # presets - write_geo_knobs = [ - ("file_type", "abc"), - ("storageFormat", "Ogawa"), - ("writeGeometries", False), - ("writePointClouds", False), - ("writeAxes", False) - ] + def _get_camera_export_presets(self, instance): + """ + Args: + instance (dict): The current instance being published. + + Returns: + presets (list.): The camera export presets to use. + """ + write_geo_knobs = [ + ("writeGeometries", False), + ("writePointClouds", False), + ("writeAxes", False) + ] + + publish_settings = get_publish_config() + extract_camera_settings = publish_settings.get("ExtractCameraFormat", {}) + export_camera_settings = extract_camera_settings.get("export_camera_format", "abc") + + if export_camera_settings == "abc": + write_geo_knobs.insert(0, ("file_type", "abc")) + write_geo_knobs.append((("storageFormat", "Ogawa"))) + + elif export_camera_settings == "fbx": + write_geo_knobs.insert(0, ("file_type", "fbx")) + write_geo_knobs.append(("writeLights", False)) + + else: + raise ValueError("Invalid Camera export format: %s" % export_format) + + return write_geo_knobs + def process(self, instance): camera_node = instance.data["transientData"]["node"] @@ -43,7 +67,8 @@ def process(self, instance): staging_dir = self.staging_dir(instance) # get extension form preset - extension = next((k[1] for k in self.write_geo_knobs + export_presets = self._get_camera_export_presets(instance) + extension = next((k[1] for k in export_presets if k[0] == "file_type"), None) if not extension: raise RuntimeError( @@ -68,7 +93,7 @@ def process(self, instance): wg_n = nuke.createNode("WriteGeo") wg_n["file"].setValue(file_path) # add path to write to - for k, v in self.write_geo_knobs: + for k, v in export_presets: wg_n[k].setValue(v) rm_nodes.append(wg_n) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index c52c9e9..7daec5b 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -32,6 +32,14 @@ def nuke_product_types_enum(): ] + nuke_render_publish_types_enum() +def nuke_export_formats_enum(): + """Return all nuke export format available in creators.""" + return [ + {"value": "abc", "label": "Alembic"}, + {"value": "fbx", "label": "FBX"}, + ] + + class NodeModel(BaseSettingsModel): name: str = SettingsField( title="Node name" @@ -186,6 +194,15 @@ class FVFXScopeOfWorkModel(BaseSettingsModel): template: str = SettingsField(title="Template") +class ExtractCameraFormatModel(BaseSettingsModel): + export_camera_format: str = SettingsField( + enum_resolver=nuke_export_formats_enum, + conditionalEnum=True, + title="Camera export format", + description="Switch between different camera export formats", + ) + + class ExctractSlateFrameParamModel(BaseSettingsModel): f_submission_note: FSubmissionNoteModel = SettingsField( title="f_submission_note", @@ -260,6 +277,10 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=ExtractReviewIntermediatesModel ) ) + ExtractCameraFormat: ExtractCameraFormatModel = SettingsField( + title="Extract Camera Format", + default_factory=ExtractCameraFormatModel + ) ExtractSlateFrame: ExtractSlateFrameModel = SettingsField( title="Extract Slate Frame", default_factory=ExtractSlateFrameModel @@ -387,6 +408,9 @@ class PublishPluginsModel(BaseSettingsModel): } ] }, + "ExtractCameraFormat": { + "export_camera_format": "abc", + }, "ExtractSlateFrame": { "viewer_lut_raw": False, "key_value_mapping": {