Skip to content

Commit

Permalink
Maxx 75457 support pyside6 (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
hugow authored Apr 2, 2024
1 parent 416c689 commit a2bf34d
Show file tree
Hide file tree
Showing 45 changed files with 416 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=pymxs,debugpy,PySide2,menuhook,mxthread,socketio
ignored-modules=pymxs,debugpy,qtpy,menuhook,mxthread,socketio

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ working as expected

### New content

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

### Samples

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
can be found in the 3ds Max online documentation.

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

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

## Python Samples

Expand Down
3 changes: 3 additions & 0 deletions doc/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ It is possible to break up the installation in two steps.

- The [installstartup.sh](/installstartup.sh) script can be used
from bash to install pip and [pystartup.ms](/src/pystartup/pystartup.ms).
In 2025 and above, [adn-devtech-python-howtos](/src/adn-devtech-python-howtos)
is installed instead of pystartup.ms.

It needs to run in the 3ds Max installation directory.

You may do only this step if you don't want the HowTos but you
Expand Down
91 changes: 91 additions & 0 deletions doc/pluginpackage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Plugin Packages in 2025 and Integration With the New Menu System

In 3ds Max 2025 and above, plugin packages may contain python script components.
When installing the samples from this repo in a 2025 version of 3ds Max,
the [adn-devtech-python-howtos](/src/adn-devtech-python-howtos) plugin package
is copied to "$ProgramData/Autodesk/ApplicationPlugins". The [PackageContents.xml](/src/adn-devtech-python-howtos/PackageContents.xml)
file of this plugin package declares a python pre-start-up script:

```xml
<Components Description="pre-start-up scripts parts">
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
<ComponentEntry ModuleName="./scripts/pyStartup.py" />
</Components>
```

The [./scripts/pyStartup.py](/src/adn-devtech-python-howtos/scripts/pyStartup.py) script
first does what [pystartup.ms](/src/pystartup/pystartup.ms) used to do:

```python
def _python_startup():
try:
import pkg_resources
except ImportError:
print('startup Python modules require pip to be installed.')
return
for dist in pkg_resources.working_set:
entrypt = pkg_resources.get_entry_info(dist, '3dsMax', 'startup')
if not (entrypt is None):
try:
fcn = entrypt.load()
fcn()
except Exception as e:
print(f'skipped package startup for {dist} because {e}, startup not working')

```

Then it integrates the samples into the new menu system of 2025:

```python
# configure 2025 menus
from pymxs import runtime as rt
from menuhook import register_howtos_menu_2025
def menu_func():
menumgr = rt.callbacks.notificationparam()
register_howtos_menu_2025(menumgr)

# menu system
cuiregid = rt.name("cuiRegisterMenus")
howtoid = rt.name("pyScriptHowtoMenu")
rt.callbacks.removescripts(id=cuiregid)
rt.callbacks.addscript(cuiregid, menu_func, id=howtoid)

```


## Integration With the New Menu System

The [menuhook](/src/menuhook/) code has been reworked to integrate menu items for the
various howtos into the new menu system:

```python
def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_menuid=None, id_2025=None):
```

Takes two new parameters:
- `in2025_menuid` : the guid of the containing menu
- `id_2025` : the guid of the item to create

And stores the needed menu items in the `registered_items` list:

```python
registered_items.append((in2025_menuid, id_2025, category, action))
```

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:

```python
# hook the registered items
for reg in registered_items:
(in2025_menuid, id_2025, category, action) = reg
scriptmenu = menumgr.getmenubyid(in2025_menuid)
if scriptmenu is not None:
try:
actionitem = scriptmenu.createaction(id_2025, 647394, f"{action}`{category}")
except Exception as e:
print(f"Could not create item {category}, {action} in menu {in2025_menuid} because {e}")
else:
print(f"Could not create item {category}, {action}, in missing menu {in2025_menuid}")


```
13 changes: 13 additions & 0 deletions doc/uninstall.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ needed to remove them are explained at the bottom of this page).

## Removing pystartup.ms (manual uninstall)

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

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

### For 3dsMax 2025 and greater

Starting with 2025, pystartup.ms is no longer needed. Instead the
[adn-devtech-python-howtos](/src/adn-devtech-python-howtos) directory is
copied to "C:\ProgramData\Autodesk\ApplicationPlugins".

It can be manually removed by doing (from gitbash):

```bash
rm -fr "$ProgramData/Autodesk/ApplicationPlugins/adn-devtech-python-howtos"
```

## Removing pip (manual uninstall)

The installation script also install pip in user mode.
Expand Down
12 changes: 10 additions & 2 deletions scripts/inst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ installpip() {
./python.exe "$getpip/get-pip.py" --user
fi
fi
# update to the latest pip
./python.exe -m pip install --upgrade pip
./python.exe -m pip install wheel
}

# install pystartup.ms
# install pystartup.ms or adn-devtech-python-howtos (plugin package) for 2025
installpystartup() {
cp "$script/src/pystartup/pystartup.ms" "$startuppath"
if [ "$version" -lt "2025" ]
then
cp "$script/src/pystartup/pystartup.ms" "$startuppath"
else
cp -fr "$script/src/adn-devtech-python-howtos" "$ProgramData/Autodesk/ApplicationPlugins"
fi
}


Expand Down
19 changes: 19 additions & 0 deletions src/adn-devtech-python-howtos/PackageContents.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationPackage
SchemaVersion="1.0"
AutodeskProduct="3ds Max"
Name="ADNDevTechPythonHowTos"
Description=""
AppVersion="0.0.1"
ProductType="Application"
AppNameSpace="appstore.exchange.autodesk.com"
Author="Autodesk"
ProductCode="{31C5CD2E-F2A7-4716-9C50-B4CD11C1C568}"
UpgradeCode= "{14C34C70-07C7-4562-B3A8-5A8C4C95C97A}">
<CompanyDetails Name="Autodesk" Url="https://www.autodesk.com" />
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
<Components Description="pre-start-up scripts parts">
<RuntimeRequirements OS="Win64" Platform="3ds Max" SeriesMin="2025" SeriesMax="2025" />
<ComponentEntry ModuleName="./scripts/pyStartup.py" />
</Components>
</ApplicationPackage>
31 changes: 31 additions & 0 deletions src/adn-devtech-python-howtos/scripts/pyStartup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
def _python_startup():
try:
import pkg_resources
except ImportError:
print('startup Python modules require pip to be installed.')
return
for dist in pkg_resources.working_set:
entrypt = pkg_resources.get_entry_info(dist, '3dsMax', 'startup')
if not (entrypt is None):
try:
fcn = entrypt.load()
fcn()
except Exception as e:
print(f'skipped package startup for {dist} because {e}, startup not working')

# configure 2025 menus
from pymxs import runtime as rt
from menuhook import register_howtos_menu_2025
def menu_func():
menumgr = rt.callbacks.notificationparam()
register_howtos_menu_2025(menumgr)

# menu system
cuiregid = rt.name("cuiRegisterMenus")
howtoid = rt.name("pyScriptHowtoMenu")
rt.callbacks.removescripts(id=cuiregid)
rt.callbacks.addscript(cuiregid, menu_func, id=howtoid)


_python_startup()

110 changes: 93 additions & 17 deletions src/packages/inbrowserhelp/inbrowserhelp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,99 @@
"""
inbrowserhelp example: inbrowserhelp sample
"""
from sys import version_info
import webbrowser
import menuhook
from pymxs import runtime as rt
import menuhook
MAX_VERSION = rt.maxversion()[7]
MAX_HELP = f"help.autodesk.com/view/MAXDEV/{MAX_VERSION}/ENU"

TOPICS = [
("gettingstarted", "Getting Started With Python in 3ds Max",
f"{MAX_HELP}/?guid=Max_Python_API_tutorials_creating_the_dialog_html"),
("howtos", "Python HowTos Github Repo",
"github.com/ADN-DevTech/3dsMax-Python-HowTos"),
("samples", "Python samples (Github Repo)",
"github.com/ADN-DevTech/3dsMax-Python-HowTos/tree/master/src/samples"),
("pymxs", "Pymxs Online Documentation",
f"{MAX_HELP}/?guid=Max_Python_API_using_pymxs_html"),
("pyside2", "Qt for Python Documentation (PySide2)",
"doc.qt.io/qtforpython/contents.html"),
("python", "Python 3.7 Documentation",
"docs.python.org/3.7/")
MAX_HELP = "help.autodesk.com/view/MAXDEV"

PYTHON_VERSION = f"{version_info[0]}.{version_info[1]}"

MAX_VERSION_TOPICS = {
2021: [
("gettingstarted",
"Getting Started With Python in 3ds Max",
f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_about_the_3ds_max_python_api_html",
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
("pymxs",
"Pymxs Online Documentation",
f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_using_pymxs_pymxs_module_html",
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
],
2022: [
("gettingstarted",
"Getting Started With Python in 3ds Max",
f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
("pymxs",
"Pymxs Online Documentation",
f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_using_pymxs_html",
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
],
2023: [
("gettingstarted",
"Getting Started With Python in 3ds Max",
f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
("pymxs",
"Pymxs Online Documentation",
f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_using_pymxs_html",
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
],
2024: [
("gettingstarted",
"Getting Started With Python in 3ds Max",
f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
("pymxs",
"Pymxs Online Documentation",
f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_using_pymxs_html",
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
],
2025: [
("gettingstarted",
"Getting Started With Python in 3ds Max",
f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html",
"64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"),
("pymxs",
"Pymxs Online Documentation",
f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_using_pymxs_html",
"44985F87-C175-4F3D-B70F-9FA0B6242AE1")
]
}

def get_version_topics(version):
"""Get the version-dependent topics, defaulting on the latest
if the requested one does not exist"""
return MAX_VERSION_TOPICS[version if version in MAX_VERSION_TOPICS else 2024]

V_TOPICS = get_version_topics(MAX_VERSION)

PYSIDE6_DOC = ("pyside6",
"Qt for Python Documentation (PySide6)",
"doc.qt.io/qtforpython-6/index.html",
"E0E5F945-CD55-404A-840B-81540829E4C4")

PYSIDE2_DOC = ("pyside2",
"Qt for Python Documentation (PySide2)",
"doc.qt.io/qtforpython-5/contents.html",
"13EEE11E-1BBB-470E-B757-F536D91215A9")

TOPICS = V_TOPICS + [
("howtos",
"Python HowTos Github Repo",
"github.com/ADN-DevTech/3dsMax-Python-HowTos",
"2504EEA5-27D6-4EA0-A7A3-B3C058777ADC"),
("samples",
"Python samples (Github Repo)",
"github.com/ADN-DevTech/3dsMax-Python-HowTos/tree/master/src/samples",
"8ED9D9CC-3799-435D-8016-0F8F16D84004"),
PYSIDE6_DOC if MAX_VERSION >= 2025 else PYSIDE2_DOC,
("python",
f"Python {PYTHON_VERSION} Documentation",
f"docs.python.org/{PYTHON_VERSION}/",
"B51BCC07-D9E3-439C-AC88-85BD64B97912")
]

MENU_LOCATION = ["&Scripting", "Python3 Development", "Browse Documentation"]
Expand All @@ -35,4 +109,6 @@ def startup():
lambda topic=topic: webbrowser.open(f"https://{topic[2]}"),
MENU_LOCATION,
text=topic[1],
tooltip=topic[1])
tooltip=topic[1],
in2025_menuid=menuhook.BROWSE_DOCUMENTATION,
id_2025=topic[3])
6 changes: 3 additions & 3 deletions src/packages/menuhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ never be overridden.

## Q & A

*Q:* Why not using PySide2 directly?
*Q:* Why not using PySide directly?

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

Expand Down
Loading

0 comments on commit a2bf34d

Please sign in to comment.