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

Max: Implementation of OCIO configuration #5499

Merged
merged 23 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4ee32a
pre-hook ocio configuration for max 2024
moonyuet Aug 25, 2023
20df677
add ocio display view transform options in creator settings
moonyuet Aug 29, 2023
1c3764f
hound
moonyuet Aug 29, 2023
42033a0
remove unnecessary codes
moonyuet Aug 30, 2023
f0436f7
hound
moonyuet Aug 30, 2023
fc3598c
oscar's comment on the code changes
moonyuet Aug 31, 2023
fd58f34
Merge branch 'develop' into enhancement/ocio_configuration_max_2024
moonyuet Sep 1, 2023
8ea9755
Libor's comment on the ocio settngs in publish tab and wip of the sma…
moonyuet Sep 1, 2023
3caaf2a
hound & fixing the render camera issue
moonyuet Sep 1, 2023
0cee443
add colorspace data in collect review
moonyuet Sep 4, 2023
4b8a686
make sure the system wont do any popup if there is no main window
moonyuet Sep 5, 2023
c501330
Update openpype/hosts/max/api/lib.py
moonyuet Sep 5, 2023
c88b710
resolve the qt style issue with jakub's comment
moonyuet Sep 5, 2023
4aa8f5a
big roy's comments on the code fix
moonyuet Sep 6, 2023
1eaddb5
hound
moonyuet Sep 6, 2023
35b4666
add functions to check if the 3dsmax is in batch mode before popup di…
moonyuet Sep 6, 2023
fab390f
big roy's comment on check_colorspace function and some fix on IS_HEA…
moonyuet Sep 6, 2023
6d1407f
hound
moonyuet Sep 6, 2023
d887726
jakub's comment and resolve the conflict
moonyuet Sep 11, 2023
02a1602
grab the data from the colorspace settings instead of allowing the us…
moonyuet Sep 21, 2023
e6db06c
hound
moonyuet Sep 21, 2023
61e5005
hound
moonyuet Sep 21, 2023
c4202e7
Merge branch 'develop' into enhancement/ocio_configuration_max_2024
moonyuet Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openpype/hooks/pre_ocio_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class OCIOEnvHook(PreLaunchHook):
"fusion",
"blender",
"aftereffects",
"max",
"3dsmax",
"houdini",
"maya",
"nuke",
Expand Down
74 changes: 74 additions & 0 deletions openpype/hosts/max/api/lib.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
# -*- coding: utf-8 -*-
"""Library of functions useful for 3dsmax pipeline."""
import contextlib
import logging
import json
from typing import Any, Dict, Union

import six
from openpype.pipeline import get_current_project_name, colorspace
from openpype.settings import get_project_settings
from openpype.pipeline.context_tools import (
get_current_project, get_current_project_asset)
from openpype.style import load_stylesheet
from pymxs import runtime as rt


JSON_PREFIX = "JSON::"
log = logging.getLogger("openpype.hosts.max")


def get_main_window():
"""Acquire Max's main window"""
from qtpy import QtWidgets
top_widgets = QtWidgets.QApplication.topLevelWidgets()
name = "QmaxApplicationWindow"
for widget in top_widgets:
if (
widget.inherits("QMainWindow")
and widget.metaObject().className() == name
):
return widget
raise RuntimeError('Count not find 3dsMax main window.')


def imprint(node_name: str, data: dict) -> bool:
Expand Down Expand Up @@ -277,6 +297,7 @@ def set_context_setting():
"""
reset_scene_resolution()
reset_frame_range()
reset_colorspace()


def get_max_version():
Expand All @@ -292,6 +313,14 @@ def get_max_version():
return max_info[7]


def is_HEADLESS():
moonyuet marked this conversation as resolved.
Show resolved Hide resolved
"""Check if 3dsMax runs in batch mode.
If it returns True, it runs in 3dsbatch.exe
If it returns False, it runs in 3dsmax.exe
"""
return rt.maxops.isInNonInteractiveMode()


@contextlib.contextmanager
def viewport_camera(camera):
original = rt.viewport.getCamera()
Expand All @@ -314,6 +343,51 @@ def set_timeline(frameStart, frameEnd):
return rt.animationRange


def reset_colorspace():
"""OCIO Configuration
Supports in 3dsMax 2024+

