From 04dfe1863f267b210f6a3dc57b8f9890957e8d3c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 20:46:27 +0800 Subject: [PATCH 1/6] Implement the validator for camera attributes in Max host --- openpype/hosts/max/api/action.py | 39 +++++++++++ .../publish/validate_camera_attributes.py | 59 +++++++++++++++++ openpype/pipeline/publish/lib.py | 2 +- .../defaults/project_settings/max.json | 10 +++ .../schemas/schema_max_publish.json | 66 +++++++++++++++++++ .../max/server/settings/publishers.py | 26 +++++++- server_addon/max/server/version.py | 2 +- 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/api/action.py create mode 100644 openpype/hosts/max/plugins/publish/validate_camera_attributes.py diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py new file mode 100644 index 00000000000..506847652bb --- /dev/null +++ b/openpype/hosts/max/api/action.py @@ -0,0 +1,39 @@ +from pymxs import runtime as rt + +import pyblish.api + +from openpype.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] + self.log.info( + "Selecting invalid objects: %s", ", ".join(invalid_names) + ) + + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py new file mode 100644 index 00000000000..e279b414757 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -0,0 +1,59 @@ +import pyblish.api +from pymxs import runtime as rt + +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) +from openpype.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 = 'Camera Attributes' + actions = [SelectInvalidAction] + optional = True + + DEFAULTS = ["fov", "nearrange", "farrange", + "nearclip","farclip"] + CAM_TYPE = ["Freecamera", "Targetcamera", + "Physical"] + + @classmethod + def get_invalid(cls, instance): + invalid = [] + 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 rt.getProperty(camera, attr) != default_value: + cls.log.error( + f"Invalid attribute value: {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( + f"Invalid camera attributes: {invalid}") diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 4ea2f932f14..87ca3323cbd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -74,7 +74,7 @@ def get_template_name_profiles( project_settings ["global"] ["publish"] - ["IntegrateAssetNew"] + ["IntegrateHeroVersion"] ["template_name_profiles"] ) if legacy_profiles: diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc6..a0a4fcf83d8 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/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/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda988..90a5d2fc18d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -48,6 +48,72 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateCameraAttributes", + "label": "Validate Camera Attributes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "number", + "key": "fov", + "label": "Focal Length", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "nearrange", + "label": "Near Range", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farrange", + "label": "Far Range", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + }, + { + "type": "number", + "key": "nearclip", + "label": "Near Clip", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farclip", + "label": "Far Clip", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + } + ] + }, + { "type": "dict", "collapsible": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99be..340f344a9f9 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -27,6 +27,17 @@ def validate_json(cls, value): return value +class ValidateCameraAttributesModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + fov: float = Field(0.0, title="Focal Length") + nearrange: float = Field(0.0, title="Near Range") + farrange: float = Field(0.0, title="Far Range") + nearclip: float = Field(0.0, title="Near Clip") + farclip: float = Field(0.0, title="Far Clip") + + class FamilyMappingItemModel(BaseSettingsModel): product_types: list[str] = Field( default_factory=list, @@ -63,7 +74,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateAttributesModel, title="Validate Attributes" ) - + ValidateCameraAttributes: ValidateCameraAttributesModel = Field( + default_factory=ValidateCameraAttributesModel, + title="Validate Camera Attributes" + ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" @@ -101,6 +115,16 @@ class PublishersModel(BaseSettingsModel): "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 bbab0242f6a..1276d0254ff 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" From c29a4fdfd64dccb22375749b766b9e01aca9d815 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 21:08:51 +0800 Subject: [PATCH 2/6] hound shut --- .../hosts/max/plugins/publish/validate_camera_attributes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index e279b414757..d97d5529e3d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -23,7 +23,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, optional = True DEFAULTS = ["fov", "nearrange", "farrange", - "nearclip","farclip"] + "nearclip", "farclip"] CAM_TYPE = ["Freecamera", "Targetcamera", "Physical"] @@ -32,7 +32,9 @@ def get_invalid(cls, instance): invalid = [] cameras = instance.data["members"] project_settings = instance.context.data["project_settings"].get("max") - cam_attr_settings = project_settings["publish"]["ValidateCameraAttributes"] + cam_attr_settings = ( + project_settings["publish"]["ValidateCameraAttributes"] + ) for camera in cameras: if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: cls.log.debug( From 3c878ccc3e34d8bb6884e731cc035881a0449947 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 13:27:32 +0800 Subject: [PATCH 3/6] make sure the validator skips checking when the value of the attribute sets to zero --- .../max/plugins/publish/validate_camera_attributes.py | 11 ++++++++--- .../projects_schema/schemas/schema_max_publish.json | 4 ++++ server_addon/max/server/settings/publishers.py | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index d97d5529e3d..36f0beab65d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -18,7 +18,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder families = ['camera'] hosts = ['max'] - label = 'Camera Attributes' + label = 'Validate Camera Attributes' actions = [SelectInvalidAction] optional = True @@ -42,9 +42,14 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) + if default_value == 0: + cls.log.debug( + f"the value of {attr} in setting set to" + " zero. Skipping the check..") + continue if rt.getProperty(camera, attr) != default_value: cls.log.error( - f"Invalid attribute value: {attr} " + f"Invalid attribute value for {camera.name}:{attr} " f"(should be: {default_value}))") invalid.append(camera) @@ -58,4 +63,4 @@ def process(self, instance): if invalid: raise PublishValidationError( - f"Invalid camera attributes: {invalid}") + "Invalid camera attributes found. See log.") diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index 90a5d2fc18d..1e7a7c0c739 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -79,6 +79,10 @@ "minimum": 0, "maximum": 100.0 }, + { + "type": "label", + "label": "If the value of the camera attributes set to 0, the system automatically skips checking it" + }, { "type": "number", "key": "nearrange", diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 340f344a9f9..a092f54a62b 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -76,7 +76,11 @@ class PublishersModel(BaseSettingsModel): ) ValidateCameraAttributes: ValidateCameraAttributesModel = Field( default_factory=ValidateCameraAttributesModel, - title="Validate Camera Attributes" + title="Validate Camera Attributes", + description=( + "If the value of the camera attributes set to 0, " + "the system automatically skips checking it" + ) ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, From 9baff5eccfea5e2c7a5dc8c15efc4b75a916799f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 18:04:19 +0800 Subject: [PATCH 4/6] add repair actions for validate camera attributes --- .../publish/validate_camera_attributes.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 36f0beab65d..94aa5742d82 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -2,6 +2,7 @@ from pymxs import runtime as rt from openpype.pipeline.publish import ( + RepairAction, OptionalPyblishPluginMixin, PublishValidationError ) @@ -19,7 +20,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, families = ['camera'] hosts = ['max'] label = 'Validate Camera Attributes' - actions = [SelectInvalidAction] + actions = [SelectInvalidAction, RepairAction] optional = True DEFAULTS = ["fov", "nearrange", "farrange", @@ -42,10 +43,11 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - if default_value == 0: + cls.log.debug(f"default value: {default_value}") + if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" - " zero. Skipping the check..") + " zero. Skipping the check.") continue if rt.getProperty(camera, attr) != default_value: cls.log.error( @@ -57,10 +59,26 @@ def get_invalid(cls, instance): def process(self, instance): if not self.is_active(instance.data): - self.log.debug("Skipping Validate Camera Attributes...") + 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) From 15167fa0b805b03657857097db52dd554c163620 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 22:46:21 +0800 Subject: [PATCH 5/6] the validator posts on warning message if the users dont use generic types & round 1 decimal places for all check values --- .../max/plugins/publish/validate_camera_attributes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 94aa5742d82..4eec1951e5d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -31,6 +31,11 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, @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 = ( @@ -43,13 +48,12 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - cls.log.debug(f"default value: {default_value}") if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" " zero. Skipping the check.") continue - if rt.getProperty(camera, attr) != default_value: + 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}))") From 3c47798d121215505c7b0349482f7947f1b79dbe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 20:07:16 +0800 Subject: [PATCH 6/6] edit the action.py too regarding to #6164 --- openpype/hosts/max/api/action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652bb..c3c1957af10 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,7 +31,10 @@ def process(self, context, plugin): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + 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) )