mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #5820 from ynput/enhancement/OP-7072_Validate-Loaded-Plugins
Max: Validate loaded plugins tweaks
This commit is contained in:
commit
ccdda3d817
6 changed files with 217 additions and 49 deletions
133
openpype/hosts/max/plugins/publish/validate_loaded_plugin.py
Normal file
133
openpype/hosts/max/plugins/publish/validate_loaded_plugin.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Validator for USD plugin."""
|
||||
from pyblish.api import InstancePlugin, ValidatorOrder
|
||||
from pymxs import runtime as rt
|
||||
|
||||
from openpype.pipeline import (
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
|
||||
def get_plugins() -> list:
|
||||
"""Get plugin list from 3ds max."""
|
||||
manager = rt.PluginManager
|
||||
count = manager.pluginDllCount
|
||||
plugin_info_list = []
|
||||
for p in range(1, count + 1):
|
||||
plugin_info = manager.pluginDllName(p)
|
||||
plugin_info_list.append(plugin_info)
|
||||
|
||||
return plugin_info_list
|
||||
|
||||
|
||||
class ValidateUSDPlugin(OptionalPyblishPluginMixin,
|
||||
InstancePlugin):
|
||||
"""Validates if USD plugin is installed or loaded in 3ds max."""
|
||||
|
||||
order = ValidatorOrder - 0.01
|
||||
families = ["model"]
|
||||
hosts = ["max"]
|
||||
label = "Validate USD Plugin loaded"
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
"""Plugin entry point."""
|
||||
|
||||
for sc in ValidateUSDPlugin.__subclasses__():
|
||||
self.log.info(sc)
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
plugin_info = get_plugins()
|
||||
usd_import = "usdimport.dli"
|
||||
if usd_import not in plugin_info:
|
||||
raise PublishValidationError(f"USD Plugin {usd_import} not found")
|
||||
usd_export = "usdexport.dle"
|
||||
if usd_export not in plugin_info:
|
||||
raise PublishValidationError(f"USD Plugin {usd_export} not found")
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@
|
|||
"ValidateAttributes": {
|
||||
"enabled": false,
|
||||
"attributes": {}
|
||||
},
|
||||
"ValidateLoadedPlugin": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"family_plugins_mapping": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,26 @@ class ValidateAttributesModel(BaseSettingsModel):
|
|||
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")
|
||||
|
|
@ -44,6 +64,10 @@ class PublishersModel(BaseSettingsModel):
|
|||
title="Validate Attributes"
|
||||
)
|
||||
|
||||
ValidateLoadedPlugin: ValidateLoadedPluginModel = Field(
|
||||
default_factory=ValidateLoadedPluginModel,
|
||||
title="Validate Loaded Plugin"
|
||||
)
|
||||
|
||||
DEFAULT_PUBLISH_SETTINGS = {
|
||||
"ValidateFrameRange": {
|
||||
|
|
@ -55,4 +79,9 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"enabled": False,
|
||||
"attributes": "{}"
|
||||
},
|
||||
"ValidateLoadedPlugin": {
|
||||
"enabled": False,
|
||||
"optional": True,
|
||||
"family_plugins_mapping": []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue