Corona material property names in MaxScript: Physical vs Legacy vs Max Physical

Interior CGI of a meeting room at Mosley St, with timber slatted walls, sage joinery, a long green table and tall windows onto the street. By Vaars Studio.

If you script materials in 3ds Max with Corona, the first thing to know is that the same slot has a different scripting name on each material class. Diffuse colour is baseColor on CoronaPhysicalMtl, diffuseColor on CoronaLegacyMtl and CoronaMtl, and base_color on Max's own PhysicalMaterial. A script written against one class will quietly do nothing on another, and that is behind most of the scripts in our notes that reported "Done" and changed nothing.

This guide is the reference we keep for those names, with the gotchas we hit and the ways a script can reach every material in a scene. The Physical names were confirmed with getPropNames on Corona 12 with 3ds Max 2025. We have not yet rechecked them on Corona 14, so run the dump below on your own build before relying on them.

It is the second guide in our 3ds Max series. The first, How to prompt AI to write 3ds Max scripts that actually work, covers the rules and template these names plug into.

Check the class first

Pick the column in the table below by the material's class, not by what the slot is called in the editor. This read only dump prints the class and every property the selected object's material actually has:

m = selection[1].material
format "% [%]\n" m.name (classof m)
for p in (getPropNames m) do (
    try ( format "  % = %\n" p (getProperty m p) ) catch ( format "  %\n" p )
)
m = selection[1].material
format "% [%]\n" m.name (classof m)
for p in (getPropNames m) do (
    try ( format "  % = %\n" p (getProperty m p) ) catch ( format "  %\n" p )
)
m = selection[1].material
format "% [%]\n" m.name (classof m)
for p in (getPropNames m) do (
    try ( format "  % = %\n" p (getProperty m p) ) catch ( format "  %\n" p )
)

Use getPropNames rather than showProperties. On some Max 2025 builds showProperties prints to the editor window and the Listener just says OK.

The property reference

Confirmed on Corona 12 with Max 2025. Blank cells are slots we have no confirmed name for.

Slot

CoronaPhysicalMtl

CoronaLegacyMtl / CoronaMtl

Max PhysicalMaterial

Diffuse colour

baseColor

diffuseColor

base_color

Diffuse map

baseTexmap (+ baseTexmapOn)

texmap_diffuse

base_color_map

Reflection

baseRoughness (0 sharp, 1 matte)

reflectionGlossiness (1 sharp, 0 matte)

roughness

Roughness map

baseRoughnessTexmap



IOR

baseIor

ior


Bump map

baseBumpTexmap (+ baseBumpTexmapOn)

texmap_bump

bump_map

Bump amount

baseBumpMapAmount

bumpAmount

bump_map_amt

Self illum level

selfIllumLevel

selfIllumination / selfIllumMultiplier

emission

Self illum colour

selfIllumColor

selfIllumColor


Self illum map

selfIllumTexmap (+ selfIllumTexmapOn)

texmap_self_illum

emission_map

Opacity

opacityLevel, opacityTexmap, opacityCutout

texmap_opacity


Refraction

refractionAmount, refractionAmountTexmap



Displacement

displacementTexmap, displacementMinimum, displacementMaximum



Clearcoat

clearcoatAmount, clearcoatIor, clearcoatRoughness



Sheen

sheenAmount, sheenColor, sheenRoughness



Translucency

translucencyFraction, translucencyColor



The gotchas

  • Lowercase i. selfillumMapAmount on Physical has a lowercase i in illum.

  • Zero is not off. Zeroing the self illum level is not enough if a map is still plugged in. Set the map to undefined and the colour to black too. In our notes, "self illum still glowing at 0" came down to exactly this.

  • Legacy names on Physical fail. Using selfIllumMultiplier on a Physical material gives an unknown property error. The Physical name is selfIllumLevel.

  • Roughness and glossiness run opposite ways. Physical scripts reflection as baseRoughness, where 0 is sharp. Legacy uses reflectionGlossiness, where 1 is sharp. They are inverses: glossiness = 1 minus roughness. Our matte script gave Physical baseRoughness = R and Legacy reflectionGlossiness = 1 - R, so one value drove both classes.

  • The UI may say Glossiness anyway. Physical shows "Glossiness" in the UI on some builds, but the only property we have found is baseRoughness, and a glossiness script on one build left the field reading 0.9. This is still unresolved in our notes (see the last section).