"""
if int(get_max_version()) < 2024:
return
project_name = get_current_project_name()
colorspace_mgr = rt.ColorPipelineMgr
project_settings = get_project_settings(project_name)

max_config_data = colorspace.get_imageio_config(
project_name, "max", project_settings)
if max_config_data:
ocio_config_path = max_config_data["path"]
colorspace_mgr = rt.ColorPipelineMgr
colorspace_mgr.Mode = rt.Name("OCIO_Custom")
colorspace_mgr.OCIOConfigPath = ocio_config_path

moonyuet marked this conversation as resolved.
Show resolved Hide resolved
colorspace_mgr.OCIOConfigPath = ocio_config_path


def check_colorspace():
parent = get_main_window()
if parent is None:
log.info("Skipping outdated pop-up "
"because Max main window can't be found.")
if int(get_max_version()) >= 2024:
color_mgr = rt.ColorPipelineMgr
project_name = get_current_project_name()
project_settings = get_project_settings(project_name)
max_config_data = colorspace.get_imageio_config(
project_name, "max", project_settings)
if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"):
if not is_HEADLESS():
from openpype.widgets import popup
dialog = popup.Popup(parent=parent)
dialog.setWindowTitle("Warning: Wrong OCIO Mode")
dialog.setMessage("This scene has wrong OCIO "
"Mode setting.")
dialog.setButtonText("Fix")
dialog.setStyleSheet(load_stylesheet())
dialog.on_clicked.connect(reset_colorspace)
dialog.show()

def unique_namespace(namespace, format="%02d",
prefix="", suffix="", con_suffix="CON"):
"""Return unique namespace
Expand Down
8 changes: 8 additions & 0 deletions openpype/hosts/max/api/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def build_openpype_menu(self) -> QtWidgets.QAction:
frame_action.triggered.connect(self.frame_range_callback)
openpype_menu.addAction(frame_action)

colorspace_action = QtWidgets.QAction("Set Colorspace", openpype_menu)
colorspace_action.triggered.connect(self.colorspace_callback)
openpype_menu.addAction(colorspace_action)

return openpype_menu

def load_callback(self):
Expand Down Expand Up @@ -148,3 +152,7 @@ def resolution_callback(self):
def frame_range_callback(self):
"""Callback to reset frame range"""
return lib.reset_frame_range()

def colorspace_callback(self):
"""Callback to reset colorspace"""
return lib.reset_colorspace()
3 changes: 3 additions & 0 deletions openpype/hosts/max/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def context_setting():
rt.callbacks.addScript(rt.Name('systemPostNew'),
context_setting)

rt.callbacks.addScript(rt.Name('filePostOpen'),
lib.check_colorspace)
moonyuet marked this conversation as resolved.
Show resolved Hide resolved

