Merge branch 'develop' into feature/AY-4802_resolve-editorial-load-editorial-exchange-package

This commit is contained in:
Jakub Ježek 2024-05-30 12:12:18 +02:00 committed by GitHub
commit 3f933c06c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
95 changed files with 597 additions and 215 deletions

View file

@ -15,11 +15,11 @@ from ayon_core.pipeline.publish.lib import (
replace_with_published_scene_path
)
from ayon_core.pipeline.publish import KnownPublishError
from ayon_core.hosts.max.api.lib import (
from ayon_max.api.lib import (
get_current_renderer,
get_multipass_setting
)
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_max.api.lib_rendersettings import RenderSettings
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
@ -205,11 +205,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
def _use_published_name(self, data, project_settings):
# Not all hosts can import these modules.
from ayon_core.hosts.max.api.lib import (
from ayon_max.api.lib import (
get_current_renderer,
get_multipass_setting
)
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_max.api.lib_rendersettings import RenderSettings
instance = self._instance
job_info = copy.deepcopy(self.job_info)

View file

@ -1,6 +1,10 @@
import pyblish.api
from ayon_core.lib import filter_profiles
from ayon_core.pipeline.publish import (
PublishValidationError, OptionalPyblishPluginMixin
PublishValidationError,
OptionalPyblishPluginMixin,
get_current_host_name,
)
@ -13,12 +17,35 @@ class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
order = pyblish.api.ValidatorOrder
label = "Validate Version"
hosts = ["nuke", "maya", "houdini", "blender",
"photoshop", "aftereffects"]
optional = False
active = True
@classmethod
def apply_settings(cls, settings):
# Disable if no profile is found for the current host
profiles = (
settings
["core"]
["publish"]
["ValidateVersion"]
["plugin_state_profiles"]
)
profile = filter_profiles(
profiles, {"host_names": get_current_host_name()}
)
if not profile:
cls.enabled = False
return
# Apply settings from profile
for attr_name in {
"enabled",
"optional",
"active",
}:
setattr(cls, attr_name, profile[attr_name])
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -14,6 +14,7 @@ from .hierarchy import (
)
from .thumbnails import ThumbnailsModel
from .selection import HierarchyExpectedSelection
from .users import UsersModel
__all__ = (
@ -32,4 +33,6 @@ __all__ = (
"ThumbnailsModel",
"HierarchyExpectedSelection",
"UsersModel",
)

View file

@ -0,0 +1,84 @@
import ayon_api
from ayon_core.lib import CacheItem
class UserItem:
def __init__(
self,
username,
full_name,
email,
avatar_url,
active,
):
self.username = username
self.full_name = full_name
self.email = email
self.avatar_url = avatar_url
self.active = active
@classmethod
def from_entity_data(cls, user_data):
return cls(
user_data["name"],
user_data["attrib"]["fullName"],
user_data["attrib"]["email"],
user_data["attrib"]["avatarUrl"],
user_data["active"],
)
class UsersModel:
def __init__(self, controller):
self._controller = controller
self._users_cache = CacheItem(default_factory=list)
def get_user_items(self):
"""Get user items.
Returns:
List[UserItem]: List of user items.
"""
self._invalidate_cache()
return self._users_cache.get_data()
def get_user_items_by_name(self):
"""Get user items by name.
Implemented as most of cases using this model will need to find
user information by username.
Returns:
Dict[str, UserItem]: Dictionary of user items by name.
"""
return {
user_item.username: user_item
for user_item in self.get_user_items()
}
def get_user_item_by_username(self, username):
"""Get user item by username.
Args:
username (str): Username.
Returns:
Union[UserItem, None]: User item or None if not found.
"""
self._invalidate_cache()
for user_item in self.get_user_items():
if user_item.username == username:
return user_item
return None
def _invalidate_cache(self):
if self._users_cache.is_valid:
return
self._users_cache.update_data([
UserItem.from_entity_data(user)
for user in ayon_api.get_users()
])

View file

@ -13,8 +13,10 @@ class WorkfileInfo:
task_id (str): Task id.
filepath (str): Filepath.
filesize (int): File size.
creation_time (int): Creation time (timestamp).
modification_time (int): Modification time (timestamp).
creation_time (float): Creation time (timestamp).
modification_time (float): Modification time (timestamp).
created_by (Union[str, none]): User who created the file.
updated_by (Union[str, none]): User who last updated the file.
note (str): Note.
"""
@ -26,6 +28,8 @@ class WorkfileInfo:
filesize,
creation_time,
modification_time,
created_by,
updated_by,
note,
):
self.folder_id = folder_id
@ -34,6 +38,8 @@ class WorkfileInfo:
self.filesize = filesize
self.creation_time = creation_time
self.modification_time = modification_time
self.created_by = created_by
self.updated_by = updated_by
self.note = note
def to_data(self):
@ -50,6 +56,8 @@ class WorkfileInfo:
"filesize": self.filesize,
"creation_time": self.creation_time,
"modification_time": self.modification_time,
"created_by": self.created_by,
"updated_by": self.updated_by,
"note": self.note,
}
@ -212,6 +220,7 @@ class FileItem:
dirpath (str): Directory path of file.
filename (str): Filename.
modified (float): Modified timestamp.
created_by (Optional[str]): Username.
representation_id (Optional[str]): Representation id of published
workfile.
filepath (Optional[str]): Prepared filepath.
@ -223,6 +232,8 @@ class FileItem:
dirpath,
filename,
modified,
created_by=None,
updated_by=None,
representation_id=None,
filepath=None,
exists=None
@ -230,6 +241,8 @@ class FileItem:
self.filename = filename
self.dirpath = dirpath
self.modified = modified
self.created_by = created_by
self.updated_by = updated_by
self.representation_id = representation_id
self._filepath = filepath
self._exists = exists
@ -269,6 +282,7 @@ class FileItem:
"filename": self.filename,
"dirpath": self.dirpath,
"modified": self.modified,
"created_by": self.created_by,
"representation_id": self.representation_id,
"filepath": self.filepath,
"exists": self.exists,
@ -522,6 +536,16 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon):
pass
@abstractmethod
def get_user_items_by_name(self):
"""Get user items available on AYON server.
Returns:
Dict[str, UserItem]: User items by username.
"""
pass
# Host information
@abstractmethod
def get_workfile_extensions(self):

View file

@ -19,6 +19,7 @@ from ayon_core.tools.common_models import (
HierarchyModel,
HierarchyExpectedSelection,
ProjectsModel,
UsersModel,
)
from .abstract import (
@ -161,6 +162,7 @@ class BaseWorkfileController(
self._save_is_enabled = True
# Expected selected folder and task
self._users_model = self._create_users_model()
self._expected_selection = self._create_expected_selection_obj()
self._selection_model = self._create_selection_model()
self._projects_model = self._create_projects_model()
@ -176,6 +178,12 @@ class BaseWorkfileController(
def is_host_valid(self):
return self._host_is_valid
def _create_users_model(self):
return UsersModel(self)
def _create_workfiles_model(self):
return WorkfilesModel(self)
def _create_expected_selection_obj(self):
return WorkfilesToolExpectedSelection(self)
@ -188,9 +196,6 @@ class BaseWorkfileController(
def _create_hierarchy_model(self):
return HierarchyModel(self)
def _create_workfiles_model(self):
return WorkfilesModel(self)
@property
def event_system(self):
"""Inner event system for workfiles tool controller.
@ -272,6 +277,9 @@ class BaseWorkfileController(
{"enabled": enabled}
)
def get_user_items_by_name(self):
return self._users_model.get_user_items_by_name()
# Host information
def get_workfile_extensions(self):
host = self._host

View file

@ -6,6 +6,7 @@ import arrow
import ayon_api
from ayon_api.operations import OperationsSession
from ayon_core.lib import get_ayon_username
from ayon_core.pipeline.template_data import (
get_template_data,
get_task_template_data,
@ -23,6 +24,8 @@ from ayon_core.tools.workfiles.abstract import (
WorkfileInfo,
)
_NOT_SET = object()
class CommentMatcher(object):
"""Use anatomy and work file data to parse comments from filenames.
@ -188,10 +191,17 @@ class WorkareaModel:
if ext not in self._extensions:
continue
modified = os.path.getmtime(filepath)
items.append(
FileItem(workdir, filename, modified)
workfile_info = self._controller.get_workfile_info(
folder_id, task_id, filepath
)
modified = os.path.getmtime(filepath)
items.append(FileItem(
workdir,
filename,
modified,
workfile_info.created_by,
workfile_info.updated_by,
))
return items
def _get_template_key(self, fill_data):
@ -439,6 +449,7 @@ class WorkfileEntitiesModel:
self._controller = controller
self._cache = {}
self._items = {}
self._current_username = _NOT_SET
def _get_workfile_info_identifier(
self, folder_id, task_id, rootless_path
@ -459,8 +470,12 @@ class WorkfileEntitiesModel:
self, folder_id, task_id, workfile_info, filepath
):
note = ""
created_by = None
updated_by = None
if workfile_info:
note = workfile_info["attrib"].get("description") or ""
created_by = workfile_info.get("createdBy")
updated_by = workfile_info.get("updatedBy")
filestat = os.stat(filepath)
return WorkfileInfo(
@ -470,6 +485,8 @@ class WorkfileEntitiesModel:
filesize=filestat.st_size,
creation_time=filestat.st_ctime,
modification_time=filestat.st_mtime,
created_by=created_by,
updated_by=updated_by,
note=note
)
@ -481,7 +498,7 @@ class WorkfileEntitiesModel:
for workfile_info in ayon_api.get_workfiles_info(
self._controller.get_current_project_name(),
task_ids=[task_id],
fields=["id", "path", "attrib"],
fields=["id", "path", "attrib", "createdBy", "updatedBy"],
):
workfile_identifier = self._get_workfile_info_identifier(
folder_id, task_id, workfile_info["path"]
@ -525,18 +542,32 @@ class WorkfileEntitiesModel:
self._items.pop(identifier, None)
return
if note is None:
return
old_note = workfile_info.get("attrib", {}).get("note")
new_workfile_info = copy.deepcopy(workfile_info)
attrib = new_workfile_info.setdefault("attrib", {})
attrib["description"] = note
update_data = {}
if note is not None and old_note != note:
update_data["attrib"] = {"description": note}
attrib = new_workfile_info.setdefault("attrib", {})
attrib["description"] = note
username = self._get_current_username()
# Automatically fix 'createdBy' and 'updatedBy' fields
# NOTE both fields were not automatically filled by server
# until 1.1.3 release.
if workfile_info.get("createdBy") is None:
update_data["createdBy"] = username
new_workfile_info["createdBy"] = username
if workfile_info.get("updatedBy") != username:
update_data["updatedBy"] = username
new_workfile_info["updatedBy"] = username
if not update_data:
return
self._cache[identifier] = new_workfile_info
self._items.pop(identifier, None)
if old_note == note:
return
project_name = self._controller.get_current_project_name()
@ -545,7 +576,7 @@ class WorkfileEntitiesModel:
project_name,
"workfile",
workfile_info["id"],
{"attrib": {"description": note}},
update_data,
)
session.commit()
@ -554,13 +585,18 @@ class WorkfileEntitiesModel:
project_name = self._controller.get_current_project_name()
username = self._get_current_username()
workfile_info = {
"path": rootless_path,
"taskId": task_id,
"attrib": {
"extension": extension,
"description": note
}
},
# TODO remove 'createdBy' and 'updatedBy' fields when server is
# or above 1.1.3 .
"createdBy": username,
"updatedBy": username,
}
session = OperationsSession()
@ -568,6 +604,11 @@ class WorkfileEntitiesModel:
session.commit()
return workfile_info
def _get_current_username(self):
if self._current_username is _NOT_SET:
self._current_username = get_ayon_username()
return self._current_username
class PublishWorkfilesModel:
"""Model for handling of published workfiles.
@ -599,7 +640,7 @@ class PublishWorkfilesModel:
return self._cached_repre_extensions
def _file_item_from_representation(
self, repre_entity, project_anatomy, task_name=None
self, repre_entity, project_anatomy, author, task_name=None
):
if task_name is not None:
task_info = repre_entity["context"].get("task")
@ -634,6 +675,8 @@ class PublishWorkfilesModel:
dirpath,
filename,
created_at.float_timestamp,
author,
None,
repre_entity["id"]
)
@ -643,9 +686,9 @@ class PublishWorkfilesModel:
# Get subset docs of folder
product_entities = ayon_api.get_products(
project_name,
folder_ids=[folder_id],
product_types=["workfile"],
fields=["id", "name"]
folder_ids={folder_id},
product_types={"workfile"},
fields={"id", "name"}
)
output = []
@ -657,25 +700,33 @@ class PublishWorkfilesModel:
version_entities = ayon_api.get_versions(
project_name,
product_ids=product_ids,
fields=["id", "productId"]
fields={"id", "author"}
)
version_ids = {version["id"] for version in version_entities}
if not version_ids:
versions_by_id = {
version["id"]: version
for version in version_entities
}
if not versions_by_id:
return output
# Query representations of filtered versions and add filter for
# extension
repre_entities = ayon_api.get_representations(
project_name,
version_ids=version_ids
version_ids=set(versions_by_id)
)
project_anatomy = self._controller.project_anatomy
# Filter queried representations by task name if task is set
file_items = []
for repre_entity in repre_entities:
version_id = repre_entity["versionId"]
version_entity = versions_by_id[version_id]
file_item = self._file_item_from_representation(
repre_entity, project_anatomy, task_name
repre_entity,
project_anatomy,
version_entity["author"],
task_name,
)
if file_item is not None:
file_items.append(file_item)

View file

@ -13,7 +13,8 @@ from .utils import BaseOverlayFrame
REPRE_ID_ROLE = QtCore.Qt.UserRole + 1
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 3
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 4
class PublishedFilesModel(QtGui.QStandardItemModel):
@ -23,13 +24,19 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
controller (AbstractWorkfilesFrontend): The control object.
"""
columns = [
"Name",
"Author",
"Date Modified",
]
date_modified_col = columns.index("Date Modified")
def __init__(self, controller):
super(PublishedFilesModel, self).__init__()
self.setColumnCount(2)
self.setHeaderData(0, QtCore.Qt.Horizontal, "Name")
self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified")
self.setColumnCount(len(self.columns))
for idx, label in enumerate(self.columns):
self.setHeaderData(idx, QtCore.Qt.Horizontal, label)
controller.register_event_callback(
"selection.task.changed",
@ -185,6 +192,8 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
self._remove_empty_item()
self._remove_missing_context_item()
user_items_by_name = self._controller.get_user_items_by_name()
items_to_remove = set(self._items_by_id.keys())
new_items = []
for file_item in file_items:
@ -205,8 +214,15 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
else:
flags = QtCore.Qt.NoItemFlags
author = file_item.created_by
user_item = user_items_by_name.get(author)
if user_item is not None and user_item.full_name:
author = user_item.full_name
item.setFlags(flags)
item.setData(file_item.filepath, FILEPATH_ROLE)
item.setData(author, AUTHOR_ROLE)
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
self._items_by_id[repre_id] = item
@ -225,22 +241,30 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
# Use flags of first column for all columns
if index.column() != 0:
index = self.index(index.row(), 0, index.parent())
return super(PublishedFilesModel, self).flags(index)
return super().flags(index)
def data(self, index, role=None):
if role is None:
role = QtCore.Qt.DisplayRole
# Handle roles for first column
if index.column() == 1:
if role == QtCore.Qt.DecorationRole:
return None
col = index.column()
if col != 1:
return super().data(index, role)
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
if role == QtCore.Qt.DecorationRole:
return None
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
if col == 1:
role = AUTHOR_ROLE
elif col == 2:
role = DATE_MODIFIED_ROLE
index = self.index(index.row(), 0, index.parent())
else:
return None
index = self.index(index.row(), 0, index.parent())
return super(PublishedFilesModel, self).data(index, role)
return super().data(index, role)
class SelectContextOverlay(BaseOverlayFrame):
@ -295,7 +319,7 @@ class PublishedFilesWidget(QtWidgets.QWidget):
view.setModel(proxy_model)
time_delegate = PrettyTimeDelegate()
view.setItemDelegateForColumn(1, time_delegate)
view.setItemDelegateForColumn(model.date_modified_col, time_delegate)
# Default to a wider first filename column it is what we mostly care
# about and the date modified is relatively small anyway.

View file

@ -10,7 +10,8 @@ from ayon_core.tools.utils.delegates import PrettyTimeDelegate
FILENAME_ROLE = QtCore.Qt.UserRole + 1
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 3
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 4
class WorkAreaFilesModel(QtGui.QStandardItemModel):
@ -21,14 +22,20 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
"""
refreshed = QtCore.Signal()
columns = [
"Name",
"Author",
"Date Modified",
]
date_modified_col = columns.index("Date Modified")
def __init__(self, controller):
super(WorkAreaFilesModel, self).__init__()
self.setColumnCount(2)
self.setColumnCount(len(self.columns))
self.setHeaderData(0, QtCore.Qt.Horizontal, "Name")
self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified")
for idx, label in enumerate(self.columns):
self.setHeaderData(idx, QtCore.Qt.Horizontal, label)
controller.register_event_callback(
"selection.folder.changed",
@ -186,6 +193,7 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
return
self._remove_empty_item()
self._remove_missing_context_item()
user_items_by_name = self._controller.get_user_items_by_name()
items_to_remove = set(self._items_by_filename.keys())
new_items = []
@ -205,7 +213,13 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
item.setData(file_item.filename, QtCore.Qt.DisplayRole)
item.setData(file_item.filename, FILENAME_ROLE)
updated_by = file_item.updated_by
user_item = user_items_by_name.get(updated_by)
if user_item is not None and user_item.full_name:
updated_by = user_item.full_name
item.setData(file_item.filepath, FILEPATH_ROLE)
item.setData(updated_by, AUTHOR_ROLE)
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
self._items_by_filename[file_item.filename] = item
@ -224,22 +238,30 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
# Use flags of first column for all columns
if index.column() != 0:
index = self.index(index.row(), 0, index.parent())
return super(WorkAreaFilesModel, self).flags(index)
return super().flags(index)
def data(self, index, role=None):
if role is None:
role = QtCore.Qt.DisplayRole
# Handle roles for first column
if index.column() == 1:
if role == QtCore.Qt.DecorationRole:
return None
col = index.column()
if col == 0:
return super().data(index, role)
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
if role == QtCore.Qt.DecorationRole:
return None
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
if col == 1:
role = AUTHOR_ROLE
elif col == 2:
role = DATE_MODIFIED_ROLE
index = self.index(index.row(), 0, index.parent())
else:
return None
index = self.index(index.row(), 0, index.parent())
return super(WorkAreaFilesModel, self).data(index, role)
return super().data(index, role)
def set_published_mode(self, published_mode):
if self._published_mode == published_mode:
@ -279,7 +301,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget):
view.setModel(proxy_model)
time_delegate = PrettyTimeDelegate()
view.setItemDelegateForColumn(1, time_delegate)
view.setItemDelegateForColumn(model.date_modified_col, time_delegate)
# Default to a wider first filename column it is what we mostly care
# about and the date modified is relatively small anyway.

View file

@ -147,13 +147,38 @@ class SidePanelWidget(QtWidgets.QWidget):
workfile_info.creation_time)
modification_time = datetime.datetime.fromtimestamp(
workfile_info.modification_time)
user_items_by_name = self._controller.get_user_items_by_name()
def convert_username(username):
user_item = user_items_by_name.get(username)
if user_item is not None and user_item.full_name:
return user_item.full_name
return username
created_lines = [
creation_time.strftime(datetime_format)
]
if workfile_info.created_by:
created_lines.insert(
0, convert_username(workfile_info.created_by)
)
modified_lines = [
modification_time.strftime(datetime_format)
]
if workfile_info.updated_by:
modified_lines.insert(
0, convert_username(workfile_info.updated_by)
)
lines = (
"<b>Size:</b>",
size_value,
"<b>Created:</b>",
creation_time.strftime(datetime_format),
"<br/>".join(created_lines),
"<b>Modified:</b>",
modification_time.strftime(datetime_format)
"<br/>".join(modified_lines),
)
self._orig_note = note
self._note_input.setPlainText(note)

View file

@ -107,7 +107,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget):
split_widget.addWidget(tasks_widget)
split_widget.addWidget(col_3_widget)
split_widget.addWidget(side_panel)
split_widget.setSizes([255, 160, 455, 175])
split_widget.setSizes([255, 175, 550, 190])
body_layout.addWidget(split_widget)
@ -169,7 +169,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget):
# Force focus on the open button by default, required for Houdini.
self._files_widget.setFocus()
self.resize(1200, 600)
self.resize(1260, 600)
def _create_col_1_widget(self, controller, parent):
col_widget = QtWidgets.QWidget(parent)

