Skip to content

Commit a2bf34d

Browse files
authored
Maxx 75457 support pyside6 (#40)
1 parent 416c689 commit a2bf34d

File tree

45 files changed

+416
-87
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+416
-87
lines changed

.pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
355355
# (useful for modules/projects where namespaces are manipulated during runtime
356356
# and thus existing member attributes cannot be deduced by static analysis). It
357357
# supports qualified module names, as well as Unix pattern matching.
358-
ignored-modules=pymxs,debugpy,PySide2,menuhook,mxthread,socketio
358+
ignored-modules=pymxs,debugpy,qtpy,menuhook,mxthread,socketio
359359

360360
# Show a hint with possible names when a member name was not found. The aspect
361361
# of finding the hint is based on edit distance.

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ working as expected
2727

2828
### New content
2929

30-
- Drop maxscript code on a rich text window to get a python translation [mxstranslate](/src/packages/mxstranslate/README.md)
30+
- New with 3dsMax 2025: [Plugin Packages in 2025 and Integration With the New Menu System](/doc/pluginpackage.md)
3131

3232
### Samples
3333

3434
The samples below are translations of [MAXScript How Tos](https://help.autodesk.com/view/MAXDEV/2022/ENU/?guid=GUID-25C9AD58-3665-471E-8B4B-54A094C1D5C9) that
3535
can be found in the 3ds Max online documentation.
3636

3737
The conversion from MaxScript to Python could have been more mechanical but we chose to implement
38-
the Python version in the best Python way known to us. An example of this is that we use PySide2
38+
the Python version in the best Python way known to us. An example of this is that we use PySide
3939
(Qt) for the UI as much as possible instead of using more traditional 3ds Max ui mechanisms.
4040

4141
*How To?*
@@ -56,6 +56,7 @@ the Python version in the best Python way known to us. An example of this is tha
5656
- Run code on thre main thread [mxthread](/src/packages/mxthread/README.md)
5757
- Automatically convert maxscript to python [mxs2py](/src/packages/mxs2py/README.md)
5858
- Use socketio from 3dsMax [socketioclient](/src/packages/socketioclient/README.md)
59+
- Drop maxscript code on a rich text window to get a python translation [mxstranslate](/src/packages/mxstranslate/README.md)
5960

6061
## Python Samples
6162

doc/install.md

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ It is possible to break up the installation in two steps.
3939

4040
- The [installstartup.sh](/installstartup.sh) script can be used
4141
from bash to install pip and [pystartup.ms](/src/pystartup/pystartup.ms).
42+
In 2025 and above, [adn-devtech-python-howtos](/src/adn-devtech-python-howtos)
43+
is installed instead of pystartup.ms.
44+
4245
It needs to run in the 3ds Max installation directory.
4346

4447
You may do only this step if you don't want the HowTos but you

doc/pluginpackage.md

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Plugin Packages in 2025 and Integration With the New Menu System
2+
3+
In 3ds Max 2025 and above, plugin packages may contain python script components.
4+
When installing the samples from this repo in a 2025 version of 3ds Max,
5+
the [adn-devtech-python-howtos](/src/adn-devtech-python-howtos) plugin package
6+
is copied to "$ProgramData/Autodesk/ApplicationPlugins". The [PackageContents.xml](/src/adn-devtech-python-howtos/PackageContents.xml)
7+
file of this plugin package declares a python pre-start-up script:
8+
9+
```xml
10+
<Components Description="pre-start-up scripts parts">
11+
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
12+
<ComponentEntry ModuleName="./scripts/pyStartup.py" />
13+
</Components>
14+
```
15+
16+
The [./scripts/pyStartup.py](/src/adn-devtech-python-howtos/scripts/pyStartup.py) script
17+
first does what [pystartup.ms](/src/pystartup/pystartup.ms) used to do:
18+
19+
```python
20+
def _python_startup():
21+
try:
22+
import pkg_resources
23+
except ImportError:
24+
print('startup Python modules require pip to be installed.')
25+
return
26+
for dist in pkg_resources.working_set:
27+
entrypt = pkg_resources.get_entry_info(dist, '3dsMax', 'startup')
28+
if not (entrypt is None):
29+
try:
30+
fcn = entrypt.load()
31+
fcn()
32+
except Exception as e:
33+
print(f'skipped package startup for {dist} because {e}, startup not working')
34+
35+
```
36+
37+
Then it integrates the samples into the new menu system of 2025:
38+
39+
```python
40+
# configure 2025 menus
41+
from pymxs import runtime as rt
42+
from menuhook import register_howtos_menu_2025
43+
def menu_func():
44+
menumgr = rt.callbacks.notificationparam()
45+
register_howtos_menu_2025(menumgr)
46+
47+
# menu system
48+
cuiregid = rt.name("cuiRegisterMenus")
49+
howtoid = rt.name("pyScriptHowtoMenu")
50+
rt.callbacks.removescripts(id=cuiregid)
51+
rt.callbacks.addscript(cuiregid, menu_func, id=howtoid)
52+
53+
```
54+
55+
56+
## Integration With the New Menu System
57+
58+
The [menuhook](/src/menuhook/) code has been reworked to integrate menu items for the
59+
various howtos into the new menu system:
60+
61+
```python
62+
def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_menuid=None, id_2025=None):
63+
```
64+
65+
Takes two new parameters:
66+
- `in2025_menuid` : the guid of the containing menu
67+
- `id_2025` : the guid of the item to create
68+
69+
And stores the needed menu items in the `registered_items` list:
70+
71+
```python
72+
registered_items.append((in2025_menuid, id_2025, category, action))
73+
```
74+
75+
The `register_howotos_menu_2025` function, called whenever the menu system needs to regenerate its structure, uses the `registered_items` list to add items to the menu manager:
76+
77+
```python
78+
# hook the registered items
79+
for reg in registered_items:
80+
(in2025_menuid, id_2025, category, action) = reg
81+
scriptmenu = menumgr.getmenubyid(in2025_menuid)
82+
if scriptmenu is not None:
83+
try:
84+
actionitem = scriptmenu.createaction(id_2025, 647394, f"{action}`{category}")
85+
except Exception as e:
86+
print(f"Could not create item {category}, {action} in menu {in2025_menuid} because {e}")
87+
else:
88+
print(f"Could not create item {category}, {action}, in missing menu {in2025_menuid}")
89+
90+
91+
```

doc/uninstall.md

+13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ needed to remove them are explained at the bottom of this page).
1616

1717
## Removing pystartup.ms (manual uninstall)
1818

19+
### For 3dsMax before 2025
1920
After the installation, pystartup.ms will be copied to:
2021

2122
"$HOME/AppData/Local/Autodesk/3dsMax/2022 - 64bit/ENU/scripts/startup"
@@ -29,6 +30,18 @@ It can simply be removed from there.
2930
By removing this file none of the HowTo packages will be started
3031
automatically when 3ds Max starts.
3132

33+
### For 3dsMax 2025 and greater
34+
35+
Starting with 2025, pystartup.ms is no longer needed. Instead the
36+
[adn-devtech-python-howtos](/src/adn-devtech-python-howtos) directory is
37+
copied to "C:\ProgramData\Autodesk\ApplicationPlugins".
38+
39+
It can be manually removed by doing (from gitbash):
40+
41+
```bash
42+
rm -fr "$ProgramData/Autodesk/ApplicationPlugins/adn-devtech-python-howtos"
43+
```
44+
3245
## Removing pip (manual uninstall)
3346

3447
The installation script also install pip in user mode.

scripts/inst.sh

+10-2
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,19 @@ installpip() {
6666
./python.exe "$getpip/get-pip.py" --user
6767
fi
6868
fi
69+
# update to the latest pip
70+
./python.exe -m pip install --upgrade pip
71+
./python.exe -m pip install wheel
6972
}
7073

71-
# install pystartup.ms
74+
# install pystartup.ms or adn-devtech-python-howtos (plugin package) for 2025
7275
installpystartup() {
73-
cp "$script/src/pystartup/pystartup.ms" "$startuppath"
76+
if [ "$version" -lt "2025" ]
77+
then
78+
cp "$script/src/pystartup/pystartup.ms" "$startuppath"
79+
else
80+
cp -fr "$script/src/adn-devtech-python-howtos" "$ProgramData/Autodesk/ApplicationPlugins"
81+
fi
7482
}
7583

7684

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ApplicationPackage
3+
SchemaVersion="1.0"
4+
AutodeskProduct="3ds Max"
5+
Name="ADNDevTechPythonHowTos"
6+
Description=""
7+
AppVersion="0.0.1"
8+
ProductType="Application"
9+
AppNameSpace="appstore.exchange.autodesk.com"
10+
Author="Autodesk"
11+
ProductCode="{31C5CD2E-F2A7-4716-9C50-B4CD11C1C568}"
12+
UpgradeCode= "{14C34C70-07C7-4562-B3A8-5A8C4C95C97A}">
13+
<CompanyDetails Name="Autodesk" Url="https://www.autodesk.com" />
14+
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
15+
<Components Description="pre-start-up scripts parts">
16+
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
17+
<ComponentEntry ModuleName="./scripts/pyStartup.py" />
18+
</Components>
19+
</ApplicationPackage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
def _python_startup():
2+
try:
3+
import pkg_resources
4+
except ImportError:
5+
print('startup Python modules require pip to be installed.')
6+
return
7+
for dist in pkg_resources.working_set:
8+
entrypt = pkg_resources.get_entry_info(dist, '3dsMax', 'startup')
9+
if not (entrypt is None):
10+
try:
11+
fcn = entrypt.load()
12+
fcn()
13+
except Exception as e:
14+
print(f'skipped package startup for {dist} because {e}, startup not working')
15+
16+
# configure 2025 menus
17+
from pymxs import runtime as rt
18+
from menuhook import register_howtos_menu_2025
19+
def menu_func():
20+
menumgr = rt.callbacks.notificationparam()
21+
register_howtos_menu_2025(menumgr)
22+
23+
# menu system
24+
cuiregid = rt.name("cuiRegisterMenus")
25+
howtoid = rt.name("pyScriptHowtoMenu")
26+
rt.callbacks.removescripts(id=cuiregid)
27+
rt.callbacks.addscript(cuiregid, menu_func, id=howtoid)
28+
29+
30+
_python_startup()
31+
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,99 @@
11
"""
22
inbrowserhelp example: inbrowserhelp sample
33
"""
4+
from sys import version_info
45
import webbrowser
5-
import menuhook
66
from pymxs import runtime as rt
7+
import menuhook
78
MAX_VERSION = rt.maxversion()[7]
8-
MAX_HELP = f"help.autodesk.com/view/MAXDEV/{MAX_VERSION}/ENU"
9-
10-
TOPICS = [
11-
("gettingstarted", "Getting Started With Python in 3ds Max",
12-
f"{MAX_HELP}/?guid=Max_Python_API_tutorials_creating_the_dialog_html"),
13-
("howtos", "Python HowTos Github Repo",
14-
"github.com/ADN-DevTech/3dsMax-Python-HowTos"),
15-
("samples", "Python samples (Github Repo)",
16-
"github.com/ADN-DevTech/3dsMax-Python-HowTos/tree/master/src/samples"),
17-
("pymxs", "Pymxs Online Documentation",
18-
f"{MAX_HELP}/?guid=Max_Python_API_using_pymxs_html"),
19-
("pyside2", "Qt for Python Documentation (PySide2)",
20-
"doc.qt.io/qtforpython/contents.html"),
21-
("python", "Python 3.7 Documentation",
22-
"docs.python.org/3.7/")
9+
MAX_HELP = "help.autodesk.com/view/MAXDEV"
10+
11+
PYTHON_VERSION = f"{version_info[0]}.{version_info[1]}"
12+
13+
MAX_VERSION_TOPICS = {
14+
2021: [
15+
("gettingstarted",
16+
"Getting Started With Python in 3ds Max",
17+
f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_about_the_3ds_max_python_api_html",
18+
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
19+
("pymxs",
20+
"Pymxs Online Documentation",
21+
f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_using_pymxs_pymxs_module_html",
22+
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
23+
],
24+
2022: [
25+
("gettingstarted",
26+
"Getting Started With Python in 3ds Max",
27+
f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
28+
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
29+
("pymxs",
30+
"Pymxs Online Documentation",
31+
f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_using_pymxs_html",
32+
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
33+
],
34+
2023: [
35+
("gettingstarted",
36+
"Getting Started With Python in 3ds Max",
37+
f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
38+
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
39+
("pymxs",
40+
"Pymxs Online Documentation",
41+
f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_using_pymxs_html",
42+
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
43+
],
44+
2024: [
45+
("gettingstarted",
46+
"Getting Started With Python in 3ds Max",
47+
f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
48+
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
49+
("pymxs",
50+
"Pymxs Online Documentation",
51+
f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_using_pymxs_html",
52+
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
53+
],
54+
2025: [
55+
("gettingstarted",
56+
"Getting Started With Python in 3ds Max",
57+
f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
58+
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
59+
("pymxs",
60+
"Pymxs Online Documentation",
61+
f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_using_pymxs_html",
62+
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
63+
]
64+
}
65+
66+
def get_version_topics(version):
67+
"""Get the version-dependent topics, defaulting on the latest
68+
if the requested one does not exist"""
69+
return MAX_VERSION_TOPICS[version if version in MAX_VERSION_TOPICS else 2024]
70+
71+
V_TOPICS = get_version_topics(MAX_VERSION)
72+
73+
PYSIDE6_DOC = ("pyside6",
74+
"Qt for Python Documentation (PySide6)",
75+
"doc.qt.io/qtforpython-6/index.html",
76+
"E0E5F945-CD55-404A-840B-81540829E4C4")
77+
78+
PYSIDE2_DOC = ("pyside2",
79+
"Qt for Python Documentation (PySide2)",
80+
"doc.qt.io/qtforpython-5/contents.html",
81+
"13EEE11E-1BBB-470E-B757-F536D91215A9")
82+
83+
TOPICS = V_TOPICS + [
84+
("howtos",
85+
"Python HowTos Github Repo",
86+
"github.com/ADN-DevTech/3dsMax-Python-HowTos",
87+
"2504EEA5-27D6-4EA0-A7A3-B3C058777ADC"),
88+
("samples",
89+
"Python samples (Github Repo)",
90+
"github.com/ADN-DevTech/3dsMax-Python-HowTos/tree/master/src/samples",
91+
"8ED9D9CC-3799-435D-8016-0F8F16D84004"),
92+
PYSIDE6_DOC if MAX_VERSION >= 2025 else PYSIDE2_DOC,
93+
("python",
94+
f"Python {PYTHON_VERSION} Documentation",
95+
f"docs.python.org/{PYTHON_VERSION}/",
96+
"B51BCC07-D9E3-439C-AC88-85BD64B97912")
2397
]
2498

2599
MENU_LOCATION = ["&Scripting", "Python3 Development", "Browse Documentation"]
@@ -35,4 +109,6 @@ def startup():
35109
lambda topic=topic: webbrowser.open(f"https://{topic[2]}"),
36110
MENU_LOCATION,
37111
text=topic[1],
38-
tooltip=topic[1])
112+
tooltip=topic[1],
113+
in2025_menuid=menuhook.BROWSE_DOCUMENTATION,
114+
id_2025=topic[3])

src/packages/menuhook/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ never be overridden.
5050

5151
## Q & A
5252

53-
*Q:* Why not using PySide2 directly?
53+
*Q:* Why not using PySide directly?
5454

5555
*A:* 3ds Max uses Qt for its menu and technically they can be inspected
56-
and modified during PySide2. But the Menu Manager inside 3ds Max owns the
56+
and modified during PySide. But the Menu Manager inside 3ds Max owns the
5757
menus and can regenerate them (using Qt) at any time during the execution
58-
of 3ds Max. And because of that any change made to the menus using PySide2
58+
of 3ds Max. And because of that any change made to the menus using PySide
5959
instead of the 3ds Max Menu Manager will be lost. In short: things will
6060
not behave as expected.
6161

0 commit comments

Comments
 (0)