Skip to content

Commit 3ed72da

Browse files
authored
SG-32814 Review and unify code handling supported DCC versions (shotgunsoftware#49)
* Update Max supported versions * Fixup minor regexp issue matching too large * Unify pre-commit config file * Better version management * Fixup forgotten changes * Black * Improvements * Black * Add missing comment * Remove warning * Apply review from Copilot
1 parent 3fcdaea commit 3ed72da

5 files changed

Lines changed: 158 additions & 52 deletions

File tree

engine.py

Lines changed: 150 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616

1717
import pymxs
1818

19+
# Max versions compatibility constants
20+
VERSION_OLDEST_COMPATIBLE = 2023
21+
VERSION_OLDEST_SUPPORTED = 2024
22+
VERSION_NEWEST_SUPPORTED = 2025
23+
# Caution: make sure compatibility_dialog_min_version default value in info.yml
24+
# is equal to VERSION_NEWEST_SUPPORTED
25+
1926

2027
class MaxEngine(sgtk.platform.Engine):
2128
"""
@@ -47,17 +54,13 @@ def host_info(self):
4754
}
4855
4956
References:
50-
http://docs.autodesk.com/3DSMAX/16/ENU/3ds-Max-Python-API-Documentation/index.html
57+
https://help.autodesk.com/view/MAXDEV/2026/ENU/?guid=MAXDEV_Python_what_s_new_in_3ds_max_python_api_html
5158
"""
5259
host_info = {"name": "3ds Max", "version": "unknown"}
5360

54-
try:
55-
host_info["version"] = str(
56-
self._max_version_to_year(self._get_max_version())
57-
)
58-
except:
59-
# Fallback to initialized values above
60-
pass
61+
max_version_year = self.max_version_year
62+
if max_version_year:
63+
host_info["version"] = str(max_version_year)
6164

6265
return host_info
6366

@@ -73,6 +76,7 @@ def __init__(self, *args, **kwargs):
7376
self._dock_widgets = []
7477

7578
self._max_version = None
79+
self._max_version_year = None
7680

7781
# proceed about your business
7882
sgtk.platform.Engine.__init__(self, *args, **kwargs)
@@ -98,35 +102,133 @@ def pre_app_init(self):
98102

99103
self.log_debug("%s: Initializing..." % self)
100104

101-
if self._get_max_version() > MaxEngine.MAXIMUM_SUPPORTED_VERSION:
102-
# Untested max version
105+
url_doc_supported_versions = "https://help.autodesk.com/view/SGDEV/ENU/?guid=SGD_si_integrations_engine_supported_versions_html"
106+
107+
if self.max_version_year < VERSION_OLDEST_COMPATIBLE:
108+
# Old incompatible version
109+
message = """
110+
Flow Production Tracking is no longer compatible with {product} versions older
111+
than {version}.
112+
113+
For information regarding support engine versions, please visit this page:
114+
{url_doc_supported_versions}
115+
""".strip()
116+
117+
if self.has_ui:
118+
try:
119+
QtGui.QMessageBox.critical(
120+
None, # parent
121+
"Error - Flow Production Tracking Compatibility!".ljust(
122+
# Padding to try to prevent the dialog being insanely narrow
123+
70
124+
),
125+
message.replace(
126+
# Presence of \n breaks the Rich Text Format
127+
"\n",
128+
"<br>",
129+
).format(
130+
product="3ds Max",
131+
url_doc_supported_versions='<a href="{u}">{u}</a>'.format(
132+
u=url_doc_supported_versions,
133+
),
134+
version=VERSION_OLDEST_COMPATIBLE,
135+
),
136+
)
137+
except: # nosec B110
138+
# It is unlikely that the above message will go through
139+
# on old versions of Max (Python2, Qt4, ...).
140+
# But there is nothing more we can do here.
141+
pass
142+
143+
raise sgtk.TankError(
144+
message.format(
145+
product="3ds Max",
146+
url_doc_supported_versions=url_doc_supported_versions,
147+
version=VERSION_OLDEST_COMPATIBLE,
148+
)
149+
)
103150

104-
highest_supported_version = self._max_version_to_year(
105-
MaxEngine.MAXIMUM_SUPPORTED_VERSION
151+
elif self.max_version_year < VERSION_OLDEST_SUPPORTED:
152+
# Older than the oldest supported version
153+
self.logger.warning(
154+
"Flow Production Tracking no longer supports {product} "
155+
"versions older than {version}".format(
156+
product="3ds Max",
157+
version=VERSION_OLDEST_SUPPORTED,
158+
)
106159
)
107160

108-
msg = (
109-
"Flow Production Tracking!\n\n"
110-
"The Flow Production Tracking has not yet been fully tested with 3ds Max versions greater than %s. "
111-
"You can continue to use the Toolkit but you may experience bugs or instability. "
112-
"Please report any issues you see via %s."
113-
% (
114-
highest_supported_version,
115-
sgtk.support_url,
161+
if self.has_ui:
162+
QtGui.QMessageBox.warning(
163+
None, # parent
164+
"Warning - Flow Production Tracking Compatibility!".ljust(
165+
# Padding to try to prevent the dialog being insanely narrow
166+
70
167+
),
168+
"""
169+
Flow Production Tracking no longer supports {product} versions older than
170+
{version}.
171+
You can continue to use Toolkit but you may experience bugs or instabilities.
172+
173+
For information regarding support engine versions, please visit this page:
174+
{url_doc_supported_versions}
175+
""".strip()
176+
.replace(
177+
# Presence of \n breaks the Rich Text Format
178+
"\n",
179+
"<br>",
180+
)
181+
.format(
182+
product="3ds Max",
183+
url_doc_supported_versions='<a href="{u}">{u}</a>'.format(
184+
u=url_doc_supported_versions,
185+
),
186+
version=VERSION_OLDEST_SUPPORTED,
187+
),
188+
)
189+
190+
elif self.max_version_year <= VERSION_NEWEST_SUPPORTED:
191+
# Within the range of supported versions
192+
self.logger.debug(f"Running 3ds Max version {self.max_version_year}")
193+
else: # Newer than the newest supported version (untested)
194+
self.logger.warning(
195+
"Flow Production Tracking has not yet been fully tested with "
196+
"{product} version {version}.".format(
197+
product="3ds Max",
198+
version=self.max_version_year,
116199
)
117200
)
118201

119-
# Display warning dialog
120-
max_year = self._max_version_to_year(self._get_max_version())
121-
max_next_year = highest_supported_version + 1
122-
if max_year >= self.get_setting(
123-
"compatibility_dialog_min_version", max_next_year
202+
if self.has_ui and self.max_version_year >= self.get_setting(
203+
"compatibility_dialog_min_version"
124204
):
125205
QtGui.QMessageBox.warning(
126-
None, "PTR Warning", "Warning - {0}".format(msg)
206+
None, # parent
207+
"Warning - Flow Production Tracking Compatibility!".ljust(
208+
# Padding to try to prevent the dialog being insanely narrow
209+
70
210+
),
211+
"""
212+
Flow Production Tracking has not yet been fully tested with {product} version
213+
{version}.
214+
You can continue to use Toolkit but you may experience bugs or instabilities.
215+
216+
Please report any issues to:
217+
{support_url}
218+
""".strip()
219+
.replace(
220+
# Presence of \n breaks the Rich Text Format
221+
"\n",
222+
"<br>",
223+
)
224+
.format(
225+
product="3ds Max",
226+
support_url='<a href="{u}">{u}</a>'.format(
227+
u=sgtk.support_url,
228+
),
229+
version=self.max_version_year,
230+
),
127231
)
128-
# and log the warning
129-
self.log_warning(msg)
130232

131233
self._safe_dialog = []
132234
# Keep the dialog to prevent the garbage collector from delete it
@@ -165,7 +267,7 @@ def eventFilter(self, obj, event):
165267
# possible that we'll get back a QCoreApplication from Max, which won't
166268
# carry references to a stylesheet. In that case, we apply our styling
167269
# to the dialog parent, which will be the top-level Max window.
168-
if self._max_version_to_year(self._get_max_version()) < 2018:
270+
if self.max_version_year < 2018:
169271
parent_widget = sgtk.platform.qt.QtCore.QCoreApplication.instance()
170272
else:
171273
parent_widget = self._get_dialog_parent()
@@ -175,7 +277,7 @@ def eventFilter(self, obj, event):
175277
if "toolkit 3dsmax style extension" not in curr_stylesheet:
176278
# If we're in pre-2017 Max then we need to handle our own styling. Otherwise
177279
# we just inherit from Max.
178-
if self._max_version_to_year(self._get_max_version()) < 2017:
280+
if self.max_version_year < 2017:
179281
self._initialize_dark_look_and_feel()
180282

181283
curr_stylesheet += "\n\n /* toolkit 3dsmax style extension */ \n\n"
@@ -239,7 +341,7 @@ def _post_app_init(self):
239341
Called from the main thread when all apps have initialized
240342
"""
241343
# set up menu handler
242-
if self._max_version_to_year(self._get_max_version()) >= 2025:
344+
if self.max_version_year >= 2025:
243345
self._menu_generator = self.tk_3dsmax.MenuGenerator_callbacks(self)
244346
self._add_shotgun_menu()
245347

@@ -361,8 +463,7 @@ def destroy_engine(self):
361463
Called when the engine is shutting down
362464
"""
363465
self.log_debug("%s: Destroying..." % self)
364-
365-
if self._max_version_to_year(self._get_max_version()) < 2025:
466+
if self.max_version_year < 2025:
366467
pymxs.runtime.callbacks.removeScripts(
367468
pymxs.runtime.Name("postLoadingMenus"),
368469
id=pymxs.runtime.Name("sg_tk_on_menus_loaded"),
@@ -410,7 +511,7 @@ def _get_dialog_parent(self):
410511
# Older versions of Max make use of special logic in _create_dialog
411512
# to handle window parenting. If we can, though, we should go with
412513
# the more standard approach to getting the main window.
413-
if self._max_version_to_year(self._get_max_version()) > 2020:
514+
if self.max_version_year > 2020:
414515
# getMAXHWND returned a float instead of a long, which was completely
415516
# unusable with PySide in 2017 to 2020, but starting 2021
416517
# we can start using it properly.
@@ -442,7 +543,7 @@ def show_panel(self, panel_id, title, bundle, widget_class, *args, **kwargs):
442543

443544
self.log_debug("Begin showing panel %s" % panel_id)
444545

445-
if self._max_version_to_year(self._get_max_version()) <= 2017:
546+
if self.max_version_year <= 2017:
446547
# Qt docking is supported in version 2018 and later.
447548
self.log_warning(
448549
"Panel functionality not implemented. Falling back to showing "
@@ -657,17 +758,22 @@ def safe_dialog_exec(self, func):
657758
dialog.activateWindow() # for Windows
658759
dialog.raise_() # for MacOS
659760

660-
##########################################################################################
661-
# Latest supported max version
662-
MAXIMUM_SUPPORTED_VERSION = 28000
663-
664-
def _max_version_to_year(self, version):
761+
@property
762+
def max_version_year(self):
665763
"""
666764
Get the max year from the max release version.
667-
Note that while 17000 is 2015, 17900 would be 2016 alpha
668765
"""
669-
year = 2000 + (math.ceil(version / 1000.0) - 2)
670-
return year
766+
767+
if self._max_version_year is None:
768+
max_version = self._get_max_version()
769+
try:
770+
assert isinstance(max_version[7], int)
771+
self._max_version_year = max_version[7]
772+
except (AssertionError, IndexError, TypeError) as err:
773+
self.log_debug("Unable to extract Max version {}".format(err))
774+
self._max_version_year = 0
775+
776+
return self._max_version_year
671777

672778
def _get_max_version(self):
673779
"""
@@ -677,7 +783,7 @@ def _get_max_version(self):
677783
# Make sure this gets executed from the main thread because pymxs can't be used
678784
# from a background thread.
679785
self._max_version = self.execute_in_main_thread(
680-
lambda: pymxs.runtime.maxVersion()[0]
786+
lambda: pymxs.runtime.maxVersion()
681787
)
682788
# 3dsMax Version returns a number which contains max version, sdk version, etc...
683789
return self._max_version

hooks/tk-multi-loader2/basic/scene_actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def execute_action(self, name, params, sg_publish_data):
161161
# If this is an Alembic cache, then we can import that.
162162
if path.lower().endswith(".abc"):
163163
# Note that native Alembic support is only available in Max 2016+.
164-
if app.engine._max_version_to_year(app.engine._get_max_version()) >= 2016:
164+
if app.engine.max_version_year >= 2016:
165165
self._import_alembic(path)
166166
else:
167167
app.log_warning(

hooks/tk-multi-shotgunpanel/basic/scene_actions.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,7 @@ def execute_action(self, name, params, sg_data):
118118
# If this is an Alembic cache, then we can import that.
119119
if name == "merge" and path.lower().endswith(".abc"):
120120
# Note that native Alembic support is only available in Max 2016+.
121-
if (
122-
app.engine._max_version_to_year(app.engine._get_max_version())
123-
>= 2016
124-
):
121+
if app.engine.max_version_year >= 2016:
125122
self._import_alembic(path)
126123
else:
127124
app.log_warning(

info.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ configuration:
5252
type: int
5353
description: "Specify the minimum Application major version that will prompt a warning if
5454
it isn't yet fully supported and tested with Toolkit. To disable the warning
55-
dialog for the version you are testing, it is recomended that you set this
55+
dialog for the version you are testing, it is recommended that you set this
5656
value to the current major version + 1."
57-
default_value: 2026
57+
default_value: 2026
5858

5959
launch_builtin_plugins:
6060
type: list

startup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
from sgtk.platform import SoftwareLauncher, SoftwareVersion, LaunchInformation
1717

18+
# Max versions compatibility constants
19+
VERSION_OLDEST_COMPATIBLE = 2021
20+
1821

1922
class MaxLauncher(SoftwareLauncher):
2023
"""
@@ -28,7 +31,7 @@ def minimum_supported_version(self):
2831
"""
2932
The minimum software version that is supported by the launcher.
3033
"""
31-
return "2021"
34+
return str(VERSION_OLDEST_COMPATIBLE)
3235

3336
def scan_software(self):
3437
"""

0 commit comments

Comments
 (0)