View file

@ -2,7 +2,11 @@ from typing import Any
from ayon_server.addons import BaseServerAddon
from .settings import CoreSettings, DEFAULT_VALUES
from .settings import (
CoreSettings,
DEFAULT_VALUES,
convert_settings_overrides,
)
class CoreAddon(BaseServerAddon):
@ -17,47 +21,8 @@ class CoreAddon(BaseServerAddon):
source_version: str,
overrides: dict[str, Any],
) -> dict[str, Any]:
self._convert_imagio_configs_0_3_1(overrides)
convert_settings_overrides(source_version, overrides)
# Use super conversion
return await super().convert_settings_overrides(
source_version, overrides
)
def _convert_imagio_configs_0_3_1(self, overrides):
"""Imageio config settings did change to profiles since 0.3.1. ."""
imageio_overrides = overrides.get("imageio") or {}
if (
"ocio_config" not in imageio_overrides
or "filepath" not in imageio_overrides["ocio_config"]
):
return
ocio_config = imageio_overrides.pop("ocio_config")
filepath = ocio_config["filepath"]
if not filepath:
return
first_filepath = filepath[0]
ocio_config_profiles = imageio_overrides.setdefault(
"ocio_config_profiles", []
)
base_value = {
"type": "builtin_path",
"product_name": "",
"host_names": [],
"task_names": [],
"task_types": [],
"custom_path": "",
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
}
if first_filepath in (
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
):
base_value["type"] = "builtin_path"
base_value["builtin_path"] = first_filepath
else:
base_value["type"] = "custom_path"
base_value["custom_path"] = first_filepath
ocio_config_profiles.append(base_value)

