From 785740e5d606b42bef9406d6596a63dd0ac13cff Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 7 Apr 2020 16:34:06 -0400 Subject: [PATCH 01/11] Reorganize directory structure --- README.md | 34 +++++++++--------- checks.sh | 30 +++++++++++----- Integration.png => doc/Integration.png | Bin Splash.png => doc/Splash.png | Bin uninstall.md => doc/uninstall.md | 0 inst.sh | 7 ++-- installhowtos.sh | 2 +- .../packages/inbrowserhelp}/LICENSE | 0 .../packages/inbrowserhelp}/README.md | 0 .../inbrowserhelp}/inbrowserhelp/__init__.py | 0 .../packages/inbrowserhelp}/setup.py | 0 {menuhook => src/packages/menuhook}/LICENSE | 0 {menuhook => src/packages/menuhook}/README.md | 0 .../packages/menuhook}/menuhook/__init__.py | 0 {menuhook => src/packages/menuhook}/setup.py | 0 .../packages/mxvscode}/.gitignore | 0 {mxvscode => src/packages/mxvscode}/LICENSE | 0 {mxvscode => src/packages/mxvscode}/README.md | 0 .../packages/mxvscode}/mxvscode/__init__.py | 0 {mxvscode => src/packages/mxvscode}/setup.py | 0 .../packages/quickpreview}/LICENSE | 0 .../packages/quickpreview}/README.md | 0 .../packages/quickpreview}/doc/Preview.png | Bin .../quickpreview}/quickpreview/__init__.py | 0 .../packages/quickpreview}/setup.py | 0 .../packages/reloadmod}/Capture.png | Bin {reloadmod => src/packages/reloadmod}/LICENSE | 0 .../packages/reloadmod}/README.md | 0 .../packages/reloadmod}/reloadmod/__init__.py | 0 .../packages/reloadmod}/reloadmod/reload.py | 0 .../packages/reloadmod}/setup.py | 0 .../packages/removeallmaterials}/LICENSE | 0 .../packages/removeallmaterials}/README.md | 0 .../removeallmaterials/__init__.py | 0 .../packages/removeallmaterials}/setup.py | 0 .../packages/renameselected}/LICENSE | 0 .../packages/renameselected}/README.md | 0 .../packages/renameselected}/doc/Dialog.png | Bin .../renameselected/__init__.py | 0 .../renameselected}/renameselected/ui.py | 0 .../packages/renameselected}/setup.py | 0 .../packages/singleinstancedlg}/LICENSE | 0 .../packages/singleinstancedlg}/README.md | 0 .../packages/singleinstancedlg}/setup.py | 0 .../singleinstancedlg/__init__.py | 0 .../singleinstancedlg/ui.py | 0 .../packages/speedsheet}/LICENSE | 0 .../packages/speedsheet}/README.md | 2 +- .../packages/speedsheet}/doc/Speedsheet.png | Bin .../packages/speedsheet}/setup.py | 0 .../speedsheet}/speedsheet/__init__.py | 0 .../packages/threadprogressbar}/LICENSE | 0 .../packages/threadprogressbar}/README.md | 2 +- .../threadprogressbar}/doc/Progress.png | Bin .../packages/threadprogressbar}/setup.py | 0 .../threadprogressbar/__init__.py | 0 .../threadprogressbar/ui.py | 0 .../packages/transformlock}/LICENSE | 0 .../packages/transformlock}/README.md | 0 .../packages/transformlock}/setup.py | 0 .../transformlock}/transformlock/__init__.py | 0 .../packages/zdepthchannel}/LICENSE | 0 .../packages/zdepthchannel}/README.md | 0 .../packages/zdepthchannel}/doc/ZDepth.png | Bin .../packages/zdepthchannel}/setup.py | 0 .../zdepthchannel}/zdepthchannel/__init__.py | 0 {pystartup => src/pystartup}/README.md | 4 +-- {pystartup => src/pystartup}/pystartup.ms | 0 uninstallhowtos.sh | 2 +- 69 files changed, 48 insertions(+), 35 deletions(-) rename Integration.png => doc/Integration.png (100%) rename Splash.png => doc/Splash.png (100%) rename uninstall.md => doc/uninstall.md (100%) rename {inbrowserhelp => src/packages/inbrowserhelp}/LICENSE (100%) rename {inbrowserhelp => src/packages/inbrowserhelp}/README.md (100%) rename {inbrowserhelp => src/packages/inbrowserhelp}/inbrowserhelp/__init__.py (100%) rename {inbrowserhelp => src/packages/inbrowserhelp}/setup.py (100%) rename {menuhook => src/packages/menuhook}/LICENSE (100%) rename {menuhook => src/packages/menuhook}/README.md (100%) rename {menuhook => src/packages/menuhook}/menuhook/__init__.py (100%) rename {menuhook => src/packages/menuhook}/setup.py (100%) rename {mxvscode => src/packages/mxvscode}/.gitignore (100%) rename {mxvscode => src/packages/mxvscode}/LICENSE (100%) rename {mxvscode => src/packages/mxvscode}/README.md (100%) rename {mxvscode => src/packages/mxvscode}/mxvscode/__init__.py (100%) rename {mxvscode => src/packages/mxvscode}/setup.py (100%) rename {quickpreview => src/packages/quickpreview}/LICENSE (100%) rename {quickpreview => src/packages/quickpreview}/README.md (100%) rename {quickpreview => src/packages/quickpreview}/doc/Preview.png (100%) rename {quickpreview => src/packages/quickpreview}/quickpreview/__init__.py (100%) rename {quickpreview => src/packages/quickpreview}/setup.py (100%) rename {reloadmod => src/packages/reloadmod}/Capture.png (100%) rename {reloadmod => src/packages/reloadmod}/LICENSE (100%) rename {reloadmod => src/packages/reloadmod}/README.md (100%) rename {reloadmod => src/packages/reloadmod}/reloadmod/__init__.py (100%) rename {reloadmod => src/packages/reloadmod}/reloadmod/reload.py (100%) rename {reloadmod => src/packages/reloadmod}/setup.py (100%) rename {removeallmaterials => src/packages/removeallmaterials}/LICENSE (100%) rename {removeallmaterials => src/packages/removeallmaterials}/README.md (100%) rename {removeallmaterials => src/packages/removeallmaterials}/removeallmaterials/__init__.py (100%) rename {removeallmaterials => src/packages/removeallmaterials}/setup.py (100%) rename {renameselected => src/packages/renameselected}/LICENSE (100%) rename {renameselected => src/packages/renameselected}/README.md (100%) rename {renameselected => src/packages/renameselected}/doc/Dialog.png (100%) rename {renameselected => src/packages/renameselected}/renameselected/__init__.py (100%) rename {renameselected => src/packages/renameselected}/renameselected/ui.py (100%) rename {renameselected => src/packages/renameselected}/setup.py (100%) rename {singleinstancedlg => src/packages/singleinstancedlg}/LICENSE (100%) rename {singleinstancedlg => src/packages/singleinstancedlg}/README.md (100%) rename {singleinstancedlg => src/packages/singleinstancedlg}/setup.py (100%) rename {singleinstancedlg => src/packages/singleinstancedlg}/singleinstancedlg/__init__.py (100%) rename {singleinstancedlg => src/packages/singleinstancedlg}/singleinstancedlg/ui.py (100%) rename {speedsheet => src/packages/speedsheet}/LICENSE (100%) rename {speedsheet => src/packages/speedsheet}/README.md (97%) rename {speedsheet => src/packages/speedsheet}/doc/Speedsheet.png (100%) rename {speedsheet => src/packages/speedsheet}/setup.py (100%) rename {speedsheet => src/packages/speedsheet}/speedsheet/__init__.py (100%) rename {threadprogressbar => src/packages/threadprogressbar}/LICENSE (100%) rename {threadprogressbar => src/packages/threadprogressbar}/README.md (97%) rename {threadprogressbar => src/packages/threadprogressbar}/doc/Progress.png (100%) rename {threadprogressbar => src/packages/threadprogressbar}/setup.py (100%) rename {threadprogressbar => src/packages/threadprogressbar}/threadprogressbar/__init__.py (100%) rename {threadprogressbar => src/packages/threadprogressbar}/threadprogressbar/ui.py (100%) rename {transformlock => src/packages/transformlock}/LICENSE (100%) rename {transformlock => src/packages/transformlock}/README.md (100%) rename {transformlock => src/packages/transformlock}/setup.py (100%) rename {transformlock => src/packages/transformlock}/transformlock/__init__.py (100%) rename {zdepthchannel => src/packages/zdepthchannel}/LICENSE (100%) rename {zdepthchannel => src/packages/zdepthchannel}/README.md (100%) rename {zdepthchannel => src/packages/zdepthchannel}/doc/ZDepth.png (100%) rename {zdepthchannel => src/packages/zdepthchannel}/setup.py (100%) rename {zdepthchannel => src/packages/zdepthchannel}/zdepthchannel/__init__.py (100%) rename {pystartup => src/pystartup}/README.md (95%) rename {pystartup => src/pystartup}/pystartup.ms (100%) diff --git a/README.md b/README.md index 41be460..235a6e8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # How To - Practical Examples For Python -![Splash](Splash.png) +![Splash](/doc/Splash.png) This repo contains various Python programming samples. @@ -18,7 +18,7 @@ directory of samples and documentation for Python developers. This being said, it is also possible to install the samples in 3ds Max. This will add a Python3 scripting menu to 3ds Max: -![Integration](Integration.png) +![Integration](/doc/Integration.png) The examples and some development goodies will be made available from there. @@ -53,7 +53,7 @@ to install the samples in 3ds Max. The script needs to be run from a 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](/pystartup/pystartup.ms). +from bash to install pip and [pystartup.ms](/src/pystartup/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 @@ -69,7 +69,7 @@ This script needs to run in the 3ds Max installation directory. ## Uninstalling the HowTos -The steps needed to uninstall the HowTos can be found in [uninstall.md](uninstall.md). +The steps needed to uninstall the HowTos can be found in [uninstall.md](/doc/uninstall.md). ### Option C: Install the howtos in a virtual environment > Note: the steps described here need to be done from a git bash prompt @@ -93,14 +93,14 @@ run [installhowtos.sh](/installhowtos.sh) from that directory. ## Packages that are not examples but that are provided in this repo -- [menuhook](menuhook/README.md) is not meant to be an example (but is still interesting as such!) but +- [menuhook](/src/packages/menuhook/README.md) is not meant to be an example (but is still interesting as such!) but as a way of attaching Python functions to 3ds Max menu items. The menuhook package is used by most of the other samples. -- [realoadmod](reloadmod/README.md) is small tool that will reload all development modules in one +- [realoadmod](/src/packages/reloadmod/README.md) is small tool that will reload all development modules in one operation -- [mxvscode](mxvscode/README.md) is a small tool that will automatically import ptvsd (the +- [mxvscode](/src/packages/mxvscode/README.md) is a small tool that will automatically import ptvsd (the VSCode debugging interface) during the startup of 3ds Max and make it accept remote connections. This may slow down the startup of 3ds Max quite a bit and is meant as a developer-only tool. @@ -116,22 +116,22 @@ the Python version in the best Python way known to us. An example of this is tha *How To?* -- Develop a Transform Lock Script [transformlock](transformlock/README.md) -- Remove all materials [removeallmaterials](removeallmaterials/README.md) -- Quickly rename selected objects [renameselected](renameselected/README.md) -- Output Object Data to File [speedsheet](speedsheet/README.md) -- Create a quick video preview [quickpreview](quickpreview/README.md) -- Access the Z-Depth Channel [zdepthchannel](zdepthchannel/README.md) +- Develop a Transform Lock Script [transformlock](/src/packages/transformlock/README.md) +- Remove all materials [removeallmaterials](/src/packages/removeallmaterials/README.md) +- Quickly rename selected objects [renameselected](/src/packages/renameselected/README.md) +- Output Object Data to File [speedsheet](/src/packages/speedsheet/README.md) +- Create a quick video preview [quickpreview](/src/packages/quickpreview/README.md) +- Access the Z-Depth Channel [zdepthchannel](/src/packages/zdepthchannel/README.md) ## Python Examples that don't come from maxscript howtos -- Update a progressbar from a Python thread [threadprogressbar](threadprogressbar/README.md) -- Create a single instance modal dialog [singleinstancedlg](singleinstancedlg/README.md) -- Add menu items to open documentation pages in the web browser [inbrowserhelp](inbrowserhelp/README.md) +- Update a progressbar from a Python thread [threadprogressbar](/src/packages/threadprogressbar/README.md) +- Create a single instance modal dialog [singleinstancedlg](/src/packages/singleinstancedlg/README.md) +- Add menu items to open documentation pages in the web browser [inbrowserhelp](/src/packages/inbrowserhelp/README.md) ## 3dsMax startup entry point -[pystartup](pystartup/README.md) provides the maxscript code that, when copied to 3ds Max's +[pystartup](/src/pystartup/README.md) provides the maxscript code that, when copied to 3ds Max's startup directory, will automatically launch pip packages with the 3dsMax startup entry point. diff --git a/checks.sh b/checks.sh index 7429315..f6cf3f7 100644 --- a/checks.sh +++ b/checks.sh @@ -1,24 +1,35 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) +packagedir="$script/src/packages" workdir=$(pwd) IFS=$'\n' +pl=pylint +lintdir() { + comment="$1" + folder="$2" + echo "$comment" + $pl "$folder" + # also prevent runtime.execute + if grep -n -R -E "runtime\.execute\(|rt.execute\(" --include '*.py' "$folder" + then + echo "pymxs.execute used" + exit 1 + fi +} lint() { - for f in $(find . -name "setup.py") + for f in $(find "$packagedir" -name "setup.py") do local package=$(basename $(dirname "$f")) - echo "$package" - pylint3 "./$package/$package" - # also prevent runtime.execute - if grep -n -R -E "runtime\.execute\(|rt.execute\(" --include '*.py' "./$package/$package" - then - echo "pymxs.execute used" - exit 1 - fi + lintdir "$package" "$packagedir/$package/$package" done } +lintsamples() { + lintdir "samples" "$script/src/samples" +} + checkmarkdown() { # find code blocks in markdown that don't specify the language git grep -n '```' -- "*.md" | @@ -73,5 +84,6 @@ checkmarkdownlinks() { lint +lintsamples checkmarkdown checkmarkdownlinks diff --git a/Integration.png b/doc/Integration.png similarity index 100% rename from Integration.png rename to doc/Integration.png diff --git a/Splash.png b/doc/Splash.png similarity index 100% rename from Splash.png rename to doc/Splash.png diff --git a/uninstall.md b/doc/uninstall.md similarity index 100% rename from uninstall.md rename to doc/uninstall.md diff --git a/inst.sh b/inst.sh index a7673ba..9652019 100644 --- a/inst.sh +++ b/inst.sh @@ -1,6 +1,7 @@ set -e script=$(dirname $(readlink -f "$0")) installdir=$(pwd) +packagedir="$script/src/packages" if [ ! -f "$installdir/installSettings.ini" ] then @@ -39,13 +40,13 @@ installpip() { # install pystartup.ms installpystartup() { - cp "$script/pystartup/pystartup.ms" "$startuppath" + cp "$script/src/pystartup/pystartup.ms" "$startuppath" } # install all Python packages in the repo with the -e option installpythonpackages() { - for f in $(find "$script" -name "setup.py") + for f in $(find "$packagedir" -name "setup.py") do local package=$(dirname "$f") "$installdir/Python37/python.exe" -m pip install --user -e "$package" @@ -54,7 +55,7 @@ installpythonpackages() { # uninstall all Python packages in the repo uninstallpythonpackages() { - for f in $(find "$script" -name "setup.py") + for f in $(find "$packagedir" -name "setup.py") do local package=$(basename "$(dirname "$f")") local pname="$package-autodesk" diff --git a/installhowtos.sh b/installhowtos.sh index 8e2dd4c..51ad995 100644 --- a/installhowtos.sh +++ b/installhowtos.sh @@ -18,7 +18,7 @@ fi venvscript () { echo "cd Scripts" echo "call activate.bat" - for f in $(find "$script" -name "setup.py") + for f in $(find "$packagedir" -name "setup.py") do local package=$(dirname "$f") echo "pip.exe install -e \"$(cygpath -d "$package")\"" diff --git a/inbrowserhelp/LICENSE b/src/packages/inbrowserhelp/LICENSE similarity index 100% rename from inbrowserhelp/LICENSE rename to src/packages/inbrowserhelp/LICENSE diff --git a/inbrowserhelp/README.md b/src/packages/inbrowserhelp/README.md similarity index 100% rename from inbrowserhelp/README.md rename to src/packages/inbrowserhelp/README.md diff --git a/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py similarity index 100% rename from inbrowserhelp/inbrowserhelp/__init__.py rename to src/packages/inbrowserhelp/inbrowserhelp/__init__.py diff --git a/inbrowserhelp/setup.py b/src/packages/inbrowserhelp/setup.py similarity index 100% rename from inbrowserhelp/setup.py rename to src/packages/inbrowserhelp/setup.py diff --git a/menuhook/LICENSE b/src/packages/menuhook/LICENSE similarity index 100% rename from menuhook/LICENSE rename to src/packages/menuhook/LICENSE diff --git a/menuhook/README.md b/src/packages/menuhook/README.md similarity index 100% rename from menuhook/README.md rename to src/packages/menuhook/README.md diff --git a/menuhook/menuhook/__init__.py b/src/packages/menuhook/menuhook/__init__.py similarity index 100% rename from menuhook/menuhook/__init__.py rename to src/packages/menuhook/menuhook/__init__.py diff --git a/menuhook/setup.py b/src/packages/menuhook/setup.py similarity index 100% rename from menuhook/setup.py rename to src/packages/menuhook/setup.py diff --git a/mxvscode/.gitignore b/src/packages/mxvscode/.gitignore similarity index 100% rename from mxvscode/.gitignore rename to src/packages/mxvscode/.gitignore diff --git a/mxvscode/LICENSE b/src/packages/mxvscode/LICENSE similarity index 100% rename from mxvscode/LICENSE rename to src/packages/mxvscode/LICENSE diff --git a/mxvscode/README.md b/src/packages/mxvscode/README.md similarity index 100% rename from mxvscode/README.md rename to src/packages/mxvscode/README.md diff --git a/mxvscode/mxvscode/__init__.py b/src/packages/mxvscode/mxvscode/__init__.py similarity index 100% rename from mxvscode/mxvscode/__init__.py rename to src/packages/mxvscode/mxvscode/__init__.py diff --git a/mxvscode/setup.py b/src/packages/mxvscode/setup.py similarity index 100% rename from mxvscode/setup.py rename to src/packages/mxvscode/setup.py diff --git a/quickpreview/LICENSE b/src/packages/quickpreview/LICENSE similarity index 100% rename from quickpreview/LICENSE rename to src/packages/quickpreview/LICENSE diff --git a/quickpreview/README.md b/src/packages/quickpreview/README.md similarity index 100% rename from quickpreview/README.md rename to src/packages/quickpreview/README.md diff --git a/quickpreview/doc/Preview.png b/src/packages/quickpreview/doc/Preview.png similarity index 100% rename from quickpreview/doc/Preview.png rename to src/packages/quickpreview/doc/Preview.png diff --git a/quickpreview/quickpreview/__init__.py b/src/packages/quickpreview/quickpreview/__init__.py similarity index 100% rename from quickpreview/quickpreview/__init__.py rename to src/packages/quickpreview/quickpreview/__init__.py diff --git a/quickpreview/setup.py b/src/packages/quickpreview/setup.py similarity index 100% rename from quickpreview/setup.py rename to src/packages/quickpreview/setup.py diff --git a/reloadmod/Capture.png b/src/packages/reloadmod/Capture.png similarity index 100% rename from reloadmod/Capture.png rename to src/packages/reloadmod/Capture.png diff --git a/reloadmod/LICENSE b/src/packages/reloadmod/LICENSE similarity index 100% rename from reloadmod/LICENSE rename to src/packages/reloadmod/LICENSE diff --git a/reloadmod/README.md b/src/packages/reloadmod/README.md similarity index 100% rename from reloadmod/README.md rename to src/packages/reloadmod/README.md diff --git a/reloadmod/reloadmod/__init__.py b/src/packages/reloadmod/reloadmod/__init__.py similarity index 100% rename from reloadmod/reloadmod/__init__.py rename to src/packages/reloadmod/reloadmod/__init__.py diff --git a/reloadmod/reloadmod/reload.py b/src/packages/reloadmod/reloadmod/reload.py similarity index 100% rename from reloadmod/reloadmod/reload.py rename to src/packages/reloadmod/reloadmod/reload.py diff --git a/reloadmod/setup.py b/src/packages/reloadmod/setup.py similarity index 100% rename from reloadmod/setup.py rename to src/packages/reloadmod/setup.py diff --git a/removeallmaterials/LICENSE b/src/packages/removeallmaterials/LICENSE similarity index 100% rename from removeallmaterials/LICENSE rename to src/packages/removeallmaterials/LICENSE diff --git a/removeallmaterials/README.md b/src/packages/removeallmaterials/README.md similarity index 100% rename from removeallmaterials/README.md rename to src/packages/removeallmaterials/README.md diff --git a/removeallmaterials/removeallmaterials/__init__.py b/src/packages/removeallmaterials/removeallmaterials/__init__.py similarity index 100% rename from removeallmaterials/removeallmaterials/__init__.py rename to src/packages/removeallmaterials/removeallmaterials/__init__.py diff --git a/removeallmaterials/setup.py b/src/packages/removeallmaterials/setup.py similarity index 100% rename from removeallmaterials/setup.py rename to src/packages/removeallmaterials/setup.py diff --git a/renameselected/LICENSE b/src/packages/renameselected/LICENSE similarity index 100% rename from renameselected/LICENSE rename to src/packages/renameselected/LICENSE diff --git a/renameselected/README.md b/src/packages/renameselected/README.md similarity index 100% rename from renameselected/README.md rename to src/packages/renameselected/README.md diff --git a/renameselected/doc/Dialog.png b/src/packages/renameselected/doc/Dialog.png similarity index 100% rename from renameselected/doc/Dialog.png rename to src/packages/renameselected/doc/Dialog.png diff --git a/renameselected/renameselected/__init__.py b/src/packages/renameselected/renameselected/__init__.py similarity index 100% rename from renameselected/renameselected/__init__.py rename to src/packages/renameselected/renameselected/__init__.py diff --git a/renameselected/renameselected/ui.py b/src/packages/renameselected/renameselected/ui.py similarity index 100% rename from renameselected/renameselected/ui.py rename to src/packages/renameselected/renameselected/ui.py diff --git a/renameselected/setup.py b/src/packages/renameselected/setup.py similarity index 100% rename from renameselected/setup.py rename to src/packages/renameselected/setup.py diff --git a/singleinstancedlg/LICENSE b/src/packages/singleinstancedlg/LICENSE similarity index 100% rename from singleinstancedlg/LICENSE rename to src/packages/singleinstancedlg/LICENSE diff --git a/singleinstancedlg/README.md b/src/packages/singleinstancedlg/README.md similarity index 100% rename from singleinstancedlg/README.md rename to src/packages/singleinstancedlg/README.md diff --git a/singleinstancedlg/setup.py b/src/packages/singleinstancedlg/setup.py similarity index 100% rename from singleinstancedlg/setup.py rename to src/packages/singleinstancedlg/setup.py diff --git a/singleinstancedlg/singleinstancedlg/__init__.py b/src/packages/singleinstancedlg/singleinstancedlg/__init__.py similarity index 100% rename from singleinstancedlg/singleinstancedlg/__init__.py rename to src/packages/singleinstancedlg/singleinstancedlg/__init__.py diff --git a/singleinstancedlg/singleinstancedlg/ui.py b/src/packages/singleinstancedlg/singleinstancedlg/ui.py similarity index 100% rename from singleinstancedlg/singleinstancedlg/ui.py rename to src/packages/singleinstancedlg/singleinstancedlg/ui.py diff --git a/speedsheet/LICENSE b/src/packages/speedsheet/LICENSE similarity index 100% rename from speedsheet/LICENSE rename to src/packages/speedsheet/LICENSE diff --git a/speedsheet/README.md b/src/packages/speedsheet/README.md similarity index 97% rename from speedsheet/README.md rename to src/packages/speedsheet/README.md index 2dc2eac..b794f94 100644 --- a/speedsheet/README.md +++ b/src/packages/speedsheet/README.md @@ -13,7 +13,7 @@ Non Goal: - explaining how to connect a Python function to a menu item (this is done -in other samples like [removeallmaterials](/removeallmaterials/README.md)) +in other samples like [removeallmaterials](/src/packages/removeallmaterials/README.md)) ## Explanations diff --git a/speedsheet/doc/Speedsheet.png b/src/packages/speedsheet/doc/Speedsheet.png similarity index 100% rename from speedsheet/doc/Speedsheet.png rename to src/packages/speedsheet/doc/Speedsheet.png diff --git a/speedsheet/setup.py b/src/packages/speedsheet/setup.py similarity index 100% rename from speedsheet/setup.py rename to src/packages/speedsheet/setup.py diff --git a/speedsheet/speedsheet/__init__.py b/src/packages/speedsheet/speedsheet/__init__.py similarity index 100% rename from speedsheet/speedsheet/__init__.py rename to src/packages/speedsheet/speedsheet/__init__.py diff --git a/threadprogressbar/LICENSE b/src/packages/threadprogressbar/LICENSE similarity index 100% rename from threadprogressbar/LICENSE rename to src/packages/threadprogressbar/LICENSE diff --git a/threadprogressbar/README.md b/src/packages/threadprogressbar/README.md similarity index 97% rename from threadprogressbar/README.md rename to src/packages/threadprogressbar/README.md index 4ce888a..a62641a 100644 --- a/threadprogressbar/README.md +++ b/src/packages/threadprogressbar/README.md @@ -7,7 +7,7 @@ *Non Goal:* - explaining how to connect a Python function to a menu item (this is done -in other samples like [removeallmaterials](/removeallmaterials/README.md)) +in other samples like [removeallmaterials](/src/packages/removeallmaterials/README.md)) ## Explanations diff --git a/threadprogressbar/doc/Progress.png b/src/packages/threadprogressbar/doc/Progress.png similarity index 100% rename from threadprogressbar/doc/Progress.png rename to src/packages/threadprogressbar/doc/Progress.png diff --git a/threadprogressbar/setup.py b/src/packages/threadprogressbar/setup.py similarity index 100% rename from threadprogressbar/setup.py rename to src/packages/threadprogressbar/setup.py diff --git a/threadprogressbar/threadprogressbar/__init__.py b/src/packages/threadprogressbar/threadprogressbar/__init__.py similarity index 100% rename from threadprogressbar/threadprogressbar/__init__.py rename to src/packages/threadprogressbar/threadprogressbar/__init__.py diff --git a/threadprogressbar/threadprogressbar/ui.py b/src/packages/threadprogressbar/threadprogressbar/ui.py similarity index 100% rename from threadprogressbar/threadprogressbar/ui.py rename to src/packages/threadprogressbar/threadprogressbar/ui.py diff --git a/transformlock/LICENSE b/src/packages/transformlock/LICENSE similarity index 100% rename from transformlock/LICENSE rename to src/packages/transformlock/LICENSE diff --git a/transformlock/README.md b/src/packages/transformlock/README.md similarity index 100% rename from transformlock/README.md rename to src/packages/transformlock/README.md diff --git a/transformlock/setup.py b/src/packages/transformlock/setup.py similarity index 100% rename from transformlock/setup.py rename to src/packages/transformlock/setup.py diff --git a/transformlock/transformlock/__init__.py b/src/packages/transformlock/transformlock/__init__.py similarity index 100% rename from transformlock/transformlock/__init__.py rename to src/packages/transformlock/transformlock/__init__.py diff --git a/zdepthchannel/LICENSE b/src/packages/zdepthchannel/LICENSE similarity index 100% rename from zdepthchannel/LICENSE rename to src/packages/zdepthchannel/LICENSE diff --git a/zdepthchannel/README.md b/src/packages/zdepthchannel/README.md similarity index 100% rename from zdepthchannel/README.md rename to src/packages/zdepthchannel/README.md diff --git a/zdepthchannel/doc/ZDepth.png b/src/packages/zdepthchannel/doc/ZDepth.png similarity index 100% rename from zdepthchannel/doc/ZDepth.png rename to src/packages/zdepthchannel/doc/ZDepth.png diff --git a/zdepthchannel/setup.py b/src/packages/zdepthchannel/setup.py similarity index 100% rename from zdepthchannel/setup.py rename to src/packages/zdepthchannel/setup.py diff --git a/zdepthchannel/zdepthchannel/__init__.py b/src/packages/zdepthchannel/zdepthchannel/__init__.py similarity index 100% rename from zdepthchannel/zdepthchannel/__init__.py rename to src/packages/zdepthchannel/zdepthchannel/__init__.py diff --git a/pystartup/README.md b/src/pystartup/README.md similarity index 95% rename from pystartup/README.md rename to src/pystartup/README.md index fae6e4b..8829316 100644 --- a/pystartup/README.md +++ b/src/pystartup/README.md @@ -25,8 +25,8 @@ yourpackagename works) and startup is an exported function of yourpackagename that will be called during startup. Most if not all Python samples in this repo implement this entry -point. [transformlock's setup script](/transformlock/setup.py) can -be taken as en example as well as [transformlock's \_\_init\_\_.py](/transformlock/transformlock/__init__.py). +point. [transformlock's setup script](/src/packages/transformlock/setup.py) can +be taken as en example as well as [transformlock's \_\_init\_\_.py](/src/packages/transformlock/transformlock/__init__.py). ## The maxscript code diff --git a/pystartup/pystartup.ms b/src/pystartup/pystartup.ms similarity index 100% rename from pystartup/pystartup.ms rename to src/pystartup/pystartup.ms diff --git a/uninstallhowtos.sh b/uninstallhowtos.sh index 5b8b7b0..5d98530 100644 --- a/uninstallhowtos.sh +++ b/uninstallhowtos.sh @@ -6,7 +6,7 @@ source "$script/inst.sh" venvscript () { echo "cd Scripts" echo "call activate.bat" - for f in $(find "$script" -name "setup.py") + for f in $(find "$packagedir" -name "setup.py") do local package=$(basename "$(dirname "$f")") echo "pip.exe uninstall -y $package-autodesk" From 6ed01fec794fececd652e194b8a443dd643e1301 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Tue, 7 Apr 2020 16:34:36 -0400 Subject: [PATCH 02/11] Add the samples --- src/samples/PySide2/combine_meshes.py | 53 ++++ src/samples/PySide2/cylinder_icon_48.png | Bin 0 -> 2198 bytes src/samples/PySide2/docking_widgets.py | 94 ++++++ src/samples/PySide2/simple_dialog.py | 50 +++ src/samples/PySide2/test_ui.ui | 99 ++++++ src/samples/PySide2/ui_loader.py | 34 +++ src/samples/__init__.py | 0 src/samples/pymxs/animation.py | 77 +++++ src/samples/pymxs/app_chunk.py | 53 ++++ src/samples/pymxs/apply_material.py | 29 ++ src/samples/pymxs/assets.py | 10 + src/samples/pymxs/bent_cylinder.py | 15 + src/samples/pymxs/class_types.py | 125 ++++++++ src/samples/pymxs/combine_selected_meshes.py | 29 ++ src/samples/pymxs/enumerate_parameters.py | 16 + src/samples/pymxs/file_save.py | 9 + src/samples/pymxs/get_rendered_normals.py | 17 ++ src/samples/pymxs/hit_test.py | 16 + src/samples/pymxs/list_scripts.py | 10 + src/samples/pymxs/make_instances.py | 36 +++ src/samples/pymxs/materials.py | 76 +++++ src/samples/pymxs/mesh_and_cpv.py | 86 ++++++ src/samples/pymxs/mxs_token.py | 90 ++++++ src/samples/pymxs/notifications.py | 303 +++++++++++++++++++ src/samples/pymxs/output_plugin_classes.py | 14 + src/samples/pymxs/poly_object.py | 42 +++ src/samples/pymxs/pymxs_classes.py | 37 +++ src/samples/pymxs/render.py | 63 ++++ src/samples/pymxs/scene_graph.py | 12 + src/samples/pymxs/sphere_borg.py | 23 ++ src/samples/pymxs/timer.py | 64 ++++ src/samples/pymxs/transform_nodes.py | 56 ++++ src/samples/pymxs/tree_of_spheres.py | 23 ++ src/samples/unicode_io.py | 139 +++++++++ 34 files changed, 1800 insertions(+) create mode 100644 src/samples/PySide2/combine_meshes.py create mode 100644 src/samples/PySide2/cylinder_icon_48.png create mode 100644 src/samples/PySide2/docking_widgets.py create mode 100644 src/samples/PySide2/simple_dialog.py create mode 100644 src/samples/PySide2/test_ui.ui create mode 100644 src/samples/PySide2/ui_loader.py create mode 100644 src/samples/__init__.py create mode 100644 src/samples/pymxs/animation.py create mode 100644 src/samples/pymxs/app_chunk.py create mode 100644 src/samples/pymxs/apply_material.py create mode 100644 src/samples/pymxs/assets.py create mode 100644 src/samples/pymxs/bent_cylinder.py create mode 100644 src/samples/pymxs/class_types.py create mode 100644 src/samples/pymxs/combine_selected_meshes.py create mode 100644 src/samples/pymxs/enumerate_parameters.py create mode 100644 src/samples/pymxs/file_save.py create mode 100644 src/samples/pymxs/get_rendered_normals.py create mode 100644 src/samples/pymxs/hit_test.py create mode 100644 src/samples/pymxs/list_scripts.py create mode 100644 src/samples/pymxs/make_instances.py create mode 100644 src/samples/pymxs/materials.py create mode 100644 src/samples/pymxs/mesh_and_cpv.py create mode 100644 src/samples/pymxs/mxs_token.py create mode 100644 src/samples/pymxs/notifications.py create mode 100644 src/samples/pymxs/output_plugin_classes.py create mode 100644 src/samples/pymxs/poly_object.py create mode 100644 src/samples/pymxs/pymxs_classes.py create mode 100644 src/samples/pymxs/render.py create mode 100644 src/samples/pymxs/scene_graph.py create mode 100644 src/samples/pymxs/sphere_borg.py create mode 100644 src/samples/pymxs/timer.py create mode 100644 src/samples/pymxs/transform_nodes.py create mode 100644 src/samples/pymxs/tree_of_spheres.py create mode 100644 src/samples/unicode_io.py diff --git a/src/samples/PySide2/combine_meshes.py b/src/samples/PySide2/combine_meshes.py new file mode 100644 index 0000000..20c3aae --- /dev/null +++ b/src/samples/PySide2/combine_meshes.py @@ -0,0 +1,53 @@ +''' +Demonstrates combining the mesh of two scene nodes +''' +from PySide2.QtWidgets import QVBoxLayout, QPushButton, QLabel, QDialog, QMessageBox +from pymxs import runtime as rt # pylint: disable=import-error +from qtmax import GetQMaxMainWindow + +def combine_two_meshes(): + """ + Convert the two provided objects to meshes and merges them as a single editable mesh. + """ + if len(rt.getCurrentSelection()) != 2: + msg = "Please select 2 nodes to combine." + show_alert(msg) + else: + first_item_selected = rt.convertToMesh(rt.getCurrentSelection()[0]) + second_item_selected = rt.convertToMesh(rt.getCurrentSelection()[1]) + # create a new, empty editable mesh for the combined meshes + new_obj = rt.Editable_mesh() + # combine 'first_item_selected' and 'second_item_selected' into the new mesh + mesh_operation = rt.meshOp + mesh_operation.attach(new_obj, first_item_selected, deleteSourceNode=False) + mesh_operation.attach(new_obj, second_item_selected, deleteSourceNode=False) + +def show_alert(message): + """ + Display a message using a Qt Message Box. + """ + msg_box = QMessageBox() + msg_box.setText(message) + msg_box.exec_() + +def demo_combine_meshes(): + """ + Demonstrates combining the mesh of two scene nodes + Prompt user to select two nodes to be merged and merge them. + """ + dialog = QDialog(GetQMaxMainWindow()) + dialog.resize(250, 100) + dialog.setWindowTitle('DEMO - Combine 2 Nodes') + + main_layout = QVBoxLayout() + label = QLabel("Combine 2 Nodes") + main_layout.addWidget(label) + + combine_btn = QPushButton("Combine") + combine_btn.clicked.connect(combine_two_meshes) + main_layout.addWidget(combine_btn) + + dialog.setLayout(main_layout) + dialog.show() + +demo_combine_meshes() diff --git a/src/samples/PySide2/cylinder_icon_48.png b/src/samples/PySide2/cylinder_icon_48.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a4b5cdea7e51b0fa9e6eed866a0c2c7b277a0f GIT binary patch literal 2198 zcmaJ@3se(l7LEjYRa6$`r9w{B%4?F$B$JRxus{N)5FQbMq9S=rOcpW|lK{aNiXc){ z6suASi!G&U1*~nc@|1GWl?DY|P(Z2)?h&b?uv8Q%xDyq(XOE}z{Qo=myZ5`Vb0#k; zBGlf_&5l4I*bCP4MEIL#zHO}VXPm`kF8*@W@#A$HFu5*WtU(BYGE9Py1S;`1M1+WC z8Qa?se*%GcKp7LSix-A-q?n2#HtSIIDm9KK5d7Ea)naKfq9aL=ZAz3&epY{lOj63Y zwB44YFZJM2vJtvXm_&uL~gg>p8dq6`~W9^r{q8%h7YmukCX1wOLFh zlU}RnlDXtJq~e89q+m>gkYEZEkb-oG#9~uGm<{jrbBcF2=bYJ zFh7J(^9yD%c`!2&WX)j(s8%ONrO2FbCGPtzmhqQZPOt_M>o83WhNaAPL6id1VOj;I zCIttZs}7Sqg<`1^H4DAWg?bY=578*o5Lt)@Q;}Y0nxlLV3mF9ZF&Hd5AgAMTGhk2- zNZ7C+zyx8YL?VR{3CtkB#moMb!8e_XOYlh4bA&i_8pvWw5II1TLoxsaS!{p}GU$L* zf-l)J7y`u-G8LytH8; zB!sP(#<|QoVwEmc&!fkd3ZwoW6}TejTN`h`6|F~>RUGP>inmGQb_y>7RcM{p+599Y zr0%;4($(<%CBFp~yL9sxuK;@fc7Ks5{PD{ERnn=MQ{hTysXME!;;2Ue>dqRPZYt3} zo9xJ}ZW$WyGDuQWPh{npOeW28w|zVJx_1&T%o}coN3D&Pd#v}&OKI+~+HdOHG7=jb zYxVEg>}0}M`P*GA?zOeIcb_k0T@3)FcSo+S&$dDW~$QtE;aM>u$BR z*^WGU(s}!~6MOl>wN0~5w3c=Z1PP`bzg?dd7x!d5@oJYF5KA94(QM`Q!nV1^D`Q4h#%zeN=_3 z4$(BrK;q&KscBhh#0#}TaS(zaPq*!9X(1uy$DX83Z)n&pt4Y{WX#Y`z9W_&tlG1Sh z*I$LUwzf-dTyAWv7#>zaU))${QmqZY{EV~JmG?#2#<;lq&xVKd>h#e^MS}MEH)ESV zwD^aIapG*GK#(zT_wKUWd^eQltQ`y)yWG^2NUg9wuqh$IvT5}-PSFpwwVyN_UR1c~ zqc(2jYjVn_0WYf``}+F6cOBIxkN>ZK+iu0_ktkz$+ z^RL!c%hIy4&@KMjLj?tnXNKAV<%fG5N=r*)5)&iJ%gcYfc+mkK8<^oSbxU*rV59KgSpu8R;?_Pxn6f`M_d32bMnsksm%i ze*Ad$373Km5e=RI0IceYZ|>~xcdM$b>>L{dYc5?9WoE8B7xV4Weuto$pNjUj9bMTq zHC5-?EQ1^h18yl8WPRga-V+bdy&!Z+`{eBGY|}x5A`ufc_^%k!R6hl77oi?vJ(nK+3v6H72pgv7ADOTXG$=jd^0- + + TabWidget + + + + 0 + 0 + 374 + 243 + + + + TabWidget + + + 0 + + + + + 0 + 0 + + + + Tab 1 + + + + QLayout::SetNoConstraint + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + PushButton + + + + + + + ... + + + + + + + + + + + + + + 0 + 0 + + + + Tab 2 + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + + + diff --git a/src/samples/PySide2/ui_loader.py b/src/samples/PySide2/ui_loader.py new file mode 100644 index 0000000..3369960 --- /dev/null +++ b/src/samples/PySide2/ui_loader.py @@ -0,0 +1,34 @@ +''' + Demonstrates loading .ui files with PySide2 +''' +import os +from PySide2.QtWidgets import QMainWindow +from PySide2.QtCore import QFile +from PySide2.QtUiTools import QUiLoader +from pymxs import runtime as rt +from qtmax import GetQMaxMainWindow + +class MyWindow(QMainWindow): + """ + Main window class object loading a .ui file + """ + def __init__(self, parent=None): + super(MyWindow, self).__init__(parent) + self.setWindowTitle('Pyside2 Qt Window') + self.init_ui() + + def init_ui(self): + """ Prepare Qt UI layout for main window content """ + ui_file = QFile(os.path.dirname(os.path.realpath(__file__)) + "\\test_ui.ui") + ui_file.open(QFile.ReadOnly) + self.loaded_ui = QUiLoader().load(ui_file, self) + ui_file.close() + +def demo_ui_loader(): + """ + Entry point to demonstrate how to load a .ui file + """ + win = MyWindow(GetQMaxMainWindow()) + win.show() + +demo_ui_loader() diff --git a/src/samples/__init__.py b/src/samples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/samples/pymxs/animation.py b/src/samples/pymxs/animation.py new file mode 100644 index 0000000..3445514 --- /dev/null +++ b/src/samples/pymxs/animation.py @@ -0,0 +1,77 @@ +''' + Demonstrates simple animation. +''' +from pymxs import runtime as rt # pylint: disable=import-error +import pymxs as mx # pylint: disable=import-error + +print("Hello World Animation") + +def print_interval(interval): + '''Prints an animation interval''' + print(f"Current Animation Range: [{interval.start},{interval.end}]") + +def set_animation_ranges(): + '''Changes the animation range from the default of 100 frames to 200 frames''' + frames = 200 + print_interval(rt.animationRange) + rt.animationRange = rt.Interval(0, frames) + # The animation slider now shows 200 frames + print_interval(rt.animationRange) + + +def animate_transform(thing): + '''Records an animation on the provided object''' + # select the object to animate so we will see the keyframes in the timeslider + rt.select(thing) + + # animate + with mx.animate(True): + with mx.redraw(True): + with mx.attime(30): + thing.pos = rt.Point3(50, 0, 0) + + with mx.attime(60): + thing.Pos = rt.Point3(100, 50, 0) + + with mx.attime(90): + thing.Pos = rt.Point3(50, 100, 0) + + with mx.attime(120): + thing.Pos = rt.Point3(0, 100, 0) + + with mx.attime(150): + thing.Pos = rt.Point3(-50, 50, 0) + + with mx.attime(180): + thing.Pos = rt.Point3(0, 0, 0) + +def playback_animation(): + '''Play back the animation 3 times''' + rt.playbackLoop = False + # play animation + print("Playing back Animation first time") + rt.sliderTime = 0 + rt.timeConfiguration.playbackSpeed = 3 # normal speed + rt.playAnimation() + + # replay it + print("Playing back Animation second time") + rt.timeConfiguration.playbackSpeed = 4 # double speed + rt.sliderTime = 0 + rt.playAnimation() + + # replay it, faster + print("Playing back Animation third time, faster") + rt.sliderTime = 0 + rt.timeConfiguration.playbackSpeed = 5 # 4x speed + rt.playAnimation() + +def demo_animation(): + '''Show how to do animation''' + rt.resetMaxFile(rt.Name('noPrompt')) + sphere = rt.sphere() + set_animation_ranges() + animate_transform(sphere) + playback_animation() + +demo_animation() diff --git a/src/samples/pymxs/app_chunk.py b/src/samples/pymxs/app_chunk.py new file mode 100644 index 0000000..89f0f5e --- /dev/null +++ b/src/samples/pymxs/app_chunk.py @@ -0,0 +1,53 @@ +''' + Demonstrates how to manage user specified data for any object derived from Animatable. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def create_scene(): + """Create and save scene_with_app_chunk.max""" + rt.resetMaxFile(rt.Name('noPrompt')) + + # Create a teapot, a scene node and a material instance, they are all + # objects of Animatable + node = rt.teapot() + teapot = node.baseObject + mtl = rt.StandardMaterial() + node.Material = mtl + node.name = "MyTeapot123" + + # Now add some user specified strings to these objects + rt.setAppdata(teapot, 112, "blah comit") + rt.setAppdata(teapot, 1234, "I'm a teapot!") + rt.setAppdata(teapot, 2345, u"我是一个茶壶!") + + rt.setAppdata(node, 5678, "Node of teapot") + rt.setAppdata(node, 7890, "This is to be removed") + rt.deleteAppdata(node, 7890) + + rt.setAppdata(mtl, 4567, "Material of teapot") + rt.saveMaxFile("scene_with_app_chunk.max") + print("scene with AppChunk is saved.") + + +def load_and_verify(): + """Load and verify scene_with_app_chunk.max""" + rt.resetMaxFile(rt.Name('noPrompt')) + rt.loadMaxFile("scene_with_app_chunk.max") + print("scene with AppChunk is loaded.") + # Find the "MyTeapot123" node + teapot_node = rt.getNodeByName("MyTeapot123") + + if teapot_node is None: + print("Error: Incorrect saved scene.") + else: + print(rt.getAppData(teapot_node, 678)) + obj = teapot_node.baseObject + print(rt.getAppData(obj, 1234)) + print(rt.getAppData(obj, 2345)) + rt.clearAllAppData(obj) + print("No 9432 app data {}".format(rt.getAppData(obj, 9432) is None)) + print("No 7890 app data {}".format(rt.getAppData(teapot_node, 9432) is None)) + print(rt.getAppData(teapot_node.Material, 4567)) + +create_scene() +load_and_verify() diff --git a/src/samples/pymxs/apply_material.py b/src/samples/pymxs/apply_material.py new file mode 100644 index 0000000..ca01c71 --- /dev/null +++ b/src/samples/pymxs/apply_material.py @@ -0,0 +1,29 @@ +''' + Applies a standard material to all nodes in the scene. + Also shows the use of generator functions in Python. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def create_sphere(): + """Create a sphere of radius 5.""" + return rt.sphere(radius=5) + +def solid_material(color): + """Create a material.""" + material = rt.StandardMaterial() + material.Ambient = color + material.Diffuse = color + material.Specular = rt.Color(255, 255, 255) + material.Shininess = 50.0 + material.ShinyStrength = 70.0 + material.SpecularLevel = 70.0 + return material + +def apply_material_to_nodes(material, nodes=rt.rootnode.children): + """Apply a material to multiple nodes.""" + for node in nodes: + node.Material = material + +create_sphere() +MAT = solid_material(rt.Color(0, 0, 255)) +apply_material_to_nodes(MAT) diff --git a/src/samples/pymxs/assets.py b/src/samples/pymxs/assets.py new file mode 100644 index 0000000..7269251 --- /dev/null +++ b/src/samples/pymxs/assets.py @@ -0,0 +1,10 @@ +''' + Lists all of the assets in a file. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +NASSETS = rt.AssetManager.GetNumAssets() +print(f"There are {NASSETS} assets created") +for i in range(NASSETS): + a = rt.AssetManager.GetAssetByIndex(i + 1) + print(f"Asset id = {a.GetAssetId()}, type = {a.getType()}, file = {a.getfilename()}") diff --git a/src/samples/pymxs/bent_cylinder.py b/src/samples/pymxs/bent_cylinder.py new file mode 100644 index 0000000..1d7a38a --- /dev/null +++ b/src/samples/pymxs/bent_cylinder.py @@ -0,0 +1,15 @@ +''' + Demonstrates creating a cylinder and appling a bend modifier. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def main(): + """Create a cylinder and add a bend modifier to it.""" + cyl = rt.cylinder() + cyl.radius = 10 + cyl.height = 30 + bend = rt.Bend() + bend.bendAngle = 45 + rt.addModifier(cyl, bend) + +main() diff --git a/src/samples/pymxs/class_types.py b/src/samples/pymxs/class_types.py new file mode 100644 index 0000000..7b94cd2 --- /dev/null +++ b/src/samples/pymxs/class_types.py @@ -0,0 +1,125 @@ +''' + Demonstrates creating many different types of scene objects that are visible in the viewport. + The scene objects are grouped by type. + The types created are Cameras, Lights, Geometric Objects, Shapes, Helpers, Modifiers + and Materials. +''' +from pymxs import runtime as rt # pylint: disable=import-error +OBJECT_DIMENSION = 5.0 +Y_STEP = OBJECT_DIMENSION * 4 +X_STEP = OBJECT_DIMENSION * 2.0 + +def create_box(): + """Create a box.""" + box = rt.box() + box.Height = OBJECT_DIMENSION + box.Width = OBJECT_DIMENSION + box.Length = OBJECT_DIMENSION + return box + +def create_text(pos, message): + """Create a text.""" + tex = rt.text() + tex.size = Y_STEP + tex.text = message + tex.position = rt.Point3(pos.x, pos.y - OBJECT_DIMENSION, pos.z) + tex.wirecolor = rt.Color(255, 128, 255) + +def create_teapot(): + """Create a teapot.""" + teapot = rt.teapot() + teapot.radius = OBJECT_DIMENSION + return teapot + +def layout_objects(title, cases, y_position, x_offset_text=-45): + """Layout a list of nodes in a line""" + create_text(rt.Point3(x_offset_text, y_position, 0), title) + x_position = 0.0 + for gen in cases: + gen.Position = rt.point3(x_position, y_position, 0) + x_position += X_STEP + if (x_position % 260.0) < 0.001: + x_position = 0.0 + y_position += Y_STEP + return y_position + +def create_classes(classes): + """Create all createble instances of the provided classes""" + for obj in classes: + try: + created = obj() + print(created) + yield created + except RuntimeError: + pass + +def create_cameras(y_position): + """Create all creatable cameras""" + print("-- Cameras") + return layout_objects("Cameras", create_classes(rt.camera.classes), y_position) + +def create_lights(y_position): + """Create all creatable lights""" + print("-- Lights") + return layout_objects("Lights", create_classes(rt.light.classes), y_position) + +def create_objects(y_position): + """Create all creatable objects""" + print("-- Geometric Objects") + return layout_objects( + "Geometric Objects", + create_classes(rt.GeometryClass.classes), + y_position, -88.0) + +def create_shapes(y_position): + """Create all creatable shapes""" + print("-- Shapes") + return layout_objects("Shapes", create_classes(rt.shape.classes), y_position) + +def create_helpers(y_position): + """Create all creatable helpers""" + print("-- Helpers") + return layout_objects("Helpers", create_classes(rt.helper.classes), y_position) + +def create_modifiers(y_position): + """Create all creatable modifiers""" + def create(): + for mod in rt.modifier.classes: + try: + created = mod() + print(created) + box = create_box() + rt.addModifier(box, created) + yield box + except RuntimeError: + pass + print("-- Modifiers") + return layout_objects("Modifiers", create(), y_position) + +def create_materials(y_position): + """Create all creatable materials""" + def create(): + for mat in rt.material.classes: + try: + created = mat() + print(mat) + teapot = create_teapot() + teapot.Material = created + yield teapot + except RuntimeError: + pass + print("-- Materials") + return layout_objects("Materials", create(), y_position) + +def create_items(): + """Create all the items in the sample.""" + rt.resetMaxFile(rt.Name('noPrompt')) + y_line = create_materials(0.0) + 40.0 + y_line = create_modifiers(y_line) + 40.0 + y_line = create_helpers(y_line) + 40.0 + y_line = create_shapes(y_line) + 40.0 + y_line = create_objects(y_line) + 40.0 + y_line = create_lights(y_line) + 40.0 + y_line = create_cameras(y_line) + 40.0 + +create_items() diff --git a/src/samples/pymxs/combine_selected_meshes.py b/src/samples/pymxs/combine_selected_meshes.py new file mode 100644 index 0000000..7102c95 --- /dev/null +++ b/src/samples/pymxs/combine_selected_meshes.py @@ -0,0 +1,29 @@ +''' +Demonstrates combining multipe scene nodes to an editable mesh. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def combine_objects(*args): + """ + Convert the two provided objects to meshes and merges them as a single mesh. + """ + first = rt.convertToMesh(args[0]) + for obj in args[1:]: + rt.attach(first, rt.convertToMesh(obj)) + + return first + +def combine_selected_objects(): + """ + Convert the selected objects into an editable mesh. The selections needs to contain + at least 2 objects to combine. + """ + if len(rt.selection) < 2: + msg = "Please select at least 2 nodes to combine." + print(msg) + rt.messageBox(msg) + else: + # combine all the selected nodes into one editable mesh + combine_objects(*rt.selection) + +combine_selected_objects() diff --git a/src/samples/pymxs/enumerate_parameters.py b/src/samples/pymxs/enumerate_parameters.py new file mode 100644 index 0000000..1da1510 --- /dev/null +++ b/src/samples/pymxs/enumerate_parameters.py @@ -0,0 +1,16 @@ +''' + Creates all geometric objects and lists their parameters. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +for cl in rt.GeometryClass.classes: + try: + obj = cl() + print(f"Properties for class {cl}") + # This would also work: + # rt.showProperties(o) + for pn in rt.getPropNames(obj): + print(f" {pn} {rt.getProperty(obj, pn)}") + except RuntimeError: + # Some geometry classes cannot be instantiated + pass diff --git a/src/samples/pymxs/file_save.py b/src/samples/pymxs/file_save.py new file mode 100644 index 0000000..d7d842d --- /dev/null +++ b/src/samples/pymxs/file_save.py @@ -0,0 +1,9 @@ +""" + Demonstrate saving a max file. +""" +from os import path +from pymxs import runtime as rt # pylint: disable=import-error + +FILEPATH = path.join(rt.sysInfo.tempdir, "test.max") +rt.saveMaxFile(FILEPATH) +print(f"Requested filename {FILEPATH}\n path {rt.maxFilePath}\n file {rt.maxFileName}") diff --git a/src/samples/pymxs/get_rendered_normals.py b/src/samples/pymxs/get_rendered_normals.py new file mode 100644 index 0000000..7997985 --- /dev/null +++ b/src/samples/pymxs/get_rendered_normals.py @@ -0,0 +1,17 @@ +''' +An example of how to get the vertices and normals of a node +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def main(): + """Create a box and display its vertices and normals.""" + box = rt.box() + print(f"node name: {box.name}") + trimesh = rt.convertTo(box, rt.TrimeshGeometry) + print(f" verts: {trimesh.numVerts}") + for vert in range(trimesh.numVerts): + normal = rt.getNormal(trimesh, vert + 1) + vertex = rt.getVert(trimesh, vert + 1) + print(f"vertex: {vertex}") + print(f"RNormal: {normal}") +main() diff --git a/src/samples/pymxs/hit_test.py b/src/samples/pymxs/hit_test.py new file mode 100644 index 0000000..8d9a1d6 --- /dev/null +++ b/src/samples/pymxs/hit_test.py @@ -0,0 +1,16 @@ +''' + Performs a hit test on an object in the active viewport. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def main(): + """Demonstrate hit testing of rays with scene objects.""" + obj = rt.sphere(radius=50) + point = rt.Point2(400, 200) + hit_ray = rt.intersectRay(obj, rt.mapScreenToWorldRay(point)) + print(f"hit success {bool(hit_ray)} for point {point}") + point = rt.Point2(0, 0) + hit_ray = rt.intersectRay(obj, rt.mapScreenToWorldRay(point)) + print(f"hit success {bool(hit_ray)} for point {point}") + +main() diff --git a/src/samples/pymxs/list_scripts.py b/src/samples/pymxs/list_scripts.py new file mode 100644 index 0000000..8e4b736 --- /dev/null +++ b/src/samples/pymxs/list_scripts.py @@ -0,0 +1,10 @@ +''' + Lists all the files in a folder +''' +import os +from pymxs import runtime as rt # pylint: disable=import-error + +PY_SCRIPTS_DIR = os.path.join(rt.getDir(rt.Name("scripts")), 'python') +for root, dirs, files in os.walk(PY_SCRIPTS_DIR, topdown=False): + for name in files: + print(name) diff --git a/src/samples/pymxs/make_instances.py b/src/samples/pymxs/make_instances.py new file mode 100644 index 0000000..b08895d --- /dev/null +++ b/src/samples/pymxs/make_instances.py @@ -0,0 +1,36 @@ +''' + Demonstrates creating instances of a node hierarchy. +''' +import pymxs # pylint: disable=import-error +from pymxs import runtime as rt # pylint: disable=import-error + +INST = rt.Name("instance") + +def create_instance_clones(obj, count, offset): + """Create count clones of obj setting the parent of each clone to the + previous clone.""" + for _ in range(count): + # the maxscript CloneNodes method accepts a named argument called 'newNodes' + # the argument must be sent by reference as it serves as an output argument + # since the argument is not also an input argument, we can simply initialize + # the byref() object as 'None' + # the output argument along with the call result is then returned in a tuple + # note: 'newNodes' returns an array of cloned nodes + # in the current case, only one element is cloned + result, cloned = rt.MaxOps.CloneNodes(obj, cloneType=INST, offset=offset, newNodes=pymxs.byref(None)) + cloned[0].parent = obj + obj = cloned[0] + +def main(): + """Demonstrate cloning""" + rt.resetMaxFile(rt.Name('noPrompt')) + + obj = rt.sphere(radius=3) + create_instance_clones(obj, 10, rt.Point3(5, 0, 0)) + rt.MaxOps.CloneNodes( + obj, + cloneType=INST, + offset=rt.Point3(0, 25, 0), + expandHierarchy=True) + +main() diff --git a/src/samples/pymxs/materials.py b/src/samples/pymxs/materials.py new file mode 100644 index 0000000..f816cdc --- /dev/null +++ b/src/samples/pymxs/materials.py @@ -0,0 +1,76 @@ +''' + Demonstrates how to iterate through materials and and apply them to objects. + It shows how to open the material editor and put materials in the editor. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def create_floor(): + """Create a rectangle for the floor""" + plane = rt.Plane() + plane.width = 120 + plane.length = 120 + +def print_material_properties(material_instance): + """Print the properties of a given material""" + print("[%s]" % material_instance.name) + for name in rt.getPropNames(material_instance): + print("\t" + name + " = " + str(rt.getProperty(material_instance, name))) + +def create_text(xpos, ypos, rot, message): + """Create a visible label on the ground for a given teapot""" + tex = rt.text() + tex.size = 10 + tex.text = message + tex.position = rt.Point3(xpos, ypos, 0) + tex.rotation = rot + tex.wireColor = rt.Color(255, 128, 255) + +def showcase_materials(materials): + """Create a teapot sample and a visible label for each provided material""" + num_materials = len(materials) + diff = 360.0 / num_materials + teapot_radius = 5.0 + radius = 50.0 + text_radius = 90.0 + index = 0 + i = 0 + + for mat in materials: + position = rt.Point3(radius, 0, 0) + rot = rt.angleAxis(i, rt.Point3(0, 0, 1)) + + teapot = rt.teapot() + teapot.radius = teapot_radius + teapot.position = position + teapot.rotation = rot + teapot.Material = mat + print_material_properties(mat) + + create_text(text_radius, 0, rot, mat.name) + if index < 24: + rt.setMeditMaterial(index + 1, mat) + index += 1 + i += diff + +def sample(): + """Create all existing materials and showcase them.""" + def try_create(mat): + """Try to create a given material. If not creatable return None.""" + try: + return mat() + except RuntimeError: + return None + rt.resetMaxFile(rt.Name('noPrompt')) + # maximize the view (select a view with only the one viewport) + rt.viewport.setLayout(rt.name("layout_1")) + # show the material editor in basic mode + rt.MatEditor.mode = rt.Name("basic") + rt.MatEditor.open() + # create a plane for the floor + create_floor() + # instantiate all materials that can be instantiated + materials = filter(lambda x: x is not None, map(try_create, rt.material.classes)) + # showcase all materials + showcase_materials(list(materials)) + +sample() diff --git a/src/samples/pymxs/mesh_and_cpv.py b/src/samples/pymxs/mesh_and_cpv.py new file mode 100644 index 0000000..3224aaf --- /dev/null +++ b/src/samples/pymxs/mesh_and_cpv.py @@ -0,0 +1,86 @@ +''' + Demonstrates how to create a mesh from scratch and to set color per vertex data. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def set_edge_visibility(mesh, face, aedge, bedge, cedge): + """Set the visibility of face edges""" + rt.setEdgeVis(mesh, face, 1, aedge) + rt.setEdgeVis(mesh, face, 2, bedge) + rt.setEdgeVis(mesh, face, 3, cedge) + +def make_pyramid_mesh(side=20.0): + """Create a pyramid from vertices and faces.""" + mesh = rt.mesh() + mesh.numverts = 4 + mesh.numfaces = 4 + + halfside = side / 2.0 + rt.SetVert(mesh, 1, rt.Point3(0.0, 0.0, side)) + rt.SetVert(mesh, 2, rt.Point3(-halfside, -halfside, 0.0)) + rt.SetVert(mesh, 3, rt.Point3(-halfside, halfside, 0.0)) + rt.SetVert(mesh, 4, rt.Point3(halfside, 0.0, 0.0)) + + rt.setFace(mesh, 1, 1, 2, 3) + set_edge_visibility(mesh, 1, True, True, False) + + rt.setFace(mesh, 2, 1, 3, 4) + set_edge_visibility(mesh, 2, True, True, False) + + rt.setFace(mesh, 3, 1, 4, 2) + set_edge_visibility(mesh, 2, True, True, False) + + rt.setFace(mesh, 4, 2, 3, 4) + set_edge_visibility(mesh, 2, True, True, False) + + rt.update(mesh) + return mesh + +def output_channel(mesh, channel, name): + """Retrieve and display the information about a given mesh map.""" + print("Channel: " + name) + if not rt.meshop.getMapSupport(mesh, channel): + print(" Not enabled") + return + + vertices = rt.meshop.getNumMapVerts(mesh, channel) + print(f" Number of texture vertices: {vertices}") + for vindex in range(1, vertices + 1): + vertex = rt.meshop.getMapVert(mesh, channel, vindex) + print(f" Texture vertex {vertex.X}, {vertex.Y}, {vertex.Z}") + + faces = rt.meshop.getNumMapFaces(mesh, channel) + print(f" Number of faces: {faces}") + for findex in range(1, faces + 1): + face = rt.meshop.getMapFace(mesh, channel, findex) + print(f" Texture vertex indices {face.X}, {face.Y}, {face.Z}") + print() + +def output_channels(mesh): + """Retrieve and display the information about all channels.""" + nummaps = rt.meshop.getNumMaps(mesh) + print(f"NumMaps: {nummaps}") + print() + output_channel(mesh, 0, "color per vertex") + output_channel(mesh, 1, "texture mapping") + +def main(): + """Create a mesh, color it, and output information about its maps.""" + # reset the scene + rt.resetMaxFile(rt.Name('noPrompt')) + # create a mesh + mesh = make_pyramid_mesh() + print("Updating the color per vertex channel") + rt.setNumCPVVerts(mesh, 2) + rt.buildVCFaces(mesh) + rt.setVertColor(mesh, 1, rt.Color(255, 0, 0)) + rt.setVertColor(mesh, 2, rt.Color(0, 0, 255)) + rt.setVCFace(mesh, 1, rt.Point3(1, 1, 2)) + rt.setVCFace(mesh, 2, rt.Point3(1, 2, 2)) + rt.setVCFace(mesh, 3, rt.Point3(2, 2, 2)) + rt.setVCFace(mesh, 4, rt.Point3(1, 1, 1)) + rt.setCVertMode(mesh, True) + rt.update(mesh) + output_channels(mesh) + +main() diff --git a/src/samples/pymxs/mxs_token.py b/src/samples/pymxs/mxs_token.py new file mode 100644 index 0000000..0fb37e6 --- /dev/null +++ b/src/samples/pymxs/mxs_token.py @@ -0,0 +1,90 @@ +''' + Demonstrates the use of mxstoken +''' +import threading +import time +import pymxs # pylint: disable=import-error + +def mxstoken_sample(): + ''' + Demonstrate 3 threads using mxstoken. + ''' + flag = True + counter = 0 + + def call_mxs_entry(): + ''' + Access pymxs in a mxstoken protected block of code. + ''' + with pymxs.mxstoken(): + pymxs.runtime.Teapot() + + def call_mxs_entry_ex_1(locker, tick, evt): + ''' + Demonstrate a first function with a mix of concurrent code + and mxstoken protected code. + ''' + try: + locker.acquire() + nonlocal flag, counter + flag = False + with pymxs.mxstoken(): + pymxs.runtime.Teapot(Name="call_mxs_entry_ex_1") + # give up lock, let ex_2 could exec codes + locker.release() + if not evt.wait(tick): + pymxs.print_( + "Error: event untriggered\n" + + "which indicates 'with block' in ex_2 haven't finished\n", + True, + True) + counter = 30 + except: + pymxs.print_("Error: unexpected exception\n", True, True) + raise + finally: + if locker.locked(): + locker.release() + + def call_mxs_entry_ex_2(locker, tick, evt): + ''' + Demonstrate a second function with a mix of concurrent code + and mxstoken protected code. + ''' + nonlocal flag, counter + while flag: + time.sleep(tick) + + try: + locker.acquire() + # we expected this block is finished + # before ex_1 wakeup from sleep + for _ in range(10): + # only a indicator, could just assign counter = 10 + counter = counter + 1 + evt.set() + with pymxs.mxstoken(): + # this block won't be executed after ex_1 with block finished + pymxs.runtime.Teapot(Name="call_mxs_entry_ex_2") + if counter != 30: + pymxs.print_( + ("Error: expected counter 30, got %d" + + "which indicates 'with block' in ex_2 haven't finished\n").format(counter), + True, + True) + finally: + if locker.locked(): + locker.release() + pymxs.print_("success\n", False, True) + + # Steps: + locker = threading.Lock() + evt = threading.Event() + thread1 = threading.Thread(target=call_mxs_entry) + thread2 = threading.Thread(target=call_mxs_entry_ex_1, args=(locker, 1, evt)) + thread3 = threading.Thread(target=call_mxs_entry_ex_2, args=(locker, 0.01, evt)) + thread1.start() + thread2.start() + thread3.start() + +mxstoken_sample() diff --git a/src/samples/pymxs/notifications.py b/src/samples/pymxs/notifications.py new file mode 100644 index 0000000..2dd78fa --- /dev/null +++ b/src/samples/pymxs/notifications.py @@ -0,0 +1,303 @@ +''' + Lists all of the notification codes broadcast by 3ds Max, + and registers a callback function for each and every one. +''' +import os +from pymxs import runtime as rt # pylint: disable=import-error + +# sadly, maxscript does not expose this list +NOTIFICATIONS = [ + "unitsChange", + "timeunitsChange", + "viewportChange", + "spacemodeChange", + "systemPreReset", + "systemPostReset", + "systemPreNew", + "systemPostNew", + "filePreOpen", + "filePostOpen", + "filePreMerge", + "filePostMerge", + "filePreSave", + "filePostSave", + "selectionSetChanged", + "bitmapChanged", + "preRender", + "preRenderFrame", + "postRender", + "postRenderFrame", + "preImport", + "postImport", + "importFailed", + "preExport", + "postExport", + "exportFailed", + "nodeRenamed", + "modPanelSelChanged", + "animateOn", + "animateOff", + "mtlLibPreOpen", + "mtlLibPostOpen", + "mtlLibPreSave", + "mtlLibPostSave", + "mtlLibPreMerge", + "mtlLibPostMerge", + "preRenderEval", + "renderParamsChanged", + "nodeCreated", + "nodeLinked", + "nodeUnlinked", + "nodeHide", + "nodeUnhide", + "nodeFreeze", + "nodeUnfreeze", + "nodePreMaterial", + "nodePostMaterial", + "sceneNodeAdded", + "selectedNodesPreDelete", + "selectedNodesPostDelete", + "mainWindowEnabled", + "preSystemShutdown", + "postSystemStartup", + "pluginLoaded", + "postSystemShutdown", + "colorChanged", + "heightMenuChanged", + "fileLinkPreBind", + "fileLinkPostBind", + "fileLinkPreDetatch", + "fileLinkPostDetatch", + "fileLinkPreReload", + "fileLinkPostReload", + "fileLinkPreAttach", + "fileLinkPostAttach", + "nodePreDelete", + "nodePostDelete", + "radiosityProcessStart", + "radiosityProcessStopped", + "radiosityProcessReset", + "radiosityProcessDone", + "modPanelObjPreChange", + "modPanelObjPostChange", + "sceneUndo", + "sceneRedo", + "manipulateModeOn", + "manipulateModeOff", + "animationRangeChange", + "filePostMergeProcess", + "filePostOpenProcess", + "svSelectionSetChanged", + "svDoubleClickGraphNode", + "preModifierAdded", + "postModifierAdded", + "preModifierDeleted", + "postModifierDeleted", + "postNodesCloned", + "preRendererChange", + "postRendererChange", + "svPreLayoutChange", + "svPostLayoutChange", + "layerCreated", + "layerDeleted", + "nodeLayerChanged", + "beginRenderingActualFrame", + "beginRenderingReflectRefractMap", + "beginRenderingTonemappingImage", + "byCategoryDisplayFilterChanged", + "customDisplayFilterChanged", + "filePreSaveOld", + "filePostSaveOld", + "filelinkPostReloadPrePrune", + "lightingUnitDisplaySystemChange", + "mtlRefAdded", + "mtlRefDeleted", + "nodeCloned", + "objectXrefPreMerge", + "objectXrefPostMerge", + "preMirrorNodes", + "postMirrorNodes", + "preNodeBonePropChanged", + "postNodeBonePropChanged", + "preNodeGeneralPropChanged", + "postNodeGeneralPropChanged", + "preNodeGiPropChanged", + "postNodeGiPropChanged", + "preNodeMentalrayPropChanged", + "postNodeMentalrayPropChanged", + "preNodeUserPropChanged", + "postNodeUserPropChanged", + "preProgress", + "postProgress", + "preNodesCloned", + "radiosityPluginChanged", + "sceneXrefPostMerge", + "sceneXrefPreMerge", + "systemPostDirChange", + "systemPreDirChange", + "tabbedDialogCreated", + "tabbedDialogDeleted", + "nodeNameSet", + "preSceneUndo", + "preSceneRedo", + "preSceneStateSave", + "postSceneStateSave", + "preSceneStateRestore", + "postSceneStateRestore", + "sceneStateDelete", + "sceneStateRename", + "filePreOpenProcess", + "filePreSaveProcess", + "filePostSaveProcess", + "classDescLoaded", + "atsPreRepathPhase", + "atsPostRepathPhase", + "proxyTempDisableStart", + "proxyTempDisableEnd", + "NamedSelSetCreated", + "NamedSelSetDeleted", + "NamedSelSetRenamed", + "ModPanelSubObjectLevelChanged", + "FailedDirectXMaterialTextureLoad", + "D3DPreDeviceReset", + "D3DPostDeviceReset", + "postSceneReset", + "animLayersEnabled", + "animLayersDisabled", + "selectionLocked", + "selectionUnlocked", + "preImageViewerDisplay", + "postImageViewerDisplay", + "imageViewerUpdate", + "activeViewportChanged", + "NamedSelSetPreModify", + "NamedSelSetPostModify", + "ClassDescAdded", + "ObjectDefinitionChangeBegin", + "ObjectDefinitionChangeEnd", + "preAppThemeChange", + "postAppThemeChange", + "preViewPanelDelete", + "preWorkspaceChange", + "postWorkspaceChange", + "preWorkspaceCollectionChange", + "postWorkspaceCollectionChange", + "mouseSettingsChanged", + "preSavingCuiToolbars", + "postSavingCuiToolbars", + "preLoadingCuiToolbars", + "postLoadingCuiToolbars", + "appActivated", + "appDeactivated", + "cuiMenusUpdate", + "fileOpenFailed", + "postRestoreObjsDeleted", + "preSavingMenus", + "postSavingMenus", + "viewportSafeFrameToggle", + "postLoadingMenus", + "layerParentChanged", + "actionItemHotkeyPreExecute", + "actionItemHotKeyPostExecute", + "actionItemExecutionStarted", + "actionItemExecutionEnded", + "interactivePluginCreationStarted", + "interactivePluginCreationEnded", + "filePostMerge2", + "postNodeSelectOperation", + "preViewportTooltip", + "welcomeScreenDone", + "playbackStart", + "playbackEnd", + "sceneExplorerNeedsUpdate", + "filePostOpenProcessFinalized", + "filePostMergeProcessFinalized", + "preProjectFolderChange", + "postProjectFolderChange", + "preStartupScriptLoad", + "activeShadeInViewportToggled", + "systemShutdownCheck", + "systemShutdownCheckFailed", + "systemShutdownCheckPassed", + "filePostMerge3", + "matLibPreOpen", + "matLibPostOpen", + "matLibPreSave", + "matLibPostSave", + "matLibPreMerge", + "matLibPostMerge", + "selNodesPreDelete", + "selNodesPostDelete", + "wmEnable"] + +def list_codes(): + """List all known notification names.""" + print(NOTIFICATIONS) + print(f"Number Notifications registered: {len(NOTIFICATIONS)}") + +def handle_notification(code): + """Handle a specific notification.""" + print(f"Notification handled: {code}") + +def handle_callback(): + """Generic callback Python handler.""" + # note: less generic callback function shall be defined for specific events + # the notificationParams() returned elements are specific to event name/type + print(f"Received event notification\n with notification parameters being {rt.callbacks.notificationParam()}") + +def create_maxscript_callback_function(): + """Register a maxscript function to a Python callcack.""" + # Create a maxscript function (called 'pcb') referencing a Python function + rt.pcb = handle_notification + +def register_all_callbacks(): + """Register all the callbacks that we know.""" + create_maxscript_callback_function() + for name in NOTIFICATIONS: + # register a maxscript line to call for specified event name + # calls the referenced Python function with an hardcoded argument + # the optional named 'id' argument is used as a best practice, + # to make it easier to find and remove callbacks later + rt.callbacks.addScript(rt.Name(name), "pcb(\"{}\")".format(name), id=rt.Name("my_mxs_handler")) + + # register a Python function to call for event name + # the registered function cannot take any arguments + # the optional named 'id' argument is used as a best practice, + # to make it easier to find and remove callbacks later + rt.callbacks.addScript(rt.Name(name), handle_callback, id=rt.Name("my_python_handler")) + +def unregister_all_callbacks(): + """Unregister all the callbacks that we know.""" + for name in NOTIFICATIONS: + rt.callbacks.removeScripts(rt.Name(name), id=rt.Name("my_mxs_handler")) + rt.callbacks.removeScripts(rt.Name(name), id=rt.Name("my_python_handler")) + +def main(): + """Demonstrate callback registration.""" + # List all callback names that we know + list_codes() + # Register all callback names + register_all_callbacks() + + # Do some things + print("Creating sphere") + sphere1 = rt.sphere() + print("Setting radius for sphere 1") + sphere1.radius = 2.0 + print("Creating sphere 2") + sphere2 = rt.sphere() + print("Setting radius on sphere 2") + sphere2.radius = 2.0 + print("Setting parent of node 2 to node 1") + sphere2.Parent = sphere1 + + print("Saving file") + output_path = os.path.join(rt.sysInfo.tempdir, 'temp.max') + rt.saveMaxFile(output_path) + print("Opening file") + rt.loadMaxFile(output_path) + + print('unregistering notification handlers') + unregister_all_callbacks() + +main() diff --git a/src/samples/pymxs/output_plugin_classes.py b/src/samples/pymxs/output_plugin_classes.py new file mode 100644 index 0000000..9407a33 --- /dev/null +++ b/src/samples/pymxs/output_plugin_classes.py @@ -0,0 +1,14 @@ +''' + Demonstrates using the PluginManager to extract information about loaded + plugins. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +# List all plug-in dlls +PLUGIN_COUNT = rt.pluginManager.pluginDllCount +print(f"Total PluginDlls: {PLUGIN_COUNT}\n") +# maxscript uses one based indices +for p in range(1, PLUGIN_COUNT + 1): + print("PluginDll:", rt.pluginManager.pluginDllFullPath(p)) + print("Description:", rt.pluginManager.pluginDllName(p)) + print("Loaded:", rt.pluginManager.isPluginDllLoaded(p)) diff --git a/src/samples/pymxs/poly_object.py b/src/samples/pymxs/poly_object.py new file mode 100644 index 0000000..fd33c81 --- /dev/null +++ b/src/samples/pymxs/poly_object.py @@ -0,0 +1,42 @@ +''' + Demonstrates how to create a mmesh from scratch and to set color per vertex data. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def make_pyramid_mesh(side=20.0): + '''Construct a pyramid from vertices and faces.''' + halfside = side / 2.0 + return rt.mesh( + vertices=[ + rt.point3(0.0, 0.0, side), + rt.point3(-halfside, -halfside, 0.0), + rt.point3(-halfside, halfside, 0.0), + rt.point3(halfside, 0.0, 0.0) + ], + faces=[ + rt.point3(1, 2, 3), + rt.point3(1, 3, 4), + rt.point3(1, 4, 2), + rt.point3(2, 3, 4), + ]) + +def color_pyramid_mesh(mesh): + '''Add two color vertices, and refer them in the faces (color the pyramid).''' + rt.setNumCPVVerts(mesh, 2, True) + rt.setVertColor(mesh, 1, rt.Point3(255, 0, 0)) + rt.setVertColor(mesh, 2, rt.Point3(0, 0, 255)) + rt.buildVCFaces(mesh) + rt.setVCFace(mesh, 1, 1, 1, 2) + rt.setVCFace(mesh, 2, 1, 2, 2) + rt.setVCFace(mesh, 3, 2, 2, 2) + rt.setVCFace(mesh, 4, 1, 1, 1) + rt.setCVertMode(mesh, True) + rt.update(mesh) + +def main(): + '''Construct a pyramid and then add colors to its faces.''' + rt.resetMaxFile(rt.Name('noPrompt')) + mesh = make_pyramid_mesh() + color_pyramid_mesh(mesh) + +main() diff --git a/src/samples/pymxs/pymxs_classes.py b/src/samples/pymxs/pymxs_classes.py new file mode 100644 index 0000000..e97a309 --- /dev/null +++ b/src/samples/pymxs/pymxs_classes.py @@ -0,0 +1,37 @@ +''' + Demonstrates using the inspect module to list all of the classes in + pymxs and the total number of members exposed. + + (Note that there is no 1:1 relationship between the classes in + maxscript and the python classes exposed in pymxs: a single pymxs + wrapper exposes almost all maxscript classes. You can use + pymxs.runtime.apropos(), showClass(), and other MAXScript inspection + methods to get information about MAXScript objects and classes) +''' +import os +import inspect +import pymxs # pylint: disable=import-error +from pymxs import runtime as rt # pylint: disable=import-error + +def inspect_pymxs(): + """Inspect the classes exported by pymxs and generate a report.""" + api = {} + classes = inspect.getmembers(pymxs, inspect.isclass) + totalcnt = 0 + for curclass in classes: + name = str(curclass[0]) + membercnt = len(curclass[1].__dict__) + totalcnt += membercnt + api[name] = membercnt + + fname = os.path.join(rt.sysInfo.tempdir, 'pyms_api.txt') + with open(fname, 'w') as output: + for k in sorted(api.keys()): + output.write(k + " has " + str(api[k]) + " members\n") + + print("Results saved to", fname) + print("Total number of classes ", len(api)) + print("Total number of API elements ", totalcnt) + print("Average number of API elements per class ", totalcnt / len(api)) + +inspect_pymxs() diff --git a/src/samples/pymxs/render.py b/src/samples/pymxs/render.py new file mode 100644 index 0000000..2242b80 --- /dev/null +++ b/src/samples/pymxs/render.py @@ -0,0 +1,63 @@ +""" + Demonstrate scene rendering with pymxs. +""" +import os +import math +import pymxs # pylint: disable=import-error +from pymxs import runtime as rt # pylint: disable=import-error + +INST = rt.Name("instance") + +def create_spheres(): + '''Create a scene made of spiralling spheres.''' + sphere = rt.sphere(radius=6.0) + revolutions = 9 * 360 + radius = 40.0 + z_sphere = 0.0 + # cloning the original sphere to create the spiral effect + for i in range(0, revolutions, 20): + # the maxscript CloneNodes method accepts a named argument called 'newNodes' + # the argument must be sent by reference as it serves as an output argument + # since the argument is not also an input argument, we can simply initialize + # the byref() object as 'None' + # the output argument along with the call result is then returned in a tuple + # note: 'newNodes' returns an array of cloned nodes + # in the current case, only one element is cloned + result, nodes = rt.MaxOps.CloneNodes(sphere, cloneType=INST, newNodes=pymxs.byref(None)) + radians = math.radians(i) + x_sphere = radius * math.cos(radians) + y_sphere = radius * math.sin(radians) + # note: 'newNodes' returned an array of cloned nodes + # in the current case, only one element is cloned + nodes[0].Position = rt.Point3(x_sphere, y_sphere, z_sphere) + z_sphere += 1.0 + radius -= 0.20 + +def maximize_perspective(): + '''Setup perspective for the render''' + rt.viewport.setLayout(rt.Name('layout_1')) + rt.viewport.setType(rt.Name('view_persp_user')) + rt.viewport.setTM( + rt.matrix3( + rt.point3(0.707107, 0.353553, -0.612372), + rt.point3(-0.707107, 0.353553, -0.612372), + rt.point3(0, 0.866025, 0.5), + rt.point3(-0.00967026,-70.3466,-552.481) + ) + ) + +def render(): + '''Render in the renderoutput directory.''' + output_path = os.path.join(rt.getDir(rt.Name("renderoutput")), 'foo.jpg') + if os.path.exists(output_path): + os.remove(output_path) + rt.render(outputFile=output_path) + +def demo_render(): + '''Create a demo scene, adjust the perspective and render the scene''' + rt.resetMaxFile(rt.Name('noPrompt')) + create_spheres() + maximize_perspective() + render() + +demo_render() diff --git a/src/samples/pymxs/scene_graph.py b/src/samples/pymxs/scene_graph.py new file mode 100644 index 0000000..ed94617 --- /dev/null +++ b/src/samples/pymxs/scene_graph.py @@ -0,0 +1,12 @@ +''' + Creates a simple text representation of the scene graph +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def output_node(node, indent=''): + """Print the scene graph as text to stdout.""" + print(indent, node.Name) + for child in node.Children: + output_node(child, indent + '--') + +output_node(rt.rootnode) diff --git a/src/samples/pymxs/sphere_borg.py b/src/samples/pymxs/sphere_borg.py new file mode 100644 index 0000000..7c564ae --- /dev/null +++ b/src/samples/pymxs/sphere_borg.py @@ -0,0 +1,23 @@ +''' + Demonstrates creating objects, object instancing, and object translation. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +INST = rt.Name("instance") + +def create_borg(obj, num, spacing): + """Create a bunch of clones of the provided object""" + for i in range(num): + for j in range(num): + for k in range(num): + if i or j or k: + point = rt.Point3(i * spacing, j * spacing, k * spacing) + rt.MaxOps.CloneNodes(obj, cloneType=INST, offset=point) + +def main(): + """Create a base object and turn it into a borg, whatever that is.""" + obj = rt.sphere() + obj.Radius = 2.0 + create_borg(obj, 4, 5.0) + +main() diff --git a/src/samples/pymxs/timer.py b/src/samples/pymxs/timer.py new file mode 100644 index 0000000..c87af06 --- /dev/null +++ b/src/samples/pymxs/timer.py @@ -0,0 +1,64 @@ +""" + Demonstrate timers. +""" +import threading +import time +import pymxs # pylint: disable=import-error + +GLOBAL_ARGS = { + "nativeCallTagA": 1, + "nativeCallTagB": "", + "nativeCallWithWrapperObjTagA": 1, + "nativeCallWithWrapperObjName": u"TestName", + "wrapObj": None +} + +def native_call(first, second, local_env): + """Run timer payload""" + local_env["nativeCallTagA"] = first + local_env["nativeCallTagB"] = second + +def native_with_wrapper(first, wrap_obj, local_env): + """Run timer payload""" + local_env["nativeCallWithWrapperObjTagA"] = first + wrap_obj.Name = local_env["nativeCallWithWrapperObjName"] + local_env["wrapObj"] = wrap_obj + +def check(local_env): + """Validate expected results""" + is_success = True + if local_env["nativeCallTagA"] != 10 or local_env["nativeCallTagB"] != "second": + is_success = False + print("Error: Incorrect native call for threading timer") + + wrap_teapot = local_env["wrapObj"] + if (local_env["nativeCallWithWrapperObjTagA"] != 10 or + wrap_teapot is None or + wrap_teapot.Name != local_env["nativeCallWithWrapperObjName"]): + is_success = False + print("Error: Incorrect native call with wrapper object for threading timer") + + return is_success + +def main(): + """Demonstrate timers""" + wrap_teapot = pymxs.runtime.Teapot() + + # test native call + native_call_timer = threading.Timer( + 0.1, + native_call, + [10, "second"], + kwargs={"local_env": GLOBAL_ARGS}) + native_with_wrapper_timer = threading.Timer( + 0.1, + native_with_wrapper, + kwargs={"first": 10, "wrap_obj": wrap_teapot, "local_env": GLOBAL_ARGS}) + print("Start timer") + native_call_timer.start() + native_with_wrapper_timer.start() + time.sleep(0.2) + if check(GLOBAL_ARGS): + print("threading timer success") + +main() diff --git a/src/samples/pymxs/transform_nodes.py b/src/samples/pymxs/transform_nodes.py new file mode 100644 index 0000000..6bfe74d --- /dev/null +++ b/src/samples/pymxs/transform_nodes.py @@ -0,0 +1,56 @@ +''' + Creates a number of boxes with random scale, position, and rotation. +''' +from random import random as rnd +import math +from pymxs import runtime as rt # pylint: disable=import-error + +def rnd_angle(): + """Return a random angle in radians.""" + return -math.pi + (rnd() * 2 * math.pi) + +def rnd_quat(): + """Return a random quaternion.""" + return rt.Quat(rnd(), rnd(), rnd(), rnd_angle()) + +def rnd_dist(): + """Return a random distance.""" + return rnd() * 100.0 - 50.0 + +def rnd_position(): + """Return a random position.""" + return rt.Point3(rnd_dist(), rnd_dist(), 0) + +def rnd_scale_amount(): + """Return a random scaling amount.""" + return rnd() * 2.0 + 0.1 + +def rnd_scale(): + """Return a random (x,y,z) scaling as a Point3.""" + return rt.Point3(rnd_scale_amount(), rnd_scale_amount(), rnd_scale_amount()) + +def random_transform_nodes(nodes): + """Apply a random transformation (scaling, rotation, position) + to a list of nodes.""" + for node in nodes: + node.Scaling = rnd_scale() + node.Rotation = rnd_quat() + node.Position = rnd_position() + +def create_nodes(count): + """Return count nodes.""" + def make_box(): + """Create a single node, a box of (10, 10, 10).""" + box = rt.box() + box.length = 10.0 + box.height = 10.0 + box.width = 10.0 + return box + return [make_box() for i in range(count)] + +def main(): + """Demonstrate the generation and transformation of 25 boxes.""" + nodes = create_nodes(25) + random_transform_nodes(nodes) + +main() diff --git a/src/samples/pymxs/tree_of_spheres.py b/src/samples/pymxs/tree_of_spheres.py new file mode 100644 index 0000000..f1424a5 --- /dev/null +++ b/src/samples/pymxs/tree_of_spheres.py @@ -0,0 +1,23 @@ +''' + Creates a hierarchy of sphere objects at different relative locations. +''' +from pymxs import runtime as rt # pylint: disable=import-error + +def create_sphere(): + """Create and return a single sphere of radius 5.""" + sphere = rt.sphere() + sphere.radius = 5 + return sphere + +def tree_of_spheres(parent, width, xinc, depth, maxdepth): + """Create a tree of spheres.""" + if depth == maxdepth: + return + for i in range(width): + sphere = create_sphere() + pos = parent.pos + sphere.pos = rt.Point3(pos.x + i * xinc, 0, pos.z + 15) + sphere.Parent = parent + tree_of_spheres(sphere, width, xinc * width, depth + 1, maxdepth) + +tree_of_spheres(create_sphere(), 2, 10, 0, 4) diff --git a/src/samples/unicode_io.py b/src/samples/unicode_io.py new file mode 100644 index 0000000..f789b93 --- /dev/null +++ b/src/samples/unicode_io.py @@ -0,0 +1,139 @@ +""" + Demonstrate file io using unicode paths and unicode content. +""" +import tempfile +import os +import codecs +import shutil + +# Strings for the file content +TEXT_STR = 'Text String: Hello!\n' +UNI_TEXT_STR = u'Unicode String: 女時代' + +# Get the current working folder +CURRENT_DIR = os.getcwd() + +# Create Unicode directory name +UNI_DIR = u'時' + +# Set our user folder to the user temp folder +TEMP_DIR = tempfile.gettempdir() + +# Create Unicode file name +UNI_FILE = u'Pÿ x Mxs.txt' + +# Set our temp folder plus the Unicode directory +FULL_PATH = TEMP_DIR + '\\' + UNI_DIR + +# Set our filename +F_NAME = UNI_FILE + +def create_uni_dir(): + """Create a directory with a unicode name.""" + # Remove directory if it already exists + if os.path.exists(FULL_PATH): + remove_uni_dir() + try: + # Make sure we are in the correct directory root + os.chdir(TEMP_DIR) + print('Working Directory:\n ' + os.getcwd()) + except IOError: + print('!FAIL! Could not set working directory!\n') + else: + print('Moved to Temp folder:\n ' + os.getcwd()) + + try: + # Make our directory + os.mkdir(FULL_PATH) + except IOError: + print('FAIL! Could not create unicode directory:\n' + FULL_PATH) + else: + print('Created unicode directory:\n' + FULL_PATH) + +def remove_uni_dir(): + """Remove a directory with a unicode name.""" + # Check if the directory exists + if os.path.exists(FULL_PATH): + try: + # Change to our working folder to be safe + os.chdir(TEMP_DIR) + print('Working Directory:\n ' + os.getcwd()) + except IOError: + print('!FAIL! Directory does not exist!\n') + else: + # Since we know we are in our working folder, remove the Unicode + # directory created my createDir() + shutil.rmtree(UNI_DIR) + print('Removed unicode directory:\n' + FULL_PATH) + +def open_file(): + """Open a file in working directory and write in it.""" + # Change to our working folder to be safe + os.chdir(TEMP_DIR) + # Set up our file and set it's encoding to UTF-8 + with codecs.open(F_NAME, encoding='utf-8', mode='w+') as thefile: + # Write to our file (this could be done as a try) + thefile.write(TEXT_STR + UNI_TEXT_STR) + print('Finished writing file to ' + F_NAME) + # Close our file + thefile.close() + +def open_file_in_uni_dir(): + """Open a file in unicode directory and write in it.""" + # Change to our working folder to be safe + os.chdir(FULL_PATH) + # Set up our file and set it's encoding to UTF-8 + with codecs.open(F_NAME, encoding='utf-8', mode='w+') as thefile: + # Write to our file (this could be done as a try) + thefile.write(TEXT_STR + UNI_TEXT_STR) + print('Finished writing file to ' + FULL_PATH + F_NAME) + # Close our file + thefile.close() + +def remove_uni_file(): + """Remove a unicode file.""" + # Change to our working folder to be safe + os.chdir(TEMP_DIR) + # Check if the file exists + if os.path.exists(TEMP_DIR + F_NAME): + print('File ' + F_NAME + ' exists and will be removed!') + try: + # Remove our file + os.remove(TEMP_DIR + F_NAME) + except IOError: + print('!FAIL! - File not deleted') + else: + print('File Removed.') + +# Create some setup stats for output +try: + STATS = unicode( + 'Setup:\n' + + 'Current directory: ' + + CURRENT_DIR + + '\nOutput filename: ' + + UNI_FILE + + '\nFile contents: ' + + TEXT_STR + + UNI_TEXT_STR) +except NameError: + STATS = ( + 'Setup:\n' + + 'Current directory: ' + + CURRENT_DIR + + '\nOutput filename: ' + + UNI_FILE + + '\nFile contents: ' + + TEXT_STR + + UNI_TEXT_STR) +# Output stats +print(STATS) + +# Run our functions +open_file() +create_uni_dir() +open_file_in_uni_dir() +# Comment these out to leave written files and created directory +# to visually verify files and files content +remove_uni_dir() +remove_uni_file() From 8a9112fe6816f84a634d2451174af47f51f4b924 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 10:15:31 -0400 Subject: [PATCH 03/11] reorganize the doc --- README.md | 77 +------------------------------ checks.sh | 2 +- doc/install.md | 82 ++++++++++++++++++++++++++++++++++ doc/uninstall.md | 2 +- src/samples/pymxs/file_save.py | 2 +- 5 files changed, 87 insertions(+), 78 deletions(-) create mode 100644 doc/install.md diff --git a/README.md b/README.md index 235a6e8..865c5c8 100644 --- a/README.md +++ b/README.md @@ -15,81 +15,8 @@ practice to package 3ds Max Python tools with pip, we provide all our examples i It is not necessary to install the HowTos: the repo can simply be used as a passive directory of samples and documentation for Python developers. -This being said, it is also possible to install the samples in 3ds Max. This -will add a Python3 scripting menu to 3ds Max: - -![Integration](/doc/Integration.png) - -The examples and some development goodies will be made available from there. - -The installation does the following: -- it installs pip in your 3ds Max installation if it's not already there -- it installs pystartup.ms that enables auto start pip packages -- it installs all the samples in --user and -e mode with pip - -If you decide to install the howtos, it is highly recommended that you clone -this git repository locally using git bash (whenever we update the samples, -you will be able to update your local version and re-run the installation scripts): - -```bash -# from the directory where you want the sample -git clone https://github.com/ADN-DevTech/3dsMax-Python-HowTos.git -``` - -Also note that *all installation steps decribed here also use git bash* (it is -possible to use another client for git but all installation scripts in -this repo use bash). - -### Option A: Install Everthing Locally in One Step (--user) -> Note: the steps described here need to be done from a git bash prompt - -The [install.sh](install.sh) script can be used from bash -to install the samples in 3ds Max. The script needs to be run from a -3ds Max installation directory. - -### Option B: Install Everything Locally in Two Steps (--user) -> Note: the steps described here need to be done from a git bash prompt - -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). -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 -want to install pip and pystartup.ms. - -- The [installhowtos.sh](installhowtos.sh) script can be used from -bash to pip install all the howtos in `--user` mode and `-e` mode (--user -means that the samples will be intalled under `~/AppData/Roaming/Python/Python37/site-packages/`, -and -e means that the packages will be installed as symlinks to the -source directories so that if the sources change the packages don't need -to be reinstalled). -This script needs to run in the 3ds Max installation directory. - -## Uninstalling the HowTos - -The steps needed to uninstall the HowTos can be found in [uninstall.md](/doc/uninstall.md). - -### Option C: Install the howtos in a virtual environment -> Note: the steps described here need to be done from a git bash prompt - -This last option requires three steps. - -It can be used to install the HowTos in a virtual environment (ex: -you may want to have a virtual environment for Python development). - -- The first step is the same as the first step described in option B: -[installstartup.sh](installstartup.sh) needs to run in the 3ds Max -installation directory to install pip if it is missing and pystartup. - -- The second step consists in installing virtualenv with pip and creating a -virtual environment. These steps are described in the [3ds Max documentation](http://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=__developer_python_3_support_virtual_env_html). -- The last step consists in installing the HowTos in the virtual environment. -From the same git bash prompt, the [installhowtos.sh](/installhowtos.sh) -script can be used to install the HowTos in a virtual environment. First `cd` -to the directory of the virtual env and then (without activating the env) simply -run [installhowtos.sh](/installhowtos.sh) from that directory. +- Installing the HowTos will add menu items to 3ds Max, and is documented [here](doc/install.md) +- After an update from github it is necessary to reinstall to get everything working as expected ## Packages that are not examples but that are provided in this repo diff --git a/checks.sh b/checks.sh index f6cf3f7..4b439c2 100644 --- a/checks.sh +++ b/checks.sh @@ -4,7 +4,7 @@ script=$(dirname $(readlink -f "$0")) packagedir="$script/src/packages" workdir=$(pwd) IFS=$'\n' -pl=pylint +pl=pylint3 lintdir() { comment="$1" diff --git a/doc/install.md b/doc/install.md new file mode 100644 index 0000000..2ed8e05 --- /dev/null +++ b/doc/install.md @@ -0,0 +1,82 @@ +# Installation + +It is not necessary to install the HowTos: the repo can simply be used as a passive +directory of samples and documentation for Python developers. + +This being said, it is also possible to install the samples in 3ds Max. This +will add a Python3 scripting menu to 3ds Max: + +![Integration](/doc/Integration.png) + +The examples and some development goodies will be made available from there. + +The installation does the following: +- it installs pip in your 3ds Max installation if it's not already there +- it installs pystartup.ms that enables auto start pip packages +- it installs all the samples in --user and -e mode with pip + +If you decide to install the howtos, it is highly recommended that you clone +this git repository locally using git bash (whenever we update the samples, +you will be able to update your local version and re-run the installation scripts): + +```bash +# from the directory where you want the sample +git clone https://github.com/ADN-DevTech/3dsMax-Python-HowTos.git +``` + +Also note that *all installation steps decribed here also use git bash* (it is +possible to use another client for git but all installation scripts in +this repo use bash). + +### Option A: Install Everthing Locally in One Step (--user) +> Note: the steps described here need to be done from a git bash prompt + +The [install.sh](install.sh) script can be used from bash +to install the samples in 3ds Max. The script needs to be run from a +3ds Max installation directory. + +### Option B: Install Everything Locally in Two Steps (--user) +> Note: the steps described here need to be done from a git bash prompt + +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). +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 +want to install pip and pystartup.ms. + +- The [installhowtos.sh](installhowtos.sh) script can be used from +bash to pip install all the howtos in `--user` mode and `-e` mode (--user +means that the samples will be intalled under `~/AppData/Roaming/Python/Python37/site-packages/`, +and -e means that the packages will be installed as symlinks to the +source directories so that if the sources change the packages don't need +to be reinstalled). +This script needs to run in the 3ds Max installation directory. + +## Uninstalling the HowTos + +The steps needed to uninstall the HowTos can be found in [uninstall.md](uninstall.md). + +### Option C: Install the howtos in a virtual environment +> Note: the steps described here need to be done from a git bash prompt + +This last option requires three steps. + +It can be used to install the HowTos in a virtual environment (ex: +you may want to have a virtual environment for Python development). + +- The first step is the same as the first step described in option B: +[installstartup.sh](installstartup.sh) needs to run in the 3ds Max +installation directory to install pip if it is missing and pystartup. + +- The second step consists in installing virtualenv with pip and creating a +virtual environment. These steps are described in the [3ds Max documentation](http://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=__developer_python_3_support_virtual_env_html). +- The last step consists in installing the HowTos in the virtual environment. +From the same git bash prompt, the [installhowtos.sh](/installhowtos.sh) +script can be used to install the HowTos in a virtual environment. First `cd` +to the directory of the virtual env and then (without activating the env) simply +run [installhowtos.sh](/installhowtos.sh) from that directory. + + diff --git a/doc/uninstall.md b/doc/uninstall.md index afe66b5..460fa93 100644 --- a/doc/uninstall.md +++ b/doc/uninstall.md @@ -55,7 +55,7 @@ The HowTos can be uninstalled individually by calling: ## Uninstalling all the HowTos at Once (manual uinstall) -The [uninstallhowtos.sh](uninstallhowtos.sh) can be used +The [uninstallhowtos.sh](/uninstallhowtos.sh) can be used to uninstall all the howtos at once. This will automatically call `./python.exe -m pip uninstall` for all the HowTos packages. diff --git a/src/samples/pymxs/file_save.py b/src/samples/pymxs/file_save.py index d7d842d..82883d5 100644 --- a/src/samples/pymxs/file_save.py +++ b/src/samples/pymxs/file_save.py @@ -1,5 +1,5 @@ """ - Demonstrate saving a max file. + Demonstrate saving a 3ds Max file. """ from os import path from pymxs import runtime as rt # pylint: disable=import-error From 5504fd02b72691a81792f1db8292a5c3d361cc27 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 10:38:45 -0400 Subject: [PATCH 04/11] minor fixes --- README.md | 34 ++++++++++++++++++++-------------- checks.sh | 4 ++-- doc/install.md | 12 +++++------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 865c5c8..a6c5499 100644 --- a/README.md +++ b/README.md @@ -18,20 +18,6 @@ directory of samples and documentation for Python developers. - Installing the HowTos will add menu items to 3ds Max, and is documented [here](doc/install.md) - After an update from github it is necessary to reinstall to get everything working as expected -## Packages that are not examples but that are provided in this repo - -- [menuhook](/src/packages/menuhook/README.md) is not meant to be an example (but is still interesting as such!) but -as a way of attaching Python functions to 3ds Max menu items. The menuhook package is used by -most of the other samples. - -- [realoadmod](/src/packages/reloadmod/README.md) is small tool that will reload all development modules in one -operation - -- [mxvscode](/src/packages/mxvscode/README.md) is a small tool that will automatically import ptvsd (the -VSCode debugging interface) during the startup of 3ds Max and make it accept remote connections. -This may slow down the startup of 3ds Max quite a bit and is meant as a developer-only tool. - - ## Python How Tos The samples below are translations of [MAXScript How Tos](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-25C9AD58-3665-471E-8B4B-54A094C1D5C9) that @@ -56,12 +42,32 @@ the Python version in the best Python way known to us. An example of this is tha - Create a single instance modal dialog [singleinstancedlg](/src/packages/singleinstancedlg/README.md) - Add menu items to open documentation pages in the web browser [inbrowserhelp](/src/packages/inbrowserhelp/README.md) +## Python Samples + +Python samples can be found in [src/samples](/src/samples). These samples may already be in your 3ds Max +installation directories. + ## 3dsMax startup entry point [pystartup](/src/pystartup/README.md) provides the maxscript code that, when copied to 3ds Max's startup directory, will automatically launch pip packages with the 3dsMax startup entry point. +## Tools + +The following packages are not really examples but python tools. + +- [menuhook](/src/packages/menuhook/README.md) is not meant to be an example (but is still interesting as such!) but +as a way of attaching Python functions to 3ds Max menu items. The menuhook package is used by +most of the other samples. + +- [realoadmod](/src/packages/reloadmod/README.md) is small tool that will reload all development modules in one +operation + +- [mxvscode](/src/packages/mxvscode/README.md) is a small tool that will automatically import ptvsd (the +VSCode debugging interface) during the startup of 3ds Max and make it accept remote connections. +This may slow down the startup of 3ds Max quite a bit and is meant as a developer-only tool. + ## Extra Goodies - [create.sh](create.sh) will generate an empty pip package in the current working directory. diff --git a/checks.sh b/checks.sh index 4b439c2..68a9a24 100644 --- a/checks.sh +++ b/checks.sh @@ -61,12 +61,12 @@ checkmdlinks() { fi elif [[ "$url" =~ ^/.* ]] then - if [ ! -f "$workdir$url" ] + if [ ! -e "$workdir$url" ] then echo "$file$line: Broken absolute link: $url" fi else - if [ ! -f "$filedir/$url" ] + if [ ! -e "$filedir/$url" ] then echo "$file:$line: Broken relative link: $url" fi diff --git a/doc/install.md b/doc/install.md index 2ed8e05..e7ec311 100644 --- a/doc/install.md +++ b/doc/install.md @@ -1,7 +1,5 @@ # Installation -It is not necessary to install the HowTos: the repo can simply be used as a passive -directory of samples and documentation for Python developers. This being said, it is also possible to install the samples in 3ds Max. This will add a Python3 scripting menu to 3ds Max: @@ -31,7 +29,7 @@ this repo use bash). ### Option A: Install Everthing Locally in One Step (--user) > Note: the steps described here need to be done from a git bash prompt -The [install.sh](install.sh) script can be used from bash +The [install.sh](/install.sh) script can be used from bash to install the samples in 3ds Max. The script needs to be run from a 3ds Max installation directory. @@ -40,14 +38,14 @@ to install the samples in 3ds Max. The script needs to be run from a It is possible to break up the installation in two steps. -- The [installstartup.sh](installstartup.sh) script can be used +- The [installstartup.sh](/installstartup.sh) script can be used from bash to install pip and [pystartup.ms](/src/pystartup/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 want to install pip and pystartup.ms. -- The [installhowtos.sh](installhowtos.sh) script can be used from +- The [installhowtos.sh](/installhowtos.sh) script can be used from bash to pip install all the howtos in `--user` mode and `-e` mode (--user means that the samples will be intalled under `~/AppData/Roaming/Python/Python37/site-packages/`, and -e means that the packages will be installed as symlinks to the @@ -57,7 +55,7 @@ This script needs to run in the 3ds Max installation directory. ## Uninstalling the HowTos -The steps needed to uninstall the HowTos can be found in [uninstall.md](uninstall.md). +The steps needed to uninstall the HowTos can be found in [uninstall.md](/uninstall.md). ### Option C: Install the howtos in a virtual environment > Note: the steps described here need to be done from a git bash prompt @@ -68,7 +66,7 @@ It can be used to install the HowTos in a virtual environment (ex: you may want to have a virtual environment for Python development). - The first step is the same as the first step described in option B: -[installstartup.sh](installstartup.sh) needs to run in the 3ds Max +[installstartup.sh](/installstartup.sh) needs to run in the 3ds Max installation directory to install pip if it is missing and pystartup. - The second step consists in installing virtualenv with pip and creating a From 0b39bf4846c8407fc1ffa0c020433b9457310a1f Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 10:43:17 -0400 Subject: [PATCH 05/11] doc improvement --- doc/install.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/install.md b/doc/install.md index e7ec311..6ea6513 100644 --- a/doc/install.md +++ b/doc/install.md @@ -1,7 +1,6 @@ # Installation - -This being said, it is also possible to install the samples in 3ds Max. This +It is possible to install the samples in 3ds Max. This will add a Python3 scripting menu to 3ds Max: ![Integration](/doc/Integration.png) From 72f2a29ea353c8d91dbe1109ff825b2959d390e8 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 11:24:54 -0400 Subject: [PATCH 06/11] further simplify directory structure --- .github/workflows/checks-workflow.yml | 4 ++-- install.sh | 2 +- installhowtos.sh | 2 +- installstartup.sh | 2 +- checks.sh => scripts/checks.sh | 0 create.sh => scripts/create.sh | 0 inst.sh => scripts/inst.sh | 0 makepreview.sh => scripts/makepreview.sh | 0 uninstall.sh | 2 +- uninstallhowtos.sh | 2 +- 10 files changed, 7 insertions(+), 7 deletions(-) rename checks.sh => scripts/checks.sh (100%) rename create.sh => scripts/create.sh (100%) rename inst.sh => scripts/inst.sh (100%) rename makepreview.sh => scripts/makepreview.sh (100%) diff --git a/.github/workflows/checks-workflow.yml b/.github/workflows/checks-workflow.yml index 05e4038..4bdf2f3 100644 --- a/.github/workflows/checks-workflow.yml +++ b/.github/workflows/checks-workflow.yml @@ -11,5 +11,5 @@ jobs: # this is a mega hack, to be fixed run: | sudo apt-get install pylint3 dos2unix - chmod +x ./checks.sh - ./checks.sh + chmod +x ./scripts/checks.sh + ./scripts/checks.sh diff --git a/install.sh b/install.sh index 7670e01..91d04ba 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -source "$script/inst.sh" +source "$script/scripts/inst.sh" # make sure we have 3ds Max in the current path if [ ! -f ./3dsmax.exe ] diff --git a/installhowtos.sh b/installhowtos.sh index 51ad995..3b5bcfb 100644 --- a/installhowtos.sh +++ b/installhowtos.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -source "$script/inst.sh" +source "$script/scripts/inst.sh" # make sure cygpath is available if ! command -v cygpath >/dev/null 2>&1 diff --git a/installstartup.sh b/installstartup.sh index cf41613..30a1955 100644 --- a/installstartup.sh +++ b/installstartup.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -source "$script/inst.sh" +source "$script/scripts/inst.sh" # make sure we have 3ds Max in the current path if [ ! -f ./3dsmax.exe ] diff --git a/checks.sh b/scripts/checks.sh similarity index 100% rename from checks.sh rename to scripts/checks.sh diff --git a/create.sh b/scripts/create.sh similarity index 100% rename from create.sh rename to scripts/create.sh diff --git a/inst.sh b/scripts/inst.sh similarity index 100% rename from inst.sh rename to scripts/inst.sh diff --git a/makepreview.sh b/scripts/makepreview.sh similarity index 100% rename from makepreview.sh rename to scripts/makepreview.sh diff --git a/uninstall.sh b/uninstall.sh index 488f23d..7628524 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -source "$script/inst.sh" +source "$script/scripts/inst.sh" # make sure we have 3ds Max in the current path if [ ! -f ./3dsmax.exe ] diff --git a/uninstallhowtos.sh b/uninstallhowtos.sh index 5d98530..4f783a7 100644 --- a/uninstallhowtos.sh +++ b/uninstallhowtos.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -source "$script/inst.sh" +source "$script/scripts/inst.sh" venvscript () { echo "cd Scripts" From 7035e4339433d55958ad31c580e3a803863bc615 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 11:48:23 -0400 Subject: [PATCH 07/11] fix testing --- README.md | 3 ++- scripts/checks.sh | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6c5499..d8b7692 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ It is not necessary to install the HowTos: the repo can simply be used as a pass directory of samples and documentation for Python developers. - Installing the HowTos will add menu items to 3ds Max, and is documented [here](doc/install.md) -- After an update from github it is necessary to reinstall to get everything working as expected +- After an update from github it is necessary to rerun install scripts to get everything +working as expected ## Python How Tos diff --git a/scripts/checks.sh b/scripts/checks.sh index 68a9a24..4269e61 100644 --- a/scripts/checks.sh +++ b/scripts/checks.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash set -e script=$(dirname $(readlink -f "$0")) -packagedir="$script/src/packages" +packagedir="$script/../src/packages" workdir=$(pwd) IFS=$'\n' pl=pylint3 @@ -27,7 +27,7 @@ lint() { } lintsamples() { - lintdir "samples" "$script/src/samples" + lintdir "samples" "$script/../src/samples" } checkmarkdown() { From fb05db6d0ad35dabb6ca2db4cf2a2c77e351ce41 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 12:11:24 -0400 Subject: [PATCH 08/11] more fixes --- README.md | 4 ++-- doc/install.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d8b7692..99ee6e6 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,11 @@ This may slow down the startup of 3ds Max quite a bit and is meant as a develope ## Extra Goodies -- [create.sh](create.sh) will generate an empty pip package in the current working directory. - [install.sh](install.sh) will install pip, install pystartup and pip install all the samples - [uninstall.sh](uninstall.sh) will uninstall what was installed with install.sh - [installstartup.sh](installstartup.sh) will install pip and pystartup and nothing more - [installhowtos.sh](installhowtos.sh) will install only the howtos (works in a virtual env) -- [checks.sh](checks.sh) runs pylint on the code, validates that 3ds Max is named properly, +- [checks.sh](/scripts/checks.sh) runs pylint on the code, validates that 3ds Max is named properly, validates that code blocks in markdown always specify the programming language, checks that all links are valid in all markdown files of the repo +- [create.sh](/scripts/create.sh) will generate an empty pip package in the current working directory. diff --git a/doc/install.md b/doc/install.md index 6ea6513..98a0801 100644 --- a/doc/install.md +++ b/doc/install.md @@ -54,7 +54,7 @@ This script needs to run in the 3ds Max installation directory. ## Uninstalling the HowTos -The steps needed to uninstall the HowTos can be found in [uninstall.md](/uninstall.md). +The steps needed to uninstall the HowTos can be found in [uninstall.md](/doc/uninstall.md). ### Option C: Install the howtos in a virtual environment > Note: the steps described here need to be done from a git bash prompt From d88dcc5c1bec0bc6cc27159af12a8e69f61ced5f Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 13:23:08 -0400 Subject: [PATCH 09/11] fix .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 4752f7c..cfa5d15 100644 --- a/.pylintrc +++ b/.pylintrc @@ -433,7 +433,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,ptvsd,PySide2 +ignored-modules=pymxs,ptvsd,PySide2,menuhook # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. From e3d1e3d9c471e76864db69f1e5d126b1e0b4dfaa Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Wed, 8 Apr 2020 15:04:56 -0400 Subject: [PATCH 10/11] Add menu item pointing to samples --- src/packages/inbrowserhelp/inbrowserhelp/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py index 7defecb..374aa17 100644 --- a/src/packages/inbrowserhelp/inbrowserhelp/__init__.py +++ b/src/packages/inbrowserhelp/inbrowserhelp/__init__.py @@ -9,6 +9,8 @@ "help.autodesk.com/view/MAXDEV/2021/ENU/?guid=__developer_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/src/samples"), ("pymxs", "Pymxs Online Documentation", "help.autodesk.com/view/MAXDEV/2021/ENU/?guid=__developer_using_pymxs_html"), ("pyside2", "Qt for Python Documentation (PySide2)", From 563959ae6e7b4f93dc5524847208e75e8ef578e9 Mon Sep 17 00:00:00 2001 From: Hugo Windisch Date: Thu, 9 Apr 2020 09:29:08 -0400 Subject: [PATCH 11/11] point the doc to MAXDEV/2021 --- README.md | 2 +- src/packages/menuhook/README.md | 8 ++++---- src/packages/quickpreview/README.md | 6 +++--- src/packages/removeallmaterials/README.md | 2 +- src/packages/renameselected/README.md | 2 +- src/packages/speedsheet/README.md | 4 ++-- src/packages/transformlock/README.md | 4 ++-- src/packages/zdepthchannel/README.md | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 99ee6e6..c4a91bf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ working as expected ## Python How Tos -The samples below are translations of [MAXScript How Tos](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-25C9AD58-3665-471E-8B4B-54A094C1D5C9) that +The samples below are translations of [MAXScript How Tos](https://help.autodesk.com/view/MAXDEV/2021/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 diff --git a/src/packages/menuhook/README.md b/src/packages/menuhook/README.md index 2e01b31..3fdb121 100644 --- a/src/packages/menuhook/README.md +++ b/src/packages/menuhook/README.md @@ -77,7 +77,7 @@ change in the future. *Q:* Doing this creates a 'macro' and this macro never goes away *A:* The Python & maxscript apis currently do not allow to remove macros -that have been created. So the [macros](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-3DC75DDE-E4BC-4033-ABA9-A42063036CB9) +that have been created. So the [macros](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-3DC75DDE-E4BC-4033-ABA9-A42063036CB9) that we create are permanent but if we don't load the Python packages that implement them during an execution of 3ds Max they become dangling. We are able to detect that @@ -86,7 +86,7 @@ and notify the users. *Q:* Would it be possible to remove menu items (to make them not permanent)? -*A:* The [menu manager](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-258F6015-6B45-4A87-A7F5-BB091A2AE065), +*A:* The [menu manager](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-258F6015-6B45-4A87-A7F5-BB091A2AE065), provides a way to remove menu items. This could be used. But the solution would not be crash proof (it would be difficult to establish that the menu item has been removed for real, by using only what the menu manager provides). @@ -104,7 +104,7 @@ from pymxs import runtime as rt To get access to the 3dsMax scripting library for Python (pymxs). -Then it uses `rt.macros` to access functions from the [macro scripts](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-3DC75DDE-E4BC-4033-ABA9-A42063036CB9). +Then it uses `rt.macros` to access functions from the [macro scripts](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-3DC75DDE-E4BC-4033-ABA9-A42063036CB9). As it is now we have to create a macro for a function we want to hook to the menu. The macro has an action and a category (that identifies it). It is done by this call: @@ -118,7 +118,7 @@ The macro has an action and a category (that identifies it). It is done by this mxs) ``` -When this (category, action) exists, we can use `rt.menuman`, the [menu manager](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-258F6015-6B45-4A87-A7F5-BB091A2AE065) +When this (category, action) exists, we can use `rt.menuman`, the [menu manager](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-258F6015-6B45-4A87-A7F5-BB091A2AE065) to find the menu in which we want to add an item: ```python diff --git a/src/packages/quickpreview/README.md b/src/packages/quickpreview/README.md index 06bbef5..1158138 100644 --- a/src/packages/quickpreview/README.md +++ b/src/packages/quickpreview/README.md @@ -2,7 +2,7 @@ ![Preview](doc/Preview.png) -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-333382D0-57AF-4797-98F2-C2BE09442607) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-333382D0-57AF-4797-98F2-C2BE09442607) [Source Code](quickpreview/__init__.py) *Goals:* @@ -39,7 +39,7 @@ entry point for 3ds Max). ## Understanding the code We use `rt.getDir(rt.Name("preview"))` to find the default directory -for previews ([3ds Max System Directories](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-F7577416-051E-478C-BB5D-81243BAAC8EC#GUID-F7577416-051E-478C-BB5D-81243BAAC8EC)). +for previews ([3ds Max System Directories](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-F7577416-051E-478C-BB5D-81243BAAC8EC#GUID-F7577416-051E-478C-BB5D-81243BAAC8EC)). We concatenate "quickpreview.avi" using the standard Python `path.join` function. ```python @@ -79,7 +79,7 @@ We then force a garbage collection: rt.gc() ``` -And launch the [RAMPlayer](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-650BE5AA-1DFB-4847-99B2-777A281490F6#GUID-650BE5AA-1DFB-4847-99B2-777A281490F6) for our generated avi: +And launch the [RAMPlayer](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-650BE5AA-1DFB-4847-99B2-777A281490F6#GUID-650BE5AA-1DFB-4847-99B2-777A281490F6) for our generated avi: ```python rt.ramplayer(preview_name, "") diff --git a/src/packages/removeallmaterials/README.md b/src/packages/removeallmaterials/README.md index 21a797b..9b98475 100644 --- a/src/packages/removeallmaterials/README.md +++ b/src/packages/removeallmaterials/README.md @@ -1,5 +1,5 @@ # HowTo: removeallmaterials -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-BB996DFB-0367-4DFF-A1CC-50BEB3A97757) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-BB996DFB-0367-4DFF-A1CC-50BEB3A97757) [Source Code](removeallmaterials/__init__.py) *Goals:* diff --git a/src/packages/renameselected/README.md b/src/packages/renameselected/README.md index bb5c696..fe72b14 100644 --- a/src/packages/renameselected/README.md +++ b/src/packages/renameselected/README.md @@ -2,7 +2,7 @@ ![Dialog](doc/Dialog.png) -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-5986CAD3-BB68-47BC-B4B2-EF84C4659271) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-5986CAD3-BB68-47BC-B4B2-EF84C4659271) [Source Code](renameselected/__init__.py) *Goals:* diff --git a/src/packages/speedsheet/README.md b/src/packages/speedsheet/README.md index b794f94..c6b94eb 100644 --- a/src/packages/speedsheet/README.md +++ b/src/packages/speedsheet/README.md @@ -2,7 +2,7 @@ ![Speedsheet](doc/Speedsheet.png) -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-2DB3A775-776F-4D63-BDFB-D99523ECB69D) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-2DB3A775-776F-4D63-BDFB-D99523ECB69D) [Source Code](speedsheet/__init__.py) *Goals:* @@ -62,7 +62,7 @@ simplifies the code and makes it more robust at the same time. Next, the code produces a list of objects at the first frame of the animation. Everything inside the `with pymxs.attime(sometime)` block will -happen at `sometime` in the time line (this is the same as the [at time](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-4E9CCD61-F575-42E1-8654-315DDF6C6A26#GUID-4E9CCD61-F575-42E1-8654-315DDF6C6A26) +happen at `sometime` in the time line (this is the same as the [at time](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-4E9CCD61-F575-42E1-8654-315DDF6C6A26#GUID-4E9CCD61-F575-42E1-8654-315DDF6C6A26) construction in MAXScript): ```python diff --git a/src/packages/transformlock/README.md b/src/packages/transformlock/README.md index 62f366c..8f58cac 100644 --- a/src/packages/transformlock/README.md +++ b/src/packages/transformlock/README.md @@ -1,6 +1,6 @@ # HowTo: transformlock -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-8EB13535-72B4-439C-94D3-E93434BA163B) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-8EB13535-72B4-439C-94D3-E93434BA163B) [Source Code](transformlock/__init__.py) *Goals:* @@ -42,7 +42,7 @@ from pymxs import runtime as rt ``` The core business logic of the program comes from the lock\_selection function. This uses -the setTransformLockFlags of ([node common methods](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-D1D7EB56-A370-4B07-99B4-BC779FB87CAF#GUID-D1D7EB56-A370-4B07-99B4-BC779FB87CAF__SECTION_130281B392F64446B4AE8562EAD75531)) +the setTransformLockFlags of ([node common methods](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-D1D7EB56-A370-4B07-99B4-BC779FB87CAF#GUID-D1D7EB56-A370-4B07-99B4-BC779FB87CAF__SECTION_130281B392F64446B4AE8562EAD75531)) to lock all (`rt.Name("all")` transforms on the whole selection (`rt.selection`)): ```python diff --git a/src/packages/zdepthchannel/README.md b/src/packages/zdepthchannel/README.md index ba7db10..a9bb13e 100644 --- a/src/packages/zdepthchannel/README.md +++ b/src/packages/zdepthchannel/README.md @@ -2,7 +2,7 @@ ![ZDpeth](doc/ZDepth.png) -[Original MaxScript Tutorial](https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=GUID-3667A33C-E3E4-4F39-A480-3713240838F1) +[Original MaxScript Tutorial](https://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-3667A33C-E3E4-4F39-A480-3713240838F1) [Source Code](zdepthchannel/__init__.py) The 3ds Max default scanline renderer generates a multitude of additional data