Recreating 3ds Max Rig Constraints in Blender with Python and JSON
14 Aug 26 (2mo ago)
There are times when I need to check how a rig behaves across different DCCs—in this case, taking a setup from 3ds Max over to Blender.
Rebuilding every single bone constraint by hand is a massive waste of time. My quick, practical workaround is to dump the constraint data out of 3ds Max into a lightweight JSON file, then run this Python script in Blender to rebuild the constraint stack on the target armature automatically.
Keep in mind this isn't an end-all rigging translation tool. It specifically handles standard PSR and aim setups—mapping 3ds Max Position, Orientation, Rotation, and LookAt constraints to Blender's COPY_LOCATION, COPY_ROTATION, and TRACK_TO. More complex setups like Path or Spline constraints are skipped and flagged in the console summary so you know exactly what needs manual attention.
The Script
import bpy
import json
import os
# --- Configuration ---
JSON_FILE_PATH = r"Z:\_vm_host\heart\constraints_only.json"
ARMATURE_NAME = "heart_rig"
# Mapping 3ds Max constraint classes to Blender constraint types
CONSTRAINT_MAPPING = {
"Position_Constraint": "COPY_LOCATION",
"Orientation_Constraint": "COPY_ROTATION",
"Rotation_Constraint": "COPY_ROTATION",
"LookAt_Constraint": "TRACK_TO",
}
def apply_bone_constraints():
if not os.path.exists(JSON_FILE_PATH):
print(f"Error: Could not find JSON file at '{JSON_FILE_PATH}'")
return
armature_obj = bpy.data.objects.get(ARMATURE_NAME)
if not armature_obj or armature_obj.type != 'ARMATURE':
print(f"Error: Armature '{ARMATURE_NAME}' not found or is not an armature.")
return
with open(JSON_FILE_PATH, 'r', encoding='utf-8') as f:
constraints_data = json.load(f)
print("-" * 40)
print(f"Starting Constraint Import for Armature: {ARMATURE_NAME}...")
# Ensure we're in object mode before manipulating pose bones
if bpy.context.object and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Track any unsupported or skipped constraints for the final summary
skipped_constraint_types = set()
for item in constraints_data:
bone_name = item.get("obj")
pbone = armature_obj.pose.bones.get(bone_name)
if not pbone:
# Uncomment if you want verbose logs on missing bones
# print(f"Warning: Bone '{bone_name}' not found. Skipping.")
continue
for c_data in item.get("constraints", []):
max_type = c_data.get("type", "").strip()
# Explicitly skip Path constraints
if "Path_Constraint" in max_type or "Path" in max_type:
skipped_constraint_types.add(max_type)
continue
blender_type = CONSTRAINT_MAPPING.get(max_type)
if not blender_type:
skipped_constraint_types.add(max_type)
continue
targets = c_data.get("target_weight", [])
if not targets:
continue
current_weight_sum = 0.0
for target_name, weight in targets:
target_pbone = armature_obj.pose.bones.get(target_name)
if not target_pbone:
print(f"Warning: Target bone '{target_name}' not found for '{bone_name}'.")
continue
b_constraint = pbone.constraints.new(type=blender_type)
b_constraint.name = max_type
b_constraint.target = armature_obj
b_constraint.subtarget = target_name
# Normalize weights sequentially across multiple targets
current_weight_sum += weight
if current_weight_sum > 0:
b_constraint.influence = weight / current_weight_sum
else:
b_constraint.influence = 0.0
# Default bone tracking axes for Blender
if blender_type == "TRACK_TO":
b_constraint.track_axis = 'TRACK_Y'
b_constraint.up_axis = 'UP_Z'
print("Constraint Import Complete.")
# --- Summary of Skipped Constraints ---
if skipped_constraint_types:
print("-" * 40)
print("SUMMARY OF SKIPPED / UNMAPPED CONSTRAINTS:")
for skipped_type in sorted(skipped_constraint_types):
print(f" - {skipped_type}")
print("-" * 40)
if __name__ == "__main__":
apply_bone_constraints()
How It Works
- Mapping Engine: A simple lookup dictionary maps 3ds Max constraint names directly to Blender's internal constraint enums (
COPY_LOCATION,COPY_ROTATION, andTRACK_TO). - Pose Bone Target Assignment: It targets the armature itself and assigns the relevant bone inside
subtarget, ensuring self-contained rig hierarchies stay clean. - Weight Normalization: For multi-target constraints, the running weight sum calculates relative influences so the blended results match how 3ds Max weights its list controllers.
- Skipped Asset Audit: Anything unmapped or explicitly bypassed (like spline path constraints) is collected into a
setand printed out at the end, letting you know exactly which bones require manual rigging.