How to prompt AI to write 3ds Max scripts that actually work
AI assistants can write MaxScript quickly, but a script that compiles is not the same as a script that changes your scene. In our own work with 3ds Max and Corona, nearly every failed script came down to one of three things: the wrong material class, the wrong property name, or a loop that never reached the material. This guide covers how we now prompt for scripts so those three problems are dealt with before the first line runs.
It is based on our notes from 3ds Max 2025 and 2026 with Corona 12, and later Corona 14. Where something was only confirmed on Corona 12, we say so.
Dump the material before you ask for a change
Never let the AI guess a property name from memory, and do not trust one from memory yourself. Run a read only dump on the material you want to change, then paste the result into the chat. The dump prints the material's class and every property it actually has.
Use getPropNames rather than showProperties. On some Max 2025 builds showProperties prints to the editor window and the Listener just says OK, so you get nothing useful to paste back.
The class matters because the same slot has a different scripting name on each material class. Diffuse colour, for example, is baseColor on CoronaPhysicalMtl, diffuseColor on CoronaLegacyMtl and CoronaMtl, and base_color on Max's own PhysicalMaterial. Reflection is baseRoughness on Physical, where 0 is sharp and 1 is matte, but reflectionGlossiness on Legacy, where 1 is sharp and 0 is matte. A script written for one class will quietly do nothing on another. The Physical names in our reference were confirmed with getPropNames on Corona 12 with Max 2025, so recheck them on Corona 14 by running the dump above.
Give the AI the 12 rules
These are the rules every script we use now follows. Paste them into the chat, or keep them in a saved instruction file your assistant reads at the start, so you do not have to repeat them.
Diagnose first, change second. Run a read only dump (
classofplusgetPropNames) before any bulk change. Never trust a property name from memory.Check the class.
CoronaPhysicalMtl,CoronaLegacyMtl/CoronaMtland Max's ownPhysicalMaterialall use different property names.Loop objects, not just
sceneMaterials. Materials assigned to objects but not sitting in the editor are invisible tosceneMaterials.Go deep. Use
getNumSubMtls/getSubMtlandgetNumSubTexmaps/getSubTexmapwith a visited array.materialListalone misses CoronaSelect, Blend and scanned materials.Default to
selection. Only touchobjectswhen you explicitly mean the whole scene.Wrap every change in an undo block:
undo "Name" on ( ... )so one Ctrl+Z rolls it back.Count before you destroy. Deleting maps or renaming files? Run a list only pass, check the count, then run the real one.
No
localat top level. Only insidefn,rollout,structor loops.Counters a function changes are
globaland never namedfixed, which is reserved.if ... dohas noelse. Useif ... then (...) else (...)for any branch.Wrap risky property sets in
try ( ) catch ( )so one odd material does not kill the run.Deliver as a .ms file with a header saying what it does, what it targets and how to undo.
Rules 8 to 10 exist because each one caused a compile or runtime error once: "no local declarations at top level", "cannot assign to read only variable" from a counter named fixed, and "unexpected else" from if ... do ( ) else ( ).
What the rules look like in our own scripts
These snippets are lifted unchanged from scripts we use, so you can see the rules in working code.
Selection only, with the whole change inside one undo block (rules 5 and 6), from a script that adds Chaos Scatter edge trimming to materials:
Count before you destroy (rule 7), from a Revit clean up panel. It lists the small objects first, tells you how many it found and only deletes after you say yes:
A visited array, so each material is handled once, and try ( ) catch ( ) around each property so one odd material does not stop the run (rules 4 and 11), from a script that switches off accidental self illumination:
When the script says "Done" and nothing changes
This is the most common failure, and it is rarely a syntax problem. The script runs to the end, prints its message and leaves the scene untouched. In our notes it came from these causes:
Wrong class. The script targeted
CoronaMtlwhen the materials wereCoronaPhysicalMtlor MaxPhysicalMaterial. Fix: printclassoffor each material first.sceneMaterialsfound nothing. The materials were only assigned to objects, not loaded in the editor. Fix: loopselectionorobjectsinstead.Nested materials were skipped. On one furniture colourway job, a collector found only 2 of 13 target materials because the rest sat inside Multi/Sub materials. Fix: a recursive walk through every sub material.
The counter stayed at 0. The counter was changed inside a function but not declared
globalat top level, so the report was wrong even when work was done.Self illumination still glowing at 0. The level was zeroed but a map was still plugged into the slot. Fix: set the map to
undefinedand the colour to black as well.Legacy names on a Physical material.
selfIllumMultiplieris a Legacy property; on Physical the level isselfIllumLevel. Check withgetPropNames.Wrong CoronaSelect property.
.selectedMapfailed; the property is.selected. It is 0 based and matches the Subtex number shown in the UI, so set it directly with no +1.
Two of these are visible in one copy of our own colourway batch renderer. The CSV reader splits each row with filterString, which silently drops empty cells, so padding the row afterwards cannot put the columns back in the right place:
And the colour switch uses .selectedMap, the property our notes record as failing, inside a try ( ) catch ( ). The catch keeps the batch running, but it also hides the error, so the paint does not change and nothing tells you:
This is why rule 11 needs the companion habit of printing what failed, as the template below does. The fixes in our notes were a character by character CSV parser and .selected.
If you see "Done" and no change, do not ask the AI to try again blind. Paste the classof output and the Listener text back, and ask it to explain which of these it hit.
Paste the Listener output back, every time
One habit paid off again and again: pasting the MaxScript Listener output back into the chat after every diagnostic run. Each time we did, a guess turned into a fix. The AI cannot see your scene; the Listener output is the only evidence it has of what class a material is, what properties exist and how many objects a loop actually reached.
It also helps to build scenes a script can drive: one master material per object that changes, one named CoronaSelect per switch, and clean object names. A screenshot of the Slate node tree is the fastest way to map a deep material chain before asking for a script that has to reach into it.
Ask for a dry run flag
Any script that changes a lot of materials, or any batch render script, should have a dry run mode. With the flag on, the script lists what it would change and how many items it found. You check the list, then switch the flag off and run it for real. For batch rendering, this lets you check the plan before committing render hours. It is also how rule 7 works in practice: count first, then destroy.
This is how the dry run looks in our colourway batch renderer. The same loop runs either way; with the flag on it prints the file it would save instead of rendering:
Start every script from a template
Rather than letting the AI invent structure each time, give it this template and ask it to fill in only the two apply functions. It already follows all 12 rules: a header, selection only, a deep walk with a visited array, a class check, try/catch around each change, an undo block and a dry run flag. Set DRY_RUN = true to list what would change, then false to apply.
The header says Corona 14, but the property names behind the example lines were confirmed on Corona 12. Run the dump on Corona 14 before relying on them.
The prompt we reuse
With the rules and template saved, the prompt itself can be short. This is the one we reuse when asking for a new script:
Use [your saved scripting rules]. Corona 14, Max 2025. Selection only, deep walk, undo block, dry run flag. Give me a read only dump first. Here is the
classofoutput: ...
Each part closes off a known failure. Naming the versions stops the AI mixing up builds. "Selection only" and "undo block" limit the damage if something goes wrong. "Deep walk" makes sure nested materials are reached. "Read only dump first" means the next script is written against real property names, and the pasted classof output tells it which class to target.
Ask for an update to your rules after a long debug
When a long debugging session finally works, ask the AI to update your saved rules or instruction file with what you learned. Otherwise the lesson stays in that one chat and you meet the same problem again in the next one.
One caution
It is easy to end up with a lot of tooling. Our own note to ourselves is to build new scripts only when a live job needs them, and to keep the ones that work in a single library folder with a header on each.
Need visuals built on a careful pipeline?
We use these habits on our own architectural and interior visualisation work. See our recent work, or get in touch to talk through your next project.