def has_unsaved_changes(self):
# TODO: how to get it from 3dsmax?
return True
Expand Down
33 changes: 27 additions & 6 deletions openpype/hosts/max/plugins/create/create_render.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating camera."""
import os
from openpype.hosts.max.api import plugin
from openpype.hosts.max.api.lib_rendersettings import RenderSettings
from openpype.hosts.max.api.lib import get_max_version
from openpype.lib import EnumDef
from pymxs import runtime as rt


class CreateRender(plugin.MaxCreator):
Expand All @@ -13,12 +15,7 @@ class CreateRender(plugin.MaxCreator):
icon = "gear"

def create(self, subset_name, instance_data, pre_create_data):
from pymxs import runtime as rt
sel_obj = list(rt.selection)
file = rt.maxFileName
filename, _ = os.path.splitext(file)
instance_data["AssetName"] = filename

instance = super(CreateRender, self).create(
subset_name,
instance_data,
Expand All @@ -30,3 +27,27 @@ def create(self, subset_name, instance_data, pre_create_data):
RenderSettings(self.project_settings).set_render_camera(sel_obj)
# set output paths for rendering(mandatory for deadline)
RenderSettings().render_output(container_name)

def get_instance_attr_defs(self):
if int(get_max_version()) >= 2024:
default_value = ""
display_views = []
colorspace_mgr = rt.ColorPipelineMgr
for display in sorted(colorspace_mgr.GetDisplayList()):
for view in sorted(colorspace_mgr.GetViewList(display)):
display_views.append({
"value": "||".join((display, view))
})
if display == "ACES" and view == "sRGB":
default_value = "{0}||{1}".format(
display, view
)
else:
display_views = ["sRGB||ACES 1.0 SDR-video"]

return [
EnumDef("ocio_display_view_transform",
display_views,
default=default_value,
label="OCIO Displays and Views")
]
16 changes: 16 additions & 0 deletions openpype/hosts/max/plugins/publish/collect_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def process(self, instance):
files_by_aov.update(aovs)

camera = rt.viewport.GetCamera()
if instance.data.get("members"):
camera_list = [member for member in instance.data["members"]
if rt.ClassOf(member) == rt.Camera.Classes]
if camera_list:
camera = camera_list[-1]

instance.data["cameras"] = [camera.name] if camera else None # noqa

if "expectedFiles" not in instance.data:
Expand Down Expand Up @@ -64,6 +70,16 @@ def process(self, instance):
instance.data["colorspaceConfig"] = ""
instance.data["colorspaceDisplay"] = "sRGB"
instance.data["colorspaceView"] = "ACES 1.0 SDR-video"

if int(get_max_version()) >= 2024:
moonyuet marked this conversation as resolved.
Show resolved Hide resolved
creator_attribute = instance.data["creator_attributes"]
display_view_transform = creator_attribute["ocio_display_view_transform"] # noqa
display, view_transform = display_view_transform.split("||", 1)
colorspace_mgr = rt.ColorPipelineMgr
instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath
instance.data["colorspaceDisplay"] = display
instance.data["colorspaceView"] = view_transform

instance.data["renderProducts"] = colorspace.ARenderProduct()
instance.data["publishJobState"] = "Suspended"
instance.data["attachTo"] = []
Expand Down
35 changes: 33 additions & 2 deletions openpype/hosts/max/plugins/publish/collect_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pyblish.api

from pymxs import runtime as rt
from openpype.lib import BoolDef
from openpype.lib import BoolDef, EnumDef
from openpype.hosts.max.api.lib import get_max_version
from openpype.pipeline.publish import OpenPypePyblishPluginMixin


Expand Down Expand Up @@ -43,6 +44,16 @@ def process(self, instance):
"dspSafeFrame": attr_values.get("dspSafeFrame"),
"dspFrameNums": attr_values.get("dspFrameNums")
}

if int(get_max_version()) >= 2024:
display_view_transform = attr_values.get(
"ocio_display_view_transform")
display, view_transform = display_view_transform.split("||", 1)
colorspace_mgr = rt.ColorPipelineMgr
instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath
instance.data["colorspaceDisplay"] = display
instance.data["colorspaceView"] = view_transform

# Enable ftrack functionality
instance.data.setdefault("families", []).append('ftrack')

Expand All @@ -54,8 +65,28 @@ def process(self, instance):

@classmethod
def get_attribute_defs(cls):

default_value = ""
display_views = []
if int(get_max_version()) >= 2024:
colorspace_mgr = rt.ColorPipelineMgr
displays = colorspace_mgr.GetDisplayList()
for display in sorted(displays):
views = colorspace_mgr.GetViewList(display)
for view in sorted(views):
display_views.append({
"value": "||".join((display, view))
})
if display == "ACES" and view == "sRGB":
default_value = "{0}||{1}".format(
display, view
)
else:
display_views = ["sRGB||ACES 1.0 SDR-video"]
return [
EnumDef("ocio_display_view_transform",
items=display_views,
default=default_value,
label="OCIO Displays and Views"),
BoolDef("dspGeometry",
label="Geometry",
default=True),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,10 @@ def _use_published_name(self, data, project_settings):
plugin_data["redshift_SeparateAovFiles"] = instance.data.get(
"separateAovFiles")
if instance.data["cameras"]:
plugin_info["Camera0"] = None
plugin_info["Camera"] = instance.data["cameras"][0]
plugin_info["Camera1"] = instance.data["cameras"][0]
camera = instance.data["cameras"][0]
plugin_info["Camera0"] = camera
plugin_info["Camera"] = camera
plugin_info["Camera1"] = camera
self.log.debug("plugin data:{}".format(plugin_data))
plugin_info.update(plugin_data)

Expand Down
Loading