Other Corona classes you will meet

  • CoronaLightMtl: real emitters. Leave these alone in any self illum clean up.

  • CoronaSelectMtl and the CoronaSelect texmap. On the texmap, the switch is .selected, not .selectedMap, and it is 0 based, matching the Subtex number in the UI.

  • CoronaScannedMtl: a NaN risk.

  • CoronaColorCorrect: its input is inputTexmap, and it has gamma.

  • CoronaDecalMtl and CoronaMtlBlend.

Reaching every material

The right property name only helps if the script reaches the material. These are the patterns in our notes, from shallowest to deepest. The shallow ones are why scripts said "Done" and changed nothing.

Pattern

Reaches

Misses

Use when

A: for m in sceneMaterials

Materials in editor slots

Anything only assigned to objects

Almost never

B: for o in selection + o.material

Top level material on each object

Anything nested

Flat imported assets

C: B + MultiMaterial.materialList

One level of Multi/Sub

CoronaSelect, Blend, nested Multi/Sub

Simple archviz scenes

D: recursive getSubMtl + visited array

Everything, at any depth

Nothing

Default for all bulk edits

E: D + getSubTexmap

Texmaps too (CoronaSelect texmap, Composite, ColorCorrect)

Nothing

Finding a named map, NaN hunts

F: getClassInstances BitmapTexture

Every bitmap in the file

Context of which material owns it

Texture collect and relink

Two rows from our notes show why this matters. sceneMaterials finds 0 materials when they are only assigned to objects and not sitting in the editor. And in one scene, a search for materials sharing a name prefix found only 2 of 13, because the rest were nested in Multi/Sub materials. A recursive walk found them all.

Pattern D, the deep walk

This is the version that worked. It follows every sub material at any depth, skips anything it has already seen, and branches on class so each one gets the right property names:

global seen = #()
global changedCount = 0

fn walkMtl node = (
    if node == undefined then return false
    if (findItem seen node) != 0 then return false
    append seen node

    if (classof node) == CoronaPhysicalMtl then (
        -- do the Physical change here
        changedCount += 1
    )
    else if (classof node) == CoronaLegacyMtl or (classof node) == CoronaMtl then (
        -- do the Legacy change here
        changedCount += 1
    )

    try ( for i = 1 to (getNumSubMtls node) do walkMtl (getSubMtl node i) ) catch()
    false
)

undo "Deep walk" on (
    for o in selection do ( if o.material != undefined do walkMtl o.material )
)
format "Done. % materials changed. Ctrl+Z to undo.\n" changedCount
global seen = #()
global changedCount = 0

fn walkMtl node = (
    if node == undefined then return false
    if (findItem seen node) != 0 then return false
    append seen node

    if (classof node) == CoronaPhysicalMtl then (
        -- do the Physical change here
        changedCount += 1
    )
    else if (classof node) == CoronaLegacyMtl or (classof node) == CoronaMtl then (
        -- do the Legacy change here
        changedCount += 1
    )

    try ( for i = 1 to (getNumSubMtls node) do walkMtl (getSubMtl node i) ) catch()
    false
)

undo "Deep walk" on (
    for o in selection do ( if o.material != undefined do walkMtl o.material )
)
format "Done. % materials changed. Ctrl+Z to undo.\n" changedCount
global seen = #()
global changedCount = 0

fn walkMtl node = (
    if node == undefined then return false
    if (findItem seen node) != 0 then return false
    append seen node

    if (classof node) == CoronaPhysicalMtl then (
        -- do the Physical change here
        changedCount += 1
    )
    else if (classof node) == CoronaLegacyMtl or (classof node) == CoronaMtl then (
        -- do the Legacy change here
        changedCount += 1
    )

    try ( for i = 1 to (getNumSubMtls node) do walkMtl (getSubMtl node i) ) catch()
    false
)

undo "Deep walk" on (
    for o in selection do ( if o.material != undefined do walkMtl o.material )
)
format "Done. % materials changed. Ctrl+Z to undo.\n" changedCount

