This section is mostly for TDs and Tech Animators.
This is not exactly and API, but there are ways to run BroDynamics simulations from your own scripts, if and when you need to automate the process.
BroDynamics Python API: Working Examples and Parameter Guide
This document shows some working examples for running BroDynamics modules directly from Python/Maya.
General usage pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | import BroTools
# Pick one of the modules to import
from BroTools.BroDynamics.modules import (
chain_pbd, # "Chain" (Primary, PBD engine)
chain, # "Chain (nHair)" (Legacy)
chain_simple, # "Chain (Spring Magic)" (Legacy)
point_chain, # "Chain (nParticle)" (Legacy)
point_nparticle, # "Spring (nParticle)" (Legacy)
point_pbd, # "Point" (Primary, PBD engine)
noise, # "Noise"
attribute_spring # "Attribute Spring"
)
# Create a simulator
sim = chain_pbd.SimulatorModule() # replace with the module you want to run
# Prepare object list (full path names recommended)
objects = ['pSphere1', 'pSphere2', 'pSphere3']
# Prepare kwargs and run (see per-mode examples below)
kwargs = {...}
sim.run(objects, **kwargs)
|
Info
- Always pass a Python list of transform node names as the first positional argument.
simulationProperties
is a dictionary with per-mode settings described below.
- Some flags belong in kwargs (top level), not inside
simulationProperties
.
- Set your Maya playback range before running. The tools simulate over the current time range.
Physics-based chain using the new PBD engine. Requires at least 2 objects.
Info
Curve parameters must be lists of CurvePoint
objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | from BroTools.BroDynamics.modules import chain_pbd
from BroTools.core.curves import CurvePoint
sim = chain_pbd.SimulatorModule()
objects = ['pSphere1', 'pSphere2', 'pSphere3']
def uniform_curve(v=1.0, interp=CurvePoint.LINEAR):
# 0..1 domain with constant value
return [
CurvePoint(0.0, v, interp),
CurvePoint(1.0, v, interp),
]
kwargs = {
'dontRefresh': False,
'cycleIterations': 0, # 0 = no cycling; N > 0 runs extra iterations for seamless loops
'attenuation': 1.0, # used internally to attenuate per-link params
'colliders': [], # list of mesh transform longNames; can be empty
'simulationProperties': {
# Basic properties (see config/chain_pbd_properties.json for full list)
'attractionStrength': 1.0,
'dampingMode': 0, # 0=Fixed, 1=Linear, 2=Quadratic
'linearDamping': 0.3,
'dragForce': 0.0,
'preserveTwist': 1.0,
'twistDrag': 0.0,
'aimDrag': 0.0,
'radius': 0.45,
'mass': 1.0,
'gravityStrength': 0.0,
'worldScale': 1.0,
'butterworthStrength': 0.5,
'eulerFilter': True,
'matchPositions': False,
'matchRotations': True,
'ignoreCollisionsBetweenParticles': True,
'fixedBase': True,
# Curves MUST be CurvePoint lists (examples below keep values uniform across chain)
'attractionStrengthCurve': uniform_curve(1.0),
'linearDampingCurve': uniform_curve(1.0),
'radiusCurve': uniform_curve(1.0),
'massCurve': uniform_curve(1.0),
'preserveTwistCurve': uniform_curve(1.0),
'twistDragCurve': uniform_curve(1.0),
'aimDragCurve': uniform_curve(1.0),
'dragForceCurve': uniform_curve(0.0),
}
}
sim.run(objects, **kwargs)
|
!!! info Testing tips:
- Select 3+ controls in a chain and use their names in objects
.
- Add collider meshes by long name to colliders
if needed.
- After run, keys will be set on the objects; optional Butterworth/Euler filters may be applied.
Legacy nHair-based chain. Requires at least 2 objects. IMPORTANT: some flags are top-level kwargs, not inside simulationProperties
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | from BroTools.BroDynamics.modules import chain
sim = chain.SimulatorModule()
objects = ['pSphere1', 'pSphere2', 'pSphere3']
kwargs = {
# Top-level flags (do NOT put these inside simulationProperties)
'dontRefresh': False,
'cycleIterations': 0,
'attenuation': 1.0, # reserved, not used by this mode
'colliders': [],
'autoShiftDistance': True, # or False + provide 'shiftDistance' below
# 'shiftDistance': 5.0,
'skipControls': 1,
'skipFrames': 1,
'aimRotation': True,
'matchPositions': False,
'preAlign': True,
'debugMode': False,
'up': [0,1,0],
'axis': [1,0,0],
'followBaseTwist': True,
'forces': [],
'collisionMode': False, # True enables nRigids/forces path
'eulerFilter': True,
'simulationProperties': {
# nHair/Nucleus related core props (required)
'nucleus_subSteps': 1,
'timeScale': 1.0,
# Hair/curve generation props used internally; safe to rely on defaults
# Add any extra supported attributes if your scene requires it
}
}
sim.run(objects, **kwargs)
|
Legacy spring-based chain. Works well for loops; accepts a single or chain of objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | from BroTools.BroDynamics.modules import chain_simple
sim = chain_simple.SimulatorModule()
objects = ['pSphere1', 'pSphere2', 'pSphere3']
kwargs = {
'dontRefresh': False,
'simulationProperties': {
'ratio': 0.7, # overall spring strength
'twistRatio': 0.7, # twist follow strength
'tension': 0.5, # similar to damping
'extend': 0.0, # positional stretch
'subDiv': 1.0, # sub-steps, use higher for fast motion
'isLoop': False # set True to help loop the result
}
}
sim.run(objects, **kwargs)
|
Mode: Chain (nParticle) — BroTools.BroDynamics.modules.point_chain
Legacy nParticle-driven chain with per-link springing and twist controls. Requires at least 2 objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | from BroTools.BroDynamics.modules import point_chain
sim = point_chain.SimulatorModule()
objects = ['pSphere1', 'pSphere2', 'pSphere3']
kwargs = {
'dontRefresh': False,
'cycleIterations': 0,
'attenuation': 1.0,
'simulationProperties': {
'timeScale': 1.0,
'twistMult': 1.0,
'twistRatio': 0.7,
'twistInertia': 0.5,
'nucleus_subSteps': 1
}
}
sim.run(objects, **kwargs)
|
Mode: Spring (nParticle) — BroTools.BroDynamics.modules.point_nparticle
Legacy nParticle spring applied per object (positions only). Works with 1+ objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | from BroTools.BroDynamics.modules import point_nparticle
sim = point_nparticle.SimulatorModule()
objects = ['pSphere1']
kwargs = {
'dontRefresh': False,
'cycleIterations': 0,
'simulationProperties': {
'nucleus_subSteps': 1,
'timeScale': 1.0,
# Forces; Y is converted to Z automatically if your scene up-axis is Z
'localForceY': 0.0,
# Optional: other nParticle properties added by createSpringParticle
# 'goalWeight[0]': 0.5,
# 'goalSmoothness': 1.0,
}
}
sim.run(objects, **kwargs)
|
Primary PBD-based point spring. Works with 1+ objects. Supports colliders and pinned objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | from BroTools.BroDynamics.modules import point_pbd
sim = point_pbd.SimulatorModule()
objects = ['pSphere1']
kwargs = {
'dontRefresh': False,
'cycleIterations': 0,
'colliders': [], # list of mesh transform longNames
'pinned': [], # list of transform longNames to keep pinned in place
'simulationProperties': {
'attractionStrength': 1.0,
'orientationAttractionStrength': 1.0,
'linearDamping': 0.3,
'angularDamping': 1.0,
'dampingMode': 0,
'radius': 0.45,
'mass': 1.0,
'dragForce': 0.1,
'worldScale': 1.0,
'connectNodes': False,
'connectDistance': 10.0,
'worldCompliance': 0.0001,
'gravityStrength': 0.0,
}
}
sim.run(objects, **kwargs)
|
Adds procedural Perlin noise to attributes. Can preserve existing animation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | from BroTools.BroDynamics.modules import noise
sim = noise.SimulatorModule()
objects = ['pSphere1']
kwargs = {
'dontRefresh': False,
'simulationProperties': {
'attributes': 'tx,ty,tz', # any numeric attrs; use Maya names or aliases
'seed': 0,
'amplitude': 1.0,
'frequency': 1.0,
'octaves': 1,
'interp': 1, # 0=None, 1=Linear, 2=Cosine, 3=Cubic
'use_fade': False,
'preserveAnimation': True # IMPORTANT: lives inside simulationProperties
}
}
sim.run(objects, **kwargs)
|
Applies 1D spring smoothing to any numeric attributes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | from BroTools.BroDynamics.modules import attribute_spring
sim = attribute_spring.SimulatorModule()
objects = ['pSphere1']
kwargs = {
'dontRefresh': False,
'simulationProperties': {
'attributes': 'tx,ty,tz', # comma-separated list
'spring': 1.0,
'damping': 0.1
}
}
sim.run(objects, **kwargs)
|
Testing checklist (Maya)
- Select or create the transforms named in
objects
.
- Ensure your playback range covers the frames you want simulated.
- Paste the example into Maya's Script Editor (Python) and run.
- Keys will be added to the target objects. For PBD modes, extra helper nodes will be created and cleaned up automatically.
- If using colliders, pass mesh transform long names in the
colliders
list.
- For
chain
(nHair) legacy mode, remember: autoShiftDistance
is a top-level kwarg (NOT inside simulationProperties
).
Troubleshooting
- If you see missing-parameter errors, verify required fields for that mode. For example,
chain
requires nucleus_subSteps
and timeScale
in simulationProperties
, and autoShiftDistance
as a top-level kwarg.
- If you get selection-related errors, pass full path node names (long names) and confirm nodes exist in the scene.
- For attribute-based modes, ensure attributes you target are settable.
- For PBD modes, try lower
linearDamping
for more motion or increase attractionStrength
to reduce lag.
Running Batch simulation from Python
There's currently no dedicated API exposed to run batch simulations. If possible, you should handle running multiple simulations with your own code. However if you need to run batch simulation that was previously set up through BroDynamics UI, there's a hacky way of doing it.
Here's how you can run Batch simulation from Python.
| import BroTools
BroTools.BroDynamics.BroDynamicsUI.initUI()
BroTools.BroDynamics.BroDynamicsUI.BroDynamicsWindow.BroBatchWindow.show()
BroTools.BroDynamics.BroDynamicsUI.BroDynamicsWindow.BroBatchWindow.refreshDropdowns()
BroTools.BroDynamics.BroDynamicsUI.BroDynamicsWindow.BroBatchWindow.startBatchSimulation()
|
This will load the first broDynamics_Data node in your scene and run a batch simulation for all objects which are setup in that node.
You can also change the node, by manually switching the id of the dropdown control, here's how you can access it:
| BroTools.BroDynamics.BroDynamicsUI.BroDynamicsWindow.BroBatchWindow.node_dropdown
|
Running in Maya Standalone (farm)
BroDynamics is an artist tool, designed primarily to be used by an animator during animation process. An animator might wish to tweak and adjust the simulation after baking it, or layer multiple simulations with different attributes to achieve the right motion.
However, there can be a number of situations where it would be beneficial to offload simulation to a farm or a cloud. For such cases BroDynamics can be used in Maya Standalone.
Below is a minimal example for running a BroDynamics job in Maya Standalone using mayapy
. It also shows how progress can be tracked through progress callbacks, and the optional usage of built-in simple CLI progress bar class.
A. Standalone script example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# BroTools Standalone Simulation Template
#
# Usage (Windows):
# "C:\\Program Files\\Autodesk\\Maya2026\\bin\\mayapy.exe" d:\\path\\to\\standalone_test_run.py
#
# Environment overrides (optional):
# BROTOOLS_ROOT, SCENE_IN, SCENE_OUT
#
# Parent directory for BroTools Installation folder.
# In this example BroTools is installed in D:\Nextcloud\Projects\Maya\BroTools\BroTools_Install\BroTools
# For example, install.mel is at D:\Nextcloud\Projects\Maya\BroTools\BroTools_Install\BroTools\install.mel
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import os
import sys
import traceback
BROTOOLS_ROOT = os.environ.get("BROTOOLS_ROOT", r"D:\Nextcloud\Projects\Maya\BroTools\BroTools_Install")
SCENE_IN = os.environ.get("SCENE_IN", r"D:\Nextcloud\Projects\Maya\BroTools\BroTools\standalone_test_scene_in.mb")
SCENE_OUT = os.environ.get("SCENE_OUT", r"D:\Nextcloud\Projects\Maya\BroTools\BroTools\standalone_test_scene_out.mb")
PACKAGE_PATHS = [["packages"], ["BroRig", "plugins"]]
if BROTOOLS_ROOT and BROTOOLS_ROOT not in sys.path:
sys.path.append(BROTOOLS_ROOT)
for pt in PACKAGE_PATHS:
path = os.path.join(BROTOOLS_ROOT, "BroTools", *pt)
if path not in sys.path:
sys.path.append(path)
import maya.standalone
maya.standalone.initialize(name="python")
from maya import cmds
plugin_fp = os.path.join(BROTOOLS_ROOT, "BroTools", "plug-ins", "BroTools.py")
if not cmds.pluginInfo(plugin_fp, query=True, loaded=True):
cmds.loadPlugin(plugin_fp)
from BroTools.BroDynamics.modules import chain_pbd
from BroTools.core.curves import CurvePoint
from BroTools.core.utils import CliProgressBar
def uniform_curve(v=1.0, interp=CurvePoint.LINEAR):
return [CurvePoint(0.0, v, interp), CurvePoint(1.0, v, interp)]
objects = ['pSphere1', 'pSphere2', 'pSphere3']
kwargs = {
'dontRefresh': True,
'cycleIterations': 0,
'attenuation': 1.0,
'colliders': [],
'simulationProperties': {
'attractionStrength': 1.0,
'dampingMode': 0,
'linearDamping': 0.3,
'dragForce': 0.0,
'preserveTwist': 1.0,
'twistDrag': 0.0,
'aimDrag': 0.0,
'radius': 0.45,
'mass': 1.0,
'gravityStrength': 0.0,
'worldScale': 1.0,
'butterworthStrength': 0.5,
'eulerFilter': True,
'matchPositions': False,
'matchRotations': True,
'ignoreCollisionsBetweenParticles': True,
'fixedBase': True,
'attractionStrengthCurve': uniform_curve(1.0),
'linearDampingCurve': uniform_curve(1.0),
'radiusCurve': uniform_curve(1.0),
'massCurve': uniform_curve(1.0),
'preserveTwistCurve': uniform_curve(1.0),
'twistDragCurve': uniform_curve(1.0),
'aimDragCurve': uniform_curve(1.0),
'dragForceCurve': uniform_curve(0.0),
}
}
exit_code = 0
# This is a built-in progress bar that ships with BroTools.
progress_bar = CliProgressBar()
# This callback is called by BroDynamics to report progress.
# You can replace this with your own progress reporting mechanism or skip this entirely.
def progress_callback(event, progress, message):
"""
SimulatorModule emits progress events through this callback.
There are three events: start, update and end.
`progress` is a float between 0 and 1.
`message` is a string describing the current operation.
"""
if event == "start":
progress_bar.start(100, message)
elif event == "update":
progress_bar.update(int(100 * progress), message)
elif event == "end":
progress_bar.end()
try:
cmds.file(SCENE_IN, open=True, force=True)
sim = chain_pbd.SimulatorModule()
sim.progress_callback = progress_callback # Optionally set the progress callback.
sim.run(objects, **kwargs)
if os.path.exists(SCENE_OUT):
os.remove(SCENE_OUT)
cmds.file(rename=SCENE_OUT)
cmds.file(save=True, type='mayaBinary')
print("BroDynamics simulation baked and scene saved:", SCENE_OUT)
except KeyboardInterrupt:
print("KeyboardInterrupt received, exiting...")
exit_code = 130
except Exception as e:
print("[ERROR]", e)
traceback.print_exc()
exit_code = 1
finally:
try:
maya.standalone.uninitialize()
except Exception:
pass
os._exit(exit_code)
|
Run it:
| "C:\Program Files\Autodesk\Maya2026\bin\mayapy.exe" D:\path\to\run_brodynamics.py
|
