3ds Max Python (pymxs): Batch Toggle & Isolate Skin and TurboSmooth Modifiers
09 Sep 26 (1d ago)
When animating scenes with multiple characters, dense clothing meshes, or high-density subdivisions in Autodesk 3ds Max, viewport performance can quickly crawl to a crawl. Heavy deformers like Skin (evaluating continuous linear blend or dual-quaternion math) and tessellators like TurboSmooth (subdividing meshes dynamically on CPU threads) place heavy demands on the viewport pipeline. Even when an object is hidden in the viewport, modifiers in its stack can continue evaluating under the hood.
Manually hunting down meshes in the Command Panel to toggle these off is slow and error-prone. Using Python with pymxs, you can automate modifier management across three primary workflows:
- Global Batch Toggles: Turn off deformation and smoothing across all geometry for playback timing, then restore them in a single sweep.
- Targeted Mesh Isolation: Keep deformation active on a single hero character or selection while muting all background assets.
- Unified Production Utility: A flexible, reusable helper suitable for binding to MacroScripts, hotkeys, or Quad menus.
Technical Considerations: pymxs Optimization
Before writing batch sweeps in 3ds Max, keep these three performance rules in mind:
1. rt.geometry vs. rt.objects
Never use for obj in rt.objects: for modifier sweeps. In 3ds Max, rt.objects traverses every scene node—including lights, cameras, Bones, CAT/Biped rigs, splines, and helper dummies that never hold skinning stacks. Restricting your loops to rt.geometry targets mesh and poly surfaces directly, eliminating unnecessary iteration overhead.
2. Viewport Redraw Locks (rt.redraw(False))
Every time you flip mod.enabled = False, 3ds Max notifies the Nitrous viewport driver to evaluate dependency graph updates and repaint the frame. On a scene with dozens of skinned parts, this causes visible micro-stutters. Wrapping changes inside a redraw context and calling completeRedraw() defers rendering until the entire sweep finishes:
with rt.redraw(False):
# Perform all batch modifier state changes here
rt.completeRedraw()
3. Selection Scoping
Hardcoding string names ("Hero_Body_Geo") is helpful in automated asset delivery pipelines, but interactive character work benefits from viewport selection scoping. Checking rt.selection lets animators and riggers run the exact same logic on arbitrary target nodes without editing code.
1. Global Toggles: Suppress & Restore
When checking playback timing in the viewport, disabling all Skin and TurboSmooth modifiers yields immediate real-time frame rates. Once playback verification is finished, you can restore them in one sweep.
Disable Scene-Wide
from pymxs import runtime as rt
def disable_all_skin_and_turbosmooth():
disabled_skin = 0
disabled_ts = 0
# Freeze viewport redraws during the batch sweep
with rt.redraw(False):
for obj in rt.geometry:
for m in obj.modifiers:
mod_class = rt.classOf(m)
if mod_class == rt.Skin and m.enabled:
m.enabled = False
disabled_skin += 1
elif mod_class == rt.TurboSmooth and m.enabled:
m.enabled = False
disabled_ts += 1
rt.completeRedraw()
print(f"[Done] Disabled {disabled_skin} Skin and {disabled_ts} TurboSmooth modifier(s).")
disable_all_skin_and_turbosmooth()
Restore Scene-Wide
from pymxs import runtime as rt
def restore_all_skin_and_turbosmooth():
restored_skin = 0
restored_ts = 0
with rt.redraw(False):
for obj in rt.geometry:
for m in obj.modifiers:
mod_class = rt.classOf(m)
if mod_class == rt.Skin and not m.enabled:
m.enabled = True
restored_skin += 1
elif mod_class == rt.TurboSmooth and not m.enabled:
m.enabled = True
restored_ts += 1
rt.completeRedraw()
print(f"[Done] Restored {restored_skin} Skin and {restored_ts} TurboSmooth modifier(s).")
restore_all_skin_and_turbosmooth()
2. Isolating Modifiers to a Target Mesh
When painting skin weights, troubleshooting joint pivots, or refining corrective shapes, you typically only need deformers active on the specific mesh you are working on.
This script sets m.enabled = is_target across all geometry, isolating the modifier on your selected node or by string lookup while silencing background clutter:
from pymxs import runtime as rt
def isolate_skin_and_ts(target_name=None, use_selection=False):
"""
Enables Skin and TurboSmooth ONLY on the target mesh;
disables them on all other geometry in the scene.
"""
# 1. Resolve target object
if use_selection:
sel = [obj for obj in rt.selection if rt.superClassOf(obj) == rt.GeometryClass]
if not sel:
print("[Error] No geometry selected. Please select a mesh first.")
return
target_obj = sel[0]
else:
target_obj = rt.getNodeByName(target_name)
if not target_obj:
print(f"[Error] Could not find object named '{target_name}'. Check spelling.")
return
disabled_skin = 0
disabled_ts = 0
# 2. Iterate geometry and enforce states
with rt.redraw(False):
for obj in rt.geometry:
is_target = (obj == target_obj)
for m in obj.modifiers:
mod_class = rt.classOf(m)
if mod_class == rt.Skin:
if m.enabled != is_target:
m.enabled = is_target
if not is_target:
disabled_skin += 1
elif mod_class == rt.TurboSmooth:
if m.enabled != is_target:
m.enabled = is_target
if not is_target:
disabled_ts += 1
rt.completeRedraw()
print(f"[Success] Isolated deformation to: '{target_obj.name}'")
print(f"[Info] Muted {disabled_skin} Skin and {disabled_ts} TurboSmooth modifier(s) on background assets.")
# --- USAGE OPTIONS ---
# Option A: By explicit object name
# isolate_skin_and_ts(target_name="Hero_Body_Geo")
# Option B: By current viewport selection
# isolate_skin_and_ts(use_selection=True)
3. Modular Pipeline Utility
For studio toolsets and shelf scripts, you can encapsulate the logic into a single generic function that accepts arbitrary modifier types, a boolean target state, and an optional selection filter:
from pymxs import runtime as rt
def set_modifier_state(modifier_classes, state=True, selection_only=False):
"""
Batch enable or disable specific modifier classes across the scene or active selection.
:param modifier_classes: List of pymxs class types (e.g., [rt.Skin, rt.TurboSmooth])
:param state: bool, True to enable, False to disable
:param selection_only: bool, if True targets selected objects only
"""
if selection_only:
targets = [obj for obj in rt.selection if rt.superClassOf(obj) == rt.GeometryClass]
else:
targets = rt.geometry
affected_count = 0
with rt.redraw(False):
for obj in targets:
if not hasattr(obj, "modifiers"):
continue
for mod in obj.modifiers:
if any(rt.classOf(mod) == target_cls for target_cls in modifier_classes):
if mod.enabled != state:
mod.enabled = state
affected_count += 1
rt.completeRedraw()
action = "Enabled" if state else "Disabled"
scope = "selected" if selection_only else "scene"
print(f"[{action}] Updated {affected_count} modifier(s) across {scope} geometry.")
# --- USAGE EXAMPLES ---
# 1. Turn OFF Skin and TurboSmooth scene-wide for playback speed:
# set_modifier_state([rt.Skin, rt.TurboSmooth], state=False)
# 2. Turn them back ON scene-wide:
# set_modifier_state([rt.Skin, rt.TurboSmooth], state=True)
# 3. Disable only on selected objects:
# set_modifier_state([rt.Skin], state=False, selection_only=True)