The counters are global on purpose: a counter a function changes stays at 0 otherwise.

Pattern E, finding a texmap by name

When the thing you need is a map rather than a material, walk texmaps as well as sub materials:

fn findTexmapByName node target = (
    if node == undefined then return undefined
    if (isProperty node #name) and node.name == target then return node
    try ( for i = 1 to (getNumSubTexmaps node) do (
        r = findTexmapByName (getSubTexmap node i) target
        if r != undefined then return r
    ) ) catch()
    try ( for i = 1 to (getNumSubMtls node) do (
        r = findTexmapByName (getSubMtl node i) target
        if r != undefined then return r
    ) ) catch()
    undefined
)
fn findTexmapByName node target = (
    if node == undefined then return undefined
    if (isProperty node #name) and node.name == target then return node
    try ( for i = 1 to (getNumSubTexmaps node) do (
        r = findTexmapByName (getSubTexmap node i) target
        if r != undefined then return r
    ) ) catch()
    try ( for i = 1 to (getNumSubMtls node) do (
        r = findTexmapByName (getSubMtl node i) target
        if r != undefined then return r
    ) ) catch()
    undefined
)
fn findTexmapByName node target = (
    if node == undefined then return undefined
    if (isProperty node #name) and node.name == target then return node
    try ( for i = 1 to (getNumSubTexmaps node) do (
        r = findTexmapByName (getSubTexmap node i) target
        if r != undefined then return r
    ) ) catch()
    try ( for i = 1 to (getNumSubMtls node) do (
        r = findTexmapByName (getSubMtl node i) target
        if r != undefined then return r
    ) ) catch()
    undefined
)

We used this to find a CoronaSelect texmap that sat in a Legacy material's diffuse slot, under two CoronaColorCorrect maps and a Composite layer. A screenshot of the Slate node tree is the fastest way to map a chain like that before scripting it.

How three of our scripts use these names

Triplanar consolidator. It reads every texture slot on a material by number with getNumSubTexmaps and getSubTexmap, skipping slots that already hold a CoronaTriplanar or an output helper. It plugs each map into one CoronaTriplanar, then wires a MultiOutputChannelTexmapToTexmap back into the original slot with setSubTexmap, one output channel per map. Working by slot number means it does not need to know the property names at all.

Scatter edge trimmer. It works on the selection and goes one level into Multi/Sub materials (pattern C). It only touches sub materials where hasProperty sub #opacityTexmap is true, the same name as the Physical opacity slot in the table. For each one it creates a unique ChaosScatterEdgeTrimming with createInstance, puts it in opacityTexmap first, then plugs the old map into the trimmer's defaultMap, and switches opacityTexmapOn on. It closes the Slate Material Editor while it runs, to stop the UI merging the new maps into one instance.

Kill accidental self illum. It looks only at CoronaPhysicalMtl, so CoronaLightMtl emitters stay lit. A material counts as emitting if selfIllumLevel is above 0, or if selfIllumTexmapOn is on with a map in selfIllumTexmap. It then sets selfIllumLevel to 0 and selfIllumTexmapOn to false, each inside try ( ) catch ( ), and prints every material it changed. It loops every object in the scene plus sceneMaterials, and goes one level into Multi/Sub.

Scripts like these are on our free 3ds Max scripts page, added one at a time.

What is still open

Two checks are still open in our notes, and both affect this table:

  • Glossiness on CoronaPhysicalMtl. Dump every property matching rough or gloss on one material, and note which property the UI field reads and which direction is matte.

  • Corona 14 names. Run the property dump on one Physical material in Corona 14 and compare it with the table.

Until then, treat the Physical column as confirmed on Corona 12 only, and dump before you script.

Need visuals built on a careful pipeline?

We use this reference on our own architectural and interior visualisation work. See our recent work, or get in touch to talk through your next project.

Let’s talk about your project.

| Architecture & Visualisation

© 2026

Set in Satoshi & IBM Plex Mono

VAARS STUDIO LLC-FZ | Visual craftsmanship | London & UAE

| Architecture & Visualisation

© 2026

Set in Satoshi & IBM Plex Mono

VAARS STUDIO LLC-FZ

Visual craftsmanship

London & UAE