Summary
3ds Max Plugin: Exporting Material/Surface from Fab fails to appear in 3ds Max (Material created but not assigned/displayed)
What type of bug are you experiencing?
Plugins
Steps to Reproduce
- Open 3ds Max (e.g., version 2024) with Fab Plugin v0.2.8 installed/running.
- Open Fab Launcher.
- Select a “Surface” or “Texture Set” asset (an asset that contains only material data and no 3D mesh).
- Make sure no object is selected in 3ds Max (to test the default behavior) or select an object.
- Click the “Export” button in Fab to send the asset to 3ds Max.
Expected Result
The imported material should be visible and usable in 3ds Max.
- If objects were selected: The material should be applied to them.
- If nothing was selected: The material should appear in the Material Editor (Active slot in Compact mode or a dedicated view in Slate mode).
Observed Result
Fab Launcher shows “Export successful”.
In 3ds Max, the status indicates “Finished importing models”, but the material is missing from the scene. It is not in the Material Editor, nor assigned to any object. The material is technically created in memory but is “orphaned” because the plugin logic skips assignment when no geometry is present in the payload.
Platform
3ds Max 2024 (and other versions) with Fab Plugin v0.2.8
Operating System
Windows
Additional Notes
Technical Analysis & Fix:
The issue lies in importer.py. The assignment logic is nested inside a loop that iterates over imported_geometries. When exporting a Surface (which has meshes: []), this loop is never entered, so the created material is never assigned or exposed.
I have implemented a fix that handles material-only payloads. It checks if imported_geometries is empty but imported_materials is not.
- If user has a selection → Assigns material to selection.
- If no selection → Opens Slate Material Editor, creates/finds a view named “Fab Material”, and adds the material node there.
Suggested Code Fix for importer.py (inside import_payload method):
# [EXISTING CODE] ... geometry assignment block ...
except:
callback_logger.log("warning", "Error assign materials to geometries, check 3DS Max console")
print(traceback.format_exc())
# [NEW FIX START] Handle material-only payload (no meshes)
if len(imported_geometries) == 0 and len(imported_materials) > 0:
try:
print("Fab: Material-only payload detected")
# Case 1: If user has objects selected, assign material to them
if len(initial_selection) > 0:
print(f"Fab: Assigning material to {len(initial_selection)} selected object(s)")
for (material, mod) in imported_materials:
if material:
for obj in initial_selection:
obj.material = material
if mod:
rt.addModifier(obj, mod)
# Case 2: No selection, add material to Slate Material Editor
else:
print("Fab: No objects selected, adding material to Slate Material Editor")
try:
# Open Slate Material Editor if not already open
if not rt.MatEditor.isOpen():
rt.MatEditor.Open()
# Set mode to Slate Material Editor (ensure Advanced mode)
rt.MatEditor.mode = rt.name("advanced")
# Setup for accessing SME via pymxs
sme = rt.sme
view_name = "Fab Material"
view_obj = None
view_index = -1
# Check if "Fab Material" view already exists
num_views = sme.getNumViews()
for i in range(1, num_views + 1):
v = sme.getView(i)
# Get view title safely
try:
v_title = str(rt.execute(f"(sme.getView {i}).name"))
except:
v_title = ""
if v_title == view_name:
view_obj = v
view_index = i
break
# Create view if it doesn't exist
if view_obj is None:
# createView returns the index (int) of the new view
view_index = sme.createView(view_name)
# Get the view interface object from the index
view_obj = sme.getView(view_index)
print(f"Fab: Created new view '{view_name}' in Slate Material Editor (Index: {view_index})")
else:
print(f"Fab: Using existing view '{view_name}' in Slate Material Editor (Index: {view_index})")
# Activate the view (requires Index)
sme.activeView = view_index
# Add materials to the view (requires View Object)
node_offset = 0
for (material, mod) in imported_materials:
if material:
# Use lowercase `createNode` and `point2`
node = view_obj.createNode(material, rt.point2(50, 50 + node_offset))
node_offset += 150
print(f"Fab: Material '{material.name}' added to Slate Material Editor - '{view_name}' view")
except Exception as e:
print(f"Fab: Error adding material to Slate Material Editor, falling back to compact mode")
print(traceback.format_exc())
# Fallback to compact material editor if SME fails
for (material, mod) in imported_materials:
if material:
rt.meditMaterials[rt.activeMeditSlot] = material
print(f"Fab: Material '{material.name}' added to active Material Editor slot")
except:
print("Fab: Error handling material-only payload")
print(traceback.format_exc())
# [NEW FIX END]