diff --git a/client/ayon_core/hosts/max/api/action.py b/client/ayon_core/hosts/max/api/action.py new file mode 100644 index 0000000000..58834d0172 --- /dev/null +++ b/client/ayon_core/hosts/max/api/action.py @@ -0,0 +1,42 @@ +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 isinstance(obj, str)] + 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/client/ayon_core/hosts/max/plugins/publish/validate_camera_attributes.py b/client/ayon_core/hosts/max/plugins/publish/validate_camera_attributes.py new file mode 100644 index 0000000000..9398cba2b7 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_camera_attributes.py @@ -0,0 +1,88 @@ +import pyblish.api +from pymxs import runtime as rt + +from ayon_core.pipeline.publish import ( + RepairAction, + OptionalPyblishPluginMixin, + PublishValidationError +) +from ayon_core.hosts.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 + + 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/client/ayon_core/settings/defaults/project_settings/max.json b/client/ayon_core/settings/defaults/project_settings/max.json index d1610610dc..a0a4fcf83d 100644 --- a/client/ayon_core/settings/defaults/project_settings/max.json +++ b/client/ayon_core/settings/defaults/project_settings/max.json @@ -56,6 +56,16 @@ "enabled": false, "attributes": {} }, + "ValidateCameraAttributes": { + "enabled": true, + "optional": true, + "active": false, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": false, "optional": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index da782cb494..9c301d10b7 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -27,6 +27,17 @@ class ValidateAttributesModel(BaseSettingsModel): return value +class ValidateCameraAttributesModel(BaseSettingsModel): + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + fov: float = SettingsField(0.0, title="Focal Length") + nearrange: float = SettingsField(0.0, title="Near Range") + farrange: float = SettingsField(0.0, title="Far Range") + nearclip: float = SettingsField(0.0, title="Near Clip") + farclip: float = SettingsField(0.0, title="Far Clip") + + class FamilyMappingItemModel(BaseSettingsModel): product_types: list[str] = SettingsField( default_factory=list, @@ -63,7 +74,14 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateAttributesModel, title="Validate Attributes" ) - + ValidateCameraAttributes: ValidateCameraAttributesModel = SettingsField( + default_factory=ValidateCameraAttributesModel, + title="Validate Camera Attributes", + description=( + "If the value of the camera attributes set to 0, " + "the system automatically skips checking it" + ) + ) ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" @@ -101,6 +119,16 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": False, "attributes": "{}" }, + "ValidateCameraAttributes": { + "enabled": True, + "optional": True, + "active": False, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": False, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5"