From bbf9825385317993989f0f4440af15abbbe25b26 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Application Admin Date: Tue, 3 Oct 2023 11:47:59 -0700 Subject: [PATCH 01/12] Support the new menu system --- README.md | 3 +- doc/install.md | 3 + doc/uninstall.md | 13 ++++ scripts/inst.sh | 9 ++- .../PackageContents.xml | 19 ++++++ .../scripts/pyStartup.py | 31 ++++++++++ .../inbrowserhelp/inbrowserhelp/__init__.py | 41 +++++++++---- src/packages/menuhook/menuhook/__init__.py | 59 +++++++++++++++++-- .../mxstranslate/mxstranslate/__init__.py | 4 +- src/packages/pyconsole/pyconsole/__init__.py | 4 +- .../quickpreview/quickpreview/__init__.py | 4 +- src/packages/reloadmod/reloadmod/__init__.py | 4 +- .../removeallmaterials/__init__.py | 4 +- .../renameselected/renameselected/__init__.py | 4 +- .../singleinstancedlg/__init__.py | 4 +- .../speedsheet/speedsheet/__init__.py | 4 +- .../threadprogressbar/__init__.py | 4 +- .../transformlock/transformlock/__init__.py | 4 +- .../zdepthchannel/zdepthchannel/__init__.py | 4 +- src/pystartup/pystartup.ms | 4 +- uninstall.sh | 6 +- 21 files changed, 196 insertions(+), 36 deletions(-) create mode 100644 src/adn-devtech-python-howtos/PackageContents.xml create mode 100644 src/adn-devtech-python-howtos/scripts/pyStartup.py diff --git a/README.md b/README.md index edd3055..8879f29 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ 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 @@ -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 diff --git a/doc/install.md b/doc/install.md index a4513f1..17670a7 100644 --- a/doc/install.md +++ b/doc/install.md @@ -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 diff --git a/doc/uninstall.md b/doc/uninstall.md index 93c4628..797d839 100644 --- a/doc/uninstall.md +++ b/doc/uninstall.md @@ -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" @@ -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 used. 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. diff --git a/scripts/inst.sh b/scripts/inst.sh index 8e6feaf..118150f 100644 --- a/scripts/inst.sh +++ b/scripts/inst.sh @@ -68,9 +68,14 @@ installpip() { fi } -# install pystartup.ms +# install pystartup.ms or adn-dectech-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 "$script/src/adn-devtech-python-howtos" "$ProgramData/Autodesk/ApplicationPlugins" + fi } diff --git a/src/adn-devtech-python-howtos/PackageContents.xml b/src/adn-devtech-python-howtos/PackageContents.xml new file mode 100644 index 0000000..a68036e --- /dev/null +++ b/src/adn-devtech-python-howtos/PackageContents.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/src/adn-devtech-python-howtos/scripts/pyStartup.py b/src/adn-devtech-python-howtos/scripts/pyStartup.py new file mode 100644 index 0000000..bed9632 --- /dev/null +++ b/src/adn-devtech-python-howtos/scripts/pyStartup.py @@ -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() + diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index 91b8f79..57680f0 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -8,18 +8,30 @@ 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/") + ("gettingstarted", + "Getting Started With Python in 3ds Max", + f"{MAX_HELP}/?guid=Max_Python_API_tutorials_creating_the_dialog_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("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"), + ("pymxs", + "Pymxs Online Documentation", + f"{MAX_HELP}/?guid=Max_Python_API_using_pymxs_html", + "44985F87-C175-4F3D-B70F-9FA0B6242AE1"), + ("pyside2", + "Qt for Python Documentation (PySide2)", + "doc.qt.io/qtforpython/contents.html", + "13EEE11E-1BBB-470E-B757-F536D91215A9"), + ("python", + "Python 3.7 Documentation", + "docs.python.org/3.7/", + "B51BCC07-D9E3-439C-AC88-85BD64B97912") ] MENU_LOCATION = ["&Scripting", "Python3 Development", "Browse Documentation"] @@ -35,4 +47,7 @@ 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]) + diff --git a/src/packages/menuhook/menuhook/__init__.py b/src/packages/menuhook/menuhook/__init__.py index 5f6fbe5..ff8f509 100644 --- a/src/packages/menuhook/menuhook/__init__.py +++ b/src/packages/menuhook/menuhook/__init__.py @@ -105,8 +105,14 @@ def add_menu_item(menu, action, category): targetmenu.addItem(newaction, -1) rt.menuman.updateMenuBar() +PYTHON_DEVELOPMENT = "82490C17-D86E-40C5-B387-C2E63A64C74D" +BROWSE_DOCUMENTATION = "DAF8D6C5-0C14-4A99-9370-8AA5329EA143" +HOW_TO = "FFBB0A45-5278-4572-8CD9-BB5B4D260153" +OTHER_SAMPLES = "CBB6F619-57B9-4C81-8135-41958BEF5BED" +registered_items = [] + #pylint: disable=too-many-arguments -def register(action, category, fcn, menu=None, text=None, tooltip=None): +def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_menuid=None, id_2025=None): """ Appends a menu item to one of the menus of the main menubar. If the action already exists, the menu is not added but the @@ -116,9 +122,52 @@ def register(action, category, fcn, menu=None, text=None, tooltip=None): - creating a macro - assign the macro function if the macro is already there - create a menu item for the macro if it is not already there + + For 2025 and above, menu items that need to be created are kept + in the global registered_items list. """ - defined = macro_defined(action, category) - add_macro(action, category, text or action, tooltip or action, fcn) - if not defined and not menu is None: - add_menu_item(menu, action, category) + if (rt.maxversion())[7] >= 2025: + if in2025_menuid is not None and id_2025 is not None: + add_macro(action, category, text or action, tooltip or action, fcn) + registered_items.append((in2025_menuid, id_2025, category, action)) + else: + defined = macro_defined(action, category) + add_macro(action, category, text or action, tooltip or action, fcn) + if not defined and not menu is None: + add_menu_item(menu, action, category) #pylint: enable=too-many-arguments + +# for 2025, pre-can a menu for the howtos +def register_howtos_menu_2025(menumgr): + """Register the menu structure in the new menu system""" + menumgr = rt.callbacks.notificationparam() + + scriptingmenu = "658724ec-de09-47dd-b723-918c59a28ad1" + scriptmenu = menumgr.getmenubyid(scriptingmenu) + + python_development = scriptmenu.createsubmenu( + PYTHON_DEVELOPMENT, + "Python 3 Development") + python_development.createsubmenu( + BROWSE_DOCUMENTATION, + "Browse Documentation") + python_development.createsubmenu( + HOW_TO, + "How To") + python_development.createsubmenu( + OTHER_SAMPLES, + "Other Samples") + + # 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}") + + diff --git a/src/packages/mxstranslate/mxstranslate/__init__.py b/src/packages/mxstranslate/mxstranslate/__init__.py index 40dd9d9..519ba60 100644 --- a/src/packages/mxstranslate/mxstranslate/__init__.py +++ b/src/packages/mxstranslate/mxstranslate/__init__.py @@ -20,4 +20,6 @@ def startup(): mxstranslate, menu=["&Scripting", "Python3 Development", "How To"], text="Translation window for mxs code", - tooltip="Translation window for mxs code") + tooltip="Translation window for mxs code", + in2025_menuid=menuhook.HOW_TO, + id_2025="004BF4E1-4AB3-42B5-979A-28662B26533C") diff --git a/src/packages/pyconsole/pyconsole/__init__.py b/src/packages/pyconsole/pyconsole/__init__.py index e994751..73d266a 100644 --- a/src/packages/pyconsole/pyconsole/__init__.py +++ b/src/packages/pyconsole/pyconsole/__init__.py @@ -19,7 +19,9 @@ def startup(): pyconsole, menu=["&Scripting", "Python3 Development", "How To"], text="Python Console", - tooltip="Python Console") + tooltip="Python Console", + in2025_menuid=menuhook.HOW_TO, + id_2025="0F52AF28-D7EE-4A04-AC9D-56C126FE9373") # Create a python console in the command panel # automatically console.new_console(tabto="CommandPanel") diff --git a/src/packages/quickpreview/quickpreview/__init__.py b/src/packages/quickpreview/quickpreview/__init__.py index fe5d1b3..826a73b 100644 --- a/src/packages/quickpreview/quickpreview/__init__.py +++ b/src/packages/quickpreview/quickpreview/__init__.py @@ -29,4 +29,6 @@ def startup(): quickpreview, menu=["&Scripting", "Python3 Development", "How To"], text="Create a quick preview", - tooltip="Create a quick preview") + tooltip="Create a quick preview", + in2025_menuid=menuhook.HOW_TO, + id_2025="E30C825C-E4CD-48FD-A1C7-A75A91B2E9C2") diff --git a/src/packages/reloadmod/reloadmod/__init__.py b/src/packages/reloadmod/reloadmod/__init__.py index 9b513af..5cd7c96 100644 --- a/src/packages/reloadmod/reloadmod/__init__.py +++ b/src/packages/reloadmod/reloadmod/__init__.py @@ -21,4 +21,6 @@ def startup(): python_reload, menu=["&Scripting", "Python3 Development"], text="Reload Python Modules", - tooltip="Reload Python Modules") + tooltip="Reload Python Modules", + in2025_menuid=menuhook.PYTHON_DEVELOPMENT, + id_2025="7A8CFC05-0752-4501-A5BD-F5AF020D7F5F") diff --git a/src/packages/removeallmaterials/removeallmaterials/__init__.py b/src/packages/removeallmaterials/removeallmaterials/__init__.py index de6f891..51960c3 100644 --- a/src/packages/removeallmaterials/removeallmaterials/__init__.py +++ b/src/packages/removeallmaterials/removeallmaterials/__init__.py @@ -19,4 +19,6 @@ def startup(): remove_all_materials, menu=["&Scripting", "Python3 Development", "How To"], text="Remove all materials from the scene", - tooltip="Remove all materials from the scene") + tooltip="Remove all materials from the scene", + in2025_menuid=menuhook.HOW_TO, + id_2025="1A3AE016-3E54-4856-9076-2BE491B2258C") diff --git a/src/packages/renameselected/renameselected/__init__.py b/src/packages/renameselected/renameselected/__init__.py index a39e879..7ecd705 100644 --- a/src/packages/renameselected/renameselected/__init__.py +++ b/src/packages/renameselected/renameselected/__init__.py @@ -28,4 +28,6 @@ def startup(): showdialog, menu=["&Scripting", "Python3 Development", "How To"], text="Rename all elements in selection", - tooltip="renameselected sample") + tooltip="renameselected sample", + in2025_menuid=menuhook.HOW_TO, + id_2025="163ACF54-313D-4B1B-8615-F6F979AE0FE7") diff --git a/src/packages/singleinstancedlg/singleinstancedlg/__init__.py b/src/packages/singleinstancedlg/singleinstancedlg/__init__.py index b52023c..0a1a544 100644 --- a/src/packages/singleinstancedlg/singleinstancedlg/__init__.py +++ b/src/packages/singleinstancedlg/singleinstancedlg/__init__.py @@ -19,4 +19,6 @@ def startup(): singleinstancedlg, menu=["&Scripting", "Python3 Development", "Other Samples"], text="Single instance modeless dialog", - tooltip="Single instance modeless dialog") + tooltip="Single instance modeless dialog", + in2025_menuid=menuhook.OTHER_SAMPLES, + id_2025="AF515BBA-E826-4DA8-B097-FA9A2C917A91") diff --git a/src/packages/speedsheet/speedsheet/__init__.py b/src/packages/speedsheet/speedsheet/__init__.py index d7ffcb5..bb7aa6f 100644 --- a/src/packages/speedsheet/speedsheet/__init__.py +++ b/src/packages/speedsheet/speedsheet/__init__.py @@ -39,4 +39,6 @@ def startup(): speedsheet, menu=["&Scripting", "Python3 Development", "How To"], text="Output Object Data to File", - tooltip="Output Object Data to File") + tooltip="Output Object Data to File", + in2025_menuid=menuhook.HOW_TO, + id_2025="FFA6888A-B27A-4FA9-BFED-86FD3683E6B6") diff --git a/src/packages/threadprogressbar/threadprogressbar/__init__.py b/src/packages/threadprogressbar/threadprogressbar/__init__.py index 84b4cd4..8596476 100644 --- a/src/packages/threadprogressbar/threadprogressbar/__init__.py +++ b/src/packages/threadprogressbar/threadprogressbar/__init__.py @@ -20,4 +20,6 @@ def startup(): threadprogressbar, menu=["&Scripting", "Python3 Development", "Other Samples"], text="Update a progress bar from a thread", - tooltip="Update a progress bar from a thread") + tooltip="Update a progress bar from a thread", + in2025_menuid=menuhook.OTHER_SAMPLES, + id_2025="AB072ECE-8665-4EC4-8D12-C0E76DA4C919") diff --git a/src/packages/transformlock/transformlock/__init__.py b/src/packages/transformlock/transformlock/__init__.py index 7a0cde2..1adbd5c 100644 --- a/src/packages/transformlock/transformlock/__init__.py +++ b/src/packages/transformlock/transformlock/__init__.py @@ -18,4 +18,6 @@ def startup(): lock_selection, menu=["&Scripting", "Python3 Development", "How To"], text="Lock transformations for the selection", - tooltip="Lock transformations for the selection") + tooltip="Lock transformations for the selection", + in2025_menuid=menuhook.HOW_TO, + id_2025="F9DA574D-185B-4C00-9C37-795B38719E78") diff --git a/src/packages/zdepthchannel/zdepthchannel/__init__.py b/src/packages/zdepthchannel/zdepthchannel/__init__.py index 29e8f83..141e954 100644 --- a/src/packages/zdepthchannel/zdepthchannel/__init__.py +++ b/src/packages/zdepthchannel/zdepthchannel/__init__.py @@ -42,4 +42,6 @@ def startup(): zdepthchannel, menu=["&Scripting", "Python3 Development", "How To"], text="Access the Z-Depth Channel", - tooltip="Access the Z-Depth Channel") + tooltip="Access the Z-Depth Channel", + in2025_menuid=menuhook.HOW_TO, + id_2025="BC1B51C9-2F02-496D-B9ED-9A61022A569D") diff --git a/src/pystartup/pystartup.ms b/src/pystartup/pystartup.ms index 0a37b16..146c41d 100644 --- a/src/pystartup/pystartup.ms +++ b/src/pystartup/pystartup.ms @@ -1,6 +1,6 @@ -if isProperty python "execute" then ( +if isProperty python "execute" && ((maxversion())[8]<2025) then ( python.execute ("def _python_startup():\n" + - " try:\n" + + " try:\n" + " import pkg_resources\n" + " except ImportError:\n" + " print('startup Python modules require pip to be installed.')\n" + diff --git a/uninstall.sh b/uninstall.sh index c05c39d..1ecec0f 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -18,5 +18,7 @@ echo "Uninstall pip" ./python.exe -m pip uninstall pip ) -echo "Uninstall pystartup" -rm "$startuppath/pystartup.ms" +echo "Uninstall pystartup and adn-devtech-python-howtos" +rm -f "$startuppath/pystartup.ms" +rm -fr "$ProgramData/Autodesk/ApplicationPlugins/adn-devtech-python-howtos" + From 2962639f7f931fe00e69b294f6a44aa3db00f346 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Application Admin Date: Tue, 3 Oct 2023 12:04:49 -0700 Subject: [PATCH 02/12] new pluginpackage md --- doc/pluginpackage.md | 91 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 doc/pluginpackage.md diff --git a/doc/pluginpackage.md b/doc/pluginpackage.md new file mode 100644 index 0000000..9145cd1 --- /dev/null +++ b/doc/pluginpackage.md @@ -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 + + + + +``` + +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 to 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 to 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_2025i` : 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}") + + +``` From 476d990599b1013efa5156fd19c158af1e401a3d Mon Sep 17 00:00:00 2001 From: Hugo Windisch Application Admin Date: Wed, 4 Oct 2023 05:26:32 -0700 Subject: [PATCH 03/12] fix doc links and install --- scripts/inst.sh | 2 +- .../inbrowserhelp/inbrowserhelp/__init__.py | 82 +++++++++++++++---- src/pystartup/pystartup.ms | 6 +- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/scripts/inst.sh b/scripts/inst.sh index 118150f..e860708 100644 --- a/scripts/inst.sh +++ b/scripts/inst.sh @@ -74,7 +74,7 @@ installpystartup() { then cp "$script/src/pystartup/pystartup.ms" "$startuppath" else - cp "$script/src/adn-devtech-python-howtos" "$ProgramData/Autodesk/ApplicationPlugins" + cp -fr "$script/src/adn-devtech-python-howtos" "$ProgramData/Autodesk/ApplicationPlugins" fi } diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index 57680f0..bc35abb 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -3,15 +3,74 @@ """ import webbrowser import menuhook +from sys import version_info from pymxs import runtime as rt 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", - "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), +PYTHON_VERSION = f"{version_info[0]}.{version_info[1]}" + +MAX_VERSION_TOPICS = { + 2021: [ + ("gettingstarted", + "Getting Started With Python in 3ds Max", + "help.autodesk.com/view/MAXDEV/2021/ENU/?guid=Max_Python_API_about_the_3ds_max_python_api_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("pymxs", + "Pymxs Online Documentation", + "help.autodesk.com/view/MAXDEV/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", + "help.autodesk.com/view/MAXDEV/2022/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("pymxs", + "Pymxs Online Documentation", + "help.autodesk.com/view/MAXDEV/2022/ENU/?guid=MAXDEV_Python_using_pymxs_html", + "44985F87-C175-4F3D-B70F-9FA0B6242AE1") + ], + 2023: [ + ("gettingstarted", + "Getting Started With Python in 3ds Max", + "help.autodesk.com/view/MAXDEV/2023/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("pymxs", + "Pymxs Online Documentation", + "help.autodesk.com/view/MAXDEV/2023/ENU/?guid=MAXDEV_Python_using_pymxs_html", + "44985F87-C175-4F3D-B70F-9FA0B6242AE1") + ], + 2024: [ + ("gettingstarted", + "Getting Started With Python in 3ds Max", + "help.autodesk.com/view/MAXDEV/2024/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("pymxs", + "Pymxs Online Documentation", + "help.autodesk.com/view/MAXDEV/2024/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", @@ -20,17 +79,10 @@ "Python samples (Github Repo)", "github.com/ADN-DevTech/3dsMax-Python-HowTos/tree/master/src/samples", "8ED9D9CC-3799-435D-8016-0F8F16D84004"), - ("pymxs", - "Pymxs Online Documentation", - f"{MAX_HELP}/?guid=Max_Python_API_using_pymxs_html", - "44985F87-C175-4F3D-B70F-9FA0B6242AE1"), - ("pyside2", - "Qt for Python Documentation (PySide2)", - "doc.qt.io/qtforpython/contents.html", - "13EEE11E-1BBB-470E-B757-F536D91215A9"), + PYSIDE6_DOC if MAX_VERSION >= 2025 else PYSIDE2_DOC, ("python", - "Python 3.7 Documentation", - "docs.python.org/3.7/", + f"Python {PYTHON_VERSION} Documentation", + f"docs.python.org/{PYTHON_VERSION}/", "B51BCC07-D9E3-439C-AC88-85BD64B97912") ] diff --git a/src/pystartup/pystartup.ms b/src/pystartup/pystartup.ms index 146c41d..2952eab 100644 --- a/src/pystartup/pystartup.ms +++ b/src/pystartup/pystartup.ms @@ -1,4 +1,4 @@ -if isProperty python "execute" && ((maxversion())[8]<2025) then ( +if isProperty python "execute" and ((maxversion())[8]<2025) then ( python.execute ("def _python_startup():\n" + " try:\n" + " import pkg_resources\n" + @@ -11,8 +11,8 @@ if isProperty python "execute" && ((maxversion())[8]<2025) then ( " try:\n" + " fcn = entrypt.load()\n" + " fcn()\n" + - " except:\n" + - " print('skipped package startup for {}, startup not working'.format(dist))\n" + + " except Exception as e:\n" + + " print(f'skipped package startup for {dist} because {e}, startup not working')\n" + "_python_startup()\n" + "del _python_startup") ) From 4cedb44524d06fbe849eb306e78714959ea13d3b Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 4 Oct 2023 07:11:00 -0700 Subject: [PATCH 04/12] Support PySide6 and PySide2 --- .pylintrc | 2 +- README.md | 2 +- src/packages/menuhook/README.md | 6 +++--- src/packages/mxstranslate/mxstranslate/translate.py | 8 ++++---- src/packages/mxstranslate/setup.py | 3 ++- src/packages/mxthread/README.md | 2 +- src/packages/mxthread/mxthread/__init__.py | 4 ++-- src/packages/mxthread/setup.py | 3 +++ src/packages/pyconsole/pyconsole/console.py | 4 ++-- src/packages/pyconsole/setup.py | 5 +++-- src/packages/renameselected/README.md | 4 ++-- src/packages/renameselected/renameselected/ui.py | 4 ++-- src/packages/renameselected/setup.py | 3 +++ src/packages/singleinstancedlg/README.md | 6 +++--- src/packages/singleinstancedlg/setup.py | 3 +++ .../singleinstancedlg/singleinstancedlg/ui.py | 4 ++-- src/packages/threadprogressbar/setup.py | 3 +++ .../threadprogressbar/threadprogressbar/ui.py | 6 +++--- src/samples/{PySide2 => PySide}/combine_meshes.py | 2 +- .../{PySide2 => PySide}/cylinder_icon_48.png | Bin src/samples/{PySide2 => PySide}/docking_widgets.py | 10 +++++----- src/samples/{PySide2 => PySide}/simple_dialog.py | 6 +++--- src/samples/{PySide2 => PySide}/test_ui.ui | 0 src/samples/{PySide2 => PySide}/ui_loader.py | 8 ++++---- 24 files changed, 56 insertions(+), 42 deletions(-) rename src/samples/{PySide2 => PySide}/combine_meshes.py (95%) rename src/samples/{PySide2 => PySide}/cylinder_icon_48.png (100%) rename src/samples/{PySide2 => PySide}/docking_widgets.py (90%) rename src/samples/{PySide2 => PySide}/simple_dialog.py (84%) rename src/samples/{PySide2 => PySide}/test_ui.ui (100%) rename src/samples/{PySide2 => PySide}/ui_loader.py (83%) diff --git a/.pylintrc b/.pylintrc index 3cce629..f4524d7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -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. diff --git a/README.md b/README.md index 8879f29..523a50f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The samples below are translations of [MAXScript How Tos](https://help.autodesk. 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?* diff --git a/src/packages/menuhook/README.md b/src/packages/menuhook/README.md index ddc0983..31fd1ff 100644 --- a/src/packages/menuhook/README.md +++ b/src/packages/menuhook/README.md @@ -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. diff --git a/src/packages/mxstranslate/mxstranslate/translate.py b/src/packages/mxstranslate/mxstranslate/translate.py index e36edc9..873c784 100644 --- a/src/packages/mxstranslate/mxstranslate/translate.py +++ b/src/packages/mxstranslate/mxstranslate/translate.py @@ -2,12 +2,12 @@ Toolbar giving access to experimental MXS -> Python translation """ #pylint: disable= import-error, invalid-name, too-few-public-methods -from PySide2.QtWidgets import (QApplication, QWidget, QDockWidget, +from qtpy.QtWidgets import (QApplication, QWidget, QDockWidget, QVBoxLayout, QLabel, QTextEdit, QStyle, QToolBar, QCommonStyle) -from PySide2.QtGui import (QSyntaxHighlighter, QTextCharFormat, +from qtpy.QtGui import (QSyntaxHighlighter, QTextCharFormat, QFont, Qt, QBrush, QColor, QKeyEvent) -from PySide2 import QtCore -from PySide2.QtCore import QMimeData, QEvent +from qtpy import QtCore +from qtpy.QtCore import QMimeData, QEvent from pygments.lexers.python import PythonLexer from pygments.token import Token from mxs2py import topy diff --git a/src/packages/mxstranslate/setup.py b/src/packages/mxstranslate/setup.py index dc268ec..21aef6f 100644 --- a/src/packages/mxstranslate/setup.py +++ b/src/packages/mxstranslate/setup.py @@ -13,7 +13,8 @@ packages=setuptools.find_packages(), entry_points={'3dsMax': 'startup=mxstranslate:startup'}, install_requires=[ - 'pygments' + 'pygments', + 'qtpy' ], python_requires='>=3.7' ) diff --git a/src/packages/mxthread/README.md b/src/packages/mxthread/README.md index b4c7aff..6189a28 100644 --- a/src/packages/mxthread/README.md +++ b/src/packages/mxthread/README.md @@ -51,7 +51,7 @@ This sample can be saved in a "testmxthread.py" file and then run in 3dsMax. ```python from mxthread import on_main_thread, main_thread_print, run_on_main_thread from pymxs import runtime as rt -from PySide2.QtCore import QThread +from qtpy.QtCore import QThread class Worker(QThread): diff --git a/src/packages/mxthread/mxthread/__init__.py b/src/packages/mxthread/mxthread/__init__.py index 77cd5a8..75f8273 100644 --- a/src/packages/mxthread/mxthread/__init__.py +++ b/src/packages/mxthread/mxthread/__init__.py @@ -6,8 +6,8 @@ import sys import os import functools -from PySide2.QtCore import QObject, Slot, Signal, QThread, QMutex, QWaitCondition, QTimer -from PySide2.QtWidgets import QApplication +from qtpy.QtCore import QObject, Slot, Signal, QThread, QMutex, QWaitCondition, QTimer +from qtpy.QtWidgets import QApplication #pylint: disable=W0703,R0903 class RunnableWaitablePayload(): diff --git a/src/packages/mxthread/setup.py b/src/packages/mxthread/setup.py index 6407fd2..cee451c 100644 --- a/src/packages/mxthread/setup.py +++ b/src/packages/mxthread/setup.py @@ -11,5 +11,8 @@ long_description_content_type="text/markdown", url="https://git.autodesk.com/windish/maxpythontutorials", packages=setuptools.find_packages(), + install_requires=[ + 'qtpy' + ], python_requires='>=3.7' ) diff --git a/src/packages/pyconsole/pyconsole/console.py b/src/packages/pyconsole/pyconsole/console.py index 3a136fc..c733392 100644 --- a/src/packages/pyconsole/pyconsole/console.py +++ b/src/packages/pyconsole/pyconsole/console.py @@ -6,8 +6,8 @@ from qtmax import GetQMaxMainWindow from pyqtconsole.console import PythonConsole import pyqtconsole.highlighter as hl -from PySide2.QtWidgets import QWidget, QDockWidget -from PySide2 import QtCore +from qtpy.QtWidgets import QWidget, QDockWidget +from qtpy import QtCore # My personal choice of colors HUGOS_THEME = { diff --git a/src/packages/pyconsole/setup.py b/src/packages/pyconsole/setup.py index 5501ae7..f86e5e3 100644 --- a/src/packages/pyconsole/setup.py +++ b/src/packages/pyconsole/setup.py @@ -12,8 +12,9 @@ url="https://git.autodesk.com/windish/maxpythontutorials", packages=setuptools.find_packages(), install_requires=[ - 'jedi==0.17.2', - 'pyqtconsole' + 'jedi==0.19.1', + 'pyqtconsole', + 'qtpy' ], entry_points={'3dsMax': 'startup=pyconsole:startup'}, python_requires='>=3.7' diff --git a/src/packages/renameselected/README.md b/src/packages/renameselected/README.md index a2361dd..0ee55a8 100644 --- a/src/packages/renameselected/README.md +++ b/src/packages/renameselected/README.md @@ -6,13 +6,13 @@ [Source Code](renameselected/__init__.py) *Goals:* -- learn how to create a dialog with PySide2 +- learn how to create a dialog with PySide - learn how to hook a Python function to a 3ds Max ui element ## Explanations This tutorial shows how to rename all selected objects using a base name, -chosen in a PySide2 dialog. +chosen in a PySide dialog. ## Using the tool diff --git a/src/packages/renameselected/renameselected/ui.py b/src/packages/renameselected/renameselected/ui.py index 8bb435f..d5710fc 100644 --- a/src/packages/renameselected/renameselected/ui.py +++ b/src/packages/renameselected/renameselected/ui.py @@ -1,9 +1,9 @@ """ - Provide a PySide2 dialog for the tool. + Provide a qtpy dialog for the tool. """ #pylint: disable=no-name-in-module #pylint: disable=too-few-public-methods -from PySide2.QtWidgets import QWidget, QDialog, QLabel, QLineEdit, QVBoxLayout, QPushButton +from qtpy.QtWidgets import QWidget, QDialog, QLabel, QLineEdit, QVBoxLayout, QPushButton from pymxs import runtime as rt class PyMaxDialog(QDialog): diff --git a/src/packages/renameselected/setup.py b/src/packages/renameselected/setup.py index 21c8417..4ab7789 100644 --- a/src/packages/renameselected/setup.py +++ b/src/packages/renameselected/setup.py @@ -11,5 +11,8 @@ long_description_content_type="text/markdown", packages=setuptools.find_packages(), entry_points={'3dsMax': 'startup=renameselected:startup'}, + install_requires=[ + 'qtpy' + ], python_requires='>=3.7' ) diff --git a/src/packages/singleinstancedlg/README.md b/src/packages/singleinstancedlg/README.md index 8b88855..b9bcc8c 100644 --- a/src/packages/singleinstancedlg/README.md +++ b/src/packages/singleinstancedlg/README.md @@ -3,12 +3,12 @@ This sample shows how to create a single instance modeless dialog. *Goal:* -- learn how how to use findChild in PySide2 to create a single instance +- learn how how to use findChild in PySide to create a single instance dialog ## Explanations -The sample creates a custome PySide2 dialog and calls `setObjectName` +The sample creates a custome PySide dialog and calls `setObjectName` on it with a unique name. The `show_dialog()` function only creates a new dialog if `findChild` cannot find the QDialog with the name specified in `setObjectName`. The dialog (either found or created) is @@ -21,7 +21,7 @@ In [ui.py](singleinstancedlg/ui.py), we first create a new custom dialog class. ```python -from PySide2.QtWidgets import QWidget, QDialog, QVBoxLayout, QPushButton +from qtpy.QtWidgets import QWidget, QDialog, QVBoxLayout, QPushButton from pymxs import runtime as rt MAIN_WINDOW = QWidget.find(rt.windows.getMAXHWND()) diff --git a/src/packages/singleinstancedlg/setup.py b/src/packages/singleinstancedlg/setup.py index 45be9c4..991422c 100644 --- a/src/packages/singleinstancedlg/setup.py +++ b/src/packages/singleinstancedlg/setup.py @@ -11,5 +11,8 @@ long_description_content_type="text/markdown", packages=setuptools.find_packages(), entry_points={'3dsMax': 'startup=singleinstancedlg:startup'}, + install_requires=[ + 'qtpy' + ], python_requires='>=3.7' ) diff --git a/src/packages/singleinstancedlg/singleinstancedlg/ui.py b/src/packages/singleinstancedlg/singleinstancedlg/ui.py index 71be59f..e853d38 100644 --- a/src/packages/singleinstancedlg/singleinstancedlg/ui.py +++ b/src/packages/singleinstancedlg/singleinstancedlg/ui.py @@ -1,10 +1,10 @@ """ - PySide2 modeless dialog that will not be started more than once + qtpy modeless dialog that will not be started more than once at the same time """ #pylint: disable=no-name-in-module #pylint: disable=too-few-public-methods -from PySide2.QtWidgets import QWidget, QDialog, QVBoxLayout, QPushButton +from qtpy.QtWidgets import QWidget, QDialog, QVBoxLayout, QPushButton from pymxs import runtime as rt MAIN_WINDOW = QWidget.find(rt.windows.getMAXHWND()) diff --git a/src/packages/threadprogressbar/setup.py b/src/packages/threadprogressbar/setup.py index b426e31..cfd0451 100644 --- a/src/packages/threadprogressbar/setup.py +++ b/src/packages/threadprogressbar/setup.py @@ -11,5 +11,8 @@ long_description_content_type="text/markdown", packages=setuptools.find_packages(), entry_points={'3dsMax': 'startup=threadprogressbar:startup'}, + install_requires=[ + 'qtpy' + ], python_requires='>=3.7' ) diff --git a/src/packages/threadprogressbar/threadprogressbar/ui.py b/src/packages/threadprogressbar/threadprogressbar/ui.py index 31d15f8..a9f20bc 100644 --- a/src/packages/threadprogressbar/threadprogressbar/ui.py +++ b/src/packages/threadprogressbar/threadprogressbar/ui.py @@ -1,11 +1,11 @@ """ - PySide2 dialog that launches a worker and monitors its progress. + qtpy dialog that launches a worker and monitors its progress. """ #pylint: disable=no-name-in-module #pylint: disable=too-few-public-methods import time -from PySide2.QtWidgets import QWidget, QDialog, QLabel, QProgressBar, QVBoxLayout, QPushButton -from PySide2.QtCore import QThread, Signal +from qtpy.QtWidgets import QWidget, QDialog, QLabel, QProgressBar, QVBoxLayout, QPushButton +from qtpy.QtCore import QThread, Signal from pymxs import runtime as rt MINRANGE = 1 diff --git a/src/samples/PySide2/combine_meshes.py b/src/samples/PySide/combine_meshes.py similarity index 95% rename from src/samples/PySide2/combine_meshes.py rename to src/samples/PySide/combine_meshes.py index 20c3aae..bd40087 100644 --- a/src/samples/PySide2/combine_meshes.py +++ b/src/samples/PySide/combine_meshes.py @@ -1,7 +1,7 @@ ''' Demonstrates combining the mesh of two scene nodes ''' -from PySide2.QtWidgets import QVBoxLayout, QPushButton, QLabel, QDialog, QMessageBox +from qtpy.QtWidgets import QVBoxLayout, QPushButton, QLabel, QDialog, QMessageBox from pymxs import runtime as rt # pylint: disable=import-error from qtmax import GetQMaxMainWindow diff --git a/src/samples/PySide2/cylinder_icon_48.png b/src/samples/PySide/cylinder_icon_48.png similarity index 100% rename from src/samples/PySide2/cylinder_icon_48.png rename to src/samples/PySide/cylinder_icon_48.png diff --git a/src/samples/PySide2/docking_widgets.py b/src/samples/PySide/docking_widgets.py similarity index 90% rename from src/samples/PySide2/docking_widgets.py rename to src/samples/PySide/docking_widgets.py index f4102e1..8092848 100644 --- a/src/samples/PySide2/docking_widgets.py +++ b/src/samples/PySide/docking_widgets.py @@ -1,14 +1,14 @@ ''' - Demonstrates how to create a QWidget with PySide2 and attach it to the 3dsmax main window. + Demonstrates how to create a QWidget with PySide and attach it to the 3dsmax main window. Creates two types of dockable widgets, a QDockWidget and a QToolbar ''' import os import ctypes -from PySide2 import QtCore -from PySide2 import QtGui -from PySide2.QtWidgets import QMainWindow, QDockWidget, QToolButton, QToolBar, QAction +from qtpy import QtCore +from qtpy import QtGui +from qtpy.QtWidgets import QMainWindow, QDockWidget, QToolButton, QToolBar, QAction from pymxs import runtime as rt from qtmax import GetQMaxMainWindow @@ -46,7 +46,7 @@ def create_cylinder(): def demo_docking_widgets(): """ - Demonstrates how to create a QWidget with PySide2 and attach it to the 3dsmax main window. + Demonstrates how to create a QWidget with PySide and attach it to the 3dsmax main window. Creates two types of dockable widgets, a QDockWidget and a QToolbar """ # Retrieve 3ds Max Main Window QWdiget diff --git a/src/samples/PySide2/simple_dialog.py b/src/samples/PySide/simple_dialog.py similarity index 84% rename from src/samples/PySide2/simple_dialog.py rename to src/samples/PySide/simple_dialog.py index 3fc5501..d076cf4 100644 --- a/src/samples/PySide2/simple_dialog.py +++ b/src/samples/PySide/simple_dialog.py @@ -1,8 +1,8 @@ ''' - Demonstrates how to create a QDialog with PySide2 and attach it to the 3ds Max main window. + Demonstrates how to create a QDialog with PySide and attach it to the 3ds Max main window. ''' -from PySide2.QtWidgets import QDialog, QLabel, QVBoxLayout, QPushButton +from qtpy.QtWidgets import QDialog, QLabel, QVBoxLayout, QPushButton from pymxs import runtime as rt from qtmax import GetQMaxMainWindow @@ -39,7 +39,7 @@ def init_ui(self): def demo_simple_dialog(): """ - Entry point for QDialog demo making use of PySide2 and pymxs + Entry point for QDialog demo making use of PySide and pymxs """ # reset 3ds Max rt.resetMaxFile(rt.Name('noPrompt')) diff --git a/src/samples/PySide2/test_ui.ui b/src/samples/PySide/test_ui.ui similarity index 100% rename from src/samples/PySide2/test_ui.ui rename to src/samples/PySide/test_ui.ui diff --git a/src/samples/PySide2/ui_loader.py b/src/samples/PySide/ui_loader.py similarity index 83% rename from src/samples/PySide2/ui_loader.py rename to src/samples/PySide/ui_loader.py index 3369960..57c310d 100644 --- a/src/samples/PySide2/ui_loader.py +++ b/src/samples/PySide/ui_loader.py @@ -1,10 +1,10 @@ ''' - Demonstrates loading .ui files with PySide2 + Demonstrates loading .ui files with PySide ''' import os -from PySide2.QtWidgets import QMainWindow -from PySide2.QtCore import QFile -from PySide2.QtUiTools import QUiLoader +from qtpy.QtWidgets import QMainWindow +from qtpy.QtCore import QFile +from qtpy.QtUiTools import QUiLoader from pymxs import runtime as rt from qtmax import GetQMaxMainWindow From c58829e45be3c1d6cd9351def185c2c8e19c2f06 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Thu, 5 Oct 2023 09:22:25 -0700 Subject: [PATCH 05/12] Code review improvements --- doc/pluginpackage.md | 6 +++--- doc/uninstall.md | 2 +- scripts/inst.sh | 2 +- src/packages/inbrowserhelp/inbrowserhelp/__init__.py | 12 +++++++++++- src/packages/speedsheet/speedsheet/__init__.py | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/pluginpackage.md b/doc/pluginpackage.md index 9145cd1..6191427 100644 --- a/doc/pluginpackage.md +++ b/doc/pluginpackage.md @@ -34,7 +34,7 @@ def _python_startup(): ``` -Then it integrates the samples to the new menu system of 2025: +Then it integrates the samples into the new menu system of 2025: ```python # configure 2025 menus @@ -56,7 +56,7 @@ Then it integrates the samples to the new menu system of 2025: ## Integration With the New Menu System The [menuhook](/src/menuhook/) code has been reworked to integrate menu items for the -various howtos to the new menu system: +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): @@ -64,7 +64,7 @@ def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_m Takes two new parameters: - `in2025_menuid` : the guid of the containing menu -- `id_2025i` : the guid of the item to create +- `id_2025` : the guid of the item to create And stores the needed menu items in the `registered_items` list: diff --git a/doc/uninstall.md b/doc/uninstall.md index 797d839..5b6b9f2 100644 --- a/doc/uninstall.md +++ b/doc/uninstall.md @@ -32,7 +32,7 @@ automatically when 3ds Max starts. ### For 3dsMax 2025 and greater -Starting with 2025, pystartup.ms is no longer used. Instead the +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". diff --git a/scripts/inst.sh b/scripts/inst.sh index e860708..2d74c16 100644 --- a/scripts/inst.sh +++ b/scripts/inst.sh @@ -68,7 +68,7 @@ installpip() { fi } -# install pystartup.ms or adn-dectech-python-howtos (plugin package) for 2025 +# install pystartup.ms or adn-devtech-python-howtos (plugin package) for 2025 installpystartup() { if [ "$version" -lt "2025" ] then diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index bc35abb..2759898 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -50,8 +50,18 @@ "Pymxs Online Documentation", "help.autodesk.com/view/MAXDEV/2024/ENU/?guid=MAXDEV_Python_using_pymxs_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") + ], + 2025: [ + ("gettingstarted", + "Getting Started With Python in 3ds Max", + "help.autodesk.com/view/MAXDEV/2025/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), + ("pymxs", + "Pymxs Online Documentation", + "help.autodesk.com/view/MAXDEV/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 diff --git a/src/packages/speedsheet/speedsheet/__init__.py b/src/packages/speedsheet/speedsheet/__init__.py index bb7aa6f..a26dca8 100644 --- a/src/packages/speedsheet/speedsheet/__init__.py +++ b/src/packages/speedsheet/speedsheet/__init__.py @@ -38,7 +38,7 @@ def startup(): "howtos", speedsheet, menu=["&Scripting", "Python3 Development", "How To"], - text="Output Object Data to File", - tooltip="Output Object Data to File", + text="Save object data to file", + tooltip="Save object data to file", in2025_menuid=menuhook.HOW_TO, id_2025="FFA6888A-B27A-4FA9-BFED-86FD3683E6B6") From 24718aa6da3e04279833dc9dad870877ecf3a377 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Thu, 5 Oct 2023 09:27:30 -0700 Subject: [PATCH 06/12] fix typo --- src/packages/singleinstancedlg/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/singleinstancedlg/README.md b/src/packages/singleinstancedlg/README.md index b9bcc8c..8ca4190 100644 --- a/src/packages/singleinstancedlg/README.md +++ b/src/packages/singleinstancedlg/README.md @@ -8,7 +8,7 @@ dialog ## Explanations -The sample creates a custome PySide dialog and calls `setObjectName` +The sample creates a custom PySide dialog and calls `setObjectName` on it with a unique name. The `show_dialog()` function only creates a new dialog if `findChild` cannot find the QDialog with the name specified in `setObjectName`. The dialog (either found or created) is From 3fb6f8d3562d930b478b19ac9a8b77461c77ad3e Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Thu, 12 Oct 2023 06:35:21 -0700 Subject: [PATCH 07/12] Update pip in the install script --- scripts/inst.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/inst.sh b/scripts/inst.sh index 2d74c16..1a82617 100644 --- a/scripts/inst.sh +++ b/scripts/inst.sh @@ -66,6 +66,9 @@ 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 or adn-devtech-python-howtos (plugin package) for 2025 From 99a541d72e664e65ebc90e61d2568afebe0a1f68 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 2 Apr 2024 08:54:24 -0700 Subject: [PATCH 08/12] fix formatting in inbrowserhelp --- .../inbrowserhelp/inbrowserhelp/__init__.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index 2759898..766ac80 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -1,64 +1,64 @@ """ inbrowserhelp example: inbrowserhelp sample """ -import webbrowser -import menuhook from sys import version_info from pymxs import runtime as rt +import webbrowser +import menuhook MAX_VERSION = rt.maxversion()[7] -MAX_HELP = f"help.autodesk.com/view/MAXDEV/{MAX_VERSION}/ENU" +MAX_HELP = f"help.autodesk.com/view/MAXDEV" PYTHON_VERSION = f"{version_info[0]}.{version_info[1]}" MAX_VERSION_TOPICS = { 2021: [ - ("gettingstarted", + ("gettingstarted", "Getting Started With Python in 3ds Max", - "help.autodesk.com/view/MAXDEV/2021/ENU/?guid=Max_Python_API_about_the_3ds_max_python_api_html", + f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_about_the_3ds_max_python_api_html", "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), - ("pymxs", + ("pymxs", "Pymxs Online Documentation", - "help.autodesk.com/view/MAXDEV/2021/ENU/?guid=Max_Python_API_using_pymxs_pymxs_module_html", + f"{MAX_HELP}/2021/ENU/?guid=Max_Python_API_using_pymxs_pymxs_module_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") ], 2022: [ - ("gettingstarted", + ("gettingstarted", "Getting Started With Python in 3ds Max", - "help.autodesk.com/view/MAXDEV/2022/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), - ("pymxs", + ("pymxs", "Pymxs Online Documentation", - "help.autodesk.com/view/MAXDEV/2022/ENU/?guid=MAXDEV_Python_using_pymxs_html", + f"{MAX_HELP}/2022/ENU/?guid=MAXDEV_Python_using_pymxs_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") ], 2023: [ - ("gettingstarted", + ("gettingstarted", "Getting Started With Python in 3ds Max", - "help.autodesk.com/view/MAXDEV/2023/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), - ("pymxs", + ("pymxs", "Pymxs Online Documentation", - "help.autodesk.com/view/MAXDEV/2023/ENU/?guid=MAXDEV_Python_using_pymxs_html", + f"{MAX_HELP}/2023/ENU/?guid=MAXDEV_Python_using_pymxs_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") ], 2024: [ - ("gettingstarted", + ("gettingstarted", "Getting Started With Python in 3ds Max", - "help.autodesk.com/view/MAXDEV/2024/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), - ("pymxs", + ("pymxs", "Pymxs Online Documentation", - "help.autodesk.com/view/MAXDEV/2024/ENU/?guid=MAXDEV_Python_using_pymxs_html", + f"{MAX_HELP}/2024/ENU/?guid=MAXDEV_Python_using_pymxs_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") ], 2025: [ - ("gettingstarted", + ("gettingstarted", "Getting Started With Python in 3ds Max", - "help.autodesk.com/view/MAXDEV/2025/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", + f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_about_the_3ds_max_python_api_html", "64651C48-F4F1-42F9-8C8A-FF5D0AA031A2"), - ("pymxs", + ("pymxs", "Pymxs Online Documentation", - "help.autodesk.com/view/MAXDEV/2025/ENU/?guid=MAXDEV_Python_using_pymxs_html", + f"{MAX_HELP}/2025/ENU/?guid=MAXDEV_Python_using_pymxs_html", "44985F87-C175-4F3D-B70F-9FA0B6242AE1") ] } @@ -70,27 +70,27 @@ def get_version_topics(version): V_TOPICS = get_version_topics(MAX_VERSION) -PYSIDE6_DOC = ("pyside6", +PYSIDE6_DOC = ("pyside6", "Qt for Python Documentation (PySide6)", "doc.qt.io/qtforpython-6/index.html", "E0E5F945-CD55-404A-840B-81540829E4C4") -PYSIDE2_DOC = ("pyside2", +PYSIDE2_DOC = ("pyside2", "Qt for Python Documentation (PySide2)", "doc.qt.io/qtforpython-5/contents.html", "13EEE11E-1BBB-470E-B757-F536D91215A9") TOPICS = V_TOPICS + [ - ("howtos", + ("howtos", "Python HowTos Github Repo", "github.com/ADN-DevTech/3dsMax-Python-HowTos", "2504EEA5-27D6-4EA0-A7A3-B3C058777ADC"), - ("samples", + ("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", + ("python", f"Python {PYTHON_VERSION} Documentation", f"docs.python.org/{PYTHON_VERSION}/", "B51BCC07-D9E3-439C-AC88-85BD64B97912") @@ -112,4 +112,4 @@ def startup(): tooltip=topic[1], in2025_menuid=menuhook.BROWSE_DOCUMENTATION, id_2025=topic[3]) - + From 9ba9c06ce4d08b202f1f13ec3767ae5d450fd23c Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 2 Apr 2024 08:59:27 -0700 Subject: [PATCH 09/12] more formatting --- src/packages/inbrowserhelp/inbrowserhelp/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index 766ac80..7255679 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -2,11 +2,11 @@ inbrowserhelp example: inbrowserhelp sample """ from sys import version_info -from pymxs import runtime as rt import webbrowser +from pymxs import runtime as rt import menuhook MAX_VERSION = rt.maxversion()[7] -MAX_HELP = f"help.autodesk.com/view/MAXDEV" +MAX_HELP = "help.autodesk.com/view/MAXDEV" PYTHON_VERSION = f"{version_info[0]}.{version_info[1]}" @@ -112,4 +112,3 @@ def startup(): tooltip=topic[1], in2025_menuid=menuhook.BROWSE_DOCUMENTATION, id_2025=topic[3]) - From 97deb9e3c8bf65f489c2fc257d870bc110ba5b00 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 2 Apr 2024 09:07:10 -0700 Subject: [PATCH 10/12] more formatting --- src/packages/menuhook/menuhook/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/packages/menuhook/menuhook/__init__.py b/src/packages/menuhook/menuhook/__init__.py index ff8f509..a59ff0e 100644 --- a/src/packages/menuhook/menuhook/__init__.py +++ b/src/packages/menuhook/menuhook/__init__.py @@ -135,7 +135,7 @@ def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_m add_macro(action, category, text or action, tooltip or action, fcn) if not defined and not menu is None: add_menu_item(menu, action, category) -#pylint: enable=too-many-arguments +#pylint: enable=too-many-arguments, line-too-long, broad-exception-caught # for 2025, pre-can a menu for the howtos def register_howtos_menu_2025(menumgr): @@ -164,10 +164,8 @@ def register_howtos_menu_2025(menumgr): scriptmenu = menumgr.getmenubyid(in2025_menuid) if scriptmenu is not None: try: - actionitem = scriptmenu.createaction(id_2025, 647394, f"{action}`{category}") + 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}") - - From a0209ea34586aeb6d4a7e11552517b32fe5cce53 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 2 Apr 2024 09:13:05 -0700 Subject: [PATCH 11/12] more formatting --- src/packages/menuhook/menuhook/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/packages/menuhook/menuhook/__init__.py b/src/packages/menuhook/menuhook/__init__.py index a59ff0e..e9eaee3 100644 --- a/src/packages/menuhook/menuhook/__init__.py +++ b/src/packages/menuhook/menuhook/__init__.py @@ -111,7 +111,7 @@ def add_menu_item(menu, action, category): OTHER_SAMPLES = "CBB6F619-57B9-4C81-8135-41958BEF5BED" registered_items = [] -#pylint: disable=too-many-arguments +#pylint: disable=too-many-arguments, line-too-long def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_menuid=None, id_2025=None): """ Appends a menu item to one of the menus of the main menubar. @@ -135,7 +135,7 @@ def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_m add_macro(action, category, text or action, tooltip or action, fcn) if not defined and not menu is None: add_menu_item(menu, action, category) -#pylint: enable=too-many-arguments, line-too-long, broad-exception-caught +#pylint: enable=too-many-arguments, line-too-long # for 2025, pre-can a menu for the howtos def register_howtos_menu_2025(menumgr): @@ -157,7 +157,7 @@ def register_howtos_menu_2025(menumgr): python_development.createsubmenu( OTHER_SAMPLES, "Other Samples") - + # hook the registered items for reg in registered_items: (in2025_menuid, id_2025, category, action) = reg @@ -165,7 +165,9 @@ def register_howtos_menu_2025(menumgr): if scriptmenu is not None: try: scriptmenu.createaction(id_2025, 647394, f"{action}`{category}") + #pylint: disable=line-too-long, broad-exception-caught except Exception as e: print(f"Could not create item {category}, {action} in menu {in2025_menuid} because {e}") + #pylint: enable=line-too-long, broad-exception-caught else: print(f"Could not create item {category}, {action}, in missing menu {in2025_menuid}") From c7c3b4d4fe2ed972fb352df06daf2f18d084df39 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 2 Apr 2024 09:21:11 -0700 Subject: [PATCH 12/12] more formatting --- src/packages/menuhook/menuhook/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/packages/menuhook/menuhook/__init__.py b/src/packages/menuhook/menuhook/__init__.py index e9eaee3..2c1dbc0 100644 --- a/src/packages/menuhook/menuhook/__init__.py +++ b/src/packages/menuhook/menuhook/__init__.py @@ -135,7 +135,7 @@ def register(action, category, fcn, menu=None, text=None, tooltip=None, in2025_m add_macro(action, category, text or action, tooltip or action, fcn) if not defined and not menu is None: add_menu_item(menu, action, category) -#pylint: enable=too-many-arguments, line-too-long +#pylint: enable=too-many-arguments, line-too-long # for 2025, pre-can a menu for the howtos def register_howtos_menu_2025(menumgr): @@ -165,9 +165,9 @@ def register_howtos_menu_2025(menumgr): if scriptmenu is not None: try: scriptmenu.createaction(id_2025, 647394, f"{action}`{category}") - #pylint: disable=line-too-long, broad-exception-caught +#pylint: disable=line-too-long, broad-exception-caught except Exception as e: print(f"Could not create item {category}, {action} in menu {in2025_menuid} because {e}") - #pylint: enable=line-too-long, broad-exception-caught +#pylint: enable=line-too-long, broad-exception-caught else: print(f"Could not create item {category}, {action}, in missing menu {in2025_menuid}")