View file

@ -1,7 +1,10 @@
from .main import CoreSettings, DEFAULT_VALUES
from .conversion import convert_settings_overrides
__all__ = (
"CoreSettings",
"DEFAULT_VALUES",
"convert_settings_overrides",
)

View file

@ -0,0 +1,86 @@
import copy
from typing import Any
from .publish_plugins import DEFAULT_PUBLISH_VALUES
def _convert_imageio_configs_0_3_1(overrides):
"""Imageio config settings did change to profiles since 0.3.1. ."""
imageio_overrides = overrides.get("imageio") or {}
if (
"ocio_config" not in imageio_overrides
or "filepath" not in imageio_overrides["ocio_config"]
):
return
ocio_config = imageio_overrides.pop("ocio_config")
filepath = ocio_config["filepath"]
if not filepath:
return
first_filepath = filepath[0]
ocio_config_profiles = imageio_overrides.setdefault(
"ocio_config_profiles", []
)
base_value = {
"type": "builtin_path",
"product_name": "",
"host_names": [],
"task_names": [],
"task_types": [],
"custom_path": "",
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
}
if first_filepath in (
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
):
base_value["type"] = "builtin_path"
base_value["builtin_path"] = first_filepath
else:
base_value["type"] = "custom_path"
base_value["custom_path"] = first_filepath
ocio_config_profiles.append(base_value)
def _convert_validate_version_0_3_3(publish_overrides):
"""ValidateVersion plugin changed in 0.3.3."""
if "ValidateVersion" not in publish_overrides:
return
validate_version = publish_overrides["ValidateVersion"]
# Already new settings
if "plugin_state_profiles" in validate_version:
return
# Use new default profile as base
profile = copy.deepcopy(
DEFAULT_PUBLISH_VALUES["ValidateVersion"]["plugin_state_profiles"][0]
)
# Copy values from old overrides to new overrides
for key in {
"enabled",
"optional",
"active",
}:
if key not in validate_version:
continue
profile[key] = validate_version.pop(key)
validate_version["plugin_state_profiles"] = [profile]
def _conver_publish_plugins(overrides):
if "publish" not in overrides:
return
_convert_validate_version_0_3_3(overrides["publish"])
def convert_settings_overrides(
source_version: str,
overrides: dict[str, Any],
) -> dict[str, Any]:
_convert_imageio_configs_0_3_1(overrides)
_conver_publish_plugins(overrides)
return overrides

View file

@ -59,7 +59,7 @@ class CollectFramesFixDefModel(BaseSettingsModel):
)
class ValidateOutdatedContainersProfile(BaseSettingsModel):
class PluginStateByHostModelProfile(BaseSettingsModel):
_layout = "expanded"
# Filtering
host_names: list[str] = SettingsField(
@ -72,17 +72,12 @@ class ValidateOutdatedContainersProfile(BaseSettingsModel):
active: bool = SettingsField(True, title="Active")
class ValidateOutdatedContainersModel(BaseSettingsModel):
"""Validate if Publishing intent was selected.
It is possible to disable validation for specific publishing context
with profiles.
"""
class PluginStateByHostModel(BaseSettingsModel):
_isGroup = True
plugin_state_profiles: list[ValidateOutdatedContainersProfile] = SettingsField(
plugin_state_profiles: list[PluginStateByHostModelProfile] = SettingsField(
default_factory=list,
title="Plugin enable state profiles",
description="Change plugin state based on host name."
)
@ -563,7 +558,7 @@ class ExtractBurninProfile(BaseSettingsModel):
_layout = "expanded"
product_types: list[str] = SettingsField(
default_factory=list,
title="Produt types"
title="Product types"
)
hosts: list[str] = SettingsField(
default_factory=list,
@ -793,12 +788,16 @@ class PublishPuginsModel(BaseSettingsModel):
default_factory=ValidateBaseModel,
title="Validate Editorial Asset Name"
)
ValidateVersion: ValidateBaseModel = SettingsField(
default_factory=ValidateBaseModel,
title="Validate Version"
ValidateVersion: PluginStateByHostModel = SettingsField(
default_factory=PluginStateByHostModel,
title="Validate Version",
description=(
"Validate that product version to integrate"
" is newer than latest version in AYON."
)
)
ValidateOutdatedContainers: ValidateOutdatedContainersModel = SettingsField(
default_factory=ValidateOutdatedContainersModel,
ValidateOutdatedContainers: PluginStateByHostModel = SettingsField(
default_factory=PluginStateByHostModel,
title="Validate Containers"
)
ValidateIntent: ValidateIntentModel = SettingsField(
@ -882,9 +881,21 @@ DEFAULT_PUBLISH_VALUES = {
"active": True
},
"ValidateVersion": {
"enabled": True,
"optional": False,
"active": True
"plugin_state_profiles": [
{
"host_names": [
"aftereffects",
"blender",
"houdini",
"maya",
"nuke",
"photoshop",
],
"enabled": True,
"optional": False,
"active": True
}
]
},
"ValidateOutdatedContainers": {
"plugin_state_profiles": [

View file

@ -20,7 +20,7 @@ from pymxs import runtime as rt
JSON_PREFIX = "JSON::"
log = logging.getLogger("ayon_core.hosts.max")
log = logging.getLogger("ayon_max")
def get_main_window():

View file

@ -6,7 +6,7 @@ import os
from pymxs import runtime as rt
from ayon_core.hosts.max.api.lib import get_current_renderer
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

View file

@ -5,7 +5,7 @@ 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_core.hosts.max.api.lib import (
from ayon_max.api.lib import (
set_render_frame_range,
get_current_renderer,
get_default_render_folder

View file

@ -5,7 +5,7 @@ from qtpy import QtWidgets, QtCore
from pymxs import runtime as rt
from ayon_core.tools.utils import host_tools
from ayon_core.hosts.max.api import lib
from ayon_max.api import lib
class AYONMenu(object):

View file

@ -14,14 +14,14 @@ from ayon_core.pipeline import (
AVALON_CONTAINER_ID,
AYON_CONTAINER_ID,
)
from ayon_core.hosts.max.api.menu import AYONMenu
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.plugin import MS_CUSTOM_ATTRIB
from ayon_core.hosts.max import MAX_HOST_DIR
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_core.hosts.max")
log = logging.getLogger("ayon_max")
PLUGINS_DIR = os.path.join(MAX_HOST_DIR, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")

View file

@ -3,7 +3,7 @@ import contextlib
from pymxs import runtime as rt
from .lib import get_max_version, render_resolution
log = logging.getLogger("ayon_core.hosts.max")
log = logging.getLogger("ayon_max")
@contextlib.contextmanager

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Pre-launch to force 3ds max startup script."""
import os
from ayon_core.hosts.max import MAX_HOST_DIR
from ayon_max import MAX_HOST_DIR
from ayon_applications import PreLaunchHook, LaunchTypes

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating camera."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreateCamera(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreateCamera(plugin.MaxCreator):
label = "Camera"
product_type = "camera"
icon = "gear"
settings_category = "max"

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating raw max scene."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreateMaxScene(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreateMaxScene(plugin.MaxCreator):
label = "Max Scene"
product_type = "maxScene"
icon = "gear"
settings_category = "max"

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for model."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreateModel(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreateModel(plugin.MaxCreator):
label = "Model"
product_type = "model"
icon = "gear"
settings_category = "max"

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating pointcache alembics."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreatePointCache(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreatePointCache(plugin.MaxCreator):
label = "Point Cache"
product_type = "pointcache"
icon = "gear"
settings_category = "max"

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating point cloud."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreatePointCloud(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreatePointCloud(plugin.MaxCreator):
label = "Point Cloud"
product_type = "pointcloud"
icon = "gear"
settings_category = "max"

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating camera."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreateRedshiftProxy(plugin.MaxCreator):
@ -8,3 +8,5 @@ class CreateRedshiftProxy(plugin.MaxCreator):
label = "Redshift Proxy"
product_type = "redshiftproxy"
icon = "gear"
settings_category = "max"

View file

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating camera."""
import os
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
from ayon_core.lib import BoolDef
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_max.api.lib_rendersettings import RenderSettings
class CreateRender(plugin.MaxCreator):
@ -13,6 +13,8 @@ class CreateRender(plugin.MaxCreator):
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

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating review in Max."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
from ayon_core.lib import BoolDef, EnumDef, NumberDef
@ -12,6 +12,8 @@ class CreateReview(plugin.MaxCreator):
product_type = "review"
icon = "video-camera"
settings_category = "max"
review_width = 1920
review_height = 1080
percentSize = 100

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating TyCache."""
from ayon_core.hosts.max.api import plugin
from ayon_max.api import plugin
class CreateTyCache(plugin.MaxCreator):
@ -9,3 +9,5 @@ class CreateTyCache(plugin.MaxCreator):
label = "TyCache"
product_type = "tycache"
icon = "gear"
settings_category = "max"

View file

@ -3,8 +3,8 @@
import ayon_api
from ayon_core.pipeline import CreatedInstance, AutoCreator
from ayon_core.hosts.max.api import plugin
from ayon_core.hosts.max.api.lib import read, imprint
from ayon_max.api import plugin
from ayon_max.api.lib import read, imprint
from pymxs import runtime as rt
@ -17,6 +17,8 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator):
default_variant = "Main"
settings_category = "max"
def create(self):
variant = self.default_variant
current_instance = next(

View file

@ -1,12 +1,12 @@
import os
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set
)
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,

View file

@ -1,14 +1,14 @@
import os
from qtpy import QtWidgets, QtCore
from ayon_core.lib.attribute_definitions import EnumDef
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set,
is_headless
)
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise, get_previous_loaded_object,
update_custom_attribute_data,
remove_container_data

View file

@ -1,12 +1,12 @@
import os
from ayon_core.pipeline import load, get_representation_path
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
maintained_selection, unique_namespace
)

View file

@ -1,17 +1,17 @@
import os
from ayon_core.pipeline import load, get_representation_path
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise, get_previous_loaded_object,
update_custom_attribute_data,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set
)
from ayon_core.hosts.max.api.lib import maintained_selection
from ayon_max.api.lib import maintained_selection
class FbxModelLoader(load.LoaderPlugin):

View file

@ -1,13 +1,13 @@
import os
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
maintained_selection,
object_transform_set
)
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,

View file

@ -2,15 +2,15 @@ import os
from pymxs import runtime as rt
from ayon_core.pipeline.load import LoadError
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set,
get_plugins
)
from ayon_core.hosts.max.api.lib import maintained_selection
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.lib import maintained_selection
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,

View file

@ -6,9 +6,9 @@ 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_core.hosts.max.api import lib, maintained_selection
from ayon_core.hosts.max.api.lib import unique_namespace
from ayon_core.hosts.max.api.pipeline import (
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,
remove_container_data

View file

@ -1,20 +1,20 @@
import os
from ayon_core.pipeline import load, get_representation_path
from ayon_core.pipeline.load import LoadError
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,
remove_container_data
)
from ayon_core.hosts.max.api.lib import (
from ayon_max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set,
get_plugins
)
from ayon_core.hosts.max.api import lib
from ayon_max.api import lib
from pymxs import runtime as rt

View file

@ -1,11 +1,11 @@
import os
from ayon_core.hosts.max.api import lib, maintained_selection
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib, maintained_selection
from ayon_max.api.lib import (
unique_namespace,
)
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,

View file

@ -6,14 +6,14 @@ from ayon_core.pipeline import (
get_representation_path
)
from ayon_core.pipeline.load import LoadError
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
update_custom_attribute_data,
get_previous_loaded_object,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib
from ayon_max.api.lib import (
unique_namespace,
get_plugins
)

View file

@ -1,10 +1,10 @@
import os
from ayon_core.hosts.max.api import lib, maintained_selection
from ayon_core.hosts.max.api.lib import (
from ayon_max.api import lib, maintained_selection
from ayon_max.api.lib import (
unique_namespace,
)
from ayon_core.hosts.max.api.pipeline import (
from ayon_max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data,

View file

@ -5,10 +5,10 @@ import pyblish.api
from pymxs import runtime as rt
from ayon_core.pipeline.publish import KnownPublishError
from ayon_core.hosts.max.api import colorspace
from ayon_core.hosts.max.api.lib import get_max_version, get_current_renderer
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_core.hosts.max.api.lib_renderproducts import RenderProducts
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):

View file

@ -4,7 +4,7 @@ import pyblish.api
from pymxs import runtime as rt
from ayon_core.lib import BoolDef
from ayon_core.hosts.max.api.lib import get_max_version
from ayon_max.api.lib import get_max_version
from ayon_core.pipeline.publish import (
AYONPyblishPluginMixin,
KnownPublishError

View file

@ -41,8 +41,8 @@ import os
import pyblish.api
from ayon_core.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_core.hosts.max.api.lib import suspended_refresh
from ayon_max.api import maintained_selection
from ayon_max.api.lib import suspended_refresh
from ayon_core.lib import BoolDef

View file

@ -2,8 +2,8 @@ import os
import pyblish.api
from ayon_core.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_core.hosts.max.api.lib import convert_unit_scale
from ayon_max.api import maintained_selection
from ayon_max.api.lib import convert_unit_scale
class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin):

View file

@ -15,6 +15,8 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin):
families = ["camera", "maxScene", "model"]
optional = True
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -2,8 +2,8 @@ import os
import pyblish.api
from ayon_core.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_core.hosts.max.api.lib import suspended_refresh
from ayon_max.api import maintained_selection
from ayon_max.api.lib import suspended_refresh
from ayon_core.pipeline.publish import KnownPublishError
@ -18,6 +18,8 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin):
families = ["model"]
optional = True
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -3,7 +3,7 @@ import os
import pyblish.api
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_max.api import maintained_selection
from ayon_core.pipeline import OptionalPyblishPluginMixin, publish
@ -17,6 +17,8 @@ class ExtractModelUSD(publish.Extractor,
families = ["model"]
optional = True
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -3,7 +3,7 @@ import os
import pyblish.api
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_max.api import maintained_selection
from ayon_core.pipeline import publish

View file

@ -2,7 +2,7 @@ import os
import pyblish.api
from ayon_core.pipeline import publish
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_max.api import maintained_selection
class ExtractRedshiftProxy(publish.Extractor):

View file

@ -1,7 +1,7 @@
import os
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.max.api.preview_animation import (
from ayon_max.api.preview_animation import (
render_preview_animation
)

View file

@ -1,7 +1,7 @@
import os
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.max.api.preview_animation import render_preview_animation
from ayon_max.api.preview_animation import render_preview_animation
class ExtractThumbnail(publish.Extractor):

View file

@ -3,7 +3,7 @@ import os
import pyblish.api
from pymxs import runtime as rt
from ayon_core.hosts.max.api import maintained_selection
from ayon_max.api import maintained_selection
from ayon_core.pipeline import publish

View file

@ -5,8 +5,8 @@ import tempfile
from pymxs import runtime as rt
from ayon_core.lib import run_subprocess
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_core.hosts.max.api.lib_renderproducts import RenderProducts
from ayon_max.api.lib_rendersettings import RenderSettings
from ayon_max.api.lib_renderproducts import RenderProducts
class SaveScenesForCamera(pyblish.api.InstancePlugin):

View file

@ -61,6 +61,8 @@ class ValidateAttributes(OptionalPyblishPluginMixin,
actions = [RepairContextAction]
optional = True
settings_category = "max"
@classmethod
def get_invalid(cls, context):
attributes = json.loads(

View file

@ -6,7 +6,7 @@ from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
)
from ayon_core.hosts.max.api.action import SelectInvalidAction
from ayon_max.api.action import SelectInvalidAction
class ValidateCameraAttributes(OptionalPyblishPluginMixin,
@ -23,6 +23,8 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin,
actions = [SelectInvalidAction, RepairAction]
optional = True
settings_category = "max"
DEFAULTS = ["fov", "nearrange", "farrange",
"nearclip", "farclip"]
CAM_TYPE = ["Freecamera", "Targetcamera",

View file

@ -10,7 +10,7 @@ from ayon_core.pipeline.publish import (
PublishValidationError,
KnownPublishError
)
from ayon_core.hosts.max.api.lib import get_frame_range, set_timeline
from ayon_max.api.lib import get_frame_range, set_timeline
class ValidateFrameRange(pyblish.api.InstancePlugin,
@ -36,6 +36,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin,
optional = True
actions = [RepairAction]
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
self.log.debug("Skipping Validate Frame Range...")

View file

@ -7,7 +7,7 @@ from ayon_core.pipeline.publish import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.hosts.max.api.action import SelectInvalidAction
from ayon_max.api.action import SelectInvalidAction
from pymxs import runtime as rt
@ -27,6 +27,8 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin,
hosts = ["max"]
actions = [SelectInvalidAction, RepairAction]
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -9,7 +9,7 @@ from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
)
from ayon_core.hosts.max.api.lib import get_plugins
from ayon_max.api.lib import get_plugins
class ValidateLoadedPlugin(OptionalPyblishPluginMixin,
@ -25,6 +25,8 @@ class ValidateLoadedPlugin(OptionalPyblishPluginMixin,
optional = True
actions = [RepairAction]
settings_category = "max"
family_plugins_mapping = []
@classmethod

View file

@ -1,6 +1,6 @@
import pyblish.api
from ayon_core.hosts.max.api.action import SelectInvalidAction
from ayon_max.api.action import SelectInvalidAction
from ayon_core.pipeline.publish import (
ValidateMeshOrder,
OptionalPyblishPluginMixin,
@ -30,6 +30,8 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin,
actions = [SelectInvalidAction]
optional = True
settings_category = "max"
@classmethod
def get_invalid(cls, instance):
meshes = [member for member in instance.data["members"]

View file

@ -4,7 +4,7 @@ import re
import pyblish.api
from ayon_core.hosts.max.api.action import SelectInvalidAction
from ayon_max.api.action import SelectInvalidAction
from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
@ -39,6 +39,9 @@ class ValidateModelName(pyblish.api.InstancePlugin,
families = ["model"]
label = "Validate Model Name"
actions = [SelectInvalidAction]
settings_category = "max"
# defined by settings
regex = r"(.*)_(?P<subset>.*)_(GEO)"
# cache

View file

@ -5,7 +5,7 @@ from ayon_core.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.hosts.max.api.action import SelectInvalidAction
from ayon_max.api.action import SelectInvalidAction
def get_invalid_keys(obj):
@ -39,6 +39,8 @@ class ValidateNoAnimation(pyblish.api.InstancePlugin,
label = "Validate No Animation"
actions = [SelectInvalidAction]
settings_category = "max"
def process(self, instance):
if not self.is_active(instance.data):
return

View file

@ -4,7 +4,7 @@ from ayon_core.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin)
from ayon_core.pipeline.publish import RepairAction
from ayon_core.hosts.max.api.lib import get_current_renderer
from ayon_max.api.lib import get_current_renderer
from pymxs import runtime as rt

View file

@ -3,7 +3,7 @@ import pyblish.api
from ayon_core.pipeline import PublishValidationError
from pymxs import runtime as rt
from ayon_core.pipeline.publish import RepairAction
from ayon_core.hosts.max.api.lib import get_current_renderer
from ayon_max.api.lib import get_current_renderer
class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin):

View file

@ -7,7 +7,7 @@ from ayon_core.pipeline.publish import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
from ayon_max.api.lib_rendersettings import RenderSettings
class ValidateRenderPasses(OptionalPyblishPluginMixin,
@ -21,6 +21,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin,
label = "Validate Render Passes"
actions = [RepairAction]
settings_category = "max"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:

View file

@ -7,7 +7,7 @@ from ayon_core.pipeline.publish import (
RepairAction,
PublishValidationError
)
from ayon_core.hosts.max.api.lib import (
from ayon_max.api.lib import (
reset_scene_resolution,
imprint
)

View file

@ -1,15 +1,13 @@
# -*- 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)
from ayon_core.hosts.max.api import MaxHost
from ayon_core.pipeline import install_host
host = MaxHost()
install_host(host)

View file

@ -1,3 +1,9 @@
name = "max"
title = "Max"
version = "0.1.7"
version = "0.2.0"
client_dir = "ayon_max"
ayon_required_addons = {
"core": ">0.3.2",
}
ayon_compatible_addons = {}