Cross-DCC Rigging: Exporting 3ds Max Skin Weights to mGear (.jSkin) with Python
09 Sep 26 (1d ago)
Transferring character deformation data across different Digital Content Creation (DCC) tools is a common bottleneck in multi-software animation pipelines. If your character models are rigged or weight-painted in Autodesk 3ds Max, but your primary animation and rigging framework runs in Autodesk Maya with mGear, moving skin weights cleanly without loss of precision is critical.
mGear stores its skinning data in a standardized JSON structure with the .jSkin extension.
Using pymxs, you can automate the extraction of 3ds Max skinOps data, validate vertex integrity to prevent silent mismatches, and format the output so it imports directly into Maya via mGear's skinning tools.
Here is an analysis of how to map 3ds Max weights to mGear schema, common vertex count validation traps, and the complete export script.
The Cross-DCC Indexing Problem
When porting skin data between 3ds Max and Maya, you must account for fundamental architecture differences:
- 1-Based vs. 0-Based Indices:
- 3ds Max's
skinOpsAPI indexes vertices and bones starting at 1. - Maya and mGear's dictionary keys map vertices starting at 0.
- Every vertex loop in Max must offset index keys using
str(i - 1)to match Maya’s internal vertex table.
- 3ds Max's
- The Command Panel Dependency:
- Unlike Maya's independent node graph queries, 3ds Max’s
skinOpsfunctions (such asGetVertexWeightCountandGetVertexWeight) require the target object to be selected and the Modify Panel active with the Skin modifier focused. QueryingskinOpswithout entering the Modify panel returns empty arrays or throws a runtime exception.
- Unlike Maya's independent node graph queries, 3ds Max’s
3ds Max (skinOps) mGear JSON Format (.jSkin)
Vertex ID: 1 ───────── (i - 1) ──► "weights": {
Bone: "Spine_01" "Spine_01": {
Weight: 0.85 "0": 0.85
}
}
The Vertex Validation Gate
A common disaster when exporting weights occurs when downstream modifiers (like TurboSmooth, Edit Poly, or symmetry mirrors) sit on top of the modifier stack.
- If you query
rt.getNumVerts(obj.mesh), 3ds Max returns the vertex count of the final evaluated stack. - If a
TurboSmoothsits aboveSkin,actual_vertswill read 4x or 16x higher thanskin_verts, causing false mismatches. - Conversely, if an
Edit Polycollapsed or welded points below the Skin modifier, the Skin modifier will hold a corrupt internal table that crashes upon import into Maya.
The script guards against this by explicitly comparing the geometry's baseline vertex count against rt.skinOps.GetNumberVertices(skin_mod).
Complete Python Script (pymxs)
Run this script directly in the 3ds Max Python Script Editor. It loops through your specified mesh names, runs the validation checks, and writes out .jSkin files ready for Maya.
import json
import os
import pymxs
rt = pymxs.runtime
def export_mgear_jskin(obj_names, output_dir):
"""
Exports 3ds Max Skin modifier weights into Maya mGear .jSkin format.
:param obj_names: List of string names of objects in the scene.
:param output_dir: Destination folder path for .jSkin JSON files.
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for obj_name in obj_names:
obj = rt.getNodeByName(obj_name)
if not obj:
print(f"[Warning] Object '{obj_name}' not found. Skipping.")
continue
# Locate Skin Modifier
skin_mod = None
for mod in obj.modifiers:
if rt.classOf(mod) == rt.Skin:
skin_mod = mod
break
if not skin_mod:
print(f"[Warning] No Skin modifier found on '{obj_name}'. Skipping.")
continue
# Activate Modify Panel (Mandatory for skinOps evaluation)
rt.select(obj)
rt.setCommandPanelTaskMode(rt.Name("modify"))
rt.modPanel.setCurrentObject(skin_mod)
# --- VALIDATION GATE ---
# Query total vertices registered in the Skin modifier
skin_verts = rt.skinOps.GetNumberVertices(skin_mod)
# Query base mesh vertex count (evaluating base object before downstream tessellation)
base_obj = obj.baseObject
try:
actual_verts = rt.getNumVerts(base_obj.mesh)
except Exception:
actual_verts = rt.getNumVerts(obj)
print(f"\n--- Validating '{obj_name}' ---")
print(f"Base Geometry Vertices : {actual_verts}")
print(f"Skin Modifier Vertices : {skin_verts}")
if actual_verts != skin_verts:
print(f"[ERROR] Vertex count mismatch on '{obj_name}'!")
print(f"Skin table ({skin_verts}) does not match base geometry ({actual_verts}).")
print("Aborting export for this object to prevent corrupt weight data.\n")
continue
print(f"Validation passed. Serializing weights for mGear...")
weights_data = {}
# Loop through vertices (3ds Max uses 1-based indexing)
for i in range(1, skin_verts + 1):
num_bones = rt.skinOps.GetVertexWeightCount(skin_mod, i)
for j in range(1, num_bones + 1):
weight = rt.skinOps.GetVertexWeight(skin_mod, i, j)
if weight > 0.0:
bone_id = rt.skinOps.GetVertexWeightBoneID(skin_mod, i, j)
bone_name = rt.skinOps.GetBoneName(skin_mod, bone_id, 0)
if bone_name not in weights_data:
weights_data[bone_name] = {}
# Offset to 0-based index string for Maya/mGear compatibility
vert_idx_str = str(i - 1)
weights_data[bone_name][vert_idx_str] = round(float(weight), 6)
# Structure mGear jSkin schema
obj_dic = {
"blendWeights": {},
"nameSpace": "",
"normalizeWeights": 1,
"objName": obj_name,
"skinClsName": skin_mod.name,
"skinDataFormat": "compressed",
"skinningMethod": 0,
"vertexCount": skin_verts,
"weights": weights_data
}
mgear_json = {
"bypassObj": [],
"objDDic": [obj_dic],
"objs": [obj_name]
}
output_filepath = os.path.join(output_dir, f"{obj_name}.jSkin")
try:
with open(output_filepath, 'w', encoding='utf-8') as f:
json.dump(mgear_json, f, indent=4)
print(f"[SUCCESS] Exported '{obj_name}' -> {output_filepath}\n")
except IOError as err:
print(f"[Error] Failed to write file for '{obj_name}': {err}\n")
# --- CONFIGURATION & RUN ---
target_objects = ["body", "Male_Body"]
export_directory = r"C:\temp\mgear_skin_exports"
export_mgear_jskin(target_objects, export_directory)
Importing into Maya via mGear
Once the .jSkin files are saved:
- Open your Maya scene containing the target character geometry.
- In the top menu, go to mGear > Skin & Weights > Import Skin.
- Select the exported
.jSkinfile.
Because joint names and 0-based vertex indices match the Maya mesh topology, mGear binds the joints, builds the skinCluster, and populates the weight arrays without requiring manual bone re-mapping.