Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5820 from ynput/enhancement/OP-7072_Validate-Load…
Browse files Browse the repository at this point in the history
…ed-Plugins

Max: Validate loaded plugins tweaks
  • Loading branch information
moonyuet authored Nov 17, 2023
2 parents 6cefe85 + 1646d3a commit ccdda3d
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 49 deletions.
133 changes: 133 additions & 0 deletions openpype/hosts/max/plugins/publish/validate_loaded_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""Validator for Loaded Plugin."""
import os
import pyblish.api
from pymxs import runtime as rt

from openpype.pipeline.publish import (
RepairAction,
OptionalPyblishPluginMixin,
PublishValidationError
)
from openpype.hosts.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]

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

invalid = []
# Find all plug-in requirements for current instance
instance_families = {instance.data["family"]}
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)
49 changes: 0 additions & 49 deletions openpype/hosts/max/plugins/publish/validate_usd_plugin.py

This file was deleted.

7 changes: 7 additions & 0 deletions openpype/settings/ayon_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,13 @@ def _convert_3dsmax_project_settings(ayon_settings, output):
attributes = {}
ayon_publish["ValidateAttributes"]["attributes"] = attributes

if "ValidateLoadedPlugin" in ayon_publish:
loaded_plugin = (
ayon_publish["ValidateLoadedPlugin"]["family_plugins_mapping"]
)
for item in loaded_plugin:
item["families"] = item.pop("product_types")

output["max"] = ayon_max


Expand Down
5 changes: 5 additions & 0 deletions openpype/settings/defaults/project_settings/max.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
"ValidateAttributes": {
"enabled": false,
"attributes": {}
},
"ValidateLoadedPlugin": {
"enabled": false,
"optional": true,
"family_plugins_mapping": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,49 @@
"label": "Attributes"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateLoadedPlugin",
"label": "Validate Loaded Plugin",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "list",
"collapsible": true,
"key": "family_plugins_mapping",
"label": "Family Plugins Mapping",
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Famiies",
"type": "list",
"object_type": "text"
},
{
"key": "plugins",
"label": "Plugins",
"type": "list",
"object_type": "text"
}
]
}
}
]
}
]
}
29 changes: 29 additions & 0 deletions server_addon/max/server/settings/publishers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ def validate_json(cls, value):
return value


class FamilyMappingItemModel(BaseSettingsModel):
product_types: list[str] = Field(
default_factory=list,
title="Product Types"
)
plugins: list[str] = Field(
default_factory=list,
title="Plugins"
)


class ValidateLoadedPluginModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
family_plugins_mapping: list[FamilyMappingItemModel] = Field(
default_factory=list,
title="Family Plugins Mapping"
)


class BasicValidateModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
Expand All @@ -44,6 +64,10 @@ class PublishersModel(BaseSettingsModel):
title="Validate Attributes"
)

ValidateLoadedPlugin: ValidateLoadedPluginModel = Field(
default_factory=ValidateLoadedPluginModel,
title="Validate Loaded Plugin"
)

DEFAULT_PUBLISH_SETTINGS = {
"ValidateFrameRange": {
Expand All @@ -55,4 +79,9 @@ class PublishersModel(BaseSettingsModel):
"enabled": False,
"attributes": "{}"
},
"ValidateLoadedPlugin": {
"enabled": False,
"optional": True,
"family_plugins_mapping": []
}
}

0 comments on commit ccdda3d

Please sign in to comment.