From b69085f65daa6e7a3a98fe61b40e1ef8d4c27ab7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:46:30 +0200 Subject: [PATCH] removed max addon --- server_addon/max/client/ayon_max/__init__.py | 13 - server_addon/max/client/ayon_max/addon.py | 28 -- .../max/client/ayon_max/api/__init__.py | 20 - .../max/client/ayon_max/api/action.py | 42 --- .../max/client/ayon_max/api/colorspace.py | 50 --- .../client/ayon_max/api/lib_renderproducts.py | 275 -------------- .../client/ayon_max/api/lib_rendersettings.py | 227 ------------ server_addon/max/client/ayon_max/api/menu.py | 167 --------- .../max/client/ayon_max/api/pipeline.py | 297 --------------- .../client/ayon_max/api/preview_animation.py | 344 ------------------ .../ayon_max/hooks/force_startup_script.py | 27 -- .../client/ayon_max/hooks/inject_python.py | 20 - .../max/client/ayon_max/hooks/set_paths.py | 18 - .../max/client/ayon_max/plugins/__init__.py | 0 .../ayon_max/plugins/create/create_camera.py | 13 - .../plugins/create/create_maxScene.py | 13 - .../ayon_max/plugins/create/create_model.py | 13 - .../plugins/create/create_pointcache.py | 13 - .../plugins/create/create_pointcloud.py | 13 - .../plugins/create/create_redshift_proxy.py | 12 - .../ayon_max/plugins/create/create_render.py | 52 --- .../ayon_max/plugins/create/create_review.py | 122 ------- .../plugins/create/create_workfile.py | 119 ------ .../ayon_max/plugins/load/load_camera_fbx.py | 101 ----- .../ayon_max/plugins/load/load_max_scene.py | 178 --------- .../ayon_max/plugins/load/load_model.py | 123 ------- .../ayon_max/plugins/load/load_model_fbx.py | 98 ----- .../ayon_max/plugins/load/load_model_obj.py | 89 ----- .../ayon_max/plugins/load/load_model_usd.py | 120 ------ .../ayon_max/plugins/load/load_pointcache.py | 132 ------- .../plugins/load/load_pointcache_ornatrix.py | 111 ------ .../ayon_max/plugins/load/load_pointcloud.py | 69 ---- .../plugins/load/load_redshift_proxy.py | 78 ---- .../plugins/publish/collect_current_file.py | 23 -- .../plugins/publish/collect_render.py | 122 ------- .../plugins/publish/collect_review.py | 153 -------- .../plugins/publish/collect_workfile.py | 46 --- .../plugins/publish/extract_alembic.py | 139 ------- .../ayon_max/plugins/publish/extract_fbx.py | 83 ----- .../plugins/publish/extract_max_scene_raw.py | 49 --- .../plugins/publish/extract_model_obj.py | 59 --- .../plugins/publish/extract_model_usd.py | 94 ----- .../plugins/publish/extract_redshift_proxy.py | 61 ---- .../publish/extract_review_animation.py | 64 ---- .../plugins/publish/extract_thumbnail.py | 51 --- .../publish/help/validate_model_name.xml | 26 -- .../publish/increment_workfile_version.py | 19 - .../ayon_max/plugins/publish/save_scene.py | 25 -- .../publish/save_scenes_for_cameras.py | 105 ------ .../plugins/publish/validate_attributes.py | 143 -------- .../publish/validate_camera_attributes.py | 90 ----- .../publish/validate_camera_contents.py | 43 --- .../publish/validate_extended_viewport.py | 29 -- .../publish/validate_instance_has_members.py | 25 -- .../publish/validate_instance_in_context.py | 86 ----- .../plugins/publish/validate_loaded_plugin.py | 143 -------- .../plugins/publish/validate_mesh_has_uv.py | 62 ---- .../publish/validate_model_contents.py | 44 --- .../plugins/publish/validate_model_name.py | 123 ------- .../plugins/publish/validate_no_animation.py | 69 ---- .../plugins/publish/validate_pointcloud.py | 126 ------- .../publish/validate_renderable_camera.py | 46 --- .../validate_renderer_redshift_proxy.py | 54 --- .../plugins/publish/validate_renderpasses.py | 187 ---------- .../publish/validate_resolution_setting.py | 92 ----- .../plugins/publish/validate_scene_saved.py | 18 - .../max/client/ayon_max/startup/startup.ms | 15 - .../max/client/ayon_max/startup/startup.py | 13 - server_addon/max/client/ayon_max/version.py | 3 - server_addon/max/package.py | 9 - server_addon/max/server/__init__.py | 13 - server_addon/max/server/settings/__init__.py | 10 - .../server/settings/create_review_settings.py | 91 ----- server_addon/max/server/settings/imageio.py | 63 ---- server_addon/max/server/settings/main.py | 94 ----- .../max/server/settings/render_settings.py | 47 --- 76 files changed, 5854 deletions(-) delete mode 100644 server_addon/max/client/ayon_max/__init__.py delete mode 100644 server_addon/max/client/ayon_max/addon.py delete mode 100644 server_addon/max/client/ayon_max/api/__init__.py delete mode 100644 server_addon/max/client/ayon_max/api/action.py delete mode 100644 server_addon/max/client/ayon_max/api/colorspace.py delete mode 100644 server_addon/max/client/ayon_max/api/lib_renderproducts.py delete mode 100644 server_addon/max/client/ayon_max/api/lib_rendersettings.py delete mode 100644 server_addon/max/client/ayon_max/api/menu.py delete mode 100644 server_addon/max/client/ayon_max/api/pipeline.py delete mode 100644 server_addon/max/client/ayon_max/api/preview_animation.py delete mode 100644 server_addon/max/client/ayon_max/hooks/force_startup_script.py delete mode 100644 server_addon/max/client/ayon_max/hooks/inject_python.py delete mode 100644 server_addon/max/client/ayon_max/hooks/set_paths.py delete mode 100644 server_addon/max/client/ayon_max/plugins/__init__.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_camera.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_maxScene.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_model.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_pointcache.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_pointcloud.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_redshift_proxy.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_render.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_review.py delete mode 100644 server_addon/max/client/ayon_max/plugins/create/create_workfile.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_camera_fbx.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_max_scene.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_model.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_model_fbx.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_model_obj.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_model_usd.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_pointcache.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_pointcache_ornatrix.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_pointcloud.py delete mode 100644 server_addon/max/client/ayon_max/plugins/load/load_redshift_proxy.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/collect_current_file.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/collect_render.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/collect_review.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/collect_workfile.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_alembic.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_fbx.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_max_scene_raw.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_model_obj.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_model_usd.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_redshift_proxy.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_review_animation.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/extract_thumbnail.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/help/validate_model_name.xml delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/increment_workfile_version.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/save_scene.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/save_scenes_for_cameras.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_attributes.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_camera_attributes.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_camera_contents.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_extended_viewport.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_instance_has_members.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_instance_in_context.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_loaded_plugin.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_mesh_has_uv.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_model_contents.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_model_name.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_no_animation.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_pointcloud.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_renderable_camera.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_renderer_redshift_proxy.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_renderpasses.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_resolution_setting.py delete mode 100644 server_addon/max/client/ayon_max/plugins/publish/validate_scene_saved.py delete mode 100644 server_addon/max/client/ayon_max/startup/startup.ms delete mode 100644 server_addon/max/client/ayon_max/startup/startup.py delete mode 100644 server_addon/max/client/ayon_max/version.py delete mode 100644 server_addon/max/package.py delete mode 100644 server_addon/max/server/__init__.py delete mode 100644 server_addon/max/server/settings/__init__.py delete mode 100644 server_addon/max/server/settings/create_review_settings.py delete mode 100644 server_addon/max/server/settings/imageio.py delete mode 100644 server_addon/max/server/settings/main.py delete mode 100644 server_addon/max/server/settings/render_settings.py diff --git a/server_addon/max/client/ayon_max/__init__.py b/server_addon/max/client/ayon_max/__init__.py deleted file mode 100644 index 77293f9aa9..0000000000 --- a/server_addon/max/client/ayon_max/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .version import __version__ -from .addon import ( - MaxAddon, - MAX_HOST_DIR, -) - - -__all__ = ( - "__version__", - - "MaxAddon", - "MAX_HOST_DIR", -) diff --git a/server_addon/max/client/ayon_max/addon.py b/server_addon/max/client/ayon_max/addon.py deleted file mode 100644 index 9cc0cda1ee..0000000000 --- a/server_addon/max/client/ayon_max/addon.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -import os -from ayon_core.addon import AYONAddon, IHostAddon - -from .version import __version__ - -MAX_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class MaxAddon(AYONAddon, IHostAddon): - name = "max" - version = __version__ - host_name = "max" - - def add_implementation_envs(self, env, _app): - # Remove auto screen scale factor for Qt - # - let 3dsmax decide it's value - env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) - - def get_workfile_extensions(self): - return [".max"] - - def get_launch_hook_paths(self, app): - if app.host_name != self.host_name: - return [] - return [ - os.path.join(MAX_HOST_DIR, "hooks") - ] diff --git a/server_addon/max/client/ayon_max/api/__init__.py b/server_addon/max/client/ayon_max/api/__init__.py deleted file mode 100644 index 92097cc98b..0000000000 --- a/server_addon/max/client/ayon_max/api/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -"""Public API for 3dsmax""" - -from .pipeline import ( - MaxHost, -) - - -from .lib import ( - maintained_selection, - lsattr, - get_all_children -) - -__all__ = [ - "MaxHost", - "maintained_selection", - "lsattr", - "get_all_children" -] diff --git a/server_addon/max/client/ayon_max/api/action.py b/server_addon/max/client/ayon_max/api/action.py deleted file mode 100644 index bed72bc493..0000000000 --- a/server_addon/max/client/ayon_max/api/action.py +++ /dev/null @@ -1,42 +0,0 @@ -from pymxs import runtime as rt - -import pyblish.api - -from ayon_core.pipeline.publish import get_errored_instances_from_context - - -class SelectInvalidAction(pyblish.api.Action): - """Select invalid objects in Blender when a publish plug-in failed.""" - label = "Select Invalid" - on = "failed" - icon = "search" - - def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context, - plugin=plugin) - - # Get the invalid nodes for the plug-ins - self.log.info("Finding invalid nodes...") - invalid = list() - for instance in errored_instances: - invalid_nodes = plugin.get_invalid(instance) - if invalid_nodes: - if isinstance(invalid_nodes, (list, tuple)): - invalid.extend(invalid_nodes) - else: - self.log.warning( - "Failed plug-in doesn't have any selectable objects." - ) - - if not invalid: - self.log.info("No invalid nodes found.") - return - invalid_names = [obj.name for obj in invalid if not isinstance(obj, tuple)] - if not invalid_names: - invalid_names = [obj.name for obj, _ in invalid] - invalid = [obj for obj, _ in invalid] - self.log.info( - "Selecting invalid objects: %s", ", ".join(invalid_names) - ) - - rt.Select(invalid) diff --git a/server_addon/max/client/ayon_max/api/colorspace.py b/server_addon/max/client/ayon_max/api/colorspace.py deleted file mode 100644 index fafee4ee04..0000000000 --- a/server_addon/max/client/ayon_max/api/colorspace.py +++ /dev/null @@ -1,50 +0,0 @@ -import attr -from pymxs import runtime as rt - - -@attr.s -class LayerMetadata(object): - """Data class for Render Layer metadata.""" - frameStart = attr.ib() - frameEnd = attr.ib() - - -@attr.s -class RenderProduct(object): - """Getting Colorspace as - Specific Render Product Parameter for submitting - publish job. - """ - colorspace = attr.ib() # colorspace - view = attr.ib() - productName = attr.ib(default=None) - - -class ARenderProduct(object): - - def __init__(self): - """Constructor.""" - # Initialize - self.layer_data = self._get_layer_data() - self.layer_data.products = self.get_colorspace_data() - - def _get_layer_data(self): - return LayerMetadata( - frameStart=int(rt.rendStart), - frameEnd=int(rt.rendEnd), - ) - - def get_colorspace_data(self): - """To be implemented by renderer class. - This should return a list of RenderProducts. - Returns: - list: List of RenderProduct - """ - colorspace_data = [ - RenderProduct( - colorspace="sRGB", - view="ACES 1.0", - productName="" - ) - ] - return colorspace_data diff --git a/server_addon/max/client/ayon_max/api/lib_renderproducts.py b/server_addon/max/client/ayon_max/api/lib_renderproducts.py deleted file mode 100644 index 82a6a0c20c..0000000000 --- a/server_addon/max/client/ayon_max/api/lib_renderproducts.py +++ /dev/null @@ -1,275 +0,0 @@ -# Render Element Example : For scanline render, VRay -# https://help.autodesk.com/view/MAXDEV/2022/ENU/?guid=GUID-E8F75D47-B998-4800-A3A5-610E22913CFC -# arnold -# https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_3ds_max_ax_maxscript_commands_ax_renderview_commands_html -import os - -from pymxs import runtime as rt - -from ayon_max.api.lib import get_current_renderer -from ayon_core.pipeline import get_current_project_name -from ayon_core.settings import get_project_settings - - -class RenderProducts(object): - - def __init__(self, project_settings=None): - self._project_settings = project_settings - if not self._project_settings: - self._project_settings = get_project_settings( - get_current_project_name() - ) - - def get_beauty(self, container): - render_dir = os.path.dirname(rt.rendOutputFilename) - - output_file = os.path.join(render_dir, container) - - setting = self._project_settings - img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa - - start_frame = int(rt.rendStart) - end_frame = int(rt.rendEnd) + 1 - - return { - "beauty": self.get_expected_beauty( - output_file, start_frame, end_frame, img_fmt - ) - } - - def get_multiple_beauty(self, outputs, cameras): - beauty_output_frames = dict() - for output, camera in zip(outputs, cameras): - filename, ext = os.path.splitext(output) - filename = filename.replace(".", "") - ext = ext.replace(".", "") - start_frame = int(rt.rendStart) - end_frame = int(rt.rendEnd) + 1 - new_beauty = self.get_expected_beauty( - filename, start_frame, end_frame, ext - ) - beauty_output = ({ - f"{camera}_beauty": new_beauty - }) - beauty_output_frames.update(beauty_output) - return beauty_output_frames - - def get_multiple_aovs(self, outputs, cameras): - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - aovs_frames = {} - for output, camera in zip(outputs, cameras): - filename, ext = os.path.splitext(output) - filename = filename.replace(".", "") - ext = ext.replace(".", "") - start_frame = int(rt.rendStart) - end_frame = int(rt.rendEnd) + 1 - - if renderer in [ - "ART_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - render_name = self.get_render_elements_name() - if render_name: - for name in render_name: - aovs_frames.update({ - f"{camera}_{name}": self.get_expected_aovs( - filename, name, start_frame, - end_frame, ext) - }) - elif renderer == "Redshift_Renderer": - render_name = self.get_render_elements_name() - if render_name: - rs_aov_files = rt.Execute("renderers.current.separateAovFiles") # noqa - # this doesn't work, always returns False - # rs_AovFiles = rt.RedShift_Renderer().separateAovFiles - if ext == "exr" and not rs_aov_files: - for name in render_name: - if name == "RsCryptomatte": - aovs_frames.update({ - f"{camera}_{name}": self.get_expected_aovs( - filename, name, start_frame, - end_frame, ext) - }) - else: - for name in render_name: - aovs_frames.update({ - f"{camera}_{name}": self.get_expected_aovs( - filename, name, start_frame, - end_frame, ext) - }) - elif renderer == "Arnold": - render_name = self.get_arnold_product_name() - if render_name: - for name in render_name: - aovs_frames.update({ - f"{camera}_{name}": self.get_expected_arnold_product( # noqa - filename, name, start_frame, - end_frame, ext) - }) - elif renderer in [ - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3" - ]: - if ext != "exr": - render_name = self.get_render_elements_name() - if render_name: - for name in render_name: - aovs_frames.update({ - f"{camera}_{name}": self.get_expected_aovs( - filename, name, start_frame, - end_frame, ext) - }) - - return aovs_frames - - def get_aovs(self, container): - render_dir = os.path.dirname(rt.rendOutputFilename) - - output_file = os.path.join(render_dir, - container) - - setting = self._project_settings - img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa - - start_frame = int(rt.rendStart) - end_frame = int(rt.rendEnd) + 1 - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - render_dict = {} - - if renderer in [ - "ART_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - render_name = self.get_render_elements_name() - if render_name: - for name in render_name: - render_dict.update({ - name: self.get_expected_aovs( - output_file, name, start_frame, - end_frame, img_fmt) - }) - elif renderer == "Redshift_Renderer": - render_name = self.get_render_elements_name() - if render_name: - rs_aov_files = rt.Execute("renderers.current.separateAovFiles") - # this doesn't work, always returns False - # rs_AovFiles = rt.RedShift_Renderer().separateAovFiles - if img_fmt == "exr" and not rs_aov_files: - for name in render_name: - if name == "RsCryptomatte": - render_dict.update({ - name: self.get_expected_aovs( - output_file, name, start_frame, - end_frame, img_fmt) - }) - else: - for name in render_name: - render_dict.update({ - name: self.get_expected_aovs( - output_file, name, start_frame, - end_frame, img_fmt) - }) - - elif renderer == "Arnold": - render_name = self.get_arnold_product_name() - if render_name: - for name in render_name: - render_dict.update({ - name: self.get_expected_arnold_product( - output_file, name, start_frame, - end_frame, img_fmt) - }) - elif renderer in [ - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3" - ]: - if img_fmt != "exr": - render_name = self.get_render_elements_name() - if render_name: - for name in render_name: - render_dict.update({ - name: self.get_expected_aovs( - output_file, name, start_frame, - end_frame, img_fmt) # noqa - }) - - return render_dict - - def get_expected_beauty(self, folder, start_frame, end_frame, fmt): - beauty_frame_range = [] - for f in range(start_frame, end_frame): - frame = "%04d" % f - beauty_output = f"{folder}.{frame}.{fmt}" - beauty_output = beauty_output.replace("\\", "/") - beauty_frame_range.append(beauty_output) - - return beauty_frame_range - - def get_arnold_product_name(self): - """Get all the Arnold AOVs name""" - aov_name = [] - - amw = rt.MaxToAOps.AOVsManagerWindow() - aov_mgr = rt.renderers.current.AOVManager - # Check if there is any aov group set in AOV manager - aov_group_num = len(aov_mgr.drivers) - if aov_group_num < 1: - return - for i in range(aov_group_num): - # get the specific AOV group - aov_name.extend(aov.name for aov in aov_mgr.drivers[i].aov_list) - # close the AOVs manager window - amw.close() - - return aov_name - - def get_expected_arnold_product(self, folder, name, - start_frame, end_frame, fmt): - """Get all the expected Arnold AOVs""" - aov_list = [] - for f in range(start_frame, end_frame): - frame = "%04d" % f - render_element = f"{folder}_{name}.{frame}.{fmt}" - render_element = render_element.replace("\\", "/") - aov_list.append(render_element) - - return aov_list - - def get_render_elements_name(self): - """Get all the render element names for general """ - render_name = [] - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - if render_elem_num < 1: - return - # get render elements from the renders - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - if renderlayer_name.enabled: - target, renderpass = str(renderlayer_name).split(":") - render_name.append(renderpass) - - return render_name - - def get_expected_aovs(self, folder, name, - start_frame, end_frame, fmt): - """Get all the expected render element output files. """ - render_elements = [] - for f in range(start_frame, end_frame): - frame = "%04d" % f - render_element = f"{folder}_{name}.{frame}.{fmt}" - render_element = render_element.replace("\\", "/") - render_elements.append(render_element) - - return render_elements - - def image_format(self): - return self._project_settings["max"]["RenderSettings"]["image_format"] # noqa diff --git a/server_addon/max/client/ayon_max/api/lib_rendersettings.py b/server_addon/max/client/ayon_max/api/lib_rendersettings.py deleted file mode 100644 index 4b65e1397e..0000000000 --- a/server_addon/max/client/ayon_max/api/lib_rendersettings.py +++ /dev/null @@ -1,227 +0,0 @@ -import os -from pymxs import runtime as rt -from ayon_core.lib import Logger -from ayon_core.settings import get_project_settings -from ayon_core.pipeline import get_current_project_name -from ayon_core.pipeline.context_tools import get_current_folder_entity - -from ayon_max.api.lib import ( - set_render_frame_range, - get_current_renderer, - get_default_render_folder -) - - -class RenderSettings(object): - - log = Logger.get_logger("RenderSettings") - - _aov_chars = { - "dot": ".", - "dash": "-", - "underscore": "_" - } - - def __init__(self, project_settings=None): - """ - Set up the naming convention for the render - elements for the deadline submission - """ - - self._project_settings = project_settings - if not self._project_settings: - self._project_settings = get_project_settings( - get_current_project_name() - ) - - def set_render_camera(self, selection): - for sel in selection: - # to avoid Attribute Error from pymxs wrapper - if rt.classOf(sel) in rt.Camera.classes: - rt.viewport.setCamera(sel) - return - raise RuntimeError("Active Camera not found") - - def render_output(self, container): - folder = rt.maxFilePath - # hard-coded, should be customized in the setting - file = rt.maxFileName - folder = folder.replace("\\", "/") - # hard-coded, set the renderoutput path - setting = self._project_settings - render_folder = get_default_render_folder(setting) - filename, ext = os.path.splitext(file) - output_dir = os.path.join(folder, - render_folder, - filename) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - # hard-coded, should be customized in the setting - folder_attributes = get_current_folder_entity()["attrib"] - - # get project resolution - width = folder_attributes.get("resolutionWidth") - height = folder_attributes.get("resolutionHeight") - # Set Frame Range - frame_start = folder_attributes.get("frame_start") - frame_end = folder_attributes.get("frame_end") - set_render_frame_range(frame_start, frame_end) - # get the production render - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - output = os.path.join(output_dir, container) - try: - aov_separator = self._aov_chars[( - self._project_settings["max"] - ["RenderSettings"] - ["aov_separator"] - )] - except KeyError: - aov_separator = "." - output_filename = f"{output}..{img_fmt}" - output_filename = output_filename.replace("{aov_separator}", - aov_separator) - rt.rendOutputFilename = output_filename - if renderer == "VUE_File_Renderer": - return - # TODO: Finish the arnold render setup - if renderer == "Arnold": - self.arnold_setup() - - if renderer in [ - "ART_Renderer", - "Redshift_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - self.render_element_layer(output, width, height, img_fmt) - - rt.rendSaveFile = True - - if rt.renderSceneDialog.isOpen(): - rt.renderSceneDialog.close() - - def arnold_setup(self): - # get Arnold RenderView run in the background - # for setting up renderable camera - arv = rt.MAXToAOps.ArnoldRenderView() - render_camera = rt.viewport.GetCamera() - if render_camera: - arv.setOption("Camera", str(render_camera)) - - # TODO: add AOVs and extension - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - setup_cmd = ( - f""" - amw = MaxtoAOps.AOVsManagerWindow() - amw.close() - aovmgr = renderers.current.AOVManager - aovmgr.drivers = #() - img_fmt = "{img_fmt}" - if img_fmt == "png" then driver = ArnoldPNGDriver() - if img_fmt == "jpg" then driver = ArnoldJPEGDriver() - if img_fmt == "exr" then driver = ArnoldEXRDriver() - if img_fmt == "tif" then driver = ArnoldTIFFDriver() - if img_fmt == "tiff" then driver = ArnoldTIFFDriver() - append aovmgr.drivers driver - aovmgr.drivers[1].aov_list = #() - """) - - rt.execute(setup_cmd) - arv.close() - - def render_element_layer(self, dir, width, height, ext): - """For Renderers with render elements""" - rt.renderWidth = width - rt.renderHeight = height - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - if render_elem_num < 0: - return - - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = f"{dir}_{renderpass}..{ext}" - render_elem.SetRenderElementFileName(i, aov_name) - - def get_render_output(self, container, output_dir): - output = os.path.join(output_dir, container) - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - output_filename = f"{output}..{img_fmt}" - return output_filename - - def get_render_element(self): - orig_render_elem = [] - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - if render_elem_num < 0: - return - - for i in range(render_elem_num): - render_element = render_elem.GetRenderElementFilename(i) - orig_render_elem.append(render_element) - - return orig_render_elem - - def get_batch_render_elements(self, container, - output_dir, camera): - render_element_list = list() - output = os.path.join(output_dir, container) - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - if render_elem_num < 0: - return - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = f"{output}_{camera}_{renderpass}..{img_fmt}" - render_element_list.append(aov_name) - return render_element_list - - def get_batch_render_output(self, camera): - target_layer_no = rt.batchRenderMgr.FindView(camera) - target_layer = rt.batchRenderMgr.GetView(target_layer_no) - return target_layer.outputFilename - - def batch_render_elements(self, camera): - target_layer_no = rt.batchRenderMgr.FindView(camera) - target_layer = rt.batchRenderMgr.GetView(target_layer_no) - outputfilename = target_layer.outputFilename - directory = os.path.dirname(outputfilename) - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - if render_elem_num < 0: - return - ext = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = f"{directory}_{camera}_{renderpass}..{ext}" - render_elem.SetRenderElementFileName(i, aov_name) - - def batch_render_layer(self, container, - output_dir, cameras): - outputs = list() - output = os.path.join(output_dir, container) - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - for cam in cameras: - camera = rt.getNodeByName(cam) - layer_no = rt.batchRenderMgr.FindView(cam) - renderlayer = None - if layer_no == 0: - renderlayer = rt.batchRenderMgr.CreateView(camera) - else: - renderlayer = rt.batchRenderMgr.GetView(layer_no) - # use camera name as renderlayer name - renderlayer.name = cam - renderlayer.outputFilename = f"{output}_{cam}..{img_fmt}" - outputs.append(renderlayer.outputFilename) - return outputs diff --git a/server_addon/max/client/ayon_max/api/menu.py b/server_addon/max/client/ayon_max/api/menu.py deleted file mode 100644 index 25dd39fd84..0000000000 --- a/server_addon/max/client/ayon_max/api/menu.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- -"""3dsmax menu definition of AYON.""" -import os -from qtpy import QtWidgets, QtCore -from pymxs import runtime as rt - -from ayon_core.tools.utils import host_tools -from ayon_max.api import lib - - -class AYONMenu(object): - """Object representing AYON menu. - - This is using "hack" to inject itself before "Help" menu of 3dsmax. - For some reason `postLoadingMenus` event doesn't fire, and main menu - if probably re-initialized by menu templates, se we wait for at least - 1 event Qt event loop before trying to insert. - - """ - - def __init__(self): - super().__init__() - self.main_widget = self.get_main_widget() - self.menu = None - - timer = QtCore.QTimer() - # set number of event loops to wait. - timer.setInterval(1) - timer.timeout.connect(self._on_timer) - timer.start() - - self._timer = timer - self._counter = 0 - - def _on_timer(self): - if self._counter < 1: - self._counter += 1 - return - - self._counter = 0 - self._timer.stop() - self._build_ayon_menu() - - @staticmethod - def get_main_widget(): - """Get 3dsmax main window.""" - return QtWidgets.QWidget.find(rt.windows.getMAXHWND()) - - def get_main_menubar(self) -> QtWidgets.QMenuBar: - """Get main Menubar by 3dsmax main window.""" - return list(self.main_widget.findChildren(QtWidgets.QMenuBar))[0] - - def _get_or_create_ayon_menu( - self, name: str = "&AYON", - before: str = "&Help") -> QtWidgets.QAction: - """Create AYON menu. - - Args: - name (str, Optional): AYON menu name. - before (str, Optional): Name of the 3dsmax main menu item to - add AYON menu before. - - Returns: - QtWidgets.QAction: AYON menu action. - - """ - if self.menu is not None: - return self.menu - - menu_bar = self.get_main_menubar() - menu_items = menu_bar.findChildren( - QtWidgets.QMenu, options=QtCore.Qt.FindDirectChildrenOnly) - help_action = None - for item in menu_items: - if name in item.title(): - # we already have AYON menu - return item - - if before in item.title(): - help_action = item.menuAction() - tab_menu_label = os.environ.get("AYON_MENU_LABEL") or "AYON" - op_menu = QtWidgets.QMenu("&{}".format(tab_menu_label)) - menu_bar.insertMenu(help_action, op_menu) - - self.menu = op_menu - return op_menu - - def _build_ayon_menu(self) -> QtWidgets.QAction: - """Build items in AYON menu.""" - ayon_menu = self._get_or_create_ayon_menu() - load_action = QtWidgets.QAction("Load...", ayon_menu) - load_action.triggered.connect(self.load_callback) - ayon_menu.addAction(load_action) - - publish_action = QtWidgets.QAction("Publish...", ayon_menu) - publish_action.triggered.connect(self.publish_callback) - ayon_menu.addAction(publish_action) - - manage_action = QtWidgets.QAction("Manage...", ayon_menu) - manage_action.triggered.connect(self.manage_callback) - ayon_menu.addAction(manage_action) - - library_action = QtWidgets.QAction("Library...", ayon_menu) - library_action.triggered.connect(self.library_callback) - ayon_menu.addAction(library_action) - - ayon_menu.addSeparator() - - workfiles_action = QtWidgets.QAction("Work Files...", ayon_menu) - workfiles_action.triggered.connect(self.workfiles_callback) - ayon_menu.addAction(workfiles_action) - - ayon_menu.addSeparator() - - res_action = QtWidgets.QAction("Set Resolution", ayon_menu) - res_action.triggered.connect(self.resolution_callback) - ayon_menu.addAction(res_action) - - frame_action = QtWidgets.QAction("Set Frame Range", ayon_menu) - frame_action.triggered.connect(self.frame_range_callback) - ayon_menu.addAction(frame_action) - - colorspace_action = QtWidgets.QAction("Set Colorspace", ayon_menu) - colorspace_action.triggered.connect(self.colorspace_callback) - ayon_menu.addAction(colorspace_action) - - unit_scale_action = QtWidgets.QAction("Set Unit Scale", ayon_menu) - unit_scale_action.triggered.connect(self.unit_scale_callback) - ayon_menu.addAction(unit_scale_action) - - return ayon_menu - - def load_callback(self): - """Callback to show Loader tool.""" - host_tools.show_loader(parent=self.main_widget) - - def publish_callback(self): - """Callback to show Publisher tool.""" - host_tools.show_publisher(parent=self.main_widget) - - def manage_callback(self): - """Callback to show Scene Manager/Inventory tool.""" - host_tools.show_scene_inventory(parent=self.main_widget) - - def library_callback(self): - """Callback to show Library Loader tool.""" - host_tools.show_library_loader(parent=self.main_widget) - - def workfiles_callback(self): - """Callback to show Workfiles tool.""" - host_tools.show_workfiles(parent=self.main_widget) - - def resolution_callback(self): - """Callback to reset scene resolution""" - return lib.reset_scene_resolution() - - def frame_range_callback(self): - """Callback to reset frame range""" - return lib.reset_frame_range() - - def colorspace_callback(self): - """Callback to reset colorspace""" - return lib.reset_colorspace() - - def unit_scale_callback(self): - """Callback to reset unit scale""" - return lib.reset_unit_scale() diff --git a/server_addon/max/client/ayon_max/api/pipeline.py b/server_addon/max/client/ayon_max/api/pipeline.py deleted file mode 100644 index a87cd657ce..0000000000 --- a/server_addon/max/client/ayon_max/api/pipeline.py +++ /dev/null @@ -1,297 +0,0 @@ -# -*- coding: utf-8 -*- -"""Pipeline tools for AYON 3ds max integration.""" -import os -import logging -from operator import attrgetter - -import json - -from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost -import pyblish.api -from ayon_core.pipeline import ( - register_creator_plugin_path, - register_loader_plugin_path, - AVALON_CONTAINER_ID, - AYON_CONTAINER_ID, -) -from ayon_max.api.menu import AYONMenu -from ayon_max.api import lib -from ayon_max.api.plugin import MS_CUSTOM_ATTRIB -from ayon_max import MAX_HOST_DIR - -from pymxs import runtime as rt # noqa - -log = logging.getLogger("ayon_max") - -PLUGINS_DIR = os.path.join(MAX_HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") - - -class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): - - name = "max" - menu = None - - def __init__(self): - super(MaxHost, self).__init__() - self._op_events = {} - self._has_been_setup = False - - def install(self): - pyblish.api.register_host("max") - - pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugin_path(LOAD_PATH) - register_creator_plugin_path(CREATE_PATH) - - # self._register_callbacks() - self.menu = AYONMenu() - - self._has_been_setup = True - - rt.callbacks.addScript(rt.Name('systemPostNew'), on_new) - - rt.callbacks.addScript(rt.Name('filePostOpen'), - lib.check_colorspace) - - rt.callbacks.addScript(rt.Name('postWorkspaceChange'), - self._deferred_menu_creation) - rt.NodeEventCallback( - nameChanged=lib.update_modifier_node_names) - - def workfile_has_unsaved_changes(self): - return rt.getSaveRequired() - - def get_workfile_extensions(self): - return [".max"] - - def save_workfile(self, dst_path=None): - rt.saveMaxFile(dst_path) - return dst_path - - def open_workfile(self, filepath): - rt.checkForSave() - rt.loadMaxFile(filepath) - return filepath - - def get_current_workfile(self): - return os.path.join(rt.maxFilePath, rt.maxFileName) - - def get_containers(self): - return ls() - - def _register_callbacks(self): - rt.callbacks.removeScripts(id=rt.name("OpenPypeCallbacks")) - - rt.callbacks.addScript( - rt.Name("postLoadingMenus"), - self._deferred_menu_creation, id=rt.Name('OpenPypeCallbacks')) - - def _deferred_menu_creation(self): - self.log.info("Building menu ...") - self.menu = AYONMenu() - - @staticmethod - def create_context_node(): - """Helper for creating context holding node.""" - - root_scene = rt.rootScene - - create_attr_script = (""" -attributes "OpenPypeContext" -( - parameters main rollout:params - ( - context type: #string - ) - - rollout params "OpenPype Parameters" - ( - editText editTextContext "Context" type: #string - ) -) - """) - - attr = rt.execute(create_attr_script) - rt.custAttributes.add(root_scene, attr) - - return root_scene.OpenPypeContext.context - - def update_context_data(self, data, changes): - try: - _ = rt.rootScene.OpenPypeContext.context - except AttributeError: - # context node doesn't exists - self.create_context_node() - - rt.rootScene.OpenPypeContext.context = json.dumps(data) - - def get_context_data(self): - try: - context = rt.rootScene.OpenPypeContext.context - except AttributeError: - # context node doesn't exists - context = self.create_context_node() - if not context: - context = "{}" - return json.loads(context) - - def save_file(self, dst_path=None): - # Force forwards slashes to avoid segfault - dst_path = dst_path.replace("\\", "/") - rt.saveMaxFile(dst_path) - - -def parse_container(container): - """Return the container node's full container data. - - Args: - container (str): A container node name. - - Returns: - dict: The container schema data for this container node. - - """ - data = lib.read(container) - - # Backwards compatibility pre-schemas for containers - data["schema"] = data.get("schema", "openpype:container-3.0") - - # Append transient data - data["objectName"] = container.Name - return data - - -def ls(): - """Get all AYON containers.""" - objs = rt.objects - containers = [ - obj for obj in objs - if rt.getUserProp(obj, "id") in { - AYON_CONTAINER_ID, AVALON_CONTAINER_ID - } - ] - - for container in sorted(containers, key=attrgetter("name")): - yield parse_container(container) - - -def on_new(): - lib.set_context_setting() - if rt.checkForSave(): - rt.resetMaxFile(rt.Name("noPrompt")) - rt.clearUndoBuffer() - rt.redrawViews() - - -def containerise(name: str, nodes: list, context, - namespace=None, loader=None, suffix="_CON"): - data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, - "name": name, - "namespace": namespace or "", - "loader": loader, - "representation": context["representation"]["id"], - } - container_name = f"{namespace}:{name}{suffix}" - container = rt.container(name=container_name) - import_custom_attribute_data(container, nodes) - if not lib.imprint(container_name, data): - print(f"imprinting of {container_name} failed.") - return container - - -def load_custom_attribute_data(): - """Re-loading the AYON custom parameter built by the creator - - Returns: - attribute: re-loading the custom OP attributes set in Maxscript - """ - return rt.Execute(MS_CUSTOM_ATTRIB) - - -def import_custom_attribute_data(container: str, selections: list): - """Importing the Openpype/AYON custom parameter built by the creator - - Args: - container (str): target container which adds custom attributes - selections (list): nodes to be added into - group in custom attributes - """ - attrs = load_custom_attribute_data() - modifier = rt.EmptyModifier() - rt.addModifier(container, modifier) - container.modifiers[0].name = "OP Data" - rt.custAttributes.add(container.modifiers[0], attrs) - node_list = [] - sel_list = [] - for i in selections: - node_ref = rt.NodeTransformMonitor(node=i) - node_list.append(node_ref) - sel_list.append(str(i)) - - # Setting the property - rt.setProperty( - container.modifiers[0].openPypeData, - "all_handles", node_list) - rt.setProperty( - container.modifiers[0].openPypeData, - "sel_list", sel_list) - - -def update_custom_attribute_data(container: str, selections: list): - """Updating the AYON custom parameter built by the creator - - Args: - container (str): target container which adds custom attributes - selections (list): nodes to be added into - group in custom attributes - """ - if container.modifiers[0].name == "OP Data": - rt.deleteModifier(container, container.modifiers[0]) - import_custom_attribute_data(container, selections) - - -def get_previous_loaded_object(container: str): - """Get previous loaded_object through the OP data - - Args: - container (str): the container which stores the OP data - - Returns: - node_list(list): list of nodes which are previously loaded - """ - node_list = [] - node_transform_monitor_list = rt.getProperty( - container.modifiers[0].openPypeData, "all_handles") - for node_transform_monitor in node_transform_monitor_list: - node_list.append(node_transform_monitor.node) - return node_list - - -def remove_container_data(container_node: str): - """Function to remove container data after updating, switching or deleting it. - - Args: - container_node (str): container node - """ - if container_node.modifiers[0].name == "OP Data": - all_set_members_names = [ - member.node for member - in container_node.modifiers[0].openPypeData.all_handles] - # clean up the children of alembic dummy objects - for current_set_member in all_set_members_names: - shape_list = [members for members in current_set_member.Children - if rt.ClassOf(members) == rt.AlembicObject - or rt.isValidNode(members)] - if shape_list: # noqa - rt.Delete(shape_list) - rt.Delete(current_set_member) - rt.deleteModifier(container_node, container_node.modifiers[0]) - - rt.Delete(container_node) - rt.redrawViews() diff --git a/server_addon/max/client/ayon_max/api/preview_animation.py b/server_addon/max/client/ayon_max/api/preview_animation.py deleted file mode 100644 index acda5360a1..0000000000 --- a/server_addon/max/client/ayon_max/api/preview_animation.py +++ /dev/null @@ -1,344 +0,0 @@ -import logging -import contextlib -from pymxs import runtime as rt -from .lib import get_max_version, render_resolution - -log = logging.getLogger("ayon_max") - - -@contextlib.contextmanager -def play_preview_when_done(has_autoplay): - """Set preview playback option during context - - Args: - has_autoplay (bool): autoplay during creating - preview animation - """ - current_playback = rt.preferences.playPreviewWhenDone - try: - rt.preferences.playPreviewWhenDone = has_autoplay - yield - finally: - rt.preferences.playPreviewWhenDone = current_playback - - -@contextlib.contextmanager -def viewport_layout_and_camera(camera, layout="layout_1"): - """Set viewport layout and camera during context - ***For 3dsMax 2024+ - Args: - camera (str): viewport camera - layout (str): layout to use in viewport, defaults to `layout_1` - Use None to not change viewport layout during context. - """ - needs_maximise = 0 - # Set to first active non extended viewport - rt.viewport.activeViewportEx(1) - original_camera = rt.viewport.getCamera() - original_type = rt.viewport.getType() - review_camera = rt.getNodeByName(camera) - - try: - if rt.viewport.getLayout() != rt.name(layout): - rt.execute("max tool maximize") - needs_maximise = 1 - rt.viewport.setCamera(review_camera) - yield - finally: - if needs_maximise == 1: - rt.execute("max tool maximize") - if original_type == rt.Name("view_camera"): - rt.viewport.setCamera(original_camera) - else: - rt.viewport.setType(original_type) - - -@contextlib.contextmanager -def viewport_preference_setting(general_viewport, - nitrous_manager, - nitrous_viewport, - vp_button_mgr): - """Function to set viewport setting during context - ***For Max Version < 2024 - Args: - camera (str): Viewport camera for review render - general_viewport (dict): General viewport setting - nitrous_manager (dict): Nitrous graphic manager - nitrous_viewport (dict): Nitrous setting for - preview animation - vp_button_mgr (dict): Viewport button manager Setting - preview_preferences (dict): Preview Preferences Setting - """ - orig_vp_grid = rt.viewport.getGridVisibility(1) - orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() - - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - vp_button_mgr_original = { - key: getattr(rt.ViewportButtonMgr, key) for key in vp_button_mgr - } - nitrous_manager_original = { - key: getattr(nitrousGraphicMgr, key) for key in nitrous_manager - } - nitrous_viewport_original = { - key: getattr(viewport_setting, key) for key in nitrous_viewport - } - - try: - rt.viewport.setGridVisibility(1, general_viewport["dspGrid"]) - rt.viewport.EnableSolidBackgroundColorMode(general_viewport["dspBkg"]) - for key, value in vp_button_mgr.items(): - setattr(rt.ViewportButtonMgr, key, value) - for key, value in nitrous_manager.items(): - setattr(nitrousGraphicMgr, key, value) - for key, value in nitrous_viewport.items(): - if nitrous_viewport[key] != nitrous_viewport_original[key]: - setattr(viewport_setting, key, value) - yield - - finally: - rt.viewport.setGridVisibility(1, orig_vp_grid) - rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) - for key, value in vp_button_mgr_original.items(): - setattr(rt.ViewportButtonMgr, key, value) - for key, value in nitrous_manager_original.items(): - setattr(nitrousGraphicMgr, key, value) - for key, value in nitrous_viewport_original.items(): - setattr(viewport_setting, key, value) - - -def _render_preview_animation_max_2024( - filepath, start, end, percentSize, ext, viewport_options): - """Render viewport preview with MaxScript using `CreateAnimation`. - ****For 3dsMax 2024+ - Args: - filepath (str): filepath for render output without frame number and - extension, for example: /path/to/file - start (int): startFrame - end (int): endFrame - percentSize (float): render resolution multiplier by 100 - e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x - viewport_options (dict): viewport setting options, e.g. - {"vpStyle": "defaultshading", "vpPreset": "highquality"} - Returns: - list: Created files - """ - # the percentSize argument must be integer - percent = int(percentSize) - filepath = filepath.replace("\\", "/") - preview_output = f"{filepath}..{ext}" - frame_template = f"{filepath}.{{:04d}}.{ext}" - job_args = [] - for key, value in viewport_options.items(): - if isinstance(value, bool): - if value: - job_args.append(f"{key}:{value}") - elif isinstance(value, str): - if key == "vpStyle": - if value == "Realistic": - value = "defaultshading" - elif value == "Shaded": - log.warning( - "'Shaded' Mode not supported in " - "preview animation in Max 2024.\n" - "Using 'defaultshading' instead.") - value = "defaultshading" - elif value == "ConsistentColors": - value = "flatcolor" - else: - value = value.lower() - elif key == "vpPreset": - if value == "Quality": - value = "highquality" - elif value == "Customize": - value = "userdefined" - else: - value = value.lower() - job_args.append(f"{key}: #{value}") - - job_str = ( - f'CreatePreview filename:"{preview_output}" outputAVI:false ' - f"percentSize:{percent} start:{start} end:{end} " - f"{' '.join(job_args)} " - "autoPlay:false" - ) - rt.completeRedraw() - rt.execute(job_str) - # Return the created files - return [frame_template.format(frame) for frame in range(start, end + 1)] - - -def _render_preview_animation_max_pre_2024( - filepath, startFrame, endFrame, - width, height, percentSize, ext): - """Render viewport animation by creating bitmaps - ***For 3dsMax Version <2024 - Args: - filepath (str): filepath without frame numbers and extension - startFrame (int): start frame - endFrame (int): end frame - width (int): render resolution width - height (int): render resolution height - percentSize (float): render resolution multiplier by 100 - e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x - ext (str): image extension - Returns: - list: Created filepaths - """ - - # get the screenshot - percent = percentSize / 100.0 - res_width = width * percent - res_height = height * percent - frame_template = "{}.{{:04}}.{}".format(filepath, ext) - frame_template.replace("\\", "/") - files = [] - user_cancelled = False - for frame in range(startFrame, endFrame + 1): - rt.sliderTime = frame - filepath = frame_template.format(frame) - preview_res = rt.bitmap( - res_width, res_height, filename=filepath - ) - dib = rt.gw.getViewportDib() - dib_width = float(dib.width) - dib_height = float(dib.height) - # aspect ratio - viewportRatio = dib_width / dib_height - renderRatio = float(res_width / res_height) - if viewportRatio < renderRatio: - heightCrop = (dib_width / renderRatio) - topEdge = int((dib_height - heightCrop) / 2.0) - tempImage_bmp = rt.bitmap(dib_width, heightCrop) - src_box_value = rt.Box2(0, topEdge, dib_width, heightCrop) - rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) - rt.copy(tempImage_bmp, preview_res) - rt.close(tempImage_bmp) - elif viewportRatio > renderRatio: - widthCrop = dib_height * renderRatio - leftEdge = int((dib_width - widthCrop) / 2.0) - tempImage_bmp = rt.bitmap(widthCrop, dib_height) - src_box_value = rt.Box2(leftEdge, 0, widthCrop, dib_height) - rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) - rt.copy(tempImage_bmp, preview_res) - rt.close(tempImage_bmp) - else: - rt.copy(dib, preview_res) - rt.save(preview_res) - rt.close(preview_res) - rt.close(dib) - files.append(filepath) - if rt.keyboard.escPressed: - user_cancelled = True - break - # clean up the cache - rt.gc(delayed=True) - if user_cancelled: - raise RuntimeError("User cancelled rendering of viewport animation.") - return files - - -def render_preview_animation( - filepath, - ext, - camera, - start_frame=None, - end_frame=None, - percentSize=100.0, - width=1920, - height=1080, - viewport_options=None): - """Render camera review animation - Args: - filepath (str): filepath to render to, without frame number and - extension - ext (str): output file extension - camera (str): viewport camera for preview render - start_frame (int): start frame - end_frame (int): end frame - percentSize (float): render resolution multiplier by 100 - e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x - width (int): render resolution width - height (int): render resolution height - viewport_options (dict): viewport setting options - Returns: - list: Rendered output files - """ - if start_frame is None: - start_frame = int(rt.animationRange.start) - if end_frame is None: - end_frame = int(rt.animationRange.end) - - if viewport_options is None: - viewport_options = viewport_options_for_preview_animation() - with play_preview_when_done(False): - with viewport_layout_and_camera(camera): - if int(get_max_version()) < 2024: - with viewport_preference_setting( - viewport_options["general_viewport"], - viewport_options["nitrous_manager"], - viewport_options["nitrous_viewport"], - viewport_options["vp_btn_mgr"] - ): - return _render_preview_animation_max_pre_2024( - filepath, - start_frame, - end_frame, - width, - height, - percentSize, - ext - ) - else: - with render_resolution(width, height): - return _render_preview_animation_max_2024( - filepath, - start_frame, - end_frame, - percentSize, - ext, - viewport_options - ) - - -def viewport_options_for_preview_animation(): - """Get default viewport options for `render_preview_animation`. - - Returns: - dict: viewport setting options - """ - # viewport_options should be the dictionary - if int(get_max_version()) < 2024: - return { - "visualStyleMode": "defaultshading", - "viewportPreset": "highquality", - "vpTexture": False, - "dspGeometry": True, - "dspShapes": False, - "dspLights": False, - "dspCameras": False, - "dspHelpers": False, - "dspParticles": True, - "dspBones": False, - "dspBkg": True, - "dspGrid": False, - "dspSafeFrame": False, - "dspFrameNums": False - } - else: - viewport_options = {} - viewport_options["general_viewport"] = { - "dspBkg": True, - "dspGrid": False - } - viewport_options["nitrous_manager"] = { - "AntialiasingQuality": "None" - } - viewport_options["nitrous_viewport"] = { - "VisualStyleMode": "defaultshading", - "ViewportPreset": "highquality", - "UseTextureEnabled": False - } - viewport_options["vp_btn_mgr"] = { - "EnableButtons": False} - return viewport_options diff --git a/server_addon/max/client/ayon_max/hooks/force_startup_script.py b/server_addon/max/client/ayon_max/hooks/force_startup_script.py deleted file mode 100644 index 1699ea300a..0000000000 --- a/server_addon/max/client/ayon_max/hooks/force_startup_script.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -"""Pre-launch to force 3ds max startup script.""" -import os -from ayon_max import MAX_HOST_DIR -from ayon_applications import PreLaunchHook, LaunchTypes - - -class ForceStartupScript(PreLaunchHook): - """Inject AYON environment to 3ds max. - - Note that this works in combination whit 3dsmax startup script that - is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH - environment. - - Hook `GlobalHostDataHook` must be executed before this hook. - """ - app_groups = {"3dsmax", "adsk_3dsmax"} - order = 11 - launch_types = {LaunchTypes.local} - - def execute(self): - startup_args = [ - "-U", - "MAXScript", - os.path.join(MAX_HOST_DIR, "startup", "startup.ms"), - ] - self.launch_context.launch_args.append(startup_args) diff --git a/server_addon/max/client/ayon_max/hooks/inject_python.py b/server_addon/max/client/ayon_max/hooks/inject_python.py deleted file mode 100644 index fc9626ab87..0000000000 --- a/server_addon/max/client/ayon_max/hooks/inject_python.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -"""Pre-launch hook to inject python environment.""" -import os -from ayon_applications import PreLaunchHook, LaunchTypes - - -class InjectPythonPath(PreLaunchHook): - """Inject AYON environment to 3dsmax. - - Note that this works in combination whit 3dsmax startup script that - is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH - environment. - - Hook `GlobalHostDataHook` must be executed before this hook. - """ - app_groups = {"3dsmax", "adsk_3dsmax"} - launch_types = {LaunchTypes.local} - - def execute(self): - self.launch_context.env["MAX_PYTHONPATH"] = os.environ["PYTHONPATH"] diff --git a/server_addon/max/client/ayon_max/hooks/set_paths.py b/server_addon/max/client/ayon_max/hooks/set_paths.py deleted file mode 100644 index f066de092e..0000000000 --- a/server_addon/max/client/ayon_max/hooks/set_paths.py +++ /dev/null @@ -1,18 +0,0 @@ -from ayon_applications import PreLaunchHook, LaunchTypes - - -class SetPath(PreLaunchHook): - """Set current dir to workdir. - - Hook `GlobalHostDataHook` must be executed before this hook. - """ - app_groups = {"max"} - launch_types = {LaunchTypes.local} - - def execute(self): - workdir = self.launch_context.env.get("AYON_WORKDIR", "") - if not workdir: - self.log.warning("BUG: Workdir is not filled.") - return - - self.launch_context.kwargs["cwd"] = workdir diff --git a/server_addon/max/client/ayon_max/plugins/__init__.py b/server_addon/max/client/ayon_max/plugins/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/server_addon/max/client/ayon_max/plugins/create/create_camera.py b/server_addon/max/client/ayon_max/plugins/create/create_camera.py deleted file mode 100644 index 451e178afc..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_camera.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating camera.""" -from ayon_max.api import plugin - - -class CreateCamera(plugin.MaxCreator): - """Creator plugin for Camera.""" - identifier = "io.openpype.creators.max.camera" - label = "Camera" - product_type = "camera" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_maxScene.py b/server_addon/max/client/ayon_max/plugins/create/create_maxScene.py deleted file mode 100644 index ee58ef663d..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_maxScene.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating raw max scene.""" -from ayon_max.api import plugin - - -class CreateMaxScene(plugin.MaxCreator): - """Creator plugin for 3ds max scenes.""" - identifier = "io.openpype.creators.max.maxScene" - label = "Max Scene" - product_type = "maxScene" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_model.py b/server_addon/max/client/ayon_max/plugins/create/create_model.py deleted file mode 100644 index f48182ecd7..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_model.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for model.""" -from ayon_max.api import plugin - - -class CreateModel(plugin.MaxCreator): - """Creator plugin for Model.""" - identifier = "io.openpype.creators.max.model" - label = "Model" - product_type = "model" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_pointcache.py b/server_addon/max/client/ayon_max/plugins/create/create_pointcache.py deleted file mode 100644 index 6d7aabe12c..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_pointcache.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating pointcache alembics.""" -from ayon_max.api import plugin - - -class CreatePointCache(plugin.MaxCreator): - """Creator plugin for Point caches.""" - identifier = "io.openpype.creators.max.pointcache" - label = "Point Cache" - product_type = "pointcache" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_pointcloud.py b/server_addon/max/client/ayon_max/plugins/create/create_pointcloud.py deleted file mode 100644 index 52014d77b2..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_pointcloud.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating point cloud.""" -from ayon_max.api import plugin - - -class CreatePointCloud(plugin.MaxCreator): - """Creator plugin for Point Clouds.""" - identifier = "io.openpype.creators.max.pointcloud" - label = "Point Cloud" - product_type = "pointcloud" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_redshift_proxy.py b/server_addon/max/client/ayon_max/plugins/create/create_redshift_proxy.py deleted file mode 100644 index bcc96c7efe..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_redshift_proxy.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating camera.""" -from ayon_max.api import plugin - - -class CreateRedshiftProxy(plugin.MaxCreator): - identifier = "io.openpype.creators.max.redshiftproxy" - label = "Redshift Proxy" - product_type = "redshiftproxy" - icon = "gear" - - settings_category = "max" diff --git a/server_addon/max/client/ayon_max/plugins/create/create_render.py b/server_addon/max/client/ayon_max/plugins/create/create_render.py deleted file mode 100644 index d1e236f3ef..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_render.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating camera.""" -import os -from ayon_max.api import plugin -from ayon_core.lib import BoolDef -from ayon_max.api.lib_rendersettings import RenderSettings - - -class CreateRender(plugin.MaxCreator): - """Creator plugin for Renders.""" - identifier = "io.openpype.creators.max.render" - label = "Render" - product_type = "maxrender" - icon = "gear" - - settings_category = "max" - - def create(self, product_name, instance_data, pre_create_data): - from pymxs import runtime as rt - file = rt.maxFileName - filename, _ = os.path.splitext(file) - instance_data["AssetName"] = filename - instance_data["multiCamera"] = pre_create_data.get("multi_cam") - num_of_renderlayer = rt.batchRenderMgr.numViews - if num_of_renderlayer > 0: - rt.batchRenderMgr.DeleteView(num_of_renderlayer) - - instance = super(CreateRender, self).create( - product_name, - instance_data, - pre_create_data) - - container_name = instance.data.get("instance_node") - # set output paths for rendering(mandatory for deadline) - RenderSettings().render_output(container_name) - # TODO: create multiple camera options - if self.selected_nodes: - selected_nodes_name = [] - for sel in self.selected_nodes: - name = sel.name - selected_nodes_name.append(name) - RenderSettings().batch_render_layer( - container_name, filename, - selected_nodes_name) - - def get_pre_create_attr_defs(self): - attrs = super(CreateRender, self).get_pre_create_attr_defs() - return attrs + [ - BoolDef("multi_cam", - label="Multiple Cameras Submission", - default=False), - ] diff --git a/server_addon/max/client/ayon_max/plugins/create/create_review.py b/server_addon/max/client/ayon_max/plugins/create/create_review.py deleted file mode 100644 index a49490519a..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_review.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating review in Max.""" -from ayon_max.api import plugin -from ayon_core.lib import BoolDef, EnumDef, NumberDef - - -class CreateReview(plugin.MaxCreator): - """Review in 3dsMax""" - - identifier = "io.openpype.creators.max.review" - label = "Review" - product_type = "review" - icon = "video-camera" - - settings_category = "max" - - review_width = 1920 - review_height = 1080 - percentSize = 100 - keep_images = False - image_format = "png" - visual_style = "Realistic" - viewport_preset = "Quality" - vp_texture = True - anti_aliasing = "None" - - def apply_settings(self, project_settings): - settings = project_settings["max"]["CreateReview"] # noqa - - # Take some defaults from settings - self.review_width = settings.get("review_width", self.review_width) - self.review_height = settings.get("review_height", self.review_height) - self.percentSize = settings.get("percentSize", self.percentSize) - self.keep_images = settings.get("keep_images", self.keep_images) - self.image_format = settings.get("image_format", self.image_format) - self.visual_style = settings.get("visual_style", self.visual_style) - self.viewport_preset = settings.get( - "viewport_preset", self.viewport_preset) - self.anti_aliasing = settings.get( - "anti_aliasing", self.anti_aliasing) - self.vp_texture = settings.get("vp_texture", self.vp_texture) - - def create(self, product_name, instance_data, pre_create_data): - # Transfer settings from pre create to instance - creator_attributes = instance_data.setdefault( - "creator_attributes", dict()) - for key in ["imageFormat", - "keepImages", - "review_width", - "review_height", - "percentSize", - "visualStyleMode", - "viewportPreset", - "antialiasingQuality", - "vpTexture"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] - - super(CreateReview, self).create( - product_name, - instance_data, - pre_create_data) - - def get_instance_attr_defs(self): - image_format_enum = ["exr", "jpg", "png", "tga"] - - visual_style_preset_enum = [ - "Realistic", "Shaded", "Facets", - "ConsistentColors", "HiddenLine", - "Wireframe", "BoundingBox", "Ink", - "ColorInk", "Acrylic", "Tech", "Graphite", - "ColorPencil", "Pastel", "Clay", "ModelAssist" - ] - preview_preset_enum = [ - "Quality", "Standard", "Performance", - "DXMode", "Customize"] - anti_aliasing_enum = ["None", "2X", "4X", "8X"] - - return [ - NumberDef("review_width", - label="Review width", - decimals=0, - minimum=0, - default=self.review_width), - NumberDef("review_height", - label="Review height", - decimals=0, - minimum=0, - default=self.review_height), - NumberDef("percentSize", - label="Percent of Output", - default=self.percentSize, - minimum=1, - decimals=0), - BoolDef("keepImages", - label="Keep Image Sequences", - default=self.keep_images), - EnumDef("imageFormat", - image_format_enum, - default=self.image_format, - label="Image Format Options"), - EnumDef("visualStyleMode", - visual_style_preset_enum, - default=self.visual_style, - label="Preference"), - EnumDef("viewportPreset", - preview_preset_enum, - default=self.viewport_preset, - label="Preview Preset"), - EnumDef("antialiasingQuality", - anti_aliasing_enum, - default=self.anti_aliasing, - label="Anti-aliasing Quality"), - BoolDef("vpTexture", - label="Viewport Texture", - default=self.vp_texture) - ] - - def get_pre_create_attr_defs(self): - # Use same attributes as for instance attributes - attrs = super().get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() diff --git a/server_addon/max/client/ayon_max/plugins/create/create_workfile.py b/server_addon/max/client/ayon_max/plugins/create/create_workfile.py deleted file mode 100644 index 35c41f0fcc..0000000000 --- a/server_addon/max/client/ayon_max/plugins/create/create_workfile.py +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for creating workfiles.""" -import ayon_api - -from ayon_core.pipeline import CreatedInstance, AutoCreator -from ayon_max.api import plugin -from ayon_max.api.lib import read, imprint -from pymxs import runtime as rt - - -class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): - """Workfile auto-creator.""" - identifier = "io.ayon.creators.max.workfile" - label = "Workfile" - product_type = "workfile" - icon = "fa5.file" - - default_variant = "Main" - - settings_category = "max" - - def create(self): - variant = self.default_variant - current_instance = next( - ( - instance for instance in self.create_context.instances - if instance.creator_identifier == self.identifier - ), None) - project_name = self.project_name - folder_path = self.create_context.get_current_folder_path() - task_name = self.create_context.get_current_task_name() - host_name = self.create_context.host_name - - if current_instance is None: - folder_entity = ayon_api.get_folder_by_path( - project_name, folder_path - ) - task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name - ) - product_name = self.get_product_name( - project_name, - folder_entity, - task_entity, - variant, - host_name, - ) - data = { - "folderPath": folder_path, - "task": task_name, - "variant": variant - } - - data.update( - self.get_dynamic_data( - project_name, - folder_entity, - task_entity, - variant, - host_name, - current_instance) - ) - self.log.info("Auto-creating workfile instance...") - instance_node = self.create_node(product_name) - data["instance_node"] = instance_node.name - current_instance = CreatedInstance( - self.product_type, product_name, data, self - ) - self._add_instance_to_context(current_instance) - imprint(instance_node.name, current_instance.data) - elif ( - current_instance["folderPath"] != folder_path - or current_instance["task"] != task_name - ): - # Update instance context if is not the same - folder_entity = ayon_api.get_folder_by_path( - project_name, folder_path - ) - task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name - ) - product_name = self.get_product_name( - project_name, - folder_entity, - task_entity, - variant, - host_name, - ) - - current_instance["folderPath"] = folder_entity["path"] - current_instance["task"] = task_name - current_instance["productName"] = product_name - - def collect_instances(self): - self.cache_instance_data(self.collection_shared_data) - cached_instances = self.collection_shared_data["max_cached_instances"] - for instance in cached_instances.get(self.identifier, []): - if not rt.getNodeByName(instance): - continue - created_instance = CreatedInstance.from_existing( - read(rt.GetNodeByName(instance)), self - ) - self._add_instance_to_context(created_instance) - - def update_instances(self, update_list): - for created_inst, _ in update_list: - instance_node = created_inst.get("instance_node") - imprint( - instance_node, - created_inst.data_to_store() - ) - - def create_node(self, product_name): - if rt.getNodeByName(product_name): - node = rt.getNodeByName(product_name) - return node - node = rt.Container(name=product_name) - node.isHidden = True - return node diff --git a/server_addon/max/client/ayon_max/plugins/load/load_camera_fbx.py b/server_addon/max/client/ayon_max/plugins/load/load_camera_fbx.py deleted file mode 100644 index 81ea15d52a..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_camera_fbx.py +++ /dev/null @@ -1,101 +0,0 @@ -import os - -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - object_transform_set -) -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_core.pipeline import get_representation_path, load - - -class FbxLoader(load.LoaderPlugin): - """Fbx Loader.""" - - product_types = {"camera"} - representations = {"fbx"} - order = -9 - icon = "code-fork" - color = "white" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - filepath = self.filepath_from_context(context) - filepath = os.path.normpath(filepath) - rt.FBXImporterSetParam("Animation", True) - rt.FBXImporterSetParam("Camera", True) - rt.FBXImporterSetParam("AxisConversionMethod", True) - rt.FBXImporterSetParam("Mode", rt.Name("create")) - rt.FBXImporterSetParam("Preserveinstances", True) - rt.ImportFile( - filepath, - rt.name("noPrompt"), - using=rt.FBXIMP) - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - selections = rt.GetCurrentSelection() - - for selection in selections: - selection.name = f"{namespace}:{selection.name}" - - return containerise( - name, selections, context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - node = rt.getNodeByName(node_name) - namespace, _ = get_namespace(node_name) - - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - prev_fbx_objects = rt.GetCurrentSelection() - transform_data = object_transform_set(prev_fbx_objects) - for prev_fbx_obj in prev_fbx_objects: - if rt.isValidNode(prev_fbx_obj): - rt.Delete(prev_fbx_obj) - - rt.FBXImporterSetParam("Animation", True) - rt.FBXImporterSetParam("Camera", True) - rt.FBXImporterSetParam("Mode", rt.Name("merge")) - rt.FBXImporterSetParam("AxisConversionMethod", True) - rt.FBXImporterSetParam("Preserveinstances", True) - rt.ImportFile( - path, rt.name("noPrompt"), using=rt.FBXIMP) - current_fbx_objects = rt.GetCurrentSelection() - fbx_objects = [] - for fbx_object in current_fbx_objects: - fbx_object.name = f"{namespace}:{fbx_object.name}" - fbx_objects.append(fbx_object) - fbx_transform = f"{fbx_object.name}.transform" - if fbx_transform in transform_data.keys(): - fbx_object.pos = transform_data[fbx_transform] or 0 - fbx_object.scale = transform_data[ - f"{fbx_object.name}.scale"] or 0 - - update_custom_attribute_data(node, fbx_objects) - lib.imprint(container["instance_node"], { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_max_scene.py b/server_addon/max/client/ayon_max/plugins/load/load_max_scene.py deleted file mode 100644 index 7fca69b193..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_max_scene.py +++ /dev/null @@ -1,178 +0,0 @@ -import os -from qtpy import QtWidgets, QtCore -from ayon_core.lib.attribute_definitions import EnumDef -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - object_transform_set, - is_headless -) -from ayon_max.api.pipeline import ( - containerise, get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_core.pipeline import get_representation_path, load - - -class MaterialDupOptionsWindow(QtWidgets.QDialog): - """The pop-up dialog allows users to choose material - duplicate options for importing Max objects when updating - or switching assets. - """ - def __init__(self, material_options): - super(MaterialDupOptionsWindow, self).__init__() - self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) - - self.material_option = None - self.material_options = material_options - - self.widgets = { - "label": QtWidgets.QLabel( - "Select material duplicate options before loading the max scene."), - "material_options_list": QtWidgets.QListWidget(), - "warning": QtWidgets.QLabel("No material options selected!"), - "buttons": QtWidgets.QWidget(), - "okButton": QtWidgets.QPushButton("Ok"), - "cancelButton": QtWidgets.QPushButton("Cancel") - } - for key, value in material_options.items(): - item = QtWidgets.QListWidgetItem(value) - self.widgets["material_options_list"].addItem(item) - item.setData(QtCore.Qt.UserRole, key) - # Build buttons. - layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) - layout.addWidget(self.widgets["okButton"]) - layout.addWidget(self.widgets["cancelButton"]) - # Build layout. - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["material_options_list"]) - layout.addWidget(self.widgets["buttons"]) - - self.widgets["okButton"].pressed.connect(self.on_ok_pressed) - self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) - self.widgets["material_options_list"].itemPressed.connect( - self.on_material_options_pressed) - - def on_material_options_pressed(self, item): - self.material_option = item.data(QtCore.Qt.UserRole) - - def on_ok_pressed(self): - if self.material_option is None: - self.widgets["warning"].setVisible(True) - return - self.close() - - def on_cancel_pressed(self): - self.material_option = "promptMtlDups" - self.close() - -class MaxSceneLoader(load.LoaderPlugin): - """Max Scene Loader.""" - - product_types = { - "camera", - "maxScene", - "model", - } - - representations = {"max"} - order = -8 - icon = "code-fork" - color = "green" - mtl_dup_default = "promptMtlDups" - mtl_dup_enum_dict = { - "promptMtlDups": "Prompt on Duplicate Materials", - "useMergedMtlDups": "Use Incoming Material", - "useSceneMtlDups": "Use Scene Material", - "renameMtlDups": "Merge and Rename Incoming Material" - } - @classmethod - def get_options(cls, contexts): - return [ - EnumDef("mtldup", - items=cls.mtl_dup_enum_dict, - default=cls.mtl_dup_default, - label="Material Duplicate Options") - ] - - def load(self, context, name=None, namespace=None, options=None): - from pymxs import runtime as rt - mat_dup_options = options.get("mtldup", self.mtl_dup_default) - path = self.filepath_from_context(context) - path = os.path.normpath(path) - # import the max scene by using "merge file" - path = path.replace('\\', '/') - rt.MergeMaxFile(path, rt.Name(mat_dup_options), - quiet=True, includeFullGroup=True) - max_objects = rt.getLastMergedNodes() - max_object_names = [obj.name for obj in max_objects] - # implement the OP/AYON custom attributes before load - max_container = [] - namespace = unique_namespace( - name + "_", - suffix="_", - ) - for max_obj, obj_name in zip(max_objects, max_object_names): - max_obj.name = f"{namespace}:{obj_name}" - max_container.append(max_obj) - return containerise( - name, max_container, context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - node = rt.getNodeByName(node_name) - namespace, _ = get_namespace(node_name) - # delete the old container with attribute - # delete old duplicate - # use the modifier OP data to delete the data - node_list = get_previous_loaded_object(node) - rt.select(node_list) - prev_max_objects = rt.GetCurrentSelection() - transform_data = object_transform_set(prev_max_objects) - - for prev_max_obj in prev_max_objects: - if rt.isValidNode(prev_max_obj): # noqa - rt.Delete(prev_max_obj) - material_option = self.mtl_dup_default - if not is_headless(): - window = MaterialDupOptionsWindow(self.mtl_dup_enum_dict) - window.exec_() - material_option = window.material_option - rt.MergeMaxFile(path, rt.Name(material_option), quiet=True) - - current_max_objects = rt.getLastMergedNodes() - - current_max_object_names = [obj.name for obj - in current_max_objects] - - max_objects = [] - for max_obj, obj_name in zip(current_max_objects, - current_max_object_names): - max_obj.name = f"{namespace}:{obj_name}" - max_objects.append(max_obj) - max_transform = f"{max_obj}.transform" - if max_transform in transform_data.keys(): - max_obj.pos = transform_data[max_transform] or 0 - max_obj.scale = transform_data[ - f"{max_obj}.scale"] or 0 - - update_custom_attribute_data(node, max_objects) - lib.imprint(container["instance_node"], { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_model.py b/server_addon/max/client/ayon_max/plugins/load/load_model.py deleted file mode 100644 index 2a6bc45c18..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_model.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -from ayon_core.pipeline import load, get_representation_path -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - remove_container_data -) -from ayon_max.api import lib -from ayon_max.api.lib import ( - maintained_selection, unique_namespace -) - - -class ModelAbcLoader(load.LoaderPlugin): - """Loading model with the Alembic loader.""" - - product_types = {"model"} - label = "Load Model with Alembic" - representations = {"abc"} - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - file_path = os.path.normpath(self.filepath_from_context(context)) - - abc_before = { - c - for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer - } - - rt.AlembicImport.ImportToRoot = False - rt.AlembicImport.CustomAttributes = True - rt.AlembicImport.UVs = True - rt.AlembicImport.VertexColors = True - rt.importFile(file_path, rt.name("noPrompt"), using=rt.AlembicImport) - - abc_after = { - c - for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer - } - - # This should yield new AlembicContainer node - abc_containers = abc_after.difference(abc_before) - - if len(abc_containers) != 1: - self.log.error("Something failed when loading.") - - abc_container = abc_containers.pop() - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - abc_objects = [] - for abc_object in abc_container.Children: - abc_object.name = f"{namespace}:{abc_object.name}" - abc_objects.append(abc_object) - # rename the abc container with namespace - abc_container_name = f"{namespace}:{name}" - abc_container.name = abc_container_name - abc_objects.append(abc_container) - - return containerise( - name, abc_objects, context, - namespace, loader=self.__class__.__name__ - ) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node = rt.GetNodeByName(container["instance_node"]) - node_list = [n for n in get_previous_loaded_object(node) - if rt.ClassOf(n) == rt.AlembicContainer] - with maintained_selection(): - rt.Select(node_list) - - for alembic in rt.Selection: - abc = rt.GetNodeByName(alembic.name) - rt.Select(abc.Children) - for abc_con in abc.Children: - abc_con.source = path - rt.Select(abc_con.Children) - for abc_obj in abc_con.Children: - abc_obj.source = path - lib.imprint( - container["instance_node"], - {"representation": repre_entity["id"]}, - ) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) - - - @staticmethod - def get_container_children(parent, type_name): - from pymxs import runtime as rt - - def list_children(node): - children = [] - for c in node.Children: - children.append(c) - children += list_children(c) - return children - - filtered = [] - for child in list_children(parent): - class_type = str(rt.ClassOf(child.baseObject)) - if class_type == type_name: - filtered.append(child) - - return filtered diff --git a/server_addon/max/client/ayon_max/plugins/load/load_model_fbx.py b/server_addon/max/client/ayon_max/plugins/load/load_model_fbx.py deleted file mode 100644 index 2775e1b453..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_model_fbx.py +++ /dev/null @@ -1,98 +0,0 @@ -import os -from ayon_core.pipeline import load, get_representation_path -from ayon_max.api.pipeline import ( - containerise, get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - object_transform_set -) -from ayon_max.api.lib import maintained_selection - - -class FbxModelLoader(load.LoaderPlugin): - """Fbx Model Loader.""" - - product_types = {"model"} - representations = {"fbx"} - order = -9 - icon = "code-fork" - color = "white" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - filepath = self.filepath_from_context(context) - filepath = os.path.normpath(filepath) - rt.FBXImporterSetParam("Animation", False) - rt.FBXImporterSetParam("Cameras", False) - rt.FBXImporterSetParam("Mode", rt.Name("create")) - rt.FBXImporterSetParam("Preserveinstances", True) - rt.importFile( - filepath, rt.name("noPrompt"), using=rt.FBXIMP) - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - selections = rt.GetCurrentSelection() - - for selection in selections: - selection.name = f"{namespace}:{selection.name}" - - return containerise( - name, selections, context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - node = rt.getNodeByName(node_name) - if not node: - rt.Container(name=node_name) - namespace, _ = get_namespace(node_name) - - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - prev_fbx_objects = rt.GetCurrentSelection() - transform_data = object_transform_set(prev_fbx_objects) - for prev_fbx_obj in prev_fbx_objects: - if rt.isValidNode(prev_fbx_obj): - rt.Delete(prev_fbx_obj) - - rt.FBXImporterSetParam("Animation", False) - rt.FBXImporterSetParam("Cameras", False) - rt.FBXImporterSetParam("Mode", rt.Name("create")) - rt.FBXImporterSetParam("Preserveinstances", True) - rt.importFile(path, rt.name("noPrompt"), using=rt.FBXIMP) - current_fbx_objects = rt.GetCurrentSelection() - fbx_objects = [] - for fbx_object in current_fbx_objects: - fbx_object.name = f"{namespace}:{fbx_object.name}" - fbx_objects.append(fbx_object) - fbx_transform = f"{fbx_object}.transform" - if fbx_transform in transform_data.keys(): - fbx_object.pos = transform_data[fbx_transform] or 0 - fbx_object.scale = transform_data[ - f"{fbx_object}.scale"] or 0 - - with maintained_selection(): - rt.Select(node) - update_custom_attribute_data(node, fbx_objects) - lib.imprint(container["instance_node"], { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_model_obj.py b/server_addon/max/client/ayon_max/plugins/load/load_model_obj.py deleted file mode 100644 index d38aadb5bc..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_model_obj.py +++ /dev/null @@ -1,89 +0,0 @@ -import os - -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - maintained_selection, - object_transform_set -) -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_core.pipeline import get_representation_path, load - - -class ObjLoader(load.LoaderPlugin): - """Obj Loader.""" - - product_types = {"model"} - representations = {"obj"} - order = -9 - icon = "code-fork" - color = "white" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - filepath = os.path.normpath(self.filepath_from_context(context)) - self.log.debug("Executing command to import..") - - rt.Execute(f'importFile @"{filepath}" #noPrompt using:ObjImp') - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - # create "missing" container for obj import - selections = rt.GetCurrentSelection() - # get current selection - for selection in selections: - selection.name = f"{namespace}:{selection.name}" - return containerise( - name, selections, context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - node = rt.getNodeByName(node_name) - namespace, _ = get_namespace(node_name) - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - previous_objects = rt.GetCurrentSelection() - transform_data = object_transform_set(previous_objects) - for prev_obj in previous_objects: - if rt.isValidNode(prev_obj): - rt.Delete(prev_obj) - - rt.Execute(f'importFile @"{path}" #noPrompt using:ObjImp') - # get current selection - selections = rt.GetCurrentSelection() - for selection in selections: - selection.name = f"{namespace}:{selection.name}" - selection_transform = f"{selection}.transform" - if selection_transform in transform_data.keys(): - selection.pos = transform_data[selection_transform] or 0 - selection.scale = transform_data[ - f"{selection}.scale"] or 0 - update_custom_attribute_data(node, selections) - with maintained_selection(): - rt.Select(node) - - lib.imprint(node_name, { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_model_usd.py b/server_addon/max/client/ayon_max/plugins/load/load_model_usd.py deleted file mode 100644 index f4dd41d5db..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_model_usd.py +++ /dev/null @@ -1,120 +0,0 @@ -import os - -from pymxs import runtime as rt -from ayon_core.pipeline.load import LoadError -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - object_transform_set, - get_plugins -) -from ayon_max.api.lib import maintained_selection -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_core.pipeline import get_representation_path, load - - -class ModelUSDLoader(load.LoaderPlugin): - """Loading model with the USD loader.""" - - product_types = {"model"} - label = "Load Model(USD)" - representations = {"usda"} - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - # asset_filepath - plugin_info = get_plugins() - if "usdimport.dli" not in plugin_info: - raise LoadError("No USDImporter loaded/installed in Max..") - filepath = os.path.normpath(self.filepath_from_context(context)) - import_options = rt.USDImporter.CreateOptions() - base_filename = os.path.basename(filepath) - _, ext = os.path.splitext(base_filename) - log_filepath = filepath.replace(ext, "txt") - - rt.LogPath = log_filepath - rt.LogLevel = rt.Name("info") - rt.USDImporter.importFile(filepath, - importOptions=import_options) - namespace = unique_namespace( - name + "_", - suffix="_", - ) - asset = rt.GetNodeByName(name) - usd_objects = [] - - for usd_asset in asset.Children: - usd_asset.name = f"{namespace}:{usd_asset.name}" - usd_objects.append(usd_asset) - - asset_name = f"{namespace}:{name}" - asset.name = asset_name - # need to get the correct container after renamed - asset = rt.GetNodeByName(asset_name) - usd_objects.append(asset) - - return containerise( - name, usd_objects, context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - node = rt.GetNodeByName(node_name) - namespace, name = get_namespace(node_name) - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - prev_objects = [sel for sel in rt.GetCurrentSelection() - if sel != rt.Container - and sel.name != node_name] - transform_data = object_transform_set(prev_objects) - for n in prev_objects: - rt.Delete(n) - - import_options = rt.USDImporter.CreateOptions() - base_filename = os.path.basename(path) - _, ext = os.path.splitext(base_filename) - log_filepath = path.replace(ext, "txt") - - rt.LogPath = log_filepath - rt.LogLevel = rt.Name("info") - rt.USDImporter.importFile( - path, importOptions=import_options) - - asset = rt.GetNodeByName(name) - usd_objects = [] - for children in asset.Children: - children.name = f"{namespace}:{children.name}" - usd_objects.append(children) - children_transform = f"{children}.transform" - if children_transform in transform_data.keys(): - children.pos = transform_data[children_transform] or 0 - children.scale = transform_data[ - f"{children}.scale"] or 0 - - asset.name = f"{namespace}:{asset.name}" - usd_objects.append(asset) - update_custom_attribute_data(node, usd_objects) - with maintained_selection(): - rt.Select(node) - - lib.imprint(node_name, { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_pointcache.py b/server_addon/max/client/ayon_max/plugins/load/load_pointcache.py deleted file mode 100644 index 87ea5c75bc..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_pointcache.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -"""Simple alembic loader for 3dsmax. - -Because of limited api, alembics can be only loaded, but not easily updated. - -""" -import os -from ayon_core.pipeline import load, get_representation_path -from ayon_max.api import lib, maintained_selection -from ayon_max.api.lib import unique_namespace, reset_frame_range -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - remove_container_data -) - - -class AbcLoader(load.LoaderPlugin): - """Alembic loader.""" - - product_types = {"camera", "animation", "pointcache"} - label = "Load Alembic" - representations = {"abc"} - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - - file_path = self.filepath_from_context(context) - file_path = os.path.normpath(file_path) - - abc_before = { - c - for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer - } - - rt.AlembicImport.ImportToRoot = False - # TODO: it will be removed after the improvement - # on the post-system setup - reset_frame_range() - rt.importFile(file_path, rt.name("noPrompt"), using=rt.AlembicImport) - - abc_after = { - c - for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer - } - - # This should yield new AlembicContainer node - abc_containers = abc_after.difference(abc_before) - - if len(abc_containers) != 1: - self.log.error("Something failed when loading.") - - abc_container = abc_containers.pop() - selections = rt.GetCurrentSelection() - for abc in selections: - for cam_shape in abc.Children: - cam_shape.playbackType = 0 - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - abc_objects = [] - for abc_object in abc_container.Children: - abc_object.name = f"{namespace}:{abc_object.name}" - abc_objects.append(abc_object) - # rename the abc container with namespace - abc_container_name = f"{namespace}:{name}" - abc_container.name = abc_container_name - abc_objects.append(abc_container) - - return containerise( - name, abc_objects, context, - namespace, loader=self.__class__.__name__ - ) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node = rt.GetNodeByName(container["instance_node"]) - abc_container = [n for n in get_previous_loaded_object(node) - if rt.ClassOf(n) == rt.AlembicContainer] - with maintained_selection(): - rt.Select(abc_container) - - for alembic in rt.Selection: - abc = rt.GetNodeByName(alembic.name) - rt.Select(abc.Children) - for abc_con in abc.Children: - abc_con.source = path - rt.Select(abc_con.Children) - for abc_obj in abc_con.Children: - abc_obj.source = path - lib.imprint( - container["instance_node"], - {"representation": repre_entity["id"]}, - ) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) - - - @staticmethod - def get_container_children(parent, type_name): - from pymxs import runtime as rt - - def list_children(node): - children = [] - for c in node.Children: - children.append(c) - children += list_children(c) - return children - - filtered = [] - for child in list_children(parent): - class_type = str(rt.classOf(child.baseObject)) - if class_type == type_name: - filtered.append(child) - - return filtered diff --git a/server_addon/max/client/ayon_max/plugins/load/load_pointcache_ornatrix.py b/server_addon/max/client/ayon_max/plugins/load/load_pointcache_ornatrix.py deleted file mode 100644 index bc997951c1..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_pointcache_ornatrix.py +++ /dev/null @@ -1,111 +0,0 @@ -import os -from ayon_core.pipeline import load, get_representation_path -from ayon_core.pipeline.load import LoadError -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) - -from ayon_max.api.lib import ( - unique_namespace, - get_namespace, - object_transform_set, - get_plugins -) -from ayon_max.api import lib -from pymxs import runtime as rt - - -class OxAbcLoader(load.LoaderPlugin): - """Ornatrix Alembic loader.""" - - product_types = {"camera", "animation", "pointcache"} - label = "Load Alembic with Ornatrix" - representations = {"abc"} - order = -10 - icon = "code-fork" - color = "orange" - postfix = "param" - - def load(self, context, name=None, namespace=None, data=None): - plugin_list = get_plugins() - if "ephere.plugins.autodesk.max.ornatrix.dlo" not in plugin_list: - raise LoadError("Ornatrix plugin not " - "found/installed in Max yet..") - - file_path = os.path.normpath(self.filepath_from_context(context)) - rt.AlembicImport.ImportToRoot = True - rt.AlembicImport.CustomAttributes = True - rt.importFile( - file_path, rt.name("noPrompt"), - using=rt.Ornatrix_Alembic_Importer) - - scene_object = [] - for obj in rt.rootNode.Children: - obj_type = rt.ClassOf(obj) - if str(obj_type).startswith("Ox_"): - scene_object.append(obj) - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - abc_container = [] - for abc in scene_object: - abc.name = f"{namespace}:{abc.name}" - abc_container.append(abc) - - return containerise( - name, abc_container, context, - namespace, loader=self.__class__.__name__ - ) - - def update(self, container, context): - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node_name = container["instance_node"] - namespace, name = get_namespace(node_name) - node = rt.getNodeByName(node_name) - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - selections = rt.getCurrentSelection() - transform_data = object_transform_set(selections) - for prev_obj in selections: - if rt.isValidNode(prev_obj): - rt.Delete(prev_obj) - - rt.AlembicImport.ImportToRoot = False - rt.AlembicImport.CustomAttributes = True - rt.importFile( - path, rt.name("noPrompt"), - using=rt.Ornatrix_Alembic_Importer) - - scene_object = [] - for obj in rt.rootNode.Children: - obj_type = rt.ClassOf(obj) - if str(obj_type).startswith("Ox_"): - scene_object.append(obj) - ox_abc_objects = [] - for abc in scene_object: - abc.Parent = container - abc.name = f"{namespace}:{abc.name}" - ox_abc_objects.append(abc) - ox_transform = f"{abc}.transform" - if ox_transform in transform_data.keys(): - abc.pos = transform_data[ox_transform] or 0 - abc.scale = transform_data[f"{abc}.scale"] or 0 - update_custom_attribute_data(node, ox_abc_objects) - lib.imprint( - container["instance_node"], - {"representation": repre_entity["id"]}, - ) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_pointcloud.py b/server_addon/max/client/ayon_max/plugins/load/load_pointcloud.py deleted file mode 100644 index 0fb506d5bd..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_pointcloud.py +++ /dev/null @@ -1,69 +0,0 @@ -import os - -from ayon_max.api import lib, maintained_selection -from ayon_max.api.lib import ( - unique_namespace, - -) -from ayon_max.api.pipeline import ( - containerise, - get_previous_loaded_object, - update_custom_attribute_data, - remove_container_data -) -from ayon_core.pipeline import get_representation_path, load - - -class PointCloudLoader(load.LoaderPlugin): - """Point Cloud Loader.""" - - product_types = {"pointcloud"} - representations = {"prt"} - order = -8 - icon = "code-fork" - color = "green" - postfix = "param" - - def load(self, context, name=None, namespace=None, data=None): - """load point cloud by tyCache""" - from pymxs import runtime as rt - filepath = os.path.normpath(self.filepath_from_context(context)) - obj = rt.tyCache() - obj.filename = filepath - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - obj.name = f"{namespace}:{obj.name}" - - return containerise( - name, [obj], context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - """update the container""" - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node = rt.GetNodeByName(container["instance_node"]) - node_list = get_previous_loaded_object(node) - update_custom_attribute_data( - node, node_list) - with maintained_selection(): - rt.Select(node_list) - for prt in rt.Selection: - prt.filename = path - lib.imprint(container["instance_node"], { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - """remove the container""" - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/load/load_redshift_proxy.py b/server_addon/max/client/ayon_max/plugins/load/load_redshift_proxy.py deleted file mode 100644 index 3fd84b7538..0000000000 --- a/server_addon/max/client/ayon_max/plugins/load/load_redshift_proxy.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -import clique - -from ayon_core.pipeline import ( - load, - get_representation_path -) -from ayon_core.pipeline.load import LoadError -from ayon_max.api.pipeline import ( - containerise, - update_custom_attribute_data, - get_previous_loaded_object, - remove_container_data -) -from ayon_max.api import lib -from ayon_max.api.lib import ( - unique_namespace, - get_plugins -) - - -class RedshiftProxyLoader(load.LoaderPlugin): - """Load rs files with Redshift Proxy""" - - label = "Load Redshift Proxy" - product_types = {"redshiftproxy"} - representations = {"rs"} - order = -9 - icon = "code-fork" - color = "white" - - def load(self, context, name=None, namespace=None, data=None): - from pymxs import runtime as rt - plugin_info = get_plugins() - if "redshift4max.dlr" not in plugin_info: - raise LoadError("Redshift not loaded/installed in Max..") - filepath = self.filepath_from_context(context) - rs_proxy = rt.RedshiftProxy() - rs_proxy.file = filepath - files_in_folder = os.listdir(os.path.dirname(filepath)) - collections, remainder = clique.assemble(files_in_folder) - if collections: - rs_proxy.is_sequence = True - - namespace = unique_namespace( - name + "_", - suffix="_", - ) - rs_proxy.name = f"{namespace}:{rs_proxy.name}" - - return containerise( - name, [rs_proxy], context, - namespace, loader=self.__class__.__name__) - - def update(self, container, context): - from pymxs import runtime as rt - - repre_entity = context["representation"] - path = get_representation_path(repre_entity) - node = rt.getNodeByName(container["instance_node"]) - node_list = get_previous_loaded_object(node) - rt.Select(node_list) - update_custom_attribute_data( - node, rt.Selection) - for proxy in rt.Selection: - proxy.file = path - - lib.imprint(container["instance_node"], { - "representation": repre_entity["id"] - }) - - def switch(self, container, context): - self.update(container, context) - - def remove(self, container): - from pymxs import runtime as rt - node = rt.GetNodeByName(container["instance_node"]) - remove_container_data(node) diff --git a/server_addon/max/client/ayon_max/plugins/publish/collect_current_file.py b/server_addon/max/client/ayon_max/plugins/publish/collect_current_file.py deleted file mode 100644 index 6f8b8dda4b..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/collect_current_file.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import pyblish.api - -from pymxs import runtime as rt - - -class CollectCurrentFile(pyblish.api.ContextPlugin): - """Inject the current working file.""" - - order = pyblish.api.CollectorOrder - 0.5 - label = "Max Current File" - hosts = ['max'] - - def process(self, context): - """Inject the current working file""" - folder = rt.maxFilePath - file = rt.maxFileName - if not folder or not file: - self.log.error("Scene is not saved.") - current_file = os.path.join(folder, file) - - context.data["currentFile"] = current_file - self.log.debug("Scene path: {}".format(current_file)) diff --git a/server_addon/max/client/ayon_max/plugins/publish/collect_render.py b/server_addon/max/client/ayon_max/plugins/publish/collect_render.py deleted file mode 100644 index a5e8d65df2..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/collect_render.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collect Render""" -import os -import pyblish.api - -from pymxs import runtime as rt -from ayon_core.pipeline.publish import KnownPublishError -from ayon_max.api import colorspace -from ayon_max.api.lib import get_max_version, get_current_renderer -from ayon_max.api.lib_rendersettings import RenderSettings -from ayon_max.api.lib_renderproducts import RenderProducts - - -class CollectRender(pyblish.api.InstancePlugin): - """Collect Render for Deadline""" - - order = pyblish.api.CollectorOrder + 0.02 - label = "Collect 3dsmax Render Layers" - hosts = ['max'] - families = ["maxrender"] - - def process(self, instance): - context = instance.context - folder = rt.maxFilePath - file = rt.maxFileName - current_file = os.path.join(folder, file) - filepath = current_file.replace("\\", "/") - context.data['currentFile'] = current_file - - files_by_aov = RenderProducts().get_beauty(instance.name) - aovs = RenderProducts().get_aovs(instance.name) - files_by_aov.update(aovs) - - camera = rt.viewport.GetCamera() - if instance.data.get("members"): - camera_list = [member for member in instance.data["members"] - if rt.ClassOf(member) == rt.Camera.Classes] - if camera_list: - camera = camera_list[-1] - - instance.data["cameras"] = [camera.name] if camera else None # noqa - - if instance.data.get("multiCamera"): - cameras = instance.data.get("members") - if not cameras: - raise KnownPublishError("There should be at least" - " one renderable camera in container") - sel_cam = [ - c.name for c in cameras - if rt.classOf(c) in rt.Camera.classes] - container_name = instance.data.get("instance_node") - render_dir = os.path.dirname(rt.rendOutputFilename) - outputs = RenderSettings().batch_render_layer( - container_name, render_dir, sel_cam - ) - - instance.data["cameras"] = sel_cam - - files_by_aov = RenderProducts().get_multiple_beauty( - outputs, sel_cam) - aovs = RenderProducts().get_multiple_aovs( - outputs, sel_cam) - files_by_aov.update(aovs) - - if "expectedFiles" not in instance.data: - instance.data["expectedFiles"] = list() - instance.data["files"] = list() - instance.data["expectedFiles"].append(files_by_aov) - instance.data["files"].append(files_by_aov) - - img_format = RenderProducts().image_format() - # OCIO config not support in - # most of the 3dsmax renderers - # so this is currently hard coded - # TODO: add options for redshift/vray ocio config - instance.data["colorspaceConfig"] = "" - instance.data["colorspaceDisplay"] = "sRGB" - instance.data["colorspaceView"] = "ACES 1.0 SDR-video" - - if int(get_max_version()) >= 2024: - colorspace_mgr = rt.ColorPipelineMgr # noqa - display = next( - (display for display in colorspace_mgr.GetDisplayList())) - view_transform = next( - (view for view in colorspace_mgr.GetViewList(display))) - instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath - instance.data["colorspaceDisplay"] = display - instance.data["colorspaceView"] = view_transform - - instance.data["renderProducts"] = colorspace.ARenderProduct() - instance.data["publishJobState"] = "Suspended" - instance.data["attachTo"] = [] - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - product_type = "maxrender" - # also need to get the render dir for conversion - data = { - "folderPath": instance.data["folderPath"], - "productName": str(instance.name), - "publish": True, - "maxversion": str(get_max_version()), - "imageFormat": img_format, - "productType": product_type, - "family": product_type, - "families": [product_type], - "renderer": renderer, - "source": filepath, - "plugin": "3dsmax", - "frameStart": instance.data["frameStartHandle"], - "frameEnd": instance.data["frameEndHandle"], - "farm": True - } - instance.data.update(data) - - # TODO: this should be unified with maya and its "multipart" flag - # on instance. - if renderer == "Redshift_Renderer": - instance.data.update( - {"separateAovFiles": rt.Execute( - "renderers.current.separateAovFiles")}) - - self.log.info("data: {0}".format(data)) diff --git a/server_addon/max/client/ayon_max/plugins/publish/collect_review.py b/server_addon/max/client/ayon_max/plugins/publish/collect_review.py deleted file mode 100644 index 321aa7439c..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/collect_review.py +++ /dev/null @@ -1,153 +0,0 @@ -# dont forget getting the focal length for burnin -"""Collect Review""" -import pyblish.api - -from pymxs import runtime as rt -from ayon_core.lib import BoolDef -from ayon_max.api.lib import get_max_version -from ayon_core.pipeline.publish import ( - AYONPyblishPluginMixin, - KnownPublishError -) - - -class CollectReview(pyblish.api.InstancePlugin, - AYONPyblishPluginMixin): - """Collect Review Data for Preview Animation""" - - order = pyblish.api.CollectorOrder + 0.02 - label = "Collect Review Data" - hosts = ['max'] - families = ["review"] - - def process(self, instance): - nodes = instance.data["members"] - - def is_camera(node): - is_camera_class = rt.classOf(node) in rt.Camera.classes - return is_camera_class and rt.isProperty(node, "fov") - - # Use first camera in instance - cameras = [node for node in nodes if is_camera(node)] - if cameras: - if len(cameras) > 1: - self.log.warning( - "Found more than one camera in instance, using first " - f"one found: {cameras[0]}" - ) - camera = cameras[0] - camera_name = camera.name - focal_length = camera.fov - else: - raise KnownPublishError( - "Unable to find a valid camera in 'Review' container." - " Only native max Camera supported. " - f"Found objects: {nodes}" - ) - creator_attrs = instance.data["creator_attributes"] - attr_values = self.get_attr_values_from_data(instance.data) - - general_preview_data = { - "review_camera": camera_name, - "frameStart": instance.data["frameStartHandle"], - "frameEnd": instance.data["frameEndHandle"], - "percentSize": creator_attrs["percentSize"], - "imageFormat": creator_attrs["imageFormat"], - "keepImages": creator_attrs["keepImages"], - "fps": instance.context.data["fps"], - "review_width": creator_attrs["review_width"], - "review_height": creator_attrs["review_height"], - } - - if int(get_max_version()) >= 2024: - colorspace_mgr = rt.ColorPipelineMgr # noqa - display = next( - (display for display in colorspace_mgr.GetDisplayList())) - view_transform = next( - (view for view in colorspace_mgr.GetViewList(display))) - instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath - instance.data["colorspaceDisplay"] = display - instance.data["colorspaceView"] = view_transform - - preview_data = { - "vpStyle": creator_attrs["visualStyleMode"], - "vpPreset": creator_attrs["viewportPreset"], - "vpTextures": creator_attrs["vpTexture"], - "dspGeometry": attr_values.get("dspGeometry"), - "dspShapes": attr_values.get("dspShapes"), - "dspLights": attr_values.get("dspLights"), - "dspCameras": attr_values.get("dspCameras"), - "dspHelpers": attr_values.get("dspHelpers"), - "dspParticles": attr_values.get("dspParticles"), - "dspBones": attr_values.get("dspBones"), - "dspBkg": attr_values.get("dspBkg"), - "dspGrid": attr_values.get("dspGrid"), - "dspSafeFrame": attr_values.get("dspSafeFrame"), - "dspFrameNums": attr_values.get("dspFrameNums") - } - else: - general_viewport = { - "dspBkg": attr_values.get("dspBkg"), - "dspGrid": attr_values.get("dspGrid") - } - nitrous_manager = { - "AntialiasingQuality": creator_attrs["antialiasingQuality"], - } - nitrous_viewport = { - "VisualStyleMode": creator_attrs["visualStyleMode"], - "ViewportPreset": creator_attrs["viewportPreset"], - "UseTextureEnabled": creator_attrs["vpTexture"] - } - preview_data = { - "general_viewport": general_viewport, - "nitrous_manager": nitrous_manager, - "nitrous_viewport": nitrous_viewport, - "vp_btn_mgr": {"EnableButtons": False} - } - - # Enable ftrack functionality - instance.data.setdefault("families", []).append('ftrack') - - burnin_members = instance.data.setdefault("burninDataMembers", {}) - burnin_members["focalLength"] = focal_length - - instance.data.update(general_preview_data) - instance.data["viewport_options"] = preview_data - - @classmethod - def get_attribute_defs(cls): - return [ - BoolDef("dspGeometry", - label="Geometry", - default=True), - BoolDef("dspShapes", - label="Shapes", - default=False), - BoolDef("dspLights", - label="Lights", - default=False), - BoolDef("dspCameras", - label="Cameras", - default=False), - BoolDef("dspHelpers", - label="Helpers", - default=False), - BoolDef("dspParticles", - label="Particle Systems", - default=True), - BoolDef("dspBones", - label="Bone Objects", - default=False), - BoolDef("dspBkg", - label="Background", - default=True), - BoolDef("dspGrid", - label="Active Grid", - default=False), - BoolDef("dspSafeFrame", - label="Safe Frames", - default=False), - BoolDef("dspFrameNums", - label="Frame Numbers", - default=False) - ] diff --git a/server_addon/max/client/ayon_max/plugins/publish/collect_workfile.py b/server_addon/max/client/ayon_max/plugins/publish/collect_workfile.py deleted file mode 100644 index 6eec0f7292..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/collect_workfile.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collect current work file.""" -import os -import pyblish.api - -from pymxs import runtime as rt - - -class CollectWorkfile(pyblish.api.InstancePlugin): - """Inject the current working file into context""" - - order = pyblish.api.CollectorOrder - 0.01 - label = "Collect 3dsmax Workfile" - hosts = ['max'] - families = ["workfile"] - - def process(self, instance): - """Inject the current working file.""" - context = instance.context - folder = rt.maxFilePath - file = rt.maxFileName - if not folder or not file: - self.log.error("Scene is not saved.") - ext = os.path.splitext(file)[-1].lstrip(".") - - data = {} - - data.update({ - "setMembers": context.data["currentFile"], - "frameStart": context.data["frameStart"], - "frameEnd": context.data["frameEnd"], - "handleStart": context.data["handleStart"], - "handleEnd": context.data["handleEnd"] - }) - - data["representations"] = [{ - "name": ext, - "ext": ext, - "files": file, - "stagingDir": folder, - }] - - instance.data.update(data) - self.log.debug("Collected data: {}".format(data)) - self.log.debug("Collected instance: {}".format(file)) - self.log.debug("staging Dir: {}".format(folder)) diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_alembic.py b/server_addon/max/client/ayon_max/plugins/publish/extract_alembic.py deleted file mode 100644 index b0999e5a78..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_alembic.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Export alembic file. - -Note: - Parameters on AlembicExport (AlembicExport.Parameter): - - ParticleAsMesh (bool): Sets whether particle shapes are exported - as meshes. - AnimTimeRange (enum): How animation is saved: - #CurrentFrame: saves current frame - #TimeSlider: saves the active time segments on time slider (default) - #StartEnd: saves a range specified by the Step - StartFrame (int) - EnFrame (int) - ShapeSuffix (bool): When set to true, appends the string "Shape" to the - name of each exported mesh. This property is set to false by default. - SamplesPerFrame (int): Sets the number of animation samples per frame. - Hidden (bool): When true, export hidden geometry. - UVs (bool): When true, export the mesh UV map channel. - Normals (bool): When true, export the mesh normals. - VertexColors (bool): When true, export the mesh vertex color map 0 and the - current vertex color display data when it differs - ExtraChannels (bool): When true, export the mesh extra map channels - (map channels greater than channel 1) - Velocity (bool): When true, export the meh vertex and particle velocity - data. - MaterialIDs (bool): When true, export the mesh material ID as - Alembic face sets. - Visibility (bool): When true, export the node visibility data. - LayerName (bool): When true, export the node layer name as an Alembic - object property. - MaterialName (bool): When true, export the geometry node material name as - an Alembic object property - ObjectID (bool): When true, export the geometry node g-buffer object ID as - an Alembic object property. - CustomAttributes (bool): When true, export the node and its modifiers - custom attributes into an Alembic object compound property. -""" -import os -import pyblish.api -from ayon_core.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt -from ayon_max.api import maintained_selection -from ayon_max.api.lib import suspended_refresh -from ayon_core.lib import BoolDef - - -class ExtractAlembic(publish.Extractor, - OptionalPyblishPluginMixin): - order = pyblish.api.ExtractorOrder - label = "Extract Pointcache" - hosts = ["max"] - families = ["pointcache"] - optional = True - active = True - - def process(self, instance): - 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) - - 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"] = [] - - representation = { - "name": "abc", - "ext": "abc", - "files": file_name, - "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): - defs = super(ExtractAlembic, cls).get_attribute_defs() - defs.extend([ - BoolDef("custom_attrs", - label="Custom Attributes", - default=False), - ]) - return defs - - -class ExtractCameraAlembic(ExtractAlembic): - """Extract Camera with AlembicExport.""" - label = "Extract Alembic Camera" - families = ["camera"] - optional = True - - -class ExtractModelAlembic(ExtractAlembic): - """Extract Geometry in Alembic Format""" - label = "Extract Geometry (Alembic)" - families = ["model"] - optional = True - - 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/server_addon/max/client/ayon_max/plugins/publish/extract_fbx.py b/server_addon/max/client/ayon_max/plugins/publish/extract_fbx.py deleted file mode 100644 index bdfc1d0d78..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_fbx.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt -from ayon_max.api import maintained_selection -from ayon_max.api.lib import convert_unit_scale - - -class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Geometry in FBX Format - """ - - order = pyblish.api.ExtractorOrder - 0.05 - label = "Extract FBX" - hosts = ["max"] - families = ["model"] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - - stagingdir = self.staging_dir(instance) - filename = "{name}.fbx".format(**instance.data) - filepath = os.path.join(stagingdir, filename) - self._set_fbx_attributes() - - 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.FBXEXP, - ) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "fbx", - "ext": "fbx", - "files": filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, filepath) - ) - - def _set_fbx_attributes(self): - unit_scale = convert_unit_scale() - rt.FBXExporterSetParam("Animation", False) - rt.FBXExporterSetParam("Cameras", False) - rt.FBXExporterSetParam("Lights", False) - rt.FBXExporterSetParam("PointCache", False) - rt.FBXExporterSetParam("AxisConversionMethod", "Animation") - rt.FBXExporterSetParam("UpAxis", "Y") - rt.FBXExporterSetParam("Preserveinstances", True) - if unit_scale: - rt.FBXExporterSetParam("ConvertUnit", unit_scale) - - -class ExtractCameraFbx(ExtractModelFbx): - """Extract Camera with FbxExporter.""" - - order = pyblish.api.ExtractorOrder - 0.2 - label = "Extract Fbx Camera" - families = ["camera"] - optional = True - - def _set_fbx_attributes(self): - unit_scale = convert_unit_scale() - rt.FBXExporterSetParam("Animation", True) - rt.FBXExporterSetParam("Cameras", True) - rt.FBXExporterSetParam("AxisConversionMethod", "Animation") - rt.FBXExporterSetParam("UpAxis", "Y") - rt.FBXExporterSetParam("Preserveinstances", True) - if unit_scale: - rt.FBXExporterSetParam("ConvertUnit", unit_scale) diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_max_scene_raw.py b/server_addon/max/client/ayon_max/plugins/publish/extract_max_scene_raw.py deleted file mode 100644 index ecde6d2ce9..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_max_scene_raw.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt - - -class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Raw Max Scene with SaveSelected - """ - - order = pyblish.api.ExtractorOrder - 0.2 - label = "Extract Max Scene (Raw)" - hosts = ["max"] - families = ["camera", "maxScene", "model"] - optional = True - - settings_category = "max" - - def process(self, instance): - if not self.is_active(instance.data): - return - - # publish the raw scene for camera - self.log.debug("Extracting Raw Max Scene ...") - - stagingdir = self.staging_dir(instance) - filename = "{name}.max".format(**instance.data) - - max_path = os.path.join(stagingdir, filename) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - nodes = instance.data["members"] - rt.saveNodes(nodes, max_path, quiet=True) - - self.log.info("Performing Extraction ...") - - representation = { - "name": "max", - "ext": "max", - "files": filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, max_path) - ) diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_model_obj.py b/server_addon/max/client/ayon_max/plugins/publish/extract_model_obj.py deleted file mode 100644 index 6556bd7809..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_model_obj.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt -from ayon_max.api import maintained_selection -from ayon_max.api.lib import suspended_refresh -from ayon_core.pipeline.publish import KnownPublishError - - -class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Geometry in OBJ Format - """ - - order = pyblish.api.ExtractorOrder - 0.05 - label = "Extract OBJ" - hosts = ["max"] - families = ["model"] - optional = True - - settings_category = "max" - - def process(self, instance): - if not self.is_active(instance.data): - return - - stagingdir = self.staging_dir(instance) - filename = "{name}.obj".format(**instance.data) - filepath = os.path.join(stagingdir, filename) - - 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.") - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "obj", - "ext": "obj", - "files": filename, - "stagingDir": stagingdir, - } - - instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, filepath) - ) diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_model_usd.py b/server_addon/max/client/ayon_max/plugins/publish/extract_model_usd.py deleted file mode 100644 index a48126c6e5..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_model_usd.py +++ /dev/null @@ -1,94 +0,0 @@ -import os - -import pyblish.api -from pymxs import runtime as rt - -from ayon_max.api import maintained_selection -from ayon_core.pipeline import OptionalPyblishPluginMixin, publish - - -class ExtractModelUSD(publish.Extractor, - OptionalPyblishPluginMixin): - """Extract Geometry in USDA Format.""" - - order = pyblish.api.ExtractorOrder - 0.05 - label = "Extract Geometry (USD)" - hosts = ["max"] - families = ["model"] - optional = True - - settings_category = "max" - - def process(self, instance): - if not self.is_active(instance.data): - return - - self.log.info("Extracting Geometry ...") - - stagingdir = self.staging_dir(instance) - asset_filename = "{name}.usda".format(**instance.data) - asset_filepath = os.path.join(stagingdir, - asset_filename) - self.log.info(f"Writing USD '{asset_filepath}' to '{stagingdir}'") - - log_filename = "{name}.txt".format(**instance.data) - log_filepath = os.path.join(stagingdir, - log_filename) - self.log.info(f"Writing log '{log_filepath}' to '{stagingdir}'") - - # get the nodes which need to be exported - export_options = self.get_export_options(log_filepath) - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.USDExporter.ExportFile(asset_filepath, - exportOptions=export_options, - contentSource=rt.Name("selected"), - nodeList=node_list) - - self.log.info("Performing Extraction ...") - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'usda', - 'ext': 'usda', - 'files': asset_filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - - log_representation = { - 'name': 'txt', - 'ext': 'txt', - 'files': log_filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(log_representation) - - self.log.info( - f"Extracted instance '{instance.name}' to: {asset_filepath}") - - @staticmethod - def get_export_options(log_path): - """Set Export Options for USD Exporter""" - - export_options = rt.USDExporter.createOptions() - - export_options.Meshes = True - export_options.Shapes = False - export_options.Lights = False - export_options.Cameras = False - export_options.Materials = False - export_options.MeshFormat = rt.Name('fromScene') - export_options.FileFormat = rt.Name('ascii') - export_options.UpAxis = rt.Name('y') - export_options.LogLevel = rt.Name('info') - export_options.LogPath = log_path - export_options.PreserveEdgeOrientation = True - export_options.TimeMode = rt.Name('current') - - rt.USDexporter.UIOptions = export_options - - return export_options diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_redshift_proxy.py b/server_addon/max/client/ayon_max/plugins/publish/extract_redshift_proxy.py deleted file mode 100644 index dfb3527be1..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_redshift_proxy.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish -from pymxs import runtime as rt -from ayon_max.api import maintained_selection - - -class ExtractRedshiftProxy(publish.Extractor): - """ - Extract Redshift Proxy with rsProxy - """ - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract RedShift Proxy" - hosts = ["max"] - families = ["redshiftproxy"] - - def process(self, instance): - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.debug("Extracting Redshift Proxy...") - stagingdir = self.staging_dir(instance) - rs_filename = "{name}.rs".format(**instance.data) - rs_filepath = os.path.join(stagingdir, rs_filename) - rs_filepath = rs_filepath.replace("\\", "/") - - rs_filenames = self.get_rsfiles(instance, start, end) - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - # Redshift rsProxy command - # rsProxy fp selected compress connectivity startFrame endFrame - # camera warnExisting transformPivotToOrigin - rt.rsProxy(rs_filepath, 1, 0, 0, start, end, 0, 1, 1) - - self.log.info("Performing Extraction ...") - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'rs', - 'ext': 'rs', - 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], # noqa - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - stagingdir)) - - def get_rsfiles(self, instance, startFrame, endFrame): - rs_filenames = [] - rs_name = instance.data["name"] - for frame in range(startFrame, endFrame + 1): - rs_filename = "%s.%04d.rs" % (rs_name, frame) - rs_filenames.append(rs_filename) - - return rs_filenames diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_review_animation.py b/server_addon/max/client/ayon_max/plugins/publish/extract_review_animation.py deleted file mode 100644 index b6397d404e..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_review_animation.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish -from ayon_max.api.preview_animation import ( - render_preview_animation -) - - -class ExtractReviewAnimation(publish.Extractor): - """ - Extract Review by Review Animation - """ - - order = pyblish.api.ExtractorOrder + 0.001 - label = "Extract Review Animation" - hosts = ["max"] - families = ["review"] - - def process(self, instance): - staging_dir = self.staging_dir(instance) - ext = instance.data.get("imageFormat") - start = int(instance.data["frameStart"]) - end = int(instance.data["frameEnd"]) - filepath = os.path.join(staging_dir, instance.name) - self.log.debug( - "Writing Review Animation to '{}'".format(filepath)) - - review_camera = instance.data["review_camera"] - viewport_options = instance.data.get("viewport_options", {}) - files = render_preview_animation( - filepath, - ext, - review_camera, - start, - end, - percentSize=instance.data["percentSize"], - width=instance.data["review_width"], - height=instance.data["review_height"], - viewport_options=viewport_options) - - filenames = [os.path.basename(path) for path in files] - - tags = ["review"] - if not instance.data.get("keepImages"): - tags.append("delete") - - self.log.debug("Performing Extraction ...") - - representation = { - "name": instance.data["imageFormat"], - "ext": instance.data["imageFormat"], - "files": filenames, - "stagingDir": staging_dir, - "frameStart": instance.data["frameStartHandle"], - "frameEnd": instance.data["frameEndHandle"], - "tags": tags, - "preview": True, - "camera_name": review_camera - } - self.log.debug(f"{representation}") - - if "representations" not in instance.data: - instance.data["representations"] = [] - instance.data["representations"].append(representation) diff --git a/server_addon/max/client/ayon_max/plugins/publish/extract_thumbnail.py b/server_addon/max/client/ayon_max/plugins/publish/extract_thumbnail.py deleted file mode 100644 index 183e381be2..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/extract_thumbnail.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import pyblish.api -from ayon_core.pipeline import publish -from ayon_max.api.preview_animation import render_preview_animation - - -class ExtractThumbnail(publish.Extractor): - """Extract Thumbnail for Review - """ - - order = pyblish.api.ExtractorOrder - label = "Extract Thumbnail" - hosts = ["max"] - families = ["review"] - - def process(self, instance): - ext = instance.data.get("imageFormat") - frame = int(instance.data["frameStart"]) - staging_dir = self.staging_dir(instance) - filepath = os.path.join( - staging_dir, f"{instance.name}_thumbnail") - self.log.debug("Writing Thumbnail to '{}'".format(filepath)) - - review_camera = instance.data["review_camera"] - viewport_options = instance.data.get("viewport_options", {}) - files = render_preview_animation( - filepath, - ext, - review_camera, - start_frame=frame, - end_frame=frame, - percentSize=instance.data["percentSize"], - width=instance.data["review_width"], - height=instance.data["review_height"], - viewport_options=viewport_options) - - thumbnail = next(os.path.basename(path) for path in files) - - representation = { - "name": "thumbnail", - "ext": ext, - "files": thumbnail, - "stagingDir": staging_dir, - "thumbnail": True - } - - self.log.debug(f"{representation}") - - if "representations" not in instance.data: - instance.data["representations"] = [] - instance.data["representations"].append(representation) diff --git a/server_addon/max/client/ayon_max/plugins/publish/help/validate_model_name.xml b/server_addon/max/client/ayon_max/plugins/publish/help/validate_model_name.xml deleted file mode 100644 index e41146910a..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/help/validate_model_name.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - -Invalid Model Name -## Nodes found with Invalid Model Name - -Nodes were detected in your scene which have invalid model name which does not -match the regex you preset in AYON setting. -### How to repair? -Make sure the model name aligns with validation regex in your AYON setting. - - - -### Invalid nodes - -{nodes} - - -### How could this happen? - -This often happens if you have mesh with the model naming does not match -with regex in the setting. - - - - \ No newline at end of file diff --git a/server_addon/max/client/ayon_max/plugins/publish/increment_workfile_version.py b/server_addon/max/client/ayon_max/plugins/publish/increment_workfile_version.py deleted file mode 100644 index c7c3f49626..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/increment_workfile_version.py +++ /dev/null @@ -1,19 +0,0 @@ -import pyblish.api -from ayon_core.lib import version_up -from pymxs import runtime as rt - - -class IncrementWorkfileVersion(pyblish.api.ContextPlugin): - """Increment current workfile version.""" - - order = pyblish.api.IntegratorOrder + 0.9 - label = "Increment Workfile Version" - hosts = ["max"] - families = ["maxrender", "workfile"] - - def process(self, context): - path = context.data["currentFile"] - filepath = version_up(path) - - rt.saveMaxFile(filepath) - self.log.info("Incrementing file version") diff --git a/server_addon/max/client/ayon_max/plugins/publish/save_scene.py b/server_addon/max/client/ayon_max/plugins/publish/save_scene.py deleted file mode 100644 index fe2c7f50f4..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/save_scene.py +++ /dev/null @@ -1,25 +0,0 @@ -import pyblish.api -from ayon_core.pipeline import registered_host - - -class SaveCurrentScene(pyblish.api.InstancePlugin): - """Save current scene""" - - label = "Save current file" - order = pyblish.api.ExtractorOrder - 0.49 - hosts = ["max"] - families = ["maxrender", "workfile"] - - def process(self, instance): - host = registered_host() - current_file = host.get_current_workfile() - - assert instance.context.data["currentFile"] == current_file - if instance.data["productType"] == "maxrender": - host.save_workfile(current_file) - - elif host.workfile_has_unsaved_changes(): - self.log.info(f"Saving current file: {current_file}") - host.save_workfile(current_file) - else: - self.log.debug("No unsaved changes, skipping file save..") \ No newline at end of file diff --git a/server_addon/max/client/ayon_max/plugins/publish/save_scenes_for_cameras.py b/server_addon/max/client/ayon_max/plugins/publish/save_scenes_for_cameras.py deleted file mode 100644 index a211210550..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/save_scenes_for_cameras.py +++ /dev/null @@ -1,105 +0,0 @@ -import pyblish.api -import os -import sys -import tempfile - -from pymxs import runtime as rt -from ayon_core.lib import run_subprocess -from ayon_max.api.lib_rendersettings import RenderSettings -from ayon_max.api.lib_renderproducts import RenderProducts - - -class SaveScenesForCamera(pyblish.api.InstancePlugin): - """Save scene files for multiple cameras without - editing the original scene before deadline submission - - """ - - label = "Save Scene files for cameras" - order = pyblish.api.ExtractorOrder - 0.48 - hosts = ["max"] - families = ["maxrender"] - - def process(self, instance): - if not instance.data.get("multiCamera"): - self.log.debug( - "Multi Camera disabled. " - "Skipping to save scene files for cameras") - return - current_folder = rt.maxFilePath - current_filename = rt.maxFileName - current_filepath = os.path.join(current_folder, current_filename) - camera_scene_files = [] - scripts = [] - filename, ext = os.path.splitext(current_filename) - fmt = RenderProducts().image_format() - cameras = instance.data.get("cameras") - if not cameras: - return - new_folder = f"{current_folder}_{filename}" - os.makedirs(new_folder, exist_ok=True) - for camera in cameras: - new_output = RenderSettings().get_batch_render_output(camera) # noqa - new_output = new_output.replace("\\", "/") - new_filename = f"{filename}_{camera}{ext}" - new_filepath = os.path.join(new_folder, new_filename) - new_filepath = new_filepath.replace("\\", "/") - camera_scene_files.append(new_filepath) - RenderSettings().batch_render_elements(camera) - rt.rendOutputFilename = new_output - rt.saveMaxFile(current_filepath) - script = (""" -from pymxs import runtime as rt -import os -filename = "{filename}" -new_filepath = "{new_filepath}" -new_output = "{new_output}" -camera = "{camera}" -rt.rendOutputFilename = new_output -directory = os.path.dirname(rt.rendOutputFilename) -directory = os.path.join(directory, filename) -render_elem = rt.maxOps.GetCurRenderElementMgr() -render_elem_num = render_elem.NumRenderElements() -if render_elem_num > 0: - ext = "{ext}" - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = f"{{directory}}_{camera}_{{renderpass}}..{ext}" - render_elem.SetRenderElementFileName(i, aov_name) -rt.saveMaxFile(new_filepath) - """).format(filename=instance.name, - new_filepath=new_filepath, - new_output=new_output, - camera=camera, - ext=fmt) - scripts.append(script) - - maxbatch_exe = os.path.join( - os.path.dirname(sys.executable), "3dsmaxbatch") - maxbatch_exe = maxbatch_exe.replace("\\", "/") - if sys.platform == "windows": - maxbatch_exe += ".exe" - maxbatch_exe = os.path.normpath(maxbatch_exe) - with tempfile.TemporaryDirectory() as tmp_dir_name: - tmp_script_path = os.path.join( - tmp_dir_name, "extract_scene_files.py") - self.log.info("Using script file: {}".format(tmp_script_path)) - - with open(tmp_script_path, "wt") as tmp: - for script in scripts: - tmp.write(script + "\n") - - try: - current_filepath = current_filepath.replace("\\", "/") - tmp_script_path = tmp_script_path.replace("\\", "/") - run_subprocess([maxbatch_exe, tmp_script_path, - "-sceneFile", current_filepath]) - except RuntimeError: - self.log.debug("Checking the scene files existing") - - for camera_scene in camera_scene_files: - if not os.path.exists(camera_scene): - self.log.error("Camera scene files not existed yet!") - raise RuntimeError("MaxBatch.exe doesn't run as expected") - self.log.debug(f"Found Camera scene:{camera_scene}") diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_attributes.py b/server_addon/max/client/ayon_max/plugins/publish/validate_attributes.py deleted file mode 100644 index a489533b2c..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_attributes.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validator for Attributes.""" -import json - -from pyblish.api import ContextPlugin, ValidatorOrder -from pymxs import runtime as rt - -from ayon_core.pipeline.publish import ( - OptionalPyblishPluginMixin, - PublishValidationError, - RepairContextAction -) - - -def has_property(object_name, property_name): - """Return whether an object has a property with given name""" - return rt.Execute(f'isProperty {object_name} "{property_name}"') - - -def is_matching_value(object_name, property_name, value): - """Return whether an existing property matches value `value""" - property_value = rt.Execute(f"{object_name}.{property_name}") - - # Wrap property value if value is a string valued attributes - # starting with a `#` - if ( - isinstance(value, str) and - value.startswith("#") and - not value.endswith(")") - ): - # prefix value with `#` - # not applicable for #() array value type - # and only applicable for enum i.e. #bob, #sally - property_value = f"#{property_value}" - - return property_value == value - - -class ValidateAttributes(OptionalPyblishPluginMixin, - ContextPlugin): - """Validates attributes in the project setting are consistent - with the nodes from MaxWrapper Class in 3ds max. - E.g. "renderers.current.separateAovFiles", - "renderers.production.PrimaryGIEngine" - Admin(s) need to put the dict below and enable this validator for a check: - { - "renderers.current":{ - "separateAovFiles" : True - }, - "renderers.production":{ - "PrimaryGIEngine": "#RS_GIENGINE_BRUTE_FORCE" - } - .... - } - - """ - - order = ValidatorOrder - hosts = ["max"] - label = "Attributes" - actions = [RepairContextAction] - optional = True - - settings_category = "max" - - @classmethod - def get_invalid(cls, context): - attributes = json.loads( - context.data - ["project_settings"] - ["max"] - ["publish"] - ["ValidateAttributes"] - ["attributes"] - ) - if not attributes: - return - invalid = [] - for object_name, required_properties in attributes.items(): - if not rt.Execute(f"isValidValue {object_name}"): - # Skip checking if the node does not - # exist in MaxWrapper Class - cls.log.debug(f"Unable to find '{object_name}'." - " Skipping validation of attributes.") - continue - - for property_name, value in required_properties.items(): - if not has_property(object_name, property_name): - cls.log.error( - "Non-existing property: " - f"{object_name}.{property_name}") - invalid.append((object_name, property_name)) - - if not is_matching_value(object_name, property_name, value): - cls.log.error( - f"Invalid value for: {object_name}.{property_name}" - f" should be: {value}") - invalid.append((object_name, property_name)) - - return invalid - - def process(self, context): - if not self.is_active(context.data): - self.log.debug("Skipping Validate Attributes...") - return - invalid_attributes = self.get_invalid(context) - if invalid_attributes: - bullet_point_invalid_statement = "\n".join( - "- {}".format(invalid) for invalid - in invalid_attributes - ) - report = ( - "Required Attribute(s) have invalid value(s).\n\n" - f"{bullet_point_invalid_statement}\n\n" - "You can use repair action to fix them if they are not\n" - "unknown property value(s)." - ) - raise PublishValidationError( - report, title="Invalid Value(s) for Required Attribute(s)") - - @classmethod - def repair(cls, context): - attributes = json.loads( - context.data - ["project_settings"] - ["max"] - ["publish"] - ["ValidateAttributes"] - ["attributes"] - ) - invalid_attributes = cls.get_invalid(context) - for attrs in invalid_attributes: - prop, attr = attrs - value = attributes[prop][attr] - if isinstance(value, str) and not value.startswith("#"): - attribute_fix = '{}.{}="{}"'.format( - prop, attr, value - ) - else: - attribute_fix = "{}.{}={}".format( - prop, attr, value - ) - rt.Execute(attribute_fix) diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_camera_attributes.py b/server_addon/max/client/ayon_max/plugins/publish/validate_camera_attributes.py deleted file mode 100644 index 63a2ef39a7..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_camera_attributes.py +++ /dev/null @@ -1,90 +0,0 @@ -import pyblish.api -from pymxs import runtime as rt - -from ayon_core.pipeline.publish import ( - RepairAction, - OptionalPyblishPluginMixin, - PublishValidationError -) -from ayon_max.api.action import SelectInvalidAction - - -class ValidateCameraAttributes(OptionalPyblishPluginMixin, - pyblish.api.InstancePlugin): - """Validates Camera has no invalid attribute properties - or values.(For 3dsMax Cameras only) - - """ - - order = pyblish.api.ValidatorOrder - families = ['camera'] - hosts = ['max'] - label = 'Validate Camera Attributes' - actions = [SelectInvalidAction, RepairAction] - optional = True - - settings_category = "max" - - DEFAULTS = ["fov", "nearrange", "farrange", - "nearclip", "farclip"] - CAM_TYPE = ["Freecamera", "Targetcamera", - "Physical"] - - @classmethod - def get_invalid(cls, instance): - invalid = [] - if rt.units.DisplayType != rt.Name("Generic"): - cls.log.warning( - "Generic Type is not used as a scene unit\n\n" - "sure you tweak the settings with your own values\n\n" - "before validation.") - cameras = instance.data["members"] - project_settings = instance.context.data["project_settings"].get("max") - cam_attr_settings = ( - project_settings["publish"]["ValidateCameraAttributes"] - ) - for camera in cameras: - if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: - cls.log.debug( - "Skipping camera created from external plugin..") - continue - for attr in cls.DEFAULTS: - default_value = cam_attr_settings.get(attr) - if default_value == float(0): - cls.log.debug( - f"the value of {attr} in setting set to" - " zero. Skipping the check.") - continue - if round(rt.getProperty(camera, attr), 1) != default_value: - cls.log.error( - f"Invalid attribute value for {camera.name}:{attr} " - f"(should be: {default_value}))") - invalid.append(camera) - - return invalid - - def process(self, instance): - if not self.is_active(instance.data): - self.log.debug("Skipping Validate Camera Attributes.") - return - invalid = self.get_invalid(instance) - - if invalid: - raise PublishValidationError( - "Invalid camera attributes found. See log.") - - @classmethod - def repair(cls, instance): - invalid_cameras = cls.get_invalid(instance) - project_settings = instance.context.data["project_settings"].get("max") - cam_attr_settings = ( - project_settings["publish"]["ValidateCameraAttributes"] - ) - for camera in invalid_cameras: - for attr in cls.DEFAULTS: - expected_value = cam_attr_settings.get(attr) - if expected_value == float(0): - cls.log.debug( - f"the value of {attr} in setting set to zero.") - continue - rt.setProperty(camera, attr, expected_value) diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_camera_contents.py b/server_addon/max/client/ayon_max/plugins/publish/validate_camera_contents.py deleted file mode 100644 index 334e7dcec9..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_camera_contents.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api - -from ayon_core.pipeline import PublishValidationError - - -class ValidateCameraContent(pyblish.api.InstancePlugin): - """Validates Camera instance contents. - - A Camera instance may only hold a SINGLE camera's transform - """ - - order = pyblish.api.ValidatorOrder - families = ["camera", "review"] - hosts = ["max"] - label = "Camera Contents" - camera_type = ["$Free_Camera", "$Target_Camera", - "$Physical_Camera", "$Target"] - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError(("Camera instance must only include" - "camera (and camera target). " - f"Invalid content {invalid}")) - - def get_invalid(self, instance): - """ - Get invalid nodes if the instance is not camera - """ - invalid = [] - container = instance.data["instance_node"] - self.log.info(f"Validating camera content for {container}") - - selection_list = instance.data["members"] - for sel in selection_list: - # to avoid Attribute Error from pymxs wrapper - sel_tmp = str(sel) - found = any(sel_tmp.startswith(cam) for cam in self.camera_type) - if not found: - self.log.error("Camera not found") - invalid.append(sel) - return invalid diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_extended_viewport.py b/server_addon/max/client/ayon_max/plugins/publish/validate_extended_viewport.py deleted file mode 100644 index ed476ec874..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_extended_viewport.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import PublishValidationError -from pymxs import runtime as rt - - -class ValidateExtendedViewport(pyblish.api.ContextPlugin): - """Validate if the first viewport is an extended viewport.""" - - order = pyblish.api.ValidatorOrder - families = ["review"] - hosts = ["max"] - label = "Validate Extended Viewport" - - def process(self, context): - try: - rt.viewport.activeViewportEx(1) - except RuntimeError: - raise PublishValidationError( - "Please make sure one viewport is not an extended viewport", - description = ( - "Please make sure at least one viewport is not an " - "extended viewport but a 3dsmax supported viewport " - "i.e camera/persp/orthographic view.\n\n" - "To rectify it, please go to view in the top menubar, " - "go to Views -> Viewports Configuration -> Layout and " - "right click on one of the panels to change it." - )) - diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_instance_has_members.py b/server_addon/max/client/ayon_max/plugins/publish/validate_instance_has_members.py deleted file mode 100644 index 552e9ea0e2..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_instance_has_members.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import PublishValidationError - - -class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): - """Validates Instance has members. - - Check if MaxScene containers includes any contents underneath. - """ - - order = pyblish.api.ValidatorOrder - families = ["camera", - "model", - "maxScene", - "review", - "pointcache", - "pointcloud", - "redshiftproxy"] - hosts = ["max"] - label = "Container Contents" - - def process(self, instance): - if not instance.data["members"]: - raise PublishValidationError("No content found in the container") diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_instance_in_context.py b/server_addon/max/client/ayon_max/plugins/publish/validate_instance_in_context.py deleted file mode 100644 index d5bdfe4eb0..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_instance_in_context.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate if instance context is the same as current context.""" -import pyblish.api -from ayon_core.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError, - OptionalPyblishPluginMixin -) -from ayon_max.api.action import SelectInvalidAction -from pymxs import runtime as rt - - -class ValidateInstanceInContext(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validator to check if instance context match current context. - - When working in per-shot style you always publish data in context of - current context (shot). This validator checks if this is so. It is optional - so it can be disabled when needed. - - Action on this validator will select invalid instances. - """ - order = ValidateContentsOrder - label = "Instance in same Context" - optional = True - hosts = ["max"] - actions = [SelectInvalidAction, RepairAction] - - settings_category = "max" - - def process(self, instance): - if not self.is_active(instance.data): - return - - folderPath = instance.data.get("folderPath") - task = instance.data.get("task") - context = self.get_context(instance) - if (folderPath, task) != context: - context_label = "{} > {}".format(*context) - instance_label = "{} > {}".format(folderPath, task) - message = ( - "Instance '{}' publishes to different context(folder or task) " - "than current context: {}. Current context: {}".format( - instance.name, instance_label, context_label - ) - ) - raise PublishValidationError( - message=message, - description=( - "## Publishing to a different context data(folder or task)\n" - "There are publish instances present which are publishing " - "into a different folder path or task than your current context.\n\n" - "Usually this is not what you want but there can be cases " - "where you might want to publish into another context or " - "shot. If that's the case you can disable the validation " - "on the instance to ignore it." - ) - ) - - @classmethod - def get_invalid(cls, instance): - invalid = [] - folderPath = instance.data.get("folderPath") - task = instance.data.get("task") - context = cls.get_context(instance) - if (folderPath, task) != context: - invalid.append(rt.getNodeByName(instance.name)) - return invalid - - @classmethod - def repair(cls, instance): - context_asset = instance.context.data["folderPath"] - context_task = instance.context.data["task"] - instance_node = rt.getNodeByName(instance.data.get( - "instance_node", "")) - if not instance_node: - return - rt.SetUserProp(instance_node, "folderPath", context_asset) - rt.SetUserProp(instance_node, "task", context_task) - - @staticmethod - def get_context(instance): - """Return asset, task from publishing context data""" - context = instance.context - return context.data["folderPath"], context.data["task"] diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_loaded_plugin.py b/server_addon/max/client/ayon_max/plugins/publish/validate_loaded_plugin.py deleted file mode 100644 index 1fddc7998d..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_loaded_plugin.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validator for Loaded Plugin.""" -import os -import pyblish.api -from pymxs import runtime as rt - -from ayon_core.pipeline.publish import ( - RepairAction, - OptionalPyblishPluginMixin, - PublishValidationError -) -from ayon_max.api.lib import get_plugins - - -class ValidateLoadedPlugin(OptionalPyblishPluginMixin, - pyblish.api.InstancePlugin): - """Validates if the specific plugin is loaded in 3ds max. - Studio Admin(s) can add the plugins they want to check in validation - via studio defined project settings - """ - - order = pyblish.api.ValidatorOrder - hosts = ["max"] - label = "Validate Loaded Plugins" - optional = True - actions = [RepairAction] - - settings_category = "max" - - family_plugins_mapping = [] - - @classmethod - def get_invalid(cls, instance): - """Plugin entry point.""" - family_plugins_mapping = cls.family_plugins_mapping - if not family_plugins_mapping: - return - - # Backward compatibility - settings did have 'product_types' - if "product_types" in family_plugins_mapping: - family_plugins_mapping["families"] = family_plugins_mapping.pop( - "product_types" - ) - - invalid = [] - # Find all plug-in requirements for current instance - instance_families = {instance.data["productType"]} - instance_families.update(instance.data.get("families", [])) - cls.log.debug("Checking plug-in validation " - f"for instance families: {instance_families}") - all_required_plugins = set() - - for mapping in family_plugins_mapping: - # Check for matching families - if not mapping: - return - - match_families = { - fam.strip() for fam in mapping["families"] - } - has_match = "*" in match_families or match_families.intersection( - instance_families) - - if not has_match: - continue - - cls.log.debug( - f"Found plug-in family requirements: {match_families}") - required_plugins = [ - # match lowercase and format with os.environ to allow - # plugin names defined by max version, e.g. {3DSMAX_VERSION} - plugin.format(**os.environ).lower() - for plugin in mapping["plugins"] - # ignore empty fields in settings - if plugin.strip() - ] - - all_required_plugins.update(required_plugins) - - if not all_required_plugins: - # Instance has no plug-in requirements - return - - # get all DLL loaded plugins in Max and their plugin index - available_plugins = { - plugin_name.lower(): index for index, plugin_name in enumerate( - get_plugins()) - } - # validate the required plug-ins - for plugin in sorted(all_required_plugins): - plugin_index = available_plugins.get(plugin) - if plugin_index is None: - debug_msg = ( - f"Plugin {plugin} does not exist" - " in 3dsMax Plugin List." - ) - invalid.append((plugin, debug_msg)) - continue - if not rt.pluginManager.isPluginDllLoaded(plugin_index): - debug_msg = f"Plugin {plugin} not loaded." - invalid.append((plugin, debug_msg)) - return invalid - - def process(self, instance): - if not self.is_active(instance.data): - self.log.debug("Skipping Validate Loaded Plugin...") - return - invalid = self.get_invalid(instance) - if invalid: - bullet_point_invalid_statement = "\n".join( - "- {}".format(message) for _, message in invalid - ) - report = ( - "Required plugins are not loaded.\n\n" - f"{bullet_point_invalid_statement}\n\n" - "You can use repair action to load the plugin." - ) - raise PublishValidationError( - report, title="Missing Required Plugins") - - @classmethod - def repair(cls, instance): - # get all DLL loaded plugins in Max and their plugin index - invalid = cls.get_invalid(instance) - if not invalid: - return - - # get all DLL loaded plugins in Max and their plugin index - available_plugins = { - plugin_name.lower(): index for index, plugin_name in enumerate( - get_plugins()) - } - - for invalid_plugin, _ in invalid: - plugin_index = available_plugins.get(invalid_plugin) - - if plugin_index is None: - cls.log.warning( - f"Can't enable missing plugin: {invalid_plugin}") - continue - - if not rt.pluginManager.isPluginDllLoaded(plugin_index): - rt.pluginManager.loadPluginDll(plugin_index) diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_mesh_has_uv.py b/server_addon/max/client/ayon_max/plugins/publish/validate_mesh_has_uv.py deleted file mode 100644 index 31143a60c0..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_mesh_has_uv.py +++ /dev/null @@ -1,62 +0,0 @@ - -import pyblish.api -from ayon_max.api.action import SelectInvalidAction -from ayon_core.pipeline.publish import ( - ValidateMeshOrder, - OptionalPyblishPluginMixin, - PublishValidationError -) -from pymxs import runtime as rt - - -class ValidateMeshHasUVs(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - - """Validate the current mesh has UVs. - - This validator only checks if the mesh has UVs but not - whether all the individual faces of the mesh have UVs. - - It validates whether the current mesh has texture vertices. - If the mesh does not have texture vertices, it does not - have UVs in Max. - - """ - - order = ValidateMeshOrder - hosts = ['max'] - families = ['model'] - label = 'Validate Mesh Has UVs' - actions = [SelectInvalidAction] - optional = True - - settings_category = "max" - - @classmethod - def get_invalid(cls, instance): - meshes = [member for member in instance.data["members"] - if rt.isProperty(member, "mesh")] - invalid = [member for member in meshes - if member.mesh.numTVerts == 0] - return invalid - - def process(self, instance): - if not self.is_active(instance.data): - return - invalid = self.get_invalid(instance) - if invalid: - bullet_point_invalid_statement = "\n".join( - "- {}".format(invalid.name) for invalid - in invalid - ) - report = ( - "Model meshes are required to have UVs.\n\n" - "Meshes detected with invalid or missing UVs:\n" - f"{bullet_point_invalid_statement}\n" - ) - raise PublishValidationError( - report, - description=( - "Model meshes are required to have UVs.\n\n" - "Meshes detected with no texture vertice or missing UVs"), - title="Non-mesh objects found or mesh has missing UVs") diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_model_contents.py b/server_addon/max/client/ayon_max/plugins/publish/validate_model_contents.py deleted file mode 100644 index 9a4d988aa4..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_model_contents.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from pymxs import runtime as rt - -from ayon_core.pipeline import PublishValidationError - - -class ValidateModelContent(pyblish.api.InstancePlugin): - """Validates Model instance contents. - - A model instance may only hold either geometry-related - object(excluding Shapes) or editable meshes. - """ - - order = pyblish.api.ValidatorOrder - families = ["model"] - hosts = ["max"] - label = "Model Contents" - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError(("Model instance must only include" - "Geometry and Editable Mesh. " - f"Invalid types on: {invalid}")) - - def get_invalid(self, instance): - """ - Get invalid nodes if the instance is not camera - """ - invalid = [] - container = instance.data["instance_node"] - self.log.info(f"Validating model content for {container}") - - selection_list = instance.data["members"] - for sel in selection_list: - if rt.ClassOf(sel) in rt.Camera.classes: - invalid.append(sel) - if rt.ClassOf(sel) in rt.Light.classes: - invalid.append(sel) - if rt.ClassOf(sel) in rt.Shape.classes: - invalid.append(sel) - - return invalid diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_model_name.py b/server_addon/max/client/ayon_max/plugins/publish/validate_model_name.py deleted file mode 100644 index d691b739b7..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_model_name.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate model nodes names.""" -import re - -import pyblish.api - -from ayon_max.api.action import SelectInvalidAction - -from ayon_core.pipeline.publish import ( - OptionalPyblishPluginMixin, - PublishXmlValidationError, - ValidateContentsOrder -) - -class ValidateModelName(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate Model Name. - - Validation regex is `(.*)_(?P.*)_(GEO)` by default. - The setting supports the following regex group name: - - project - - asset - - subset - - Examples: - `{SOME_RANDOM_NAME}_{YOUR_SUBSET_NAME}_GEO` should be your - default model name. - The regex of `(?P.*)` can be replaced by `(?P.*)` - and `(?P.*)`. - `(.*)_(?P.*)_(GEO)` check if your model name is - `{SOME_RANDOM_NAME}_{CURRENT_ASSET_NAME}_GEO` - `(.*)_(?P.*)_(GEO)` check if your model name is - `{SOME_RANDOM_NAME}_{CURRENT_PROJECT_NAME}_GEO` - - """ - optional = True - order = ValidateContentsOrder - hosts = ["max"] - families = ["model"] - label = "Validate Model Name" - actions = [SelectInvalidAction] - - settings_category = "max" - - # defined by settings - regex = r"(.*)_(?P.*)_(GEO)" - # cache - regex_compiled = None - - def process(self, instance): - if not self.is_active(instance.data): - return - - invalid = self.get_invalid(instance) - if invalid: - names = "\n".join( - "- {}".format(node.name) for node in invalid - ) - raise PublishXmlValidationError( - plugin=self, - message="Nodes found with invalid model names: {}".format(invalid), - formatting_data={"nodes": names} - ) - - @classmethod - def get_invalid(cls, instance): - if not cls.regex: - cls.log.warning("No regex pattern set. Nothing to validate.") - return - - members = instance.data.get("members") - if not members: - cls.log.error("No members found in the instance.") - return - - cls.regex_compiled = re.compile(cls.regex) - - invalid = [] - for obj in members: - if cls.invalid_name(instance, obj): - invalid.append(obj) - return invalid - - @classmethod - def invalid_name(cls, instance, obj): - """Function to check the object has invalid name - regarding to the validation regex in the AYON setttings - - Args: - instance (pyblish.api.instance): Instance - obj (str): object name - - Returns: - str: invalid object - """ - regex = cls.regex_compiled - name = obj.name - match = regex.match(name) - - if match is None: - cls.log.error("Invalid model name on: %s", name) - cls.log.error("Name doesn't match regex {}".format(regex.pattern)) - return obj - - # Validate regex groups - invalid = False - compare = { - "project": instance.context.data["projectName"], - "asset": instance.data["folderPath"], - "subset": instance.data["productName"] - } - for key, required_value in compare.items(): - if key in regex.groupindex: - if match.group(key) != required_value: - cls.log.error( - "Invalid %s name for the model %s, " - "required name is %s", - key, name, required_value - ) - invalid = True - - if invalid: - return obj diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_no_animation.py b/server_addon/max/client/ayon_max/plugins/publish/validate_no_animation.py deleted file mode 100644 index 26384954ca..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_no_animation.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from pymxs import runtime as rt -from ayon_core.pipeline import ( - PublishValidationError, - OptionalPyblishPluginMixin -) -from ayon_max.api.action import SelectInvalidAction - - -def get_invalid_keys(obj): - """function to check on whether there is keyframe in - - Args: - obj (str): object needed to check if there is a keyframe - - Returns: - bool: whether invalid object(s) exist - """ - for transform in ["Position", "Rotation", "Scale"]: - num_of_key = rt.NumKeys(rt.getPropertyController( - obj.controller, transform)) - if num_of_key > 0: - return True - return False - - -class ValidateNoAnimation(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validates No Animation - - Ensure no keyframes on nodes in the Instance - """ - - order = pyblish.api.ValidatorOrder - families = ["model"] - hosts = ["max"] - optional = True - label = "Validate No Animation" - actions = [SelectInvalidAction] - - settings_category = "max" - - def process(self, instance): - if not self.is_active(instance.data): - return - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - "Keyframes found on:\n\n{0}".format(invalid) - , - title="Keyframes on model" - ) - - @staticmethod - def get_invalid(instance): - """Get invalid object(s) which have keyframe(s) - - - Args: - instance (pyblish.api.instance): Instance - - Returns: - list: list of invalid objects - """ - invalid = [invalid for invalid in instance.data["members"] - if invalid.isAnimated or get_invalid_keys(invalid)] - - return invalid diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_pointcloud.py b/server_addon/max/client/ayon_max/plugins/publish/validate_pointcloud.py deleted file mode 100644 index 73b18984ed..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_pointcloud.py +++ /dev/null @@ -1,126 +0,0 @@ -import pyblish.api -from ayon_core.pipeline import PublishValidationError -from pymxs import runtime as rt - - -class ValidatePointCloud(pyblish.api.InstancePlugin): - """Validate that work file was saved.""" - - order = pyblish.api.ValidatorOrder - families = ["pointcloud"] - hosts = ["max"] - label = "Validate Point Cloud" - - def process(self, instance): - """ - Notes: - 1. Validate if the export mode of Export Particle is at PRT format - 2. Validate the partition count and range set as default value - Partition Count : 100 - Partition Range : 1 to 1 - 3. Validate if the custom attribute(s) exist as parameter(s) - of export_particle operator - - """ - report = [] - - if self.validate_export_mode(instance): - report.append("The export mode is not at PRT") - - if self.validate_partition_value(instance): - report.append(("tyFlow Partition setting is " - "not at the default value")) - - invalid_attribute = self.validate_custom_attribute(instance) - if invalid_attribute: - report.append(("Custom Attribute not found " - f":{invalid_attribute}")) - - if report: - raise PublishValidationError(f"{report}") - - def validate_custom_attribute(self, instance): - invalid = [] - container = instance.data["instance_node"] - self.log.info( - f"Validating tyFlow custom attributes for {container}") - - selection_list = instance.data["members"] - - project_settings = instance.context.data["project_settings"] - attr_settings = project_settings["max"]["PointCloud"]["attribute"] - for sel in selection_list: - obj = sel.baseobject - anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: - # get all the names of the related tyFlow nodes - sub_anim = rt.GetSubAnim(obj, anim_name) - if rt.IsProperty(sub_anim, "Export_Particles"): - event_name = sub_anim.name - opt = "${0}.{1}.export_particles".format(sel.name, - event_name) - for attr in attr_settings: - key = attr["name"] - value = attr["value"] - custom_attr = "{0}.PRTChannels_{1}".format(opt, - value) - try: - rt.Execute(custom_attr) - except RuntimeError: - invalid.append(key) - - return invalid - - def validate_partition_value(self, instance): - invalid = [] - container = instance.data["instance_node"] - self.log.info( - f"Validating tyFlow partition value for {container}") - - selection_list = instance.data["members"] - for sel in selection_list: - obj = sel.baseobject - anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: - # get all the names of the related tyFlow nodes - sub_anim = rt.GetSubAnim(obj, anim_name) - if rt.IsProperty(sub_anim, "Export_Particles"): - event_name = sub_anim.name - opt = "${0}.{1}.export_particles".format(sel.name, - event_name) - count = rt.Execute(f'{opt}.PRTPartitionsCount') - if count != 100: - invalid.append(count) - start = rt.Execute(f'{opt}.PRTPartitionsFrom') - if start != 1: - invalid.append(start) - end = rt.Execute(f'{opt}.PRTPartitionsTo') - if end != 1: - invalid.append(end) - - return invalid - - def validate_export_mode(self, instance): - invalid = [] - container = instance.data["instance_node"] - self.log.info( - f"Validating tyFlow export mode for {container}") - - con = rt.GetNodeByName(container) - selection_list = list(con.Children) - for sel in selection_list: - obj = sel.baseobject - anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: - # get all the names of the related tyFlow nodes - sub_anim = rt.GetSubAnim(obj, anim_name) - # check if there is export particle operator - boolean = rt.IsProperty(sub_anim, "Export_Particles") - event_name = sub_anim.name - if boolean: - opt = f"${sel.name}.{event_name}.export_particles" - export_mode = rt.Execute(f'{opt}.exportMode') - if export_mode != 1: - invalid.append(export_mode) - - return invalid diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_renderable_camera.py b/server_addon/max/client/ayon_max/plugins/publish/validate_renderable_camera.py deleted file mode 100644 index dc05771e1b..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_renderable_camera.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import ( - PublishValidationError, - OptionalPyblishPluginMixin) -from ayon_core.pipeline.publish import RepairAction -from ayon_max.api.lib import get_current_renderer - -from pymxs import runtime as rt - - -class ValidateRenderableCamera(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validates Renderable Camera - - Check if the renderable camera used for rendering - """ - - order = pyblish.api.ValidatorOrder - families = ["maxrender"] - hosts = ["max"] - label = "Renderable Camera" - optional = True - actions = [RepairAction] - - def process(self, instance): - if not self.is_active(instance.data): - return - if not instance.data["cameras"]: - raise PublishValidationError( - "No renderable Camera found in scene." - ) - - @classmethod - def repair(cls, instance): - - rt.viewport.setType(rt.Name("view_camera")) - camera = rt.viewport.GetCamera() - cls.log.info(f"Camera {camera} set as renderable camera") - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - if renderer == "Arnold": - arv = rt.MAXToAOps.ArnoldRenderView() - arv.setOption("Camera", str(camera)) - arv.close() - instance.data["cameras"] = [camera.name] diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_renderer_redshift_proxy.py b/server_addon/max/client/ayon_max/plugins/publish/validate_renderer_redshift_proxy.py deleted file mode 100644 index 66c69bc100..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_renderer_redshift_proxy.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import PublishValidationError -from pymxs import runtime as rt -from ayon_core.pipeline.publish import RepairAction -from ayon_max.api.lib import get_current_renderer - - -class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): - """ - Validates Redshift as the current renderer for creating - Redshift Proxy - """ - - order = pyblish.api.ValidatorOrder - families = ["redshiftproxy"] - hosts = ["max"] - label = "Redshift Renderer" - actions = [RepairAction] - - def process(self, instance): - invalid = self.get_redshift_renderer(instance) - if invalid: - raise PublishValidationError("Please install Redshift for 3dsMax" - " before using the Redshift proxy instance") # noqa - invalid = self.get_current_renderer(instance) - if invalid: - raise PublishValidationError("The Redshift proxy extraction" - "discontinued since the current renderer is not Redshift") # noqa - - def get_redshift_renderer(self, instance): - invalid = list() - max_renderers_list = str(rt.RendererClass.classes) - if "Redshift_Renderer" not in max_renderers_list: - invalid.append(max_renderers_list) - - return invalid - - def get_current_renderer(self, instance): - invalid = list() - renderer_class = get_current_renderer() - current_renderer = str(renderer_class).split(":")[0] - if current_renderer != "Redshift_Renderer": - invalid.append(current_renderer) - - return invalid - - @classmethod - def repair(cls, instance): - for Renderer in rt.RendererClass.classes: - renderer = Renderer() - if "Redshift_Renderer" in str(renderer): - rt.renderers.production = renderer - break diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_renderpasses.py b/server_addon/max/client/ayon_max/plugins/publish/validate_renderpasses.py deleted file mode 100644 index d0d47c6340..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_renderpasses.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -import pyblish.api -from pymxs import runtime as rt -from ayon_core.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError, - OptionalPyblishPluginMixin -) -from ayon_max.api.lib_rendersettings import RenderSettings - - -class ValidateRenderPasses(OptionalPyblishPluginMixin, - pyblish.api.InstancePlugin): - """Validates Render Passes before farm submission - """ - - order = ValidateContentsOrder - families = ["maxrender"] - hosts = ["max"] - label = "Validate Render Passes" - actions = [RepairAction] - - settings_category = "max" - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - bullet_point_invalid_statement = "\n".join( - f"- {err_type}: {filepath}" for err_type, filepath - in invalid - ) - report = ( - "Invalid render passes found.\n\n" - f"{bullet_point_invalid_statement}\n\n" - "You can use repair action to fix the invalid filepath." - ) - raise PublishValidationError( - report, title="Invalid Render Passes") - - @classmethod - def get_invalid(cls, instance): - """Function to get invalid beauty render outputs and - render elements. - - 1. Check Render Output Folder matches the name of - the current Max Scene, e.g. - The name of the current Max scene: - John_Doe.max - The expected render output directory: - {root[work]}/{project[name]}/{hierarchy}/{asset}/ - work/{task[name]}/render/3dsmax/John_Doe/ - - 2. Check image extension(s) of the render output(s) - matches the image format in OP/AYON setting, e.g. - The current image format in settings: png - The expected render outputs: John_Doe.png - - 3. Check filename of render element ends with the name of - render element from the 3dsMax Render Element Manager. - e.g. The name of render element: RsCryptomatte - The expected filename: {InstanceName}_RsCryptomatte.png - - Args: - instance (pyblish.api.Instance): instance - workfile_name (str): filename of the Max scene - - Returns: - list: list of invalid filename which doesn't match - with the project name - """ - invalid = [] - file = rt.maxFileName - workfile_name, ext = os.path.splitext(file) - if workfile_name not in rt.rendOutputFilename: - cls.log.error( - "Render output folder must include" - f" the max scene name {workfile_name} " - ) - invalid_folder_name = os.path.dirname( - rt.rendOutputFilename).replace( - "\\", "/").split("/")[-1] - invalid.append(("Invalid Render Output Folder", - invalid_folder_name)) - beauty_fname = os.path.basename(rt.rendOutputFilename) - beauty_name, ext = os.path.splitext(beauty_fname) - invalid_filenames = cls.get_invalid_filenames( - instance, beauty_name) - invalid.extend(invalid_filenames) - invalid_image_format = cls.get_invalid_image_format( - instance, ext.lstrip(".")) - invalid.extend(invalid_image_format) - renderer = instance.data["renderer"] - if renderer in [ - "ART_Renderer", - "Redshift_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - renderpass = str(renderlayer_name).rsplit(":", 1)[-1] - rend_file = render_elem.GetRenderElementFilename(i) - if not rend_file: - continue - - rend_fname, ext = os.path.splitext( - os.path.basename(rend_file)) - invalid_filenames = cls.get_invalid_filenames( - instance, rend_fname, renderpass=renderpass) - invalid.extend(invalid_filenames) - invalid_image_format = cls.get_invalid_image_format( - instance, ext) - invalid.extend(invalid_image_format) - elif renderer == "Arnold": - cls.log.debug( - "Renderpass validation does not support Arnold yet," - " validation skipped...") - else: - cls.log.debug( - "Skipping render element validation " - f"for renderer: {renderer}") - return invalid - - @classmethod - def get_invalid_filenames(cls, instance, file_name, renderpass=None): - """Function to get invalid filenames from render outputs. - - Args: - instance (pyblish.api.Instance): instance - file_name (str): name of the file - renderpass (str, optional): name of the renderpass. - Defaults to None. - - Returns: - list: invalid filenames - """ - invalid = [] - if instance.name not in file_name: - cls.log.error("The renderpass filename should contain the instance name.") - invalid.append(("Invalid instance name", - file_name)) - if renderpass is not None: - if not file_name.rstrip(".").endswith(renderpass): - cls.log.error( - f"Filename for {renderpass} should " - f"end with {renderpass}: {file_name}" - ) - invalid.append((f"Invalid {renderpass}", - os.path.basename(file_name))) - return invalid - - @classmethod - def get_invalid_image_format(cls, instance, ext): - """Function to check if the image format of the render outputs - aligns with that in the setting. - - Args: - instance (pyblish.api.Instance): instance - ext (str): image extension - - Returns: - list: list of files with invalid image format - """ - invalid = [] - settings = instance.context.data["project_settings"].get("max") - image_format = settings["RenderSettings"]["image_format"] - ext = ext.lstrip(".") - if ext != image_format: - msg = ( - f"Invalid image format {ext} for render outputs.\n" - f"Should be: {image_format}") - cls.log.error(msg) - invalid.append((msg, ext)) - return invalid - - @classmethod - def repair(cls, instance): - container = instance.data.get("instance_node") - # TODO: need to rename the function of render_output - RenderSettings().render_output(container) - cls.log.debug("Finished repairing the render output " - "folder and filenames.") diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_resolution_setting.py b/server_addon/max/client/ayon_max/plugins/publish/validate_resolution_setting.py deleted file mode 100644 index 9f7ec17dd9..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_resolution_setting.py +++ /dev/null @@ -1,92 +0,0 @@ -import pyblish.api -from pymxs import runtime as rt -from ayon_core.pipeline import ( - OptionalPyblishPluginMixin -) -from ayon_core.pipeline.publish import ( - RepairAction, - PublishValidationError -) -from ayon_max.api.lib import ( - reset_scene_resolution, - imprint -) - - -class ValidateResolutionSetting(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate the resolution setting aligned with DB""" - - order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender"] - hosts = ["max"] - label = "Validate Resolution Setting" - optional = True - actions = [RepairAction] - - def process(self, instance): - if not self.is_active(instance.data): - return - width, height = self.get_folder_resolution(instance) - current_width, current_height = ( - self.get_current_resolution(instance) - ) - - if current_width != width and current_height != height: - raise PublishValidationError("Resolution Setting " - "not matching resolution " - "set on asset or shot.") - if current_width != width: - raise PublishValidationError("Width in Resolution Setting " - "not matching resolution set " - "on asset or shot.") - - if current_height != height: - raise PublishValidationError("Height in Resolution Setting " - "not matching resolution set " - "on asset or shot.") - - def get_current_resolution(self, instance): - return rt.renderWidth, rt.renderHeight - - @classmethod - def get_folder_resolution(cls, instance): - task_entity = instance.data.get("taskEntity") - if task_entity: - task_attributes = task_entity["attrib"] - width = task_attributes["resolutionWidth"] - height = task_attributes["resolutionHeight"] - return int(width), int(height) - - # Defaults if not found in folder entity - return 1920, 1080 - - @classmethod - def repair(cls, instance): - reset_scene_resolution() - - -class ValidateReviewResolutionSetting(ValidateResolutionSetting): - families = ["review"] - optional = True - actions = [RepairAction] - - def get_current_resolution(self, instance): - current_width = instance.data["review_width"] - current_height = instance.data["review_height"] - return current_width, current_height - - @classmethod - def repair(cls, instance): - context_width, context_height = ( - cls.get_folder_resolution(instance) - ) - creator_attrs = instance.data["creator_attributes"] - creator_attrs["review_width"] = context_width - creator_attrs["review_height"] = context_height - creator_attrs_data = { - "creator_attributes": creator_attrs - } - # update the width and height of review - # data in creator_attributes - imprint(instance.data["instance_node"], creator_attrs_data) diff --git a/server_addon/max/client/ayon_max/plugins/publish/validate_scene_saved.py b/server_addon/max/client/ayon_max/plugins/publish/validate_scene_saved.py deleted file mode 100644 index 3028a55337..0000000000 --- a/server_addon/max/client/ayon_max/plugins/publish/validate_scene_saved.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import PublishValidationError -from pymxs import runtime as rt - - -class ValidateSceneSaved(pyblish.api.InstancePlugin): - """Validate that workfile was saved.""" - - order = pyblish.api.ValidatorOrder - families = ["workfile"] - hosts = ["max"] - label = "Validate Workfile is saved" - - def process(self, instance): - if not rt.maxFilePath or not rt.maxFileName: - raise PublishValidationError( - "Workfile is not saved", title=self.label) diff --git a/server_addon/max/client/ayon_max/startup/startup.ms b/server_addon/max/client/ayon_max/startup/startup.ms deleted file mode 100644 index c5b4f0e526..0000000000 --- a/server_addon/max/client/ayon_max/startup/startup.ms +++ /dev/null @@ -1,15 +0,0 @@ --- AYON Init Script -( - local sysPath = dotNetClass "System.IO.Path" - local sysDir = dotNetClass "System.IO.Directory" - local localScript = getThisScriptFilename() - local startup = sysPath.Combine (sysPath.GetDirectoryName localScript) "startup.py" - - local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" - systemTools.setEnvVariable "PYTHONPATH" pythonpath - - /*opens the create menu on startup to ensure users are presented with a useful default view.*/ - max create mode - - python.ExecuteFile startup -) diff --git a/server_addon/max/client/ayon_max/startup/startup.py b/server_addon/max/client/ayon_max/startup/startup.py deleted file mode 100644 index 1462cc93b7..0000000000 --- a/server_addon/max/client/ayon_max/startup/startup.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -from ayon_max.api import MaxHost -from ayon_core.pipeline import install_host -# this might happen in some 3dsmax version where PYTHONPATH isn't added -# to sys.path automatically -for path in os.environ["PYTHONPATH"].split(os.pathsep): - if path and path not in sys.path: - sys.path.append(path) - -host = MaxHost() -install_host(host) diff --git a/server_addon/max/client/ayon_max/version.py b/server_addon/max/client/ayon_max/version.py deleted file mode 100644 index acb68bbdfc..0000000000 --- a/server_addon/max/client/ayon_max/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring AYON addon 'max' version.""" -__version__ = "0.2.1" diff --git a/server_addon/max/package.py b/server_addon/max/package.py deleted file mode 100644 index 09e86f8d50..0000000000 --- a/server_addon/max/package.py +++ /dev/null @@ -1,9 +0,0 @@ -name = "max" -title = "Max" -version = "0.2.1" -client_dir = "ayon_max" - -ayon_required_addons = { - "core": ">0.3.2", -} -ayon_compatible_addons = {} diff --git a/server_addon/max/server/__init__.py b/server_addon/max/server/__init__.py deleted file mode 100644 index d03b29d249..0000000000 --- a/server_addon/max/server/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Type - -from ayon_server.addons import BaseServerAddon - -from .settings import MaxSettings, DEFAULT_VALUES - - -class MaxAddon(BaseServerAddon): - settings_model: Type[MaxSettings] = MaxSettings - - async def get_default_settings(self): - settings_model_cls = self.get_settings_model() - return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/max/server/settings/__init__.py b/server_addon/max/server/settings/__init__.py deleted file mode 100644 index 986b1903a5..0000000000 --- a/server_addon/max/server/settings/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .main import ( - MaxSettings, - DEFAULT_VALUES, -) - - -__all__ = ( - "MaxSettings", - "DEFAULT_VALUES", -) diff --git a/server_addon/max/server/settings/create_review_settings.py b/server_addon/max/server/settings/create_review_settings.py deleted file mode 100644 index 807976a391..0000000000 --- a/server_addon/max/server/settings/create_review_settings.py +++ /dev/null @@ -1,91 +0,0 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField - - -def image_format_enum(): - """Return enumerator for image output formats.""" - return [ - {"label": "exr", "value": "exr"}, - {"label": "jpg", "value": "jpg"}, - {"label": "png", "value": "png"}, - {"label": "tga", "value": "tga"} - ] - - -def visual_style_enum(): - """Return enumerator for viewport visual style.""" - return [ - {"label": "Realistic", "value": "Realistic"}, - {"label": "Shaded", "value": "Shaded"}, - {"label": "Facets", "value": "Facets"}, - {"label": "ConsistentColors", - "value": "ConsistentColors"}, - {"label": "Wireframe", "value": "Wireframe"}, - {"label": "BoundingBox", "value": "BoundingBox"}, - {"label": "Ink", "value": "Ink"}, - {"label": "ColorInk", "value": "ColorInk"}, - {"label": "Acrylic", "value": "Acrylic"}, - {"label": "Tech", "value": "Tech"}, - {"label": "Graphite", "value": "Graphite"}, - {"label": "ColorPencil", "value": "ColorPencil"}, - {"label": "Pastel", "value": "Pastel"}, - {"label": "Clay", "value": "Clay"}, - {"label": "ModelAssist", "value": "ModelAssist"} - ] - - -def preview_preset_enum(): - """Return enumerator for viewport visual preset.""" - return [ - {"label": "Quality", "value": "Quality"}, - {"label": "Standard", "value": "Standard"}, - {"label": "Performance", "value": "Performance"}, - {"label": "DXMode", "value": "DXMode"}, - {"label": "Customize", "value": "Customize"}, - ] - - -def anti_aliasing_enum(): - """Return enumerator for viewport anti-aliasing.""" - return [ - {"label": "None", "value": "None"}, - {"label": "2X", "value": "2X"}, - {"label": "4X", "value": "4X"}, - {"label": "8X", "value": "8X"} - ] - - -class CreateReviewModel(BaseSettingsModel): - review_width: int = SettingsField(1920, title="Review Width") - review_height: int = SettingsField(1080, title="Review Height") - percentSize: float = SettingsField(100.0, title="Percent of Output") - keep_images: bool = SettingsField(False, title="Keep Image Sequences") - image_format: str = SettingsField( - enum_resolver=image_format_enum, - title="Image Format Options" - ) - visual_style: str = SettingsField( - enum_resolver=visual_style_enum, - title="Preference" - ) - viewport_preset: str = SettingsField( - enum_resolver=preview_preset_enum, - title="Preview Preset" - ) - anti_aliasing: str = SettingsField( - enum_resolver=anti_aliasing_enum, - title="Anti-aliasing Quality" - ) - vp_texture: bool = SettingsField(True, title="Viewport Texture") - - -DEFAULT_CREATE_REVIEW_SETTINGS = { - "review_width": 1920, - "review_height": 1080, - "percentSize": 100.0, - "keep_images": False, - "image_format": "png", - "visual_style": "Realistic", - "viewport_preset": "Quality", - "anti_aliasing": "None", - "vp_texture": True -} diff --git a/server_addon/max/server/settings/imageio.py b/server_addon/max/server/settings/imageio.py deleted file mode 100644 index 9c6f1b6409..0000000000 --- a/server_addon/max/server/settings/imageio.py +++ /dev/null @@ -1,63 +0,0 @@ -from pydantic import validator -from ayon_server.settings import BaseSettingsModel, SettingsField -from ayon_server.settings.validators import ensure_unique_names - - -class ImageIOConfigModel(BaseSettingsModel): - """[DEPRECATED] Addon OCIO config settings. Please set the OCIO config - path in the Core addon profiles here - (ayon+settings://core/imageio/ocio_config_profiles). - """ - - override_global_config: bool = SettingsField( - False, - title="Override global OCIO config", - description=( - "DEPRECATED functionality. Please set the OCIO config path in the " - "Core addon profiles here (ayon+settings://core/imageio/" - "ocio_config_profiles)." - ), - ) - filepath: list[str] = SettingsField( - default_factory=list, - title="Config path", - description=( - "DEPRECATED functionality. Please set the OCIO config path in the " - "Core addon profiles here (ayon+settings://core/imageio/" - "ocio_config_profiles)." - ), - ) - - -class ImageIOFileRuleModel(BaseSettingsModel): - name: str = SettingsField("", title="Rule name") - pattern: str = SettingsField("", title="Regex pattern") - colorspace: str = SettingsField("", title="Colorspace name") - ext: str = SettingsField("", title="File extension") - - -class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = SettingsField(False) - rules: list[ImageIOFileRuleModel] = SettingsField( - default_factory=list, - title="Rules" - ) - - @validator("rules") - def validate_unique_outputs(cls, value): - ensure_unique_names(value) - return value - - -class ImageIOSettings(BaseSettingsModel): - activate_host_color_management: bool = SettingsField( - True, title="Enable Color Management" - ) - ocio_config: ImageIOConfigModel = SettingsField( - default_factory=ImageIOConfigModel, - title="OCIO config" - ) - file_rules: ImageIOFileRulesModel = SettingsField( - default_factory=ImageIOFileRulesModel, - title="File Rules" - ) diff --git a/server_addon/max/server/settings/main.py b/server_addon/max/server/settings/main.py deleted file mode 100644 index 7b0bfc6421..0000000000 --- a/server_addon/max/server/settings/main.py +++ /dev/null @@ -1,94 +0,0 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField -from .imageio import ImageIOSettings -from .render_settings import ( - RenderSettingsModel, DEFAULT_RENDER_SETTINGS -) -from .create_review_settings import ( - CreateReviewModel, DEFAULT_CREATE_REVIEW_SETTINGS -) -from .publishers import ( - PublishersModel, DEFAULT_PUBLISH_SETTINGS -) - - -def unit_scale_enum(): - """Return enumerator for scene unit scale.""" - return [ - {"label": "mm", "value": "Millimeters"}, - {"label": "cm", "value": "Centimeters"}, - {"label": "m", "value": "Meters"}, - {"label": "km", "value": "Kilometers"} - ] - - -class UnitScaleSettings(BaseSettingsModel): - enabled: bool = SettingsField(True, title="Enabled") - scene_unit_scale: str = SettingsField( - "Centimeters", - title="Scene Unit Scale", - enum_resolver=unit_scale_enum - ) - - -class PRTAttributesModel(BaseSettingsModel): - _layout = "compact" - name: str = SettingsField(title="Name") - value: str = SettingsField(title="Attribute") - - -class PointCloudSettings(BaseSettingsModel): - attribute: list[PRTAttributesModel] = SettingsField( - default_factory=list, title="Channel Attribute") - - -class MaxSettings(BaseSettingsModel): - unit_scale_settings: UnitScaleSettings = SettingsField( - default_factory=UnitScaleSettings, - title="Set Unit Scale" - ) - imageio: ImageIOSettings = SettingsField( - default_factory=ImageIOSettings, - title="Color Management (ImageIO)" - ) - RenderSettings: RenderSettingsModel = SettingsField( - default_factory=RenderSettingsModel, - title="Render Settings" - ) - CreateReview: CreateReviewModel = SettingsField( - default_factory=CreateReviewModel, - title="Create Review" - ) - PointCloud: PointCloudSettings = SettingsField( - default_factory=PointCloudSettings, - title="Point Cloud" - ) - publish: PublishersModel = SettingsField( - default_factory=PublishersModel, - title="Publish Plugins") - - -DEFAULT_VALUES = { - "unit_scale_settings": { - "enabled": True, - "scene_unit_scale": "Centimeters" - }, - "RenderSettings": DEFAULT_RENDER_SETTINGS, - "CreateReview": DEFAULT_CREATE_REVIEW_SETTINGS, - "PointCloud": { - "attribute": [ - {"name": "Age", "value": "age"}, - {"name": "Radius", "value": "radius"}, - {"name": "Position", "value": "position"}, - {"name": "Rotation", "value": "rotation"}, - {"name": "Scale", "value": "scale"}, - {"name": "Velocity", "value": "velocity"}, - {"name": "Color", "value": "color"}, - {"name": "TextureCoordinate", "value": "texcoord"}, - {"name": "MaterialID", "value": "matid"}, - {"name": "custFloats", "value": "custFloats"}, - {"name": "custVecs", "value": "custVecs"}, - ] - }, - "publish": DEFAULT_PUBLISH_SETTINGS - -} diff --git a/server_addon/max/server/settings/render_settings.py b/server_addon/max/server/settings/render_settings.py deleted file mode 100644 index 19d36dd0f8..0000000000 --- a/server_addon/max/server/settings/render_settings.py +++ /dev/null @@ -1,47 +0,0 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField - - -def aov_separators_enum(): - return [ - {"value": "dash", "label": "- (dash)"}, - {"value": "underscore", "label": "_ (underscore)"}, - {"value": "dot", "label": ". (dot)"} - ] - - -def image_format_enum(): - """Return enumerator for image output formats.""" - return [ - {"label": "bmp", "value": "bmp"}, - {"label": "exr", "value": "exr"}, - {"label": "tif", "value": "tif"}, - {"label": "tiff", "value": "tiff"}, - {"label": "jpg", "value": "jpg"}, - {"label": "png", "value": "png"}, - {"label": "tga", "value": "tga"}, - {"label": "dds", "value": "dds"} - ] - - -class RenderSettingsModel(BaseSettingsModel): - default_render_image_folder: str = SettingsField( - title="Default render image folder" - ) - aov_separator: str = SettingsField( - "underscore", - title="AOV Separator character", - enum_resolver=aov_separators_enum - ) - image_format: str = SettingsField( - enum_resolver=image_format_enum, - title="Output Image Format" - ) - multipass: bool = SettingsField(title="multipass") - - -DEFAULT_RENDER_SETTINGS = { - "default_render_image_folder": "renders/3dsmax", - "aov_separator": "underscore", - "image_format": "exr", - "multipass": True -}