├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── MXSPyCOM.code-workspace
├── MXSPyCOM.csproj
├── Program.cs
├── README.md
├── hello_world.ms
├── hello_world.py
├── images
├── notepad++_menu_post.png
├── notepad++_menu_pre.png
├── notepad++_run_dialog.png
├── notepad++_shortcut_dialog.png
├── pycharm_create_tool_dialog.png
├── pycharm_menu_post.png
├── pycharm_settings_dialog.png
├── sublime_key_bindings_dialog.png
├── sublime_menu_key_bindings.png
├── sublime_menu_package_control.png
├── ue_configure_tools_dialog_options_tab.png
├── ue_configure_tools_dialog_output_tab.png
├── ue_external_tools_dialog.png
├── ue_ribbon_post.png
├── ue_ribbon_pre.png
├── vs_external_tools_dialog.png
├── vs_menu_post.png
├── vs_menu_pre.png
├── wing_prefs_dialog_command_create.png
├── wing_prefs_dialog_post.png
└── wing_prefs_dialog_pre.png
├── initialize_COM_server.ms
└── scripts
└── publish.ps1
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 | .mypy_cache/
13 | bin/
14 | obj/
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | [Xx]64/
22 | [Xx]86/
23 | [Bb]uild/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | artifacts/
49 |
50 | *_i.c
51 | *_p.c
52 | *_i.h
53 | *.ilk
54 | *.meta
55 | *.obj
56 | *.pch
57 | *.pdb
58 | *.pgc
59 | *.pgd
60 | *.rsp
61 | *.sbr
62 | *.tlb
63 | *.tli
64 | *.tlh
65 | *.tmp
66 | *.tmp_proj
67 | *.log
68 | *.vspscc
69 | *.vssscc
70 | .builds
71 | *.pidb
72 | *.svclog
73 | *.scc
74 |
75 | # Chutzpah Test files
76 | _Chutzpah*
77 |
78 | # Visual C++ cache files
79 | ipch/
80 | *.aps
81 | *.ncb
82 | *.opendb
83 | *.opensdf
84 | *.sdf
85 | *.cachefile
86 | *.VC.db
87 |
88 | # Visual Studio profiler
89 | *.psess
90 | *.vsp
91 | *.vspx
92 | *.sap
93 |
94 | # TFS 2012 Local Workspace
95 | $tf/
96 |
97 | # Guidance Automation Toolkit
98 | *.gpState
99 |
100 | # ReSharper is a .NET coding add-in
101 | _ReSharper*/
102 | *.[Rr]e[Ss]harper
103 | *.DotSettings.user
104 |
105 | # JustCode is a .NET coding add-in
106 | .JustCode
107 |
108 | # TeamCity is a build add-in
109 | _TeamCity*
110 |
111 | # DotCover is a Code Coverage Tool
112 | *.dotCover
113 |
114 | # NCrunch
115 | _NCrunch_*
116 | .*crunch*.local.xml
117 | nCrunchTemp_*
118 |
119 | # MightyMoose
120 | *.mm.*
121 | AutoTest.Net/
122 |
123 | # Web workbench (sass)
124 | .sass-cache/
125 |
126 | # Installshield output folder
127 | [Ee]xpress/
128 |
129 | # DocProject is a documentation generator add-in
130 | DocProject/buildhelp/
131 | DocProject/Help/*.HxT
132 | DocProject/Help/*.HxC
133 | DocProject/Help/*.hhc
134 | DocProject/Help/*.hhk
135 | DocProject/Help/*.hhp
136 | DocProject/Help/Html2
137 | DocProject/Help/html
138 |
139 | # Click-Once directory
140 | publish/
141 |
142 | # Publish Web Output
143 | *.[Pp]ublish.xml
144 | *.azurePubxml
145 |
146 | # TODO: Un-comment the next line if you do not want to checkin
147 | # your web deploy settings because they may include unencrypted
148 | # passwords
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # NuGet Packages
153 | *.nupkg
154 | # The packages folder can be ignored because of Package Restore
155 | **/packages/*
156 | # except build/, which is used as an MSBuild target.
157 | !**/packages/build/
158 | # Uncomment if necessary however generally it will be regenerated when needed
159 | #!**/packages/repositories.config
160 | # NuGet v3's project.json files produces more ignoreable files
161 | *.nuget.props
162 | *.nuget.targets
163 |
164 | # Microsoft Azure Build Output
165 | csx/
166 | *.build.csdef
167 |
168 | # Microsoft Azure Emulator
169 | ecf/
170 | rcf/
171 |
172 | # Microsoft Azure ApplicationInsights config file
173 | ApplicationInsights.config
174 |
175 | # Windows Store app package directory
176 | AppPackages/
177 | BundleArtifacts/
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | [Ss]tyle[Cc]op.*
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.pfx
193 | *.publishsettings
194 | node_modules/
195 | orleans.codegen.cs
196 |
197 | # RIA/Silverlight projects
198 | Generated_Code/
199 |
200 | # Backup & report files from converting an old project file
201 | # to a newer Visual Studio version. Backup files are not needed,
202 | # because we have git ;-)
203 | _UpgradeReport_Files/
204 | Backup*/
205 | UpgradeLog*.XML
206 | UpgradeLog*.htm
207 |
208 | # SQL Server files
209 | *.mdf
210 | *.ldf
211 |
212 | # Business Intelligence projects
213 | *.rdl.data
214 | *.bim.layout
215 | *.bim_*.settings
216 |
217 | # Microsoft Fakes
218 | FakesAssemblies/
219 |
220 | # GhostDoc plugin setting file
221 | *.GhostDoc.xml
222 |
223 | # Node.js Tools for Visual Studio
224 | .ntvs_analysis.dat
225 |
226 | # Visual Studio 6 build log
227 | *.plg
228 |
229 | # Visual Studio 6 workspace options file
230 | *.opt
231 |
232 | # Visual Studio LightSwitch build output
233 | **/*.HTMLClient/GeneratedArtifacts
234 | **/*.DesktopClient/GeneratedArtifacts
235 | **/*.DesktopClient/ModelManifest.xml
236 | **/*.Server/GeneratedArtifacts
237 | **/*.Server/ModelManifest.xml
238 | _Pvt_Extensions
239 |
240 | # LightSwitch generated files
241 | GeneratedArtifacts/
242 | ModelManifest.xml
243 |
244 | # Paket dependency manager
245 | .paket/paket.exe
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # Documentation images
251 | images/*.*
252 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/MXSPyCOM.exe",
14 | "args": [],
15 | "cwd": "${workspaceFolder}",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach",
24 | "processId": "${command:pickProcess}"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.mypy_cache": true,
4 | "**/.vs": true,
5 | "**/bin": true,
6 | "**/obj": true,
7 | }
8 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "Build Debug",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/MXSPyCOM.csproj"
11 | ],
12 | "problemMatcher": "$msCompile"
13 | },
14 | {
15 | "label": "Build Release",
16 | "command": "dotnet",
17 | "type": "process",
18 | "args": [
19 | "build",
20 | "${workspaceFolder}/MXSPyCOM.csproj",
21 | "-c",
22 | "release"
23 | ],
24 | "problemMatcher": "$msCompile",
25 | "group": {
26 | "kind": "build",
27 | "isDefault": true
28 | }
29 | },
30 | {
31 | "label": "Publish",
32 | "type": "shell",
33 | "command": "scripts\\publish.ps1",
34 | "problemMatcher": "$msCompile"
35 | },
36 | {
37 | "label": "Watch",
38 | "command": "dotnet",
39 | "type": "process",
40 | "args": [
41 | "watch",
42 | "run",
43 | "${workspaceFolder}/MXSPyCOM.csproj"
44 | ],
45 | "problemMatcher": "$msCompile"
46 | },
47 | {
48 | "label": "Execute Script in 3ds Max (Debug)",
49 | "type": "shell",
50 | "command": "${workspaceFolder}/bin/Debug/net6.0-windows/win-x64/MXSPyCOM.exe",
51 | "args": [
52 | "-s",
53 | "${file}"
54 | ],
55 | "presentation": {
56 | "echo": false,
57 | "reveal": "never",
58 | "focus": false,
59 | "panel": "dedicated"
60 | },
61 | "problemMatcher": []
62 | },
63 | {
64 | "label": "Execute Script in 3ds Max",
65 | "type": "shell",
66 | "command": "${workspaceFolder}/bin/Release/net6.0-windows/win-x64/publish/MXSPyCOM.exe",
67 | "args": [
68 | "-s",
69 | "${file}"
70 | ],
71 | "presentation": {
72 | "echo": false,
73 | "reveal": "never",
74 | "focus": false,
75 | "panel": "dedicated"
76 | },
77 | "problemMatcher": []
78 | }
79 | ]
80 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jeff Hanna
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MXSPyCOM.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {}
8 | }
--------------------------------------------------------------------------------
/MXSPyCOM.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0-windows
6 | false
7 | true
8 | win-x64
9 | true
10 | false
11 | MXSPyCOM.Program
12 | true
13 | 1.14
14 | Jeff Hanna
15 | Technical Artists Forum, Inc
16 | Copyright © 2022, Technical Artists Forum, Inc. All rights reserved.
17 | http://www.tech-artists.org
18 | https://github.com/techartorg/MXSPyCOM
19 | A modern version of MXSCOM, to allow for editing & execution of 3ds Max MaxScript and Python files from external code editors.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Diagnostics;
4 | using System.Reflection;
5 | using System.Windows.Forms;
6 |
7 | using Westwind.Utilities;
8 |
9 |
10 | namespace MXSPyCOM
11 | {
12 | class Program
13 | {
14 | /// A modern version of MXSCOM, to allow for editing & execution of 3ds Max MaxScript and Python files from external code editors.
15 | /// In 2005 Simon Feltman released the first MXSCOM, a small Visual Basic 6 application that took commands and sent them to
16 | /// Autodesk's 3ds Max's internal COM server. This allowed users to choose their own external code editor for editing MaxScript
17 | /// and to be able to have their MaxScript code execute in 3ds Max by way of having the code editor utilize MXSCOM to send the file
18 | /// into 3ds Max and have it executed. Modern versions of Windows can not use Simon Feltman's old MXSCOM.exe program due
19 | /// to it being ActiveX based.
20 | ///
21 | /// MXSPyCOM is a C# based replacement for MXSCOM. It offers the same functionality as MXSCOM but can run on modern versions of Windows.
22 | /// It also supports editing of Python files and having them execute in versions of 3ds Max, starting with 3ds Max 2015, that support
23 | /// Python scripts.
24 | ///
25 | /// **Arguments:**
26 | ///
27 | /// None
28 | ///
29 | /// **Keyword Arguments:**
30 | ///
31 | /// None
32 | ///
33 | /// **TODO**
34 | ///
35 | /// :Add the ability to start 3ds Max if it is not found as a running process.
36 | ///
37 | /// **Author:**
38 | ///
39 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
40 |
41 | const string USAGE_INFO = "\nType \"MXSPyCOM\" for usage info.";
42 |
43 |
44 | static void execute_max_commands(string[] args, string filepath)
45 | {
46 | /// Parses the command line arguments and calls the corresponding command on Max's COM server with the provied filepath.
47 | ///
48 | /// **Arguments:**
49 | ///
50 | /// :``args``: `string[]`: The command line arguments sent to MXSPyCOM
51 | /// :``filepath: `string`: A full absolute filepath to a MaxScript (.ms) or Python (.py) file.
52 | ///
53 | /// **Keyword Arguments:**
54 | ///
55 | /// None
56 | ///
57 | /// **Returns:**
58 | ///
59 | /// None
60 | ///
61 | /// **TODO:**
62 | ///
63 | /// Should support for Max's COM server execute() function be added? It doesn't act on files, but on strings of MaxScript commands.
64 | /// It doesn't seem necessary for this tool, honestly.
65 | ///
66 | /// **Author:**
67 | ///
68 | /// Jeff Hanna, jeff@techart.online, July 11, 2016
69 |
70 | bool max_running = is_process_running("3dsmax");
71 | if (max_running)
72 | {
73 | if (args.Length == 1)
74 | {
75 | string msg = String.Format("No options provided.", USAGE_INFO);
76 | show_message(msg);
77 | }
78 | else
79 | {
80 | string prog_id = "Max.Application";
81 | Type com_type = Type.GetTypeFromProgID(prog_id);
82 | object com_obj = Activator.CreateInstance(com_type);
83 |
84 | string ext = System.IO.Path.GetExtension(filepath).ToLower();
85 |
86 | foreach (string arg in args)
87 | {
88 | switch (arg.ToLower())
89 | {
90 | case "-f":
91 | if (ext == ".py")
92 | {
93 | filepath = make_python_wrapper(filepath);
94 | }
95 |
96 | try
97 | {
98 | com_obj.GetType().InvokeMember("filein",
99 | ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod,
100 | null,
101 | com_obj,
102 | new object[] {filepath});
103 | }
104 | catch (System.Reflection.TargetInvocationException) { }
105 | break;
106 |
107 | case "-s":
108 | try
109 | {
110 | filepath = mxs_try_catch_errors_cmd(filepath);
111 | com_obj.GetType().InvokeMember("execute",
112 | ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod,
113 | null,
114 | com_obj,
115 | new object[] {filepath});
116 | }
117 | catch (System.Reflection.TargetInvocationException) { }
118 | break;
119 |
120 | case "-e":
121 | try
122 | {
123 | com_obj.GetType().InvokeMember("edit",
124 | ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod,
125 | null,
126 | com_obj,
127 | new object[] {filepath});
128 | }
129 | catch (System.Reflection.TargetInvocationException) { }
130 | break;
131 |
132 | case "-c":
133 | if (ext == ".ms")
134 | {
135 | try
136 | {
137 | com_obj.GetType().InvokeMember("encryptscript",
138 | ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod,
139 | null,
140 | com_obj,
141 | new object[] {filepath});
142 | }
143 | catch (System.Reflection.TargetInvocationException) { }
144 | }
145 | else
146 | {
147 | string msg = String.Format("Only MaxScript files can be encrypted. {0}", USAGE_INFO);
148 | show_message(msg);
149 | }
150 | break;
151 |
152 | default:
153 | return;
154 | }
155 | }
156 | }
157 | }
158 | else
159 | {
160 | string msg = "3ds Max is not currently running.";
161 | show_message(msg);
162 | }
163 |
164 | return;
165 | }
166 |
167 |
168 | static bool is_process_running(string process_name)
169 | {
170 | /// Determines if a named process is currently running.
171 | ///
172 | /// **Arguments:**
173 | ///
174 | /// :``process_name``: `string` The name of the process (minus the .exe extension) to check
175 | ///
176 | /// **Keyword Arguments:**
177 | ///
178 | /// None
179 | ///
180 | /// **Returns:**
181 | ///
182 | /// :`bool`
183 | ///
184 | /// **Author:**
185 | ///
186 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
187 |
188 | Process[] pname = Process.GetProcessesByName(process_name);
189 | if (pname.Length > 0)
190 | {
191 | return true;
192 | }
193 |
194 | return false;
195 | }
196 |
197 |
198 | static string make_python_import_reload_wrapper(string python_filepath)
199 | {
200 | /// It is not possible to directly execute Python files in 3ds Max via calling filein() on the COM server.
201 | /// Luckily MaxScript supports Python.ExecuteFile(filepath). This function takes the provided Python file
202 | /// and wraps it a Python.ExecuteFile() command within a MaxScript file that is saved to the user's %TEMP% folder.
203 | /// That temporary MaxScript file is what is sent to 3ds Max's COM server.
204 | ///
205 | /// **Arguments:**
206 | ///
207 | /// :``python_filepath``: `string` A full absolute filepath to a Python (.py) file.
208 | ///
209 | /// **Keyword Arguments:**
210 | ///
211 | /// None
212 | ///
213 | /// **Returns:**
214 | ///
215 | /// :``reload_wrapper_filepath``: `string`
216 | ///
217 | /// **Author:**
218 | ///
219 | /// Jeff Hanna, jeff@techart.online, November 22, 2019
220 |
221 | string mod_name = Path.GetFileNameWithoutExtension(python_filepath);
222 | string reload_cmd = String.Format("import sys\nif sys.version_info.major < 3:\n\timport imp as importlib\nelse:\n\timport importlib\n\t\ntry:\n\timportlib.reload({0})\nexcept:\n\tpass", mod_name);
223 | var reload_wrapper_filepath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "import_reload.py");
224 | reload_wrapper_filepath = reload_wrapper_filepath.Replace( "\\", "\\\\");
225 | System.IO.File.WriteAllText(reload_wrapper_filepath, reload_cmd);
226 |
227 | return reload_wrapper_filepath;
228 | }
229 |
230 |
231 | static string make_python_wrapper(string python_filepath)
232 | {
233 | /// For the python file being executed in 3ds Max this script writes a Maxscript wrapper file
234 | /// that can be called to reimport that Python module so that the in-memory version is updated with
235 | /// any changes made between script executions.
236 | ///
237 | /// **Arguments:**
238 | ///
239 | /// :``python_filepath``: `string` A full absolute filepath to a Python (.py) file.
240 | ///
241 | /// **Keyword Arguments:**
242 | ///
243 | /// None
244 | ///
245 | /// **Returns:**
246 | ///
247 | /// :``wrapper_filepath``: `string`
248 | ///
249 | /// **Author:**
250 | ///
251 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
252 |
253 | string reload_filepath = make_python_import_reload_wrapper( python_filepath );
254 | string cmd = String.Format("python.ExecuteFile(\"{0}\");python.ExecuteFile(\"{1}\")", reload_filepath, python_filepath);
255 | var wrapper_filepath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "maxscript_python_wrapper.ms");
256 | System.IO.File.WriteAllText(wrapper_filepath, cmd);
257 |
258 | return wrapper_filepath;
259 | }
260 |
261 |
262 | static string mxs_try_catch_errors_cmd(string filepath)
263 | {
264 | /// Wraps the given MAXScript command arg in MAXScript code that when run in 3ds Max will catch
265 | /// errors using a MAXScript try() catch() and print a 'homemade' minimally useful log message
266 | /// to the MAXScript Listener instead of the usual proper one.
267 | ///
268 | /// **Arguments:**
269 | ///
270 | /// :``command: `string` A fumaxscript command to wrap
271 | /// :``log_filepath``: `string` The filepath to use in the MAXScript Listerner log message
272 | ///
273 | /// **Keyword Arguments:**
274 | ///
275 | /// None
276 | ///
277 | /// **Returns:**
278 | ///
279 | /// :``cmd``: `string`
280 | ///
281 | /// **Author:**
282 | ///
283 | /// Gary Tyler, mail@garytyler.com, April 12, 2018
284 |
285 | string ext = System.IO.Path.GetExtension(filepath).ToLower();
286 |
287 | string location;
288 | string run_cmd;
289 |
290 | if (ext == ".py")
291 | {
292 | /// For python files, use passed filepath for location msg, no pos or line available
293 | location = String.Format("\"Error; filename: {0}\"", filepath);
294 |
295 | /// Pass thru python.ExecuteFile()
296 | string reload_filepath = make_python_import_reload_wrapper( filepath );
297 | run_cmd = String.Format("python.ExecuteFile(\"{0}\");python.ExecuteFile(\"{1}\")", reload_filepath, filepath);
298 | }
299 | else
300 | {
301 | /// For maxscript files, use provided commands for location msg
302 | string loc_file = "\" filename: \" + (getErrorSourceFileName() as string)";
303 | string loc_pos = "\"; position: \" + ((getErrorSourceFileOffset() as integer) as string)";
304 | string loc_line = "\"; line: \" + ((getErrorSourceFileLine() as integer) as string) + \"\n\"";
305 | string callstack_line = "\"callstack: \n\" + (cs as string)";
306 | location = String.Format("\"Error;\" + {0} + {1} + {2} + {3}", loc_file, loc_pos, loc_line, callstack_line);
307 |
308 | /// Pass thru filein()
309 | run_cmd = String.Format("filein(@\"{0}\")", filepath);
310 | }
311 |
312 | string exception_array = "(filterString (getCurrentException()) \"\n\")";
313 | string exception_msg = String.Format("(for i in #({0}) + {1} do setListenerSelText (\"\n\" + i))", location, exception_array);
314 | string cmd = String.Format("try({0}) catch(cs = \"\" as stringStream;stack to:cs;{1});setListenerSelText \"\n\"", run_cmd, exception_msg);
315 | return cmd;
316 | }
317 |
318 |
319 | static void show_message(string message, bool info = false)
320 | {
321 | /// Displays an error or informational dialog if the execution of MXSPyCOM encounters a problem.
322 | /// Also displays a standard help dialog if MXSPyCOM is called with no arguments or with the /? or /help arguments.
323 | ///
324 | /// **Arguments:**
325 | ///
326 | /// :``message``: `string` The message to display.
327 | ///
328 | /// **Keyword Arguments:**
329 | ///
330 | /// :``information``: `bool` Determines if the dialog should display with an informational or warning icon.
331 | ///
332 | /// **Returns:**
333 | ///
334 | /// None
335 | ///
336 | /// **Author:**
337 | ///
338 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
339 |
340 | MessageBoxIcon icon = info == true ? MessageBoxIcon.Information : MessageBoxIcon.Error;
341 |
342 | if (message.ToLower() == "help")
343 | {
344 | message = @"Used to execute MaxScript and Python scripts in 3ds Max.
345 |
346 | Usage:
347 | MXSPyCOM.exe [options]
348 |
349 | Options:
350 | -f - Execute the script in 3ds Max.
351 | -s - Execute the script in 3ds Max with no error dialogs.
352 | -e - Edit the script in 3ds Max's internal script editor.
353 | -c - Encrypt the script. Only works with MaxScript files.
354 |
355 | Commands:
356 | - Full path to the script file to execute.";
357 |
358 | show_message(message, info: true);
359 | }
360 |
361 | MessageBox.Show(message, "MXSPyCOM", MessageBoxButtons.OK, icon);
362 |
363 | Environment.Exit(0);
364 | }
365 |
366 |
367 | static string get_script_filepath(string[] args)
368 | {
369 | /// Extracts the script filepath from the command line arguments.
370 | /// If no filepath is provied in args the user is notifed and the program exits.
371 | /// If the filepath provided contains a Python script that script is wrapped in
372 | /// a MaxScript wrapper, because 3ds Max's COM interface will not correctly parse
373 | /// a Python file, and the filepath to that temporary wrapper script is returned.
374 | ///
375 | /// **Arguments:**
376 | ///
377 | /// :``args``: `string[]` The arguments provided to MXSPyCOM
378 | ///
379 | /// **Keyword Arguments:**
380 | ///
381 | /// None
382 | ///
383 | /// **Returns:**
384 | ///
385 | /// :``filepath``: `string` A full absolute filepath to the script to execute in 3ds Max.
386 | ///
387 | /// **Author:**
388 | ///
389 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
390 |
391 | string filepath = args[args.Length - 1];
392 | if (filepath.StartsWith("-"))
393 | {
394 | string msg = String.Format("No script filepath provided. {0}", USAGE_INFO);
395 | show_message(msg);
396 | }
397 | else if (!System.IO.File.Exists(filepath))
398 | {
399 | string msg = String.Format("The specified script file does not exist on disk. {0}", USAGE_INFO);
400 | show_message(msg);
401 | }
402 |
403 | filepath = filepath.Replace("\\", "\\\\");
404 | return filepath;
405 | }
406 |
407 |
408 | static void Main(string[] args)
409 | {
410 | /// The main execution function of MXSPyCOM
411 | ///
412 | /// **Arguments:**
413 | ///
414 | /// :``args``: `string[]` The arguments provided to MXSPyCOM
415 | ///
416 | /// **Keyword Arguments:**
417 | ///
418 | /// None
419 | ///
420 | /// **Returns:**
421 | ///
422 | /// None
423 | ///
424 | /// **Author:**
425 | ///
426 | /// Jeff Hanna, jeff@techart.online, July 9, 2016
427 |
428 | if (args.Length == 0)
429 | {
430 | show_message("help");
431 |
432 | // For testing
433 | //string filepath = @"d:\repos\mxspycom\hello_world.ms";
434 | //string[] test_args = new string[2] {"-f", filepath};
435 | //execute_max_commands(test_args, filepath);
436 | }
437 | else
438 | {
439 | string filepath = get_script_filepath(args);
440 | execute_max_commands(args, filepath);
441 | }
442 |
443 | Environment.Exit(0);
444 | }
445 | }
446 | }
447 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MXSPyCOM
2 | A modern version of MXSCOM, to allow for editing & execution of 3ds Max MaxScript and Python files from external code editors.
3 |
4 | In 2005 Simon Feltman released the first MXSCOM, a small Visual Basic 6 application that took commands and sent them to Autodesk's
5 | 3ds Max's internal COM server. This allowed users to choose their own external code editor for editing MaxScript and to be able to
6 | have their MaxScript code execute in 3ds Max by way of having the code editor utilize MXSCOM to send the file into 3ds Max and have it
7 | executed. Modern versions of Windows can not use Simon Feltman's old MXSCOM.exe program due to it being ActiveX based.
8 |
9 | MXSPyCOM is a C# based replacement for MXSCOM. It offers the same functionality as MXSCOM but can run on modern versions of Windows.
10 | It also supports editing of Python files and having them execute in versions of 3ds Max, starting with 3ds Max 2015, that support Python
11 | scripts.
12 |
13 | Instructions on how to install MXSPyCOM and how to configure most popular code editors to use it as an external tool can be found in [this project's Wiki](https://github.com/JeffHanna/MXSPyCOM/wiki).
14 |
--------------------------------------------------------------------------------
/hello_world.ms:
--------------------------------------------------------------------------------
1 | (
2 | messageBox "Hello World!"
3 | )
4 |
--------------------------------------------------------------------------------
/hello_world.py:
--------------------------------------------------------------------------------
1 | """
2 | A "Hello Word!" example as a Qt based messagebox.
3 | This is used as a test to ensure MXSPyCOM is working correctly with 3ds Max.
4 | """
5 |
6 | from pymxs import runtime # type: ignore
7 |
8 | try:
9 | import MaxPlus # type: ignore
10 | except ImportError:
11 | import shiboken2 # type: ignore
12 | MaxPlus = None
13 |
14 | from PySide2.QtWidgets import QMainWindow, QMessageBox, QWidget # type: ignore
15 |
16 |
17 | def main() -> None:
18 | if MaxPlus:
19 | main_window = MaxPlus.GetQMaxMainWindow() # type: QMainWindow
20 | else:
21 | main_window_widget = QWidget.find(int(runtime.windows.getMAXHWND())) # type: QWidget
22 | main_window = shiboken2.wrapInstance(shiboken2.getCppPointer(main_window_widget)[0], QMainWindow) # type: QMainWindow
23 |
24 | msg_box = QMessageBox(main_window)
25 | msg_box.setText('Hello World!')
26 | msg_box.show()
27 |
28 |
29 | if __name__ == '__main__':
30 | main()
31 |
--------------------------------------------------------------------------------
/images/notepad++_menu_post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/notepad++_menu_post.png
--------------------------------------------------------------------------------
/images/notepad++_menu_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/notepad++_menu_pre.png
--------------------------------------------------------------------------------
/images/notepad++_run_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/notepad++_run_dialog.png
--------------------------------------------------------------------------------
/images/notepad++_shortcut_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/notepad++_shortcut_dialog.png
--------------------------------------------------------------------------------
/images/pycharm_create_tool_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/pycharm_create_tool_dialog.png
--------------------------------------------------------------------------------
/images/pycharm_menu_post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/pycharm_menu_post.png
--------------------------------------------------------------------------------
/images/pycharm_settings_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/pycharm_settings_dialog.png
--------------------------------------------------------------------------------
/images/sublime_key_bindings_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/sublime_key_bindings_dialog.png
--------------------------------------------------------------------------------
/images/sublime_menu_key_bindings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/sublime_menu_key_bindings.png
--------------------------------------------------------------------------------
/images/sublime_menu_package_control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/sublime_menu_package_control.png
--------------------------------------------------------------------------------
/images/ue_configure_tools_dialog_options_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/ue_configure_tools_dialog_options_tab.png
--------------------------------------------------------------------------------
/images/ue_configure_tools_dialog_output_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/ue_configure_tools_dialog_output_tab.png
--------------------------------------------------------------------------------
/images/ue_external_tools_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/ue_external_tools_dialog.png
--------------------------------------------------------------------------------
/images/ue_ribbon_post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/ue_ribbon_post.png
--------------------------------------------------------------------------------
/images/ue_ribbon_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/ue_ribbon_pre.png
--------------------------------------------------------------------------------
/images/vs_external_tools_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/vs_external_tools_dialog.png
--------------------------------------------------------------------------------
/images/vs_menu_post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/vs_menu_post.png
--------------------------------------------------------------------------------
/images/vs_menu_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/vs_menu_pre.png
--------------------------------------------------------------------------------
/images/wing_prefs_dialog_command_create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/wing_prefs_dialog_command_create.png
--------------------------------------------------------------------------------
/images/wing_prefs_dialog_post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/wing_prefs_dialog_post.png
--------------------------------------------------------------------------------
/images/wing_prefs_dialog_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techartorg/MXSPyCOM/15f1604b87d553c45139f62f595f43241cd870f7/images/wing_prefs_dialog_pre.png
--------------------------------------------------------------------------------
/initialize_COM_server.ms:
--------------------------------------------------------------------------------
1 | (
2 | /* Dynamically writes the necessary Registry information to allow
3 | Simon Felton's MXSCOM bridge to work.
4 | IF RUNNING THIS SCRIPT ON AN VERSION OF MAX OLDER THAN MAX 10
5 | THE AVG EXTENSION *MUST* BE INSTALLED
6 | */
7 |
8 | local reg_key
9 | local max_version = ((maxVersion())[1] / 1000) as string
10 |
11 | fn create_reg_key hkey key_name ®_key key_value_name key_value_type key_value =
12 | (
13 | registry.createKey hkey key_name key:®_key
14 | registry.setValue reg_key key_value_name key_value_type key_value
15 | )
16 |
17 | fn write_sub_key_data reg_key sub_key_name sub_key_type sub_key_value =
18 | (
19 | local sub_key
20 | registry.createKey reg_key sub_key_name key:&sub_key
21 | registry.setValue sub_key "" sub_key_type sub_key_value
22 | )
23 |
24 |
25 | -- Establish a root key for generalized Max data
26 | create_reg_key HKEY_CURRENT_USER @"Software\Classes\MAX.Application" ®_key "" #REG_SZ "OLE Automation MAX Application"
27 |
28 | -- Add the Clsid information
29 | write_sub_key_data reg_key "Clsid" #REG_SZ "{7FA22CB1-D26F-11d0-B260-00A0240CEEA3}"
30 |
31 | -- Add the CurVer information
32 | write_sub_key_data reg_key "CurVer" #REG_SZ ("MAX.Application." + max_version)
33 |
34 | -- Establish a new root key for the version of Max being used
35 | create_reg_key HKEY_CURRENT_USER (@"Software\Classes\MAX.Application." + max_version) ®_key "" #REG_SZ ("OLE Automation MAX " + max_version + ".0 Application")
36 |
37 | -- Add the Clsid information
38 | write_sub_key_data reg_key "Clsid" #REG_SZ "{7FA22CB1-D26F-11d0-B260-00A0240CEEA3}"
39 |
40 | -- Make a new root key for the CLSID data
41 | create_reg_key HKEY_CURRENT_USER @"Software\Classes\CLSID\{7FA22CB1-D26F-11d0-B260-00A0240CEEA3}" ®_key "" #REG_SZ ("OLE Automation MAX " + max_version + ".0 Application")
42 |
43 | -- Add sub key data
44 | write_sub_key_data reg_key "ProgID" #REG_SZ ("MAX.Application." + max_version)
45 | write_sub_key_data reg_key "VersionIndependentProgID" #REG_SZ "MAX.Application"
46 |
47 | -- Register the running of files and executing script code to OLE.
48 | registerOLEInterface #( filein, execute, edit, encryptscript )
49 | )
50 |
--------------------------------------------------------------------------------
/scripts/publish.ps1:
--------------------------------------------------------------------------------
1 | # Build script for MXSPyCOM.
2 | # MXSPyCOM.exe is built in release, with the version number incremented by 0.01, if the user requests.
3 | # MXSPyCOM.exe , the demonstration HelloWorld Python and MaxScript scripts, the initialize_COM_server MaxScript, and
4 | # the readme.md file are archived in an MXSPyCOM.zip file and placed on the desktop, ready for including in a release
5 | # in the GitHub repository.
6 |
7 | [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
8 |
9 | $inc_ver_num = [System.Windows.Forms.MessageBox]::Show(
10 | "Increment the version number?", "Publish MXSPyCOM", [System.Windows.Forms.MessageBoxButtons]::YesNo)
11 |
12 | if ($inc_ver_num -like "yes") {
13 | [xml]$axml= Get-Content "MXSPyCOM.csproj"
14 | $ver_num_parts = $axml.Project.PropertyGroup.Version.Split(".")
15 | $major = [int]$ver_num_parts[0]
16 | $minor = [int]$ver_num_parts[-1] + 1
17 | if ($minor -gt 99) {
18 | $major += 1
19 | $minor = 00
20 | }
21 | $axml.Project.PropertyGroup.Version = $major.ToString() + "." + $minor.ToString()
22 | $axml.Save("MXSPyCOM.csproj")
23 | }
24 |
25 | dotnet publish "MXSPyCOM.csproj" -c Release
26 |
27 | $temp_path = Join-Path $env:TEMP "MXSPyCOM"
28 | mkdir $temp_path -Force
29 | Copy-Item "bin\release\net6.0-windows\win-x64\publish\MXSPyCOM.exe" $temp_path
30 | Copy-Item "README.md" $temp_path
31 | Copy-Item "hello_world.ms" $temp_path
32 | Copy-Item "hello_world.py" $temp_path
33 | Copy-Item "initialize_COM_server.ms" $temp_path
34 |
35 | $temp_filepath = Join-Path $temp_path "*"
36 | try {
37 | # The user's desktop is in the default location on their hard drive.
38 | $archive_filepath = Join-Path $env:USERPROFILE "DESKTOP" "MXSPyCOM.zip"
39 | Compress-Archive -Path $temp_filepath -DestinationPath $archive_filepath -Force
40 | }
41 | catch { # It would be better if a specific Compress-Archive exception is caught, but none can be found in docs.
42 | # The user's desktop is being managed by OneDrive.
43 | $archive_filepath = Join-Path $env:ONEDRIVE "Desktop" "MXSPyCOM.zip"
44 | Compress-Archive -Path $temp_filepath -DestinationPath $archive_filepath -Force
45 | }
46 | finally {
47 | Remove-Item $temp_path -Recurse -Force
48 | [System.Windows.Forms.MessageBox]::Show(
49 | "Finished.
50 |
51 | Include the MXSPyCOM.zip file that was written to the desktop
52 | in the new binary release on GitHub.",
53 | "Publish MXSPyCOM")
54 | }
--------------------------------------------------------------------------------