
Blender
- 3 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Reference the Blender Python API (bpy, bmesh, mathutils, gpu) for scripting meshes, modifiers, geometry nodes, shaders, rendering, and add-on development.
About
A structured reference skill covering the Blender Python API including core bpy modules, mesh/modifier/geometry-nodes work, shading, simulation, headless CLI rendering, and add-on development. A developer loads it when scripting or automating Blender in Python.
- Covers bpy.data/context/ops/types/props core API
- Includes headless CLI rendering and Extensions add-on development
Blender by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,268 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill blenderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Reference the Blender Python API (bpy, bmesh, mathutils, gpu) for scripting meshes, modifiers, geometry nodes, shaders, rendering, and add-on development.
Files
ディレクトリ構成
skills/blender/
SKILL.md
references/
python-api-core/
README.md
bpy-data.md
bpy-context.md
bpy-ops.md
bpy-types.md
bpy-props.md
bpy-utils.md
bpy-app.md
bpy-info.md
python-api-modules/
README.md
bmesh.md
mathutils.md
gpu.md
bpy-extras.md
other-modules.md
modeling/
README.md
mesh-basics.md
modifiers.md
geometry-nodes.md
curves-surfaces.md
sculpting.md
scripting/
README.md
headless-cli.md
addon-development.md
text-editor.md
extending.md
animation-rigging/
README.md
keyframes.md
drivers.md
constraints.md
armatures.md
shape-keys.md
actions-nla.md
rendering-shading/
README.md
cycles.md
eevee.md
materials-nodes.md
lighting.md
render-output.md
freestyle.md
physics/
README.md
rigid-body.md
cloth.md
soft-body.md
particles.md
fluid.md
collision-forces.md
compositing/
README.md
compositor-nodes.md
render-passes.md
common-setups.md
scene-objects/
README.md
objects-transform.md
collections.md
empties-cameras-lights.md
data-system.md
world-scene.md
samples/
README.md
object-creation.md
bmesh-editing.md
modifier-workflow.md
material-shading.md
headless-render.md
scene-management.md
addon-template.md
animation-keyframes.md
rigging-armature.md
physics-simulation.md
rendering-setup.md
compositing-nodes.md
scripts/
README.md
cli-commands.md
addon-lifecycle.md
python-environment.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| bpy.data でデータブロックを操作したい | python-api-core | references/python-api-core/README.md |
| bpy.context でアクティブオブジェクト・選択状態を取得したい | python-api-core | references/python-api-core/README.md |
| bpy.ops でオペレーターを呼び出したい | python-api-core | references/python-api-core/README.md |
| bpy.types / bpy.props でカスタム型・プロパティを定義したい | python-api-core | references/python-api-core/README.md |
| bmesh で低レベルメッシュ編集をしたい | python-api-modules | references/python-api-modules/README.md |
| mathutils の Vector / Matrix / Quaternion を使いたい | python-api-modules | references/python-api-modules/README.md |
| GPU シェーダー・ビューポートオーバーレイを描画したい | python-api-modules | references/python-api-modules/README.md |
| bpy_extras の I/O ヘルパーを使いたい | python-api-modules | references/python-api-modules/README.md |
| メッシュ編集・モディファイアー操作をしたい | modeling | references/modeling/README.md |
| Geometry Nodes をスクリプトから操作したい | modeling | references/modeling/README.md |
| カーブ・サーフェス・メタボールを扱いたい | modeling | references/modeling/README.md |
| スカルプトモードの設定を Python から変更したい | modeling | references/modeling/README.md |
| headless CLI でバックグラウンドレンダリングしたい | scripting | references/scripting/README.md |
| add-on / Extension を開発したい | scripting | references/scripting/README.md |
| テキストエディター・Python コンソールを使いたい | scripting | references/scripting/README.md |
| 内蔵 Python に外部パッケージを追加したい | scripting | references/scripting/README.md |
| キーフレームを Python から挿入・削除・補間モードを変更したい | animation-rigging | references/animation-rigging/README.md |
| ドライバーで他プロパティや Python 式を使って値を制御したい | animation-rigging | references/animation-rigging/README.md |
| アーマチュア・ボーン・IK をスクリプトでセットアップしたい | animation-rigging | references/animation-rigging/README.md |
| Shape Key・NLA・Action をスクリプトから操作したい | animation-rigging | references/animation-rigging/README.md |
| Cycles / EEVEE のレンダー設定をスクリプトから変更したい | rendering-shading | references/rendering-shading/README.md |
| マテリアル・シェーダーノードを Python で構築したい | rendering-shading | references/rendering-shading/README.md |
| ライト・ワールド HDRI を設定したい | rendering-shading | references/rendering-shading/README.md |
| レンダー出力フォーマット・解像度・カラーマネジメントを制御したい | rendering-shading | references/rendering-shading/README.md |
| リジッドボディ・布・ソフトボディのシミュレーションを設定したい | physics | references/physics/README.md |
| パーティクル・流体 (Mantaflow) をスクリプトから操作したい | physics | references/physics/README.md |
| フォースフィールド・コリジョンを Python で制御したい | physics | references/physics/README.md |
| コンポジターノードツリーを Python で構築したい | compositing | references/compositing/README.md |
| レンダーパス・AOV・Cryptomatte を設定したい | compositing | references/compositing/README.md |
| DOF・グレア・カラーグレーディングの典型ノード構成を知りたい | compositing | references/compositing/README.md |
| オブジェクトの位置・回転・スケール・行列を操作したい | scene-objects | references/scene-objects/README.md |
| コレクション階層・ビューレイヤー表示を Python で管理したい | scene-objects | references/scene-objects/README.md |
| カメラ・ライト・エンプティをスクリプトから作成・設定したい | scene-objects | references/scene-objects/README.md |
| データブロックのリンク・追記・孤立データ削除をしたい | scene-objects | references/scene-objects/README.md |
| 典型的な使い方を知りたい | samples | samples/README.md |
| インストール・CLI コマンドを知りたい | scripts | scripts/README.md |
Actions & NLA
An Action is a named data-block that stores F-Curves (keyframe animation) for one object or one armature pose. The NLA Editor (Non-Linear Animation) layers and blends multiple Actions as reusable strips.
Overview
Object.animation_data
├─ action ← currently active Action (editable in Graph/Dope Sheet)
└─ nla_tracks[]
└─ strips[] ← each strip references an Action and a frame rangeAn Action becomes a reusable clip once pushed down to the NLA stack.
Key Operations
Create and assign an Action manually
import bpy
obj = bpy.context.object
anim = obj.animation_data_create()
action = bpy.data.actions.new(name="Walk")
anim.action = action
# Add keyframes to the active action as usual
obj.location = (0, 0, 0)
obj.keyframe_insert("location", frame=1)
obj.location = (5, 0, 0)
obj.keyframe_insert("location", frame=24)Push action down to NLA (operator)
# Object must be active and have an action assigned
bpy.context.view_layer.objects.active = obj
bpy.ops.nla.action_pushdown(track_index=0)After push-down, obj.animation_data.action becomes None and a new NLA strip is created.
Add an NLA strip via data API
anim = obj.animation_data_create()
track = anim.nla_tracks.new()
track.name = "Walk Track"
walk_action = bpy.data.actions["Walk"]
strip = track.strips.new(name="Walk", start=1, action=walk_action)
strip.action_frame_start = 1
strip.action_frame_end = 24
strip.frame_start = 1
strip.frame_end = 24
strip.blend_type = 'REPLACE' # or 'ADD', 'COMBINE', 'SUBTRACT', 'MULTIPLY'
strip.influence = 1.0
strip.extrapolation = 'HOLD' # 'NOTHING' | 'HOLD' | 'HOLD_FORWARD'Blend two actions additively
base_track = anim.nla_tracks.new()
base_strip = base_track.strips.new("Idle", 1, bpy.data.actions["Idle"])
base_strip.blend_type = 'REPLACE'
add_track = anim.nla_tracks.new()
add_strip = add_track.strips.new("Breathe", 1, bpy.data.actions["Breathe"])
add_strip.blend_type = 'ADD'
add_strip.influence = 0.5Iterate all actions in the file
for action in bpy.data.actions:
print(action.name, len(action.fcurves))Remove an NLA strip
track = anim.nla_tracks["Walk Track"]
strip = track.strips["Walk"]
track.strips.remove(strip)Options / Props
| Property | Type | Description |
|---|---|---|
AnimData.action | Action | Currently active action (editable on Graph/Dope Sheet) |
AnimData.nla_tracks | collection | All NLA tracks for this object |
NlaTrack.name | str | Track display name |
NlaTrack.mute | bool | Disable the track |
NlaStrip.action | Action | Action referenced by this strip |
NlaStrip.frame_start / .frame_end | float | Strip timeline position |
NlaStrip.action_frame_start / .action_frame_end | float | Portion of action to play |
NlaStrip.blend_type | enum | REPLACE, ADD, COMBINE, SUBTRACT, MULTIPLY |
NlaStrip.influence | float | 0.0–1.0 mix weight |
NlaStrip.extrapolation | enum | NOTHING, HOLD, HOLD_FORWARD |
NlaStrip.blend_in / .blend_out | float | Frames to fade in/out |
NlaStrip.scale | float | Time-scale the action playback |
NlaStrip.repeat | float | Loop count (1.0 = play once) |
Notes
- While
AnimData.actionis set (i.e., the active action is not pushed down), the NLA tracks are in a "tweaking" state and NLA evaluation may be bypassed for that object. bpy.ops.nla.action_pushdown()requires an active object with a non-Noneactionand an NLA Editor area in the UI context; prefer the data API (track.strips.new()) in headless scripts.- Actions are shared data-blocks: multiple objects can reference the same Action. Use
action.copy()before modifying if independent copies are needed. COMBINEblend type handles additive vs. multiplicative blending automatically per channel type (e.g., quaternion rotations blend with quaternion math).
Related
- keyframes.md
- drivers.md
- shape-keys.md
Armatures
An Armature is a skeleton-like object consisting of Bones. It deforms meshes (via the Armature Modifier) and stores pose animation data. Blender uses three distinct bone data structures depending on the active mode.
Overview
| Bone type | Access path | Mode required | Purpose |
|---|---|---|---|
EditBone | obj.data.edit_bones | Edit Mode | Create / reshape bones |
Bone | obj.data.bones | Object / Pose Mode | Read-only shape; deform flags |
PoseBone | obj.pose.bones | Pose Mode | Animation, constraints, IK |
Critical: Do not hold references to EditBone objects after leaving Edit Mode — this crashes Blender.
Key Operations
Create an armature and add bones
import bpy
from mathutils import Vector
# Create armature data-block and object
arm_data = bpy.data.armatures.new("MyArmature")
arm_obj = bpy.data.objects.new("MyArmature", arm_data)
bpy.context.collection.objects.link(arm_obj)
# Activate and enter Edit Mode
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')
# Add bones
root = arm_obj.data.edit_bones.new("Root")
root.head = Vector((0, 0, 0))
root.tail = Vector((0, 0, 1))
child = arm_obj.data.edit_bones.new("Child")
child.head = Vector((0, 0, 1))
child.tail = Vector((0, 0, 2))
child.parent = root
child.use_connect = True # connect child head to parent tail
bpy.ops.object.mode_set(mode='OBJECT')
# -- Do NOT use root/child references after this line --Access pose bones and transform
bpy.ops.object.mode_set(mode='POSE')
pb = arm_obj.pose.bones["Child"]
pb.location = (0.1, 0, 0)
pb.rotation_mode = 'XYZ'
pb.rotation_euler = (0.2, 0, 0)Add an IK constraint via Python
import math
bpy.ops.object.mode_set(mode='POSE')
hand_pb = arm_obj.pose.bones["Hand"]
ik = hand_pb.constraints.new(type='IK')
ik.target = arm_obj
ik.subtarget = "IK_Target"
ik.chain_count = 3
ik.pole_target = arm_obj
ik.pole_subtarget = "Pole"
ik.pole_angle = math.radians(90)Parent a mesh to the armature with automatic weights
mesh_obj = bpy.data.objects["Body"]
mesh_obj.parent = arm_obj
mod = mesh_obj.modifiers.new(name="Armature", type='ARMATURE')
mod.object = arm_obj
mod.use_vertex_groups = True
# Then use Weight Paint mode or bpy.ops.object.parent_set(type='ARMATURE_AUTO')Inspect bone names and hierarchy
for bone in arm_obj.data.bones:
parent_name = bone.parent.name if bone.parent else "None"
print(f"{bone.name} -> parent: {parent_name}, deform: {bone.use_deform}")Options / Props
| Property | Type | Description |
|---|---|---|
EditBone.head / .tail | Vector | Start/end positions in armature space |
EditBone.roll | float | Roll angle (radians) around the bone Y axis |
EditBone.parent | EditBone | Parent bone reference |
EditBone.use_connect | bool | Connect head to parent tail |
Bone.use_deform | bool | Include in deformation calculations |
PoseBone.location | Vector | Local pose-space location offset |
PoseBone.rotation_euler | Euler | Local rotation (when rotation_mode='XYZ') |
PoseBone.constraints | collection | Bone-level constraints |
Armature.display_type | enum | Viewport display: OCTAHEDRAL, STICK, BBONE, ENVELOPE |
Notes
- Mode switching is required: edit bones are only available in Edit Mode; pose data is only actionable in Pose Mode.
- Always call
bpy.ops.object.mode_set(mode='OBJECT')to finalize edits before accessingobj.data.bonesorobj.pose.bones. - The Armature Modifier on a mesh uses vertex groups whose names match bone names to drive deformation weights.
- For programmatic weight painting, set vertex group weights via
mesh_obj.vertex_groups["BoneName"].add([vert_index], weight, 'REPLACE'). - B-Bones (Bendy Bones) add curvature along a single bone; controlled via
EditBone.bbone_segments,.bbone_x,.bbone_z.
Related
- constraints.md
- shape-keys.md
- keyframes.md
Constraints
Constraints restrict or direct an object's (or bone's) transformations based on rules or targets. They are evaluated every frame and do not bake data unless explicitly applied.
Overview
Constraints live in object.constraints (object-level) or pose_bone.constraints (bone-level). Both collections expose the same .new() / .remove() interface.
Key Operations
Add a constraint
import bpy
obj = bpy.data.objects["Cube"]
# Copy the location of a target object
c = obj.constraints.new(type='COPY_LOCATION')
c.target = bpy.data.objects["Empty"]
c.use_offset = True
c.influence = 1.0Add a constraint to a pose bone
arm_obj = bpy.data.objects["Armature"]
pb = arm_obj.pose.bones["Hand"]
ik = pb.constraints.new(type='IK')
ik.target = arm_obj
ik.subtarget = "IK_Target" # name of a bone in the same armature
ik.chain_count = 3 # 0 = full chain to root
ik.pole_target = arm_obj
ik.pole_subtarget = "Pole"
ik.pole_angle = 1.5708 # radians (90°)Remove a constraint
c = obj.constraints["Copy Location"]
obj.constraints.remove(c)Iterate constraints
for c in obj.constraints:
print(c.name, c.type, c.influence)Common Constraint Types
| Type enum | Description |
|---|---|
COPY_LOCATION | Match the target's world/local location |
COPY_ROTATION | Match the target's rotation |
COPY_SCALE | Match the target's scale |
COPY_TRANSFORMS | Copy full transform (loc+rot+scale) |
LIMIT_LOCATION | Clamp location to min/max bounds |
LIMIT_ROTATION | Clamp rotation to min/max angles |
LIMIT_SCALE | Clamp scale to min/max values |
TRACK_TO | Point an axis toward the target |
DAMPED_TRACK | Minimize-rotation tracking toward target |
CHILD_OF | Make the object behave as if parented (detachable) |
FLOOR | Prevent penetration below a floor plane |
FOLLOW_PATH | Move along a curve object |
IK | Inverse Kinematics chain solver (pose bones only) |
STRETCH_TO | Stretch and squash toward a target |
ACTION | Map a property range to an Action |
Options / Props (common to all constraints)
| Property | Type | Description |
|---|---|---|
Constraint.name | str | Display name (editable) |
Constraint.type | str | Constraint type enum (read-only after creation) |
Constraint.influence | float | 0.0–1.0 blend weight |
Constraint.mute | bool | Disable without removing |
Constraint.target | Object | Primary target object |
Constraint.subtarget | str | Bone name when target is an armature |
Constraint.owner_space | enum | WORLD, LOCAL, LOCAL_WITH_PARENT, POSE |
Constraint.target_space | enum | Space of the target evaluation |
Notes
- Constraints are evaluated in list order (top to bottom); use
obj.constraints.move(from_index, to_index)to reorder. - The IK constraint requires Pose Mode and only works on pose bones, not regular objects.
CHILD_OFhas aset_inverse_pendingproperty; callbpy.ops.constraint.childof_set_inverse(...)after setting the target to compute the offset correctly.influenceis animatable — keyframe it to blend a constraint in/out over time.
Related
- armatures.md
- keyframes.md
- drivers.md
Drivers
A Driver is a special F-Curve that computes its value from a Python expression or from another property, rather than from manually placed keyframe points. Drivers enable procedural relationships between object properties.
Overview
Drivers are stored as F-Curves in object.animation_data.drivers (not in an Action). Each driver has a driver sub-object (Driver) that holds the expression and variables.
Key Operations
Add a driver with a simple expression
import bpy
obj = bpy.context.object
# Drive Z location with a sine wave
fc = obj.driver_add("location", 2) # index 2 = Z axis
fc.driver.type = 'SCRIPTED'
fc.driver.expression = "sin(frame / 10)"driver_add(data_path, index=-1) returns an FCurve whose .driver is the Driver object.
Add a variable referencing another property
fc = obj.driver_add("location", 2)
drv = fc.driver
drv.type = 'SCRIPTED'
var = drv.variables.new()
var.name = "my_x"
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = bpy.data.objects["ControlObject"]
var.targets[0].data_path = "location[0]"
drv.expression = "my_x * 2"Use TRANSFORMS variable type
var = drv.variables.new()
var.name = "rot"
var.type = 'TRANSFORMS'
tgt = var.targets[0]
tgt.id = bpy.data.objects["Ctrl"]
tgt.transform_type = 'ROT_Z' # LOC_X/Y/Z, ROT_X/Y/Z, SCALE_X/Y/Z
tgt.transform_space = 'LOCAL_SPACE' # WORLD_SPACE | LOCAL_SPACEExtend the driver namespace with a custom function
def smooth_step(x):
return x * x * (3 - 2 * x)
bpy.app.driver_namespace["smooth_step"] = smooth_step
# Expression can now use: smooth_step(var)Register the namespace extension via a load_post handler so it persists across file loads.
Remove a driver
obj.driver_remove("location", 2)Iterate existing drivers
for fc in obj.animation_data.drivers:
print(fc.data_path, fc.array_index, fc.driver.expression)Options / Props
| Property | Type | Description |
|---|---|---|
driver_add(data_path, index) | method | Add driver; returns FCurve |
driver_remove(data_path, index) | method | Remove driver |
Driver.type | enum | AVERAGE, SUM, MIN, MAX, SCRIPTED |
Driver.expression | str | Python expression (SCRIPTED type) |
Driver.use_self | bool | Exposes self (the driven object) inside the expression |
DriverVariable.name | str | Variable name used in expression |
DriverVariable.type | enum | SINGLE_PROP, TRANSFORMS, ROTATION_DIFF, LOC_DIFF |
DriverTarget.id_type | enum | OBJECT, SCENE, MESH, ARMATURE, … |
DriverTarget.id | ID | The referenced data-block |
DriverTarget.data_path | str | RNA path within that data-block |
bpy.app.driver_namespace | dict | Global namespace for custom driver functions |
Notes
- Security: By default, Blender blocks Python driver execution. Enable Edit → Preferences → Save & Load → Auto Run Python Scripts (or run Blender with
--enable-autoexec) for scripted expressions to evaluate. - Simple math expressions (no function calls) are optimized and run without the Python interpreter overhead.
- Drivers are re-evaluated every frame; avoid expensive computations in expressions.
use_self(drv.use_self = True) makesselfavailable in the expression, referencing the object that owns the driver.
Related
- keyframes.md
- actions-nla.md
- shape-keys.md
Keyframes
A keyframe stores the value of a property at a specific point in time. Blender interpolates between keyframes on an F-Curve to produce smooth animation.
Overview
Animation data is organized in a hierarchy:
Object.animation_data → Action → FCurves → Keyframe pointsEach F-Curve (FCurve) represents one channel of one property (e.g., location[0] = X position).
Key Operations
Insert a keyframe
import bpy
obj = bpy.context.object
bpy.context.scene.frame_set(1)
obj.location = (0, 0, 0)
obj.keyframe_insert(data_path="location", frame=1)
obj.location = (2, 0, 0)
obj.keyframe_insert(data_path="location", frame=24)data_path accepts any animatable property path (e.g., "rotation_euler", "scale", "["my_prop"]"). The optional index parameter targets a specific axis (0=X, 1=Y, 2=Z); omit it to key all axes.
Delete a keyframe
obj.keyframe_delete(data_path="location", frame=24)Low-level F-Curve creation
anim = obj.animation_data_create()
action = bpy.data.actions.new(name="MyAction")
anim.action = action
fc = action.fcurves.new(data_path="location", index=0) # X axis
fc.keyframe_points.insert(frame=1, value=0.0)
fc.keyframe_points.insert(frame=24, value=2.0)
fc.update()Access existing F-Curves
action = obj.animation_data.action
for fc in action.fcurves:
print(fc.data_path, fc.array_index)
for kp in fc.keyframe_points:
print(f" frame={kp.co[0]:.1f} value={kp.co[1]:.4f} interp={kp.interpolation}")Set interpolation mode
for fc in obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'BEZIER' # 'CONSTANT' | 'LINEAR' | 'BEZIER'
kp.easing = 'AUTO' # 'AUTO' | 'EASE_IN' | 'EASE_OUT' | 'EASE_IN_OUT'Evaluate an F-Curve at a frame
fc = obj.animation_data.action.fcurves.find("location", index=0)
value_at_frame_10 = fc.evaluate(10)Options / Props
| Property | Type | Description |
|---|---|---|
keyframe_insert(data_path, frame, index, group) | method | Insert keyframe on object property |
keyframe_delete(data_path, frame, index) | method | Remove keyframe |
FCurve.data_path | str | RNA path of driven property |
FCurve.array_index | int | Axis index (0/1/2 for X/Y/Z) |
Keyframe.co | Vector2 | (frame, value) coordinate |
Keyframe.interpolation | enum | CONSTANT, LINEAR, BEZIER, SINE, QUAD, … |
Keyframe.easing | enum | AUTO, EASE_IN, EASE_OUT, EASE_IN_OUT |
FCurve.extrapolation | enum | CONSTANT (hold) or LINEAR (extend slope) |
Notes
- Always call
fc.update()after inserting points manually to rebuild the curve handles. keyframe_insert()reads the property's current value at the current frame unlessframeis given explicitly.- The Graph Editor and Dope Sheet are the UI counterparts; they visualize the same
Action/FCurvedata. - Easing types (SINE, QUAD, CUBIC, BACK, BOUNCE, ELASTIC, …) apply additional easing to BEZIER interpolation.
Related
- actions-nla.md
- drivers.md
- shape-keys.md
Animation & Rigging — Index
| Name | Description | Path |
|---|---|---|
| Keyframes | Keyframe insertion, deletion, interpolation modes, F-Curve access | keyframes.md |
| Drivers | Drive property values with Python expressions or other properties | drivers.md |
| Constraints | Object and bone constraints (Copy Location, IK, Track To, Child Of, etc.) | constraints.md |
| Armatures | Armature/bone creation, Pose Mode, weight painting, IK setup | armatures.md |
| Shape Keys | Mesh deformation via morph targets, value sliders, driver integration | shape-keys.md |
| Actions & NLA | Action data-blocks, NLA tracks/strips, push-down, clip reuse | actions-nla.md |
Shape Keys
Shape Keys (also called morph targets or blend shapes) store per-vertex position offsets relative to a Basis shape. They are commonly used for facial animation and corrective shapes.
Overview
Shape key data lives on obj.data.shape_keys (a Key data-block). Each individual shape is a ShapeKey accessible via key_blocks.
Object.data.shape_keys (Key)
└─ key_blocks[0] ← "Basis" (reference shape)
└─ key_blocks[1] ← "Smile" (relative shape)
└─ key_blocks[2] ← "Angry"Key Operations
Add shape keys
import bpy
obj = bpy.data.objects["Head"]
bpy.context.view_layer.objects.active = obj
# First call creates the Basis key
bpy.ops.object.shape_key_add(from_mix=False)
# Subsequent calls add relative keys
bpy.ops.object.shape_key_add(from_mix=False)
obj.data.shape_keys.key_blocks[-1].name = "Smile"Alternatively, use the data-level method (no operator context required):
basis = obj.shape_key_add(name="Basis")
smile = obj.shape_key_add(name="Smile", from_mix=False)Set vertex offsets on a shape key
import bmesh
obj.active_shape_key_index = 1 # select "Smile"
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
# move a vertex
bm.verts[4].co.x += 0.5
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')Set value (influence slider)
sk = obj.data.shape_keys.key_blocks["Smile"]
sk.value = 0.75 # 0.0 = no influence, 1.0 = full influenceAnimate value with keyframes
sk = obj.data.shape_keys.key_blocks["Smile"]
sk.value = 0.0
sk.keyframe_insert(data_path="value", frame=1)
sk.value = 1.0
sk.keyframe_insert(data_path="value", frame=20)Assign a vertex group (masked blend)
sk = obj.data.shape_keys.key_blocks["Smile"]
sk.vertex_group = "LeftFace" # only vertices in this group are affectedDrive a shape key from a bone rotation
sk = obj.data.shape_keys.key_blocks["Smile"]
fc = sk.driver_add("value")
drv = fc.driver
drv.type = 'SCRIPTED'
var = drv.variables.new()
var.name = "rot"
var.type = 'TRANSFORMS'
var.targets[0].id = bpy.data.objects["Armature"]
var.targets[0].bone_target = "Jaw"
var.targets[0].transform_type = 'ROT_X'
var.targets[0].transform_space = 'LOCAL_SPACE'
drv.expression = "rot / 0.5"Options / Props
| Property | Type | Description |
|---|---|---|
ShapeKey.name | str | Display name |
ShapeKey.value | float | Current influence (0.0–1.0) |
ShapeKey.relative_key | ShapeKey | The reference key for relative mode |
ShapeKey.vertex_group | str | Vertex group name to mask influence |
ShapeKey.mute | bool | Disable the shape key |
ShapeKey.slider_min / .slider_max | float | UI slider range |
Key.use_relative | bool | True = relative mode (default); False = absolute |
Key.reference_key | ShapeKey | The Basis key in relative mode |
shape_key_add(name, from_mix) | method | from_mix=True captures current mixed state |
Notes
- The Basis key is always
key_blocks[0]. Itsvalueis always 1.0 and cannot be changed. - Modifying the Basis key vertex positions in Object Mode (via
basis.data[i].co) can desync from the mesh; prefer Edit Mode edits. - Shape keys only exist on mesh, lattice, curve, and surface objects.
- To remove a shape key:
obj.active_shape_key_index = idx; bpy.ops.object.shape_key_remove(). - Shape key animation integrates with the NLA via the Action stored on
obj.data.shape_keys.animation_data.
Related
- keyframes.md
- drivers.md
- armatures.md
Common Compositing Setups
Practical node graph patterns for the most frequent compositing tasks in Blender. Each section includes an equivalent Python construction.
Overview
All setups use the canonical helpers defined in compositor-nodes.md. Copy both functions (get_compositor_tree and add_output_node) into your script before using any setup below.
import bpy
scene = bpy.context.scene
tree, links = get_compositor_tree(scene)
nodes = tree.nodes
nodes.clear() # remove any existing nodes; rebuild explicitlyAfter nodes.clear(), always create Render Layers and an output node explicitly — no default nodes remain.
---
Z-Depth Depth of Field (Defocus)
Uses the Z render pass to drive the Defocus node for a post-process DOF effect.
Required passes: use_pass_z = True
vl = scene.view_layers["ViewLayer"]
vl.use_pass_z = True
rl = nodes.new("CompositorNodeRLayers"); rl.location = (0, 0); rl.scene = scene
defoc = nodes.new("CompositorNodeDefocus"); defoc.location = (300, 0)
defoc.use_zbuffer = True
defoc.f_stop = 5.6
defoc.bokeh = 'HEXAGON'
defoc.max_blur = 24.0
links.new(rl.outputs["Image"], defoc.inputs["Image"])
links.new(rl.outputs["Depth"], defoc.inputs["Z"])
add_output_node(tree, defoc.outputs["Image"], location=(600, 0))Notes: The Defocus node reads the camera's focal distance from the active scene camera. Set camera.data.dof.focus_distance to control focus depth.
---
Glare / Bloom
Adds a glow or bloom around bright regions (useful for lights, emissive surfaces).
rl = nodes.new("CompositorNodeRLayers"); rl.location = (0, 0); rl.scene = scene
glare = nodes.new("CompositorNodeGlare"); glare.location = (300, 0)
glare.glare_type = 'FOG_GLOW' # soft bloom; use 'STREAKS' for lens streaks
glare.quality = 'HIGH'
glare.threshold = 0.8
glare.size = 8 # glow radius exponent (2^size pixels)
glare.mix = 0.0 # 0 = original + glow; 1 = glow only
links.new(rl.outputs["Image"], glare.inputs["Image"])
add_output_node(tree, glare.outputs["Image"], location=(600, 0))---
Render Layer Compositing (Alpha Over)
Composites multiple View Layers or objects on separate layers into a single output.
rl_bg = nodes.new("CompositorNodeRLayers"); rl_bg.location = (0, 100); rl_bg.scene = scene
rl_fg = nodes.new("CompositorNodeRLayers"); rl_fg.location = (0, -100); rl_fg.scene = scene
rl_fg.layer = "ForegroundLayer" # name of the second View Layer
alpha_over = nodes.new("CompositorNodeAlphaOver"); alpha_over.location = (350, 0)
alpha_over.premul = 0.0 # straight alpha (set >0 for premultiplied)
# Background → input 1; Foreground → input 2
links.new(rl_bg.outputs["Image"], alpha_over.inputs[1])
links.new(rl_fg.outputs["Image"], alpha_over.inputs[2])
add_output_node(tree, alpha_over.outputs["Image"], location=(600, 0))---
Color Grading (Basic Flow)
A simple lift/gamma/gain grade followed by an RGB curves tweak.
rl = nodes.new("CompositorNodeRLayers"); rl.location = (0, 0); rl.scene = scene
balance = nodes.new("CompositorNodeColorBalance"); balance.location = (250, 0)
curves = nodes.new("CompositorNodeCurveRGB"); curves.location = (500, 0)
# Lift/Gamma/Gain mode (default)
balance.correction_method = 'LIFT_GAMMA_GAIN'
# Adjust via balance.lift / balance.gamma / balance.gain (Color vectors)
links.new(rl.outputs["Image"], balance.inputs["Image"])
links.new(balance.outputs["Image"], curves.inputs["Image"])
add_output_node(tree, curves.outputs["Image"], location=(750, 0))---
File Output Node (Multi-Pass Export)
Writes multiple passes to separate image files (e.g., EXR layers) for external compositing.
vl = scene.view_layers["ViewLayer"]
vl.use_pass_diffuse_direct = True
vl.use_pass_glossy_direct = True
vl.use_pass_z = True
rl = nodes.new("CompositorNodeRLayers"); rl.location = (0, 0); rl.scene = scene
fileout = nodes.new("CompositorNodeOutputFile"); fileout.location = (400, 0)
fileout.base_path = "/tmp/render/"
fileout.format.file_format = 'OPEN_EXR_MULTILAYER'
# Add file slots for each pass (Blender ≤ 4.4 API)
fileout.file_slots.new("Beauty")
fileout.file_slots.new("DiffDir")
fileout.file_slots.new("GlossDir")
fileout.file_slots.new("Depth")
links.new(rl.outputs["Image"], fileout.inputs["Beauty"])
links.new(rl.outputs["DiffDir"], fileout.inputs["DiffDir"])
links.new(rl.outputs["GlossDir"],fileout.inputs["GlossDir"])
links.new(rl.outputs["Depth"], fileout.inputs["Depth"])---
Denoise Setup
Denoises a Cycles render using albedo and normal auxiliary passes for best quality.
Required passes: Denoising Data (use_pass_denoising_data = True)
vl = scene.view_layers["ViewLayer"]
vl.use_pass_denoising_data = True
rl = nodes.new("CompositorNodeRLayers"); rl.location = (0, 0); rl.scene = scene
denoise = nodes.new("CompositorNodeDenoise"); denoise.location = (300, 0)
denoise.prefilter = 'ACCURATE'
denoise.quality = 'HIGH'
denoise.use_hdr = True
links.new(rl.outputs["Noisy Image"], denoise.inputs["Image"])
links.new(rl.outputs["Denoising Albedo"], denoise.inputs["Albedo"])
links.new(rl.outputs["Denoising Normal"], denoise.inputs["Normal"])
add_output_node(tree, denoise.outputs["Image"], location=(600, 0))Notes
- Blender 5.0+:
scene.use_nodes/scene.node_treeare removed.CompositorNodeCompositeis also removed. All version branching is handled byget_compositor_tree()andadd_output_node()— do not addif hasattr(scene, 'compositing_node_group')in individual setup scripts. - Blender ≤ 4.4:
CompositorNodeCompositeis the required final output node; without it, compositing results are not applied to the render. - File Output — Blender 5.0+:
file_slots,layer_slots, andbase_pathwere removed. Usedirectory,file_name, andfile_output_itemsinstead. The exact Python API forfile_output_items(e.g.,.new()signature and socket indexing) differs from thefile_slotspattern shown above; consult the Blender 5.0 release notes for the migration guide. - When building trees from Python, call
nodes.clear()and add all required nodes explicitly to avoid stale links or duplicate nodes. - Access Render Layers pass sockets by name (
rl.outputs["Depth"]), not by index — enabled passes shift socket indices. - Alpha Over's two image inputs are accessed as
inputs[1](Background) andinputs[2](Foreground) in Python. - The
CompositorNodeOutputFilerequires a validbase_path(Blender ≤ 4.4) ordirectory(Blender 5.0+); Blender will not create missing intermediate directories automatically.
Related
- compositor-nodes.md
- render-passes.md
Compositor Nodes
The Blender Compositor is a node-based post-processing system that operates on rendered images, passes, and external images to produce final output.
Overview
The compositor API changed between Blender 4.x and 5.0.
Blender ≤ 4.4: Enable with scene.use_nodes = True; access the tree via scene.node_tree.
Blender 5.0+: scene.use_nodes and the "Use Nodes" checkbox were removed. Create a CompositorNodeTree node group and assign it to scene.compositing_node_group instead.
For cross-version scripts, use these two canonical helpers. All code examples in this skill call these helpers rather than duplicating the branching logic.
import bpy
def get_compositor_tree(scene):
"""Return (tree, links) for the compositor. Idempotent across Blender 4.x / 5.0+."""
if hasattr(scene, 'compositing_node_group'):
# Blender 5.0+: reuse existing group to avoid orphaning datablocks
tree = scene.compositing_node_group
if tree is None:
tree = bpy.data.node_groups.new("Compositor", "CompositorNodeTree")
scene.compositing_node_group = tree
else:
# Blender ≤ 4.4
scene.use_nodes = True
tree = scene.node_tree
return tree, tree.links
def add_output_node(tree, source_socket, location=(700, 0)):
"""Create the final output node (version-aware) and link source_socket to it."""
scene = bpy.context.scene
if hasattr(scene, 'compositing_node_group'):
# Blender 5.0+: NodeGroupOutput; ensure a Color OUTPUT socket exists on the interface
out = tree.nodes.new('NodeGroupOutput')
if not any(
getattr(s, 'in_out', None) == 'OUTPUT'
for s in tree.interface.items_tree
):
tree.interface.new_socket(
name="Image", socket_type='NodeSocketColor', in_out='OUTPUT'
)
tree.links.new(source_socket, out.inputs[0])
else:
# Blender ≤ 4.4: CompositorNodeComposite
out = tree.nodes.new('CompositorNodeComposite')
tree.links.new(source_socket, out.inputs["Image"])
out.location = location
return outKey Nodes
Input Nodes
| Node | Python Type | Description |
|---|---|---|
| Render Layers | CompositorNodeRLayers | Reads render passes from a View Layer |
| Image | CompositorNodeImage | Loads a static or animated image |
| Movie Clip | CompositorNodeMovieClip | Loads a movie clip data-block |
| Mask | CompositorNodeMask | Outputs a Mask data-block as a greyscale image |
Output Nodes
| Node | Python Type | Blender Version | Description |
|---|---|---|---|
| Composite | CompositorNodeComposite | ≤ 4.4 | Final output node; writes result to the render buffer |
| Group Output | NodeGroupOutput | 5.0+ | Replaces Composite node; first Color input socket drives render output |
| Viewer | CompositorNodeViewer | all | Previews data in the Image Editor during compositing |
| File Output | CompositorNodeOutputFile | all | Writes one or more passes to image files on disk |
Color Nodes
| Node | Python Type | Description |
|---|---|---|
| Mix | CompositorNodeMixRGB | Blends two images using standard blend modes |
| Alpha Over | CompositorNodeAlphaOver | Composites a foreground over a background using alpha |
| Color Balance | CompositorNodeColorBalance | Lift/Gamma/Gain or Offset/Power/Slope (ASC-CDL) color correction |
| Hue/Saturation/Value | CompositorNodeHueSat | Adjusts hue, saturation, and value |
| Bright/Contrast | CompositorNodeBrightContrast | Simple brightness and contrast adjustment |
| RGB Curves | CompositorNodeCurveRGB | Per-channel tone curve adjustment |
| Invert Color | CompositorNodeInvert | Inverts color and/or alpha channels |
Filter Nodes
| Node | Python Type | Description |
|---|---|---|
| Blur | CompositorNodeBlur | Gaussian and other kernel blurs |
| Glare | CompositorNodeGlare | Adds lens flares, fog glow, streaks, or ghosts around bright areas |
| Defocus | CompositorNodeDefocus | Z-depth-based depth-of-field blur (bokeh shapes) |
| Denoise | CompositorNodeDenoise | Intel OpenImageDenoise; removes Cycles noise in post |
| Despeckle | CompositorNodeDespeckle | Smooths noisy or speckled regions |
Python API Mapping
Enabling and Accessing the Node Tree
Use get_compositor_tree() defined in the Overview section. It handles both versions and is idempotent — calling it again on a scene that already has a compositor tree reuses the existing group rather than creating a new orphaned datablock.
import bpy
scene = bpy.context.scene
tree, links = get_compositor_tree(scene)
nodes = tree.nodesAdding and Connecting Nodes
# Clear any existing nodes, then build from scratch
nodes.clear()
# Always create Render Layers explicitly — never rely on default nodes after clear()
rl = nodes.new("CompositorNodeRLayers")
rl.location = (0, 0)
rl.scene = scene
# Add a Blur node
blur = nodes.new("CompositorNodeBlur")
blur.filter_type = 'GAUSS'
blur.size_x = 10
blur.size_y = 10
blur.location = (300, 0)
# Connect: Render Layers Image → Blur → output
links.new(rl.outputs["Image"], blur.inputs["Image"])
# Output node: use add_output_node() to keep version branching in one place
add_output_node(tree, blur.outputs["Image"], location=(600, 0))Key Node Properties
CompositorNodeBlur
| Property | Type | Values / Default | Description |
|---|---|---|---|
filter_type | enum | 'FLAT', 'GAUSS', 'FAST_GAUSS', 'CATROM', … / 'FLAT' | Kernel type |
size_x | int | [0, 2048] / 0 | Horizontal blur radius |
size_y | int | [0, 2048] / 0 | Vertical blur radius |
use_bokeh | bool | False | Circular (bokeh) kernel |
CompositorNodeGlare
| Property | Type | Values / Default | Description |
|---|---|---|---|
glare_type | enum | 'FOG_GLOW', 'GHOSTS', 'STREAKS', 'SIMPLE_STAR' / 'STREAKS' | Glare effect type |
quality | enum | 'HIGH', 'MEDIUM', 'LOW' | Trade quality for speed |
threshold | float | [0, inf] / 1.0 | Minimum brightness to apply glare |
mix | float | [-1, 1] / 0.0 | Blend between original and glare |
CompositorNodeDefocus
| Property | Type | Default | Description |
|---|---|---|---|
use_zbuffer | bool | True | Use Z input as depth map (vs. greyscale mask) |
f_stop | float | 128.0 | Simulated aperture f-stop |
bokeh | enum | 'CIRCLE' | Bokeh shape (CIRCLE, TRIANGLE, SQUARE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON) |
max_blur | float | 16.0 | Maximum blur radius in pixels |
CompositorNodeDenoise
| Property | Type | Default | Description |
|---|---|---|---|
prefilter | enum | 'ACCURATE' | Prefilter mode: NONE, FAST, ACCURATE |
quality | enum | 'HIGH' | Quality: HIGH, BALANCED, LOW |
use_hdr | bool | False | Preserve values outside 0–1 range |
Notes
- Version branching: Keep all 4.x / 5.0+ branching inside
get_compositor_tree()andadd_output_node(). Never duplicateif hasattr(scene, 'compositing_node_group')in individual scripts. - Idempotency:
get_compositor_tree()reuses an existingcompositing_node_grouprather than callingbpy.data.node_groups.new()every time. This prevents orphanedCompositor.001,Compositor.002… datablocks accumulating in the blend file. - Blender 5.0+:
scene.use_nodesandscene.node_treeare removed.CompositorNodeCompositeis also removed; useNodeGroupOutputinstead. The node group's first ColorOUTPUTinterface socket drives the render result. - Blender ≤ 4.4:
CompositorNodeCompositeis required; without it, compositing results are not applied to the final render. - After `nodes.clear()`: All default nodes (Render Layers, Composite) are gone. Always recreate them explicitly with
nodes.new(...). - Use socket names (
outputs["Image"]) rather than indices — enabled passes shift socket indices on the Render Layers node. - The Denoise node works best when paired with the Denoising Albedo and Denoising Normal passes (enable via
view_layer.use_pass_denoising_data).
Related
- render-passes.md
- common-setups.md
Compositing — Index
| Name | Description | Path |
|---|---|---|
| Compositor Nodes | Enabling the compositor, key input/output/color/filter nodes, and Python API for building node trees | compositor-nodes.md |
| Render Passes and AOVs | View Layer render pass configuration, Cryptomatte, custom AOVs, and compositor integration via Python | render-passes.md |
| Common Compositing Setups | Typical node graph patterns: depth of field, glare/bloom, layer compositing, color grading, multi-pass file output | common-setups.md |
Render Passes and AOVs
Render passes split a render into individual shading components (diffuse, glossy, Z-depth, etc.) for flexible compositing. AOVs (Arbitrary Output Variables) add custom shader data as additional passes.
Overview
Passes are enabled per View Layer in the Render Properties → View Layer panel. Once enabled, they appear as output sockets on the Render Layers compositor node and are accessible in bpy.types.ViewLayer.
Standard Render Passes
Combined and Utility
| Pass | ViewLayer Property | Description |
|---|---|---|
| Combined | use_pass_combined (default True) | Full RGBA beauty render |
| Z (Depth) | use_pass_z | Distance to nearest surface (0.0–∞) |
| Normal | use_pass_normal | Surface normal direction |
| Position | use_pass_position | World-space surface position |
| UV | use_pass_uv | Texture UV coordinates |
| Vector (Motion) | use_pass_vector | Per-pixel motion vector |
| Mist | use_pass_mist | Distance-based mist factor (0.0–1.0) |
| Ambient Occlusion | use_pass_ambient_occlusion | AO contribution |
| Shadow | use_pass_shadow | Shadow contribution |
| Object Index | use_pass_object_index | Per-object integer mask |
| Material Index | use_pass_material_index | Per-material integer mask |
| Emission | use_pass_emit | Emissive light contribution |
| Grease Pencil | use_pass_grease_pencil | Grease Pencil render in separate pass |
Diffuse, Glossy, Transmission, Subsurface
Each light interaction type has three sub-passes:
| Suffix | Meaning |
|---|---|
_direct | Direct light contribution |
_indirect | Indirect / bounce light contribution |
_color | Albedo / base color |
Examples: use_pass_diffuse_direct, use_pass_glossy_color, use_pass_transmission_indirect, use_pass_subsurface_direct.
Cryptomatte Passes
Cryptomatte encodes per-pixel object/material/asset IDs for accurate mattes in compositing.
| Property | Type | Default | Description |
|---|---|---|---|
use_pass_cryptomatte_object | bool | False | Isolate individual objects |
use_pass_cryptomatte_material | bool | False | Isolate individual materials |
use_pass_cryptomatte_asset | bool | False | Isolate objects sharing a parent (asset groups) |
use_pass_cryptomatte_accurate | bool | True | Higher-accuracy cryptomatte (slower) |
pass_cryptomatte_depth | int [2, 16] | 6 | Max unique objects distinguishable per pixel |
Use the Cryptomatte compositor node (CompositorNodeCryptomatteV2) to pick mattes interactively from the passes.
Custom AOVs (Arbitrary Output Variables)
AOVs let shader graphs write arbitrary color or scalar data into named render passes.
Setup Steps
1. In the View Layer properties → Passes → Shader AOVs panel, click + to add an AOV and give it a name (e.g., "MyColor"). 2. In a material shader graph, add a AOV Output node (ShaderNodeOutputAOV), set its name to match, and route a Color or Value into it. 3. After rendering, the named pass appears as an output socket on the Render Layers compositor node.
Python API Mapping
Enabling Passes
import bpy
scene = bpy.context.scene
vl = scene.view_layers["ViewLayer"] # bpy.types.ViewLayer
# Utility passes
vl.use_pass_z = True
vl.use_pass_normal = True
vl.use_pass_mist = True
vl.use_pass_ambient_occlusion = True
# Diffuse / Glossy
vl.use_pass_diffuse_direct = True
vl.use_pass_diffuse_color = True
vl.use_pass_glossy_direct = True
# Emission
vl.use_pass_emit = True
# Cryptomatte
vl.use_pass_cryptomatte_object = True
vl.use_pass_cryptomatte_material = True
vl.pass_cryptomatte_depth = 6Adding an AOV via Python
# Add a custom AOV named "MyColor" to the active view layer
vl = bpy.context.scene.view_layers["ViewLayer"]
aov = vl.aovs.add()
aov.name = "MyColor"
aov.type = 'COLOR' # 'COLOR' or 'VALUE'Accessing Pass Sockets in Compositor
Use get_compositor_tree() from compositor-nodes.md to obtain the tree in a version-safe, idempotent way. After clearing existing nodes, create a Render Layers node explicitly.
import bpy
scene = bpy.context.scene
tree, links = get_compositor_tree(scene)
nodes = tree.nodes
nodes.clear()
# Always create Render Layers explicitly — never assume default nodes exist
rl = nodes.new("CompositorNodeRLayers")
rl.scene = scene
# Access enabled pass outputs by name (not by index — enabled passes shift indices)
z_socket = rl.outputs["Depth"]
normal_socket = rl.outputs["Normal"]
diffuse_dir = rl.outputs["DiffDir"]
aov_socket = rl.outputs["MyColor"] # custom AOVNotes
- Pass socket names on the Render Layers node match Blender UI labels, not Python property names (e.g., property
use_pass_z→ socket"Depth"). - Enabling many passes increases render time and memory usage.
- Cryptomatte passes require Cycles or a renderer that supports them; EEVEE support varies by version.
- AOV type must match the shader AOV Output node's data type (Color vs. Value).
- The Denoising Data passes (
use_pass_denoising_data) enable Albedo and Normal inputs for the Denoise compositor node. - Blender 5.0+: Access the compositor tree via
scene.compositing_node_groupinstead ofscene.node_tree. See compositor-nodes.md for the cross-version helper pattern.
Related
- compositor-nodes.md
- common-setups.md
Curves, Surfaces, Metaballs & Text
Blender supports several non-mesh object types for modeling: Bezier and NURBS curves/surfaces, Metaball objects, and Text objects. All can be converted to meshes via Object > Convert > Mesh.
Bezier Curves
Bezier curves are defined by control points, each with two handles that control the tangent direction and curvature on either side.
Handle Types
| Type | Description |
|---|---|
FREE | Handles rotate independently; allows sharp corners |
VECTOR | Handle points toward the adjacent control point; straight segments |
ALIGNED | Both handles stay co-linear; smooth joins |
AUTO | Blender auto-adjusts handles for smooth curves |
Key Edit Mode Operations
| Operation | Shortcut | Description |
|---|---|---|
| Add point | Ctrl+Click | Extrude a new control point from selection |
| Subdivide | W → Subdivide | Inserts points between selected points |
| Toggle cyclic | Alt+C | Closes/opens the curve loop |
| Set handle type | V | Choose handle type for selected points |
| Tilt | Ctrl+T | Rotates the curve's cross-section at each point |
Curve Extrusion and Bevel
Curves can be given 3D volume via the curve data properties:
- Extrude — extends the curve profile along its normal
- Bevel Depth — adds a circular bevel profile around the curve spine
- Bevel Object — uses a custom 2D curve shape as the bevel profile
NURBS Curves and Surfaces
NURBS (Non-Uniform Rational B-Splines) use weighted control points. Blender supports NURBS curves and NURBS surface patches.
- Control point weight influences how strongly each point pulls the curve
- Order (U and V directions) controls how many points influence any given segment
- NURBS surfaces are useful for precise CAD-like shapes
Metaballs
Metaball objects are implicit surfaces defined by a scalar field. Objects in the same group blend together organically based on a shared threshold.
Element Types
| Type | Description |
|---|---|
| Ball | Spherical field; creates a rounded blob |
| Capsule | Linear field; cylindrical with rounded caps |
| Plane | Planar field; produces a flat slab with rounded edges |
| Ellipsoid | Ellipsoidal field; a stretched sphere |
Threshold controls the isosurface level (0.0–5.0). Lower values produce a smoother, larger merged surface.
Text Objects
Text objects render 3D extruded text with full control over font, size, alignment, and character spacing. Convert to mesh (Alt+C) for further polygon editing.
Key text properties: Font face, Size, Shear, Extrude depth, Bevel, Horizontal/Vertical alignment, Character spacing.
Python API Mapping
Creating a Bezier Curve
import bpy
curve_data = bpy.data.curves.new(name="MyCurve", type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(2) # adds 2 more points (total 3)
# Set control point positions and handle types
p0 = spline.bezier_points[0]
p0.co = (0, 0, 0)
p0.handle_left_type = 'AUTO'
p0.handle_right_type = 'AUTO'
p1 = spline.bezier_points[1]
p1.co = (1, 1, 0)
p1.handle_left_type = 'ALIGNED'
p1.handle_right_type = 'ALIGNED'
# Wrap in an object and link to scene
curve_obj = bpy.data.objects.new("MyCurveObj", curve_data)
bpy.context.collection.objects.link(curve_obj)Curve Extrusion and Bevel via Python
import bpy
curve_data = bpy.data.curves["MyCurve"]
curve_data.extrude = 0.1 # extrusion depth
curve_data.bevel_depth = 0.05 # bevel radius
curve_data.bevel_resolution = 4 # bevel smoothness
curve_data.use_fill_caps = True # close the endsCreating a NURBS Curve
import bpy
curve_data = bpy.data.curves.new(name="NurbsCurve", type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('NURBS')
spline.points.add(3) # total 4 control points
import mathutils
spline.points[0].co = mathutils.Vector((0, 0, 0, 1)) # (x, y, z, weight)
spline.points[1].co = mathutils.Vector((1, 2, 0, 1))
spline.points[2].co = mathutils.Vector((2, 0, 0, 1))
spline.points[3].co = mathutils.Vector((3, 1, 0, 1))
spline.order_u = 4 # cubic NURBS
curve_obj = bpy.data.objects.new("NurbsObj", curve_data)
bpy.context.collection.objects.link(curve_obj)Creating Metaballs
import bpy
# Each metaball object in the same scene family shares the same global metaball data
mball = bpy.data.metaballs.new("Meta")
mball.threshold = 0.6
mball.resolution = 0.2 # viewport resolution
# Add elements
el = mball.elements.new()
el.type = 'BALL'
el.co = (0.0, 0.0, 0.0)
el.radius = 1.0
el2 = mball.elements.new()
el2.type = 'CAPSULE'
el2.co = (1.5, 0.0, 0.0)
el2.radius = 0.8
meta_obj = bpy.data.objects.new("MetaObj", mball)
bpy.context.collection.objects.link(meta_obj)Curve-Related Operators
import bpy
# Subdivide selected segments (in Edit Mode)
bpy.ops.curve.subdivide(number_cuts=1)
# Set all selected handle types
bpy.ops.curve.handle_type_set(type='AUTO')
# Toggle cyclic
bpy.ops.curve.cyclic_toggle()
# Convert curve to mesh
bpy.ops.object.convert(target='MESH')Notes
- NURBS control points use 4D vectors
(x, y, z, weight); weight defaults to 1.0 - Metaball objects are grouped by name prefix: e.g.,
Meta,Meta.001,Meta.002all belong to the same family and blend together - Converting a Text or Curve object to mesh (
Object > Convert > Mesh) is destructive; the original curve data is replaced bpy.data.curvesholds both Curve and Surface object data; checkcurve_data.type('CURVE'vs'SURFACE')- Spline types available via
splines.new():'POLY','BEZIER','BSPLINE','CARDINAL','NURBS'
Related
- Mesh Basics
- Modifiers
Geometry Nodes
Geometry Nodes is Blender's node-based procedural modeling system. A Geometry Nodes modifier holds a node tree that takes geometry as input, transforms it through connected nodes, and outputs the result.
Overview
The workflow is: 1. Add a Geometry Nodes modifier to a mesh object 2. A new node tree is created automatically with a Group Input and Group Output node 3. Connect nodes between them to build the procedural operation 4. Expose parameters via Group Input sockets to control from the modifier panel
Key Nodes
Geometry
| Node | Description |
|---|---|
| Join Geometry | Combines multiple geometry streams into one |
| Transform Geometry | Translates, rotates, or scales geometry as a whole |
| Set Position | Moves vertices/points using a vector or offset field |
| Bounding Box | Outputs the axis-aligned bounding box of geometry |
Mesh Primitives
| Node | Description |
|---|---|
| Mesh Line | Creates a line of vertices |
| Mesh Circle | Creates a circular ring of vertices |
| Mesh Grid | Creates a flat grid mesh |
| Mesh Cube | Creates a cube mesh |
| UV Sphere | Creates a sphere using UV topology |
| Ico Sphere | Creates an icosphere mesh |
| Cylinder | Creates a cylinder mesh |
Instances
| Node | Description |
|---|---|
| Instance on Points | Places instances of geometry on each point |
| Realize Instances | Converts instances to actual geometry |
| Geometry to Instance | Wraps geometry as a single instance |
Fields & Attributes
| Node | Description |
|---|---|
| Position | Outputs the position of each element |
| Index | Outputs the integer index of each element |
| Set Material | Assigns a material to geometry |
| Capture Attribute | Stores a field value as an attribute |
| Named Attribute | Reads a named attribute from geometry |
Utilities
| Node | Description |
|---|---|
| Group Input | Receives parameters from the modifier panel |
| Group Output | Sends the final geometry out of the node tree |
| Math | Performs math operations on float values |
| Vector Math | Performs vector operations |
| Switch | Selects between two values based on a boolean |
Python API Mapping
Creating a Geometry Nodes Modifier and Node Tree
import bpy
obj = bpy.context.active_object
# Add the modifier
mod = obj.modifiers.new(name="GeometryNodes", type='NODES')
# Create a new node tree (or assign an existing one)
node_tree = bpy.data.node_groups.new(name="MyGeoNodes", type='GeometryNodeTree')
mod.node_group = node_treeAdding Nodes and Linking Them
import bpy
node_tree = bpy.data.node_groups["MyGeoNodes"]
nodes = node_tree.nodes
links = node_tree.links
# Clear default nodes if any
nodes.clear()
# Add Group Input and Output
input_node = nodes.new("NodeGroupInput")
output_node = nodes.new("NodeGroupOutput")
input_node.location = (-300, 0)
output_node.location = (300, 0)
# Add a Set Position node
set_pos = nodes.new("GeometryNodeSetPosition")
set_pos.location = (0, 0)
# Link: Group Input → Set Position → Group Output
links.new(input_node.outputs["Geometry"], set_pos.inputs["Geometry"])
links.new(set_pos.outputs["Geometry"], output_node.inputs["Geometry"])Defining Group Interface Sockets (Blender 4.0+)
import bpy
node_tree = bpy.data.node_groups["MyGeoNodes"]
# Add a Geometry input socket
node_tree.interface.new_socket(
name="Geometry",
in_out='INPUT',
socket_type='NodeSocketGeometry'
)
# Add a Float input for offset amount
node_tree.interface.new_socket(
name="Offset",
in_out='INPUT',
socket_type='NodeSocketFloat'
)
# Add a Geometry output socket
node_tree.interface.new_socket(
name="Geometry",
in_out='OUTPUT',
socket_type='NodeSocketGeometry'
)Adding a Mesh Primitive Node
import bpy
node_tree = bpy.data.node_groups["MyGeoNodes"]
nodes = node_tree.nodes
links = node_tree.links
# Add a UV Sphere primitive
sphere = nodes.new("GeometryNodeMeshUVSphere")
sphere.inputs["Segments"].default_value = 32
sphere.inputs["Rings"].default_value = 16
# Add Join Geometry
join = nodes.new("GeometryNodeJoinGeometry")
# Connect sphere → join
links.new(sphere.outputs["Mesh"], join.inputs["Geometry"])Notes
- In Blender 4.0+, group input/output sockets are managed via
node_tree.interface.new_socket()rather than the oldernode_tree.inputs.new()/node_tree.outputs.new()API - Node type identifiers (e.g.,
"GeometryNodeSetPosition") can be found in the Blender Python API reference or by hovering over nodes in the Info editor when adding them via the UI - Fields (Position, Index, etc.) are evaluated lazily per-element; they are not scalar values
- The
NODESmodifier type (obj.modifiers.new(..., type='NODES')) is the entry point; the actual node graph lives inmodifier.node_group - Use the NodeToPython add-on to auto-generate Python code from an existing Geometry Nodes tree
Related
- Modifiers
- Mesh Basics
Mesh Basics
Blender meshes consist of vertices, edges, faces, and loops. Edit Mode exposes tools for extruding, cutting, beveling, and transforming geometry. The bmesh Python module provides low-level programmatic access to these same operations.
Overview
A mesh object's data block is made of:
- Vertex — a point in 3D space; stores position and links to its disk cycle of edges
- Edge — a connection between two vertices
- Face — a polygon (tri, quad, or n-gon) that references a loop cycle on its boundary
- Loop — the per-face-vertex element; stores per-face-vertex data (UVs, vertex colors, etc.)
Key Operations
Selection Modes
| Mode | Shortcut | Description |
|---|---|---|
| Vertex Select | 1 | Select individual vertices |
| Edge Select | 2 | Select edges |
| Face Select | 3 | Select faces |
| Multi-mode | Shift+1/2/3 | Enable multiple modes simultaneously |
Transform
| Operation | Shortcut | Notes |
|---|---|---|
| Grab / Move | G | Follow with X, Y, or Z to constrain to axis; Shift+X/Y/Z to exclude axis |
| Rotate | R | Then axis key; type a number for exact degrees |
| Scale | S | Then axis key; type a number for exact factor |
Core Editing Operations
| Operation | Shortcut | Description |
|---|---|---|
| Extrude Region | E | Extrudes selected faces/edges/vertices outward |
| Inset Faces | I | Creates a smaller face within the selected face(s) |
| Loop Cut | Ctrl+R | Adds a loop of edges across a mesh ring |
| Knife | K | Freehand cutting tool; Enter to confirm |
| Bevel | Ctrl+B | Rounds edges; scroll wheel sets segment count |
| Bevel Vertices | Ctrl+Shift+B | Bevels vertices rather than edges |
| Merge | M | Merges selected vertices (At Center, At Cursor, etc.) |
| Dissolve Vertices | Ctrl+X (context) | Removes vertices without leaving holes |
| Fill | F | Creates a face from selected edges/vertices |
| Grid Fill | Ctrl+F → Grid Fill | Fills a loop with a grid of quads |
Python API Mapping
Accessing Mesh Data (Object Mode)
import bpy
obj = bpy.context.active_object
mesh = obj.data # bpy.types.Mesh
for v in mesh.vertices:
print(v.index, v.co) # position as mathutils.Vector
for e in mesh.edges:
print(e.vertices[0], e.vertices[1])
for f in mesh.polygons:
print(f.vertices[:]) # vertex indices of the faceAccessing Mesh Data via BMesh (Edit Mode)
import bpy, bmesh
obj = bpy.context.edit_object
bm = bmesh.from_edit_mesh(obj.data)
for v in bm.verts:
print(v.co)
for e in bm.edges:
print(e.verts[0].index, e.verts[1].index)
for f in bm.faces:
print([v.index for v in f.verts])
bmesh.update_edit_mesh(obj.data)BMesh Operations
import bpy, bmesh
obj = bpy.context.edit_object
bm = bmesh.from_edit_mesh(obj.data)
# Extrude selected faces
selected_faces = [f for f in bm.faces if f.select]
result = bmesh.ops.extrude_face_region(bm, geom=selected_faces)
bmesh.ops.translate(bm, vec=(0, 0, 1), verts=[v for v in result['geom'] if isinstance(v, bmesh.types.BMVert)])
# Inset faces
bmesh.ops.inset_region(bm, faces=selected_faces, thickness=0.1, depth=0.0)
# Bevel edges
selected_edges = [e for e in bm.edges if e.select]
bmesh.ops.bevel(bm, geom=selected_edges, offset=0.1, segments=2, affect='EDGES')
bmesh.update_edit_mesh(obj.data)Using bpy.ops.mesh in Edit Mode
import bpy
bpy.ops.object.mode_set(mode='EDIT')
# Set selection mode
bpy.context.tool_settings.mesh_select_mode = (False, False, True) # face select
bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={"value": (0, 0, 1)})
bpy.ops.mesh.inset(thickness=0.1, depth=0.0)
bpy.ops.mesh.loopcut(number_cuts=1, smoothness=0.0)
bpy.ops.mesh.bevel(offset=0.1, segments=2, affect='EDGES')
bpy.ops.mesh.merge(type='CENTER')
bpy.ops.mesh.dissolve_verts()
bpy.ops.mesh.fill()Notes
- Loops store per-face-vertex data (UVs, vertex colors); each edge generally has two loops (one per adjacent face)
bmesh.from_edit_mesh()gives direct access to edit-mode data without toggling modes; callbmesh.update_edit_mesh()after modificationsbpy.ops.mesh.*operators require the context to be in Edit Mode and will only affect the active object- N-gons (faces with more than 4 vertices) can cause shading and subdivision issues; prefer quads where possible
- Knife tool: press
Cto constrain to angles,Zto cut through,EnterorSpaceto confirm
Related
- Modifiers
- Sculpting
Modifiers
Modifiers are non-destructive operations that transform an object's geometry. They are evaluated from top to bottom in the stack; each modifier receives the output of the modifier above it.
Overview
Modifiers are added via the wrench icon in the Properties panel, or via Python through obj.modifiers.new(name, type). They can be applied (made permanent) or removed without affecting the base mesh.
Generate Modifiers
| Name | Type Enum | Description |
|---|---|---|
| Array | ARRAY | Creates copies of a mesh along an axis, curve, or offset |
| Bevel | BEVEL | Bevels edges or vertices; supports segments and profile |
| Boolean | BOOLEAN | Cuts, joins, or intersects with another mesh object |
| Build | BUILD | Animates mesh faces appearing or disappearing over time |
| Decimate | DECIMATE | Reduces polygon count; modes: Collapse, Un-Subdivide, Planar |
| Remesh | REMESH | Regenerates topology; modes: Blocks, Smooth, Sharp, Voxel |
| Screw | SCREW | Revolves a profile around an axis to create a screw/helix |
| Solidify | SOLIDIFY | Adds thickness to a surface mesh |
| Subdivision Surface | SUBSURF | Subdivides and smooths the mesh; Catmull-Clark or Simple |
| Mirror | MIRROR | Mirrors geometry across one or more local axes |
Deform Modifiers
| Name | Type Enum | Description |
|---|---|---|
| Armature | ARMATURE | Deforms mesh using an armature (skeleton) |
| Cast | CAST | Shifts the shape toward sphere, cylinder, or cuboid |
| Curve | CURVE | Bends a mesh along a curve object |
| Displace | DISPLACE | Displaces vertices using a texture |
| Lattice | LATTICE | Deforms mesh using a lattice control cage |
| Simple Deform | SIMPLE_DEFORM | Twist, bend, taper, or stretch operations |
| Shrinkwrap | SHRINKWRAP | Projects vertices onto the surface of another mesh |
| Warp | WARP | Warps geometry between two objects |
Physics Modifiers (Overview)
| Name | Type Enum | Notes |
|---|---|---|
| Cloth | CLOTH | Simulates cloth dynamics |
| Soft Body | SOFT_BODY | Simulates elastic/soft deformations |
| Particle System | PARTICLE_SYSTEM | Emitters or hair particles |
| Fluid | FLUID | Domain, Flow, or Effector role in fluid simulation |
Python API Mapping
Adding a Modifier
import bpy
obj = bpy.context.active_object
# Add a Subdivision Surface modifier
mod = obj.modifiers.new(name="Subdivision", type='SUBSURF')
mod.levels = 2 # viewport subdivision level
mod.render_levels = 3 # render subdivision level
# Add a Mirror modifier
mirror = obj.modifiers.new(name="Mirror", type='MIRROR')
mirror.use_axis[0] = True # mirror on X
mirror.use_bisect_axis[0] = True
# Add a Boolean modifier
bool_mod = obj.modifiers.new(name="Boolean", type='BOOLEAN')
bool_mod.operation = 'DIFFERENCE'
bool_mod.object = bpy.data.objects["Cutter"]Applying a Modifier
import bpy
obj = bpy.context.active_object
bpy.context.view_layer.objects.active = obj
# Apply by modifier name
bpy.ops.object.modifier_apply(modifier="Subdivision")
# Apply all modifiers on the object (snapshot names first; applying mutates the stack)
for mod_name in [m.name for m in obj.modifiers]:
bpy.ops.object.modifier_apply(modifier=mod_name)Configuring Common Modifiers
import bpy
obj = bpy.context.active_object
# Array modifier — 3 copies along X with 2-unit offset
arr = obj.modifiers.new(name="Array", type='ARRAY')
arr.count = 3
arr.relative_offset_displace[0] = 1.5 # X offset multiplier
# Bevel modifier
bev = obj.modifiers.new(name="Bevel", type='BEVEL')
bev.width = 0.05
bev.segments = 3
bev.limit_method = 'ANGLE'
# Decimate modifier — reduce to 50% of original polygons
dec = obj.modifiers.new(name="Decimate", type='DECIMATE')
dec.ratio = 0.5
# Solidify modifier
sol = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
sol.thickness = 0.02
sol.offset = -1.0 # grow inwardNotes
- Stack order matters: Boolean before Subdivision Surface avoids topology artifacts; Mirror before Subdivision Surface can cause seam issues if the order is reversed
bpy.ops.object.modifier_apply()requires the object to be active and in Object Mode- To apply a modifier without
bpy.ops, useobj.to_mesh()(read-only evaluated mesh) orbpy.ops.object.convert(target='MESH')to destructively apply all modifiers - The
NODESmodifier type is the Geometry Nodes modifier — it holds anode_grouppointer - Physics modifiers (Cloth, Soft Body, Fluid) require baking via
bpy.ops.ptcache.bake()or the Cache panel
Related
- Mesh Basics
- Geometry Nodes
Modeling — Index
| Name | Description | Path |
|---|---|---|
| Mesh Basics | Mesh data structure (vertex/edge/face/loop), Edit Mode operations (Extrude, Inset, Loop Cut, Knife, Bevel), selection modes, transforms, bmesh Python API | mesh-basics.md |
| Modifiers | Generate/Deform/Physics modifier stack — Array, Bevel, Boolean, Subdivision Surface, Mirror, Armature, Shrinkwrap, Cloth, and more; obj.modifiers.new() Python API | modifiers.md |
| Geometry Nodes | Procedural node-based modeling — workflow, key nodes (Mesh Primitive, Instance on Points, Set Position, Join Geometry), Group Input/Output, Python node tree scripting | geometry-nodes.md |
| Curves, Surfaces, Metaballs & Text | Bezier and NURBS curves/surfaces, Metaball elements and threshold, Text objects; bpy.data.curves.new() and bpy.ops.curve.* Python API | curves-surfaces.md |
| Sculpting | Sculpt Mode brushes (Draw, Clay, Smooth, Grab, Elastic), Mask, Face Sets, Dyntopo, Voxel Remesh; tool_settings.sculpt Python access | sculpting.md |
Sculpting
Sculpt Mode allows freeform mesh editing using brushes that push, pull, smooth, or otherwise deform geometry. It is accessed by switching the interaction mode to Sculpt Mode from the viewport header.
Overview
Sculpt Mode operates on the active mesh object. Brushes apply strokes using a radius and strength falloff. For dynamic topology changes, Dyntopo adds and removes geometry on the fly; alternatively, Voxel Remesh regenerates a uniform topology before sculpting.
Core Brushes
| Brush | Description |
|---|---|
| Draw | Pushes geometry along the surface normal; primary sculpting brush |
| Draw Sharp | Same as Draw but with a sharper falloff; good for creases |
| Clay | Builds up material like clay; useful for blocking out volume |
| Clay Strips | Adds blocky rectangular strokes; great for rough volume blocking |
| Smooth | Averages surrounding vertex positions; removes noise and softens detail |
| Grab | Pulls geometry from a grab point with proportional influence |
| Elastic Deform | Like Grab but the mesh behaves elastically; preserves volume |
| Snake Hook | Pulls geometry outward in a hook-like stroke |
| Inflate | Pushes vertices outward along their normals; expands volume |
| Pinch | Pulls vertices toward the brush center; tightens edges |
| Crease | Pushes vertices apart at the center while pulling the edges in |
| Flatten / Plane | Pushes vertices toward a reference plane |
| Scrape | Scrapes vertices above a plane down to a flat surface |
Mask
Masks prevent sculpt brushes from affecting masked areas. Masks are stored as a vertex attribute (0 = unmasked, 1 = fully masked).
| Operation | Shortcut | Description |
|---|---|---|
| Paint Mask | M | Draw mask with brush |
| Erase Mask | Alt+M | Remove mask with brush |
| Invert Mask | Ctrl+I | Flip mask values |
| Clear Mask | Alt+M (menu) | Set all vertices to 0 |
| Box Mask | B | Mask a rectangular region |
| Lasso Mask | Ctrl+ drag | Mask a freehand region |
Face Sets
Face Sets group polygon regions for isolation, masking, and sculpting workflows. Each face set has a unique integer ID visualized by color.
| Operation | Shortcut | Description |
|---|---|---|
| Draw Face Set | Available in brush list | Paints a new face set with LMB |
| Hide Face Set | H | Hides the face set under the cursor |
| Show All | Alt+H | Unhides all face sets |
| Grow/Shrink Set | Via menu | Expands or contracts face set borders |
Dyntopo
Dynamic Topology (Dyntopo) adds and removes mesh triangles during sculpting strokes to maintain a target detail level.
- Enable via Sculpt Mode header → Dyntopo checkbox
- Detail Size — controls the target edge length (Relative Detail mode)
- Detail Type: Relative, Constant Detail, Brush Detail
- Dyntopo converts the mesh to triangles; not suitable for production topology
Voxel Remesh
Generates a new all-quad (via Duals) or voxel-based mesh topology. Use before detail sculpting to get uniform geometry.
- Voxel Size — controls grid resolution; smaller = more polygons
- Accessed via right-click header → Remesh, or Properties → Data → Remesh
- Preserves volume but discards UV maps and vertex groups
Python API Mapping
Accessing Sculpt Settings
import bpy
# Access the sculpt settings
sculpt = bpy.context.scene.tool_settings.sculpt
# Set symmetry
sculpt.use_symmetry_x = True
sculpt.use_symmetry_y = False
# Enable Dyntopo settings (requires Sculpt Mode active)
sculpt.use_edge_collapse = True
sculpt.constant_detail_resolution = 9.0 # for Constant Detail modeEntering Sculpt Mode and Setting the Active Brush
import bpy
obj = bpy.context.active_object
bpy.context.view_layer.objects.active = obj
# Switch to Sculpt Mode
bpy.ops.object.mode_set(mode='SCULPT')
# Set active brush by name
bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw")Applying Voxel Remesh
import bpy
obj = bpy.context.active_object
# Set voxel size on the mesh data
obj.data.remesh_voxel_size = 0.05
obj.data.remesh_voxel_adaptivity = 0.0
# Apply the remesh
bpy.ops.object.voxel_remesh()Mask Operations via Python
import bpy
# Invert mask on active sculpt object
bpy.ops.sculpt.mask_filter(filter_type='INVERT', auto_smooth_iterations=0)
# Smooth mask weights
bpy.ops.sculpt.mask_filter(filter_type='SMOOTH')
# Fill (set all to 1) or clear (set all to 0)
bpy.ops.paint.mask_flood_fill(mode='VALUE', value=1.0) # fill
bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0.0) # clear
# Hide a face set by ID
bpy.ops.sculpt.face_set_change_visibility(mode='TOGGLE')Reading Mask Data from Mesh
import bpy
obj = bpy.context.active_object
mesh = obj.data
# Vertex paint / mask attribute layer
if ".sculpt_mask" in mesh.attributes:
mask_attr = mesh.attributes[".sculpt_mask"]
for val in mask_attr.data:
print(val.value)Notes
- Sculpt Mode operates on triangle topology internally; Dyntopo always triangulates the mesh
- Face Sets are stored as an integer face attribute named
.sculpt_face_set; values correspond to the face set ID - The Mask attribute is stored as a float vertex attribute named
.sculpt_mask(0.0 = unmasked, 1.0 = masked) bpy.ops.sculpt.*operators generally require the active mode to be'SCULPT'- Smooth brush shortcut: hold
Shiftwhile using any brush to temporarily switch to smooth - Multires modifier preserves subdivided sculpt detail at multiple levels; Dyntopo and Multires are mutually exclusive
Related
- Mesh Basics
- Modifiers
Cloth Simulation
Cloth simulation is applied via the Cloth modifier (type='CLOTH'). Settings are split across ClothSettings (structural behaviour), ClothCollisionSettings (collision), and PointCache (caching/baking).
Overview
- Add the modifier with
obj.modifiers.new(name="Cloth", type='CLOTH') ClothSettingsis accessed viamod.settingsClothCollisionSettingsis accessed viamod.collision_settings- Baking writes to
mod.point_cache
Key Settings
ClothSettings (mod.settings)
| Property | Type | Default | Description |
|---|---|---|---|
quality | int | 5 | Simulation steps per frame (higher = more accurate) |
mass | float | 0.3 | Mass of each vertex (kg, 0–inf) |
tension_stiffness | float | 15.0 | Resistance to stretching (0–10000) |
compression_stiffness | float | 15.0 | Resistance to compression (0–10000) |
shear_stiffness | float | 5.0 | Resistance to shearing (0–10000) |
bending_stiffness | float | 0.5 | Resistance to bending (0–10000) |
tension_damping | float | 5.0 | Stretching damping (0–50) |
compression_damping | float | 5.0 | Compression damping (0–50) |
shear_damping | float | 5.0 | Shear damping (0–50) |
bending_damping | float | 0.5 | Bending damping (0–1000) |
air_damping | float | 1.0 | Air resistance |
pin_stiffness | float | 1.0 | Pin spring stiffness (0–50) |
vertex_group_mass | str | "" | Vertex group for pinning vertices (Pin Group in the UI; named "mass" for historical reasons) |
vertex_group_structural_stiffness | str | "" | Vertex group for structural stiffness |
vertex_group_bending | str | "" | Vertex group for bending stiffness |
ClothCollisionSettings (mod.collision_settings)
| Property | Type | Default | Description |
|---|---|---|---|
use_collision | bool | False | Enable collision with other objects |
distance_min | float | 0.015 | Minimum distance before collision response |
friction | float | 5.0 | Friction on collision |
use_self_collision | bool | False | Enable self-collision |
self_distance_min | float | 0.015 | Minimum distance for self-collision |
self_collision_quality | int | 2 | Self-collision iterations per step |
collision_quality | int | 2 | Collision iterations per step |
damping | float | 1.0 | Velocity lost on collision |
Python API Mapping
import bpy
obj = bpy.context.active_object
# Add Cloth modifier
mod = obj.modifiers.new(name="Cloth", type='CLOTH')
settings = mod.settings
coll = mod.collision_settings
# Physical properties
settings.quality = 10
settings.mass = 0.5
settings.tension_stiffness = 20.0
settings.bending_stiffness = 1.0
# Pin group (vertices that stay fixed)
settings.vertex_group_mass = "Pin" # must exist as a vertex group
# Enable object collision
coll.use_collision = True
coll.distance_min = 0.01
coll.friction = 10.0
# Enable self-collision
coll.use_self_collision = True
coll.self_distance_min = 0.01
# Bake (Blender 3.2+ context override)
with bpy.context.temp_override(
scene=bpy.context.scene,
active_object=obj,
object=obj,
point_cache=mod.point_cache,
):
bpy.ops.ptcache.bake(bake=True)Notes
- Cloth collision with other objects requires those objects to have a Collision modifier (see collision-forces.md)
- Higher
qualityvalues improve simulation accuracy but increase computation time vertex_group_massis the pin group (despite its name): vertices with weight 1.0 are fully pinned; weight 0.0 simulates freely. The property name is a historical artifact — the UI labels it "Pin Group" under Cloth > Shape- Baked cache is invalidated by any modifier-order change or settings edit; free the bake before re-simulating
Related
- collision-forces.md
- soft-body.md
Collision & Force Fields
Two independent systems enable objects to affect physics simulations: the Collision modifier (passive deflection surface for cloth, soft body, and particles) and Force Fields (active forces applied to simulations via obj.field).
Overview
- Collision modifier: add to any mesh that should deflect cloth/soft body/particles
- Force fields: created via
bpy.ops.object.effector_add(type=...)or by settingobj.field.type - Both systems use Effector Weights to control per-simulation influence
Collision Modifier
Add with obj.modifiers.new(name="Collision", type='COLLISION'). Settings via mod.settings (CollisionSettings).
CollisionSettings
| Property | Type | Default | Description |
|---|---|---|---|
use | bool | True | Enable this object as a collider |
damping | float | 0.5 | Velocity loss on collision (0–1) |
damping_factor | float | 0.5 | Particle collision damping (0–1) |
damping_random | float | 0.0 | Random damping variation (0–1) |
friction_factor | float | 0.0 | Particle collision friction (0–1) |
friction_random | float | 0.0 | Random friction variation (0–1) |
cloth_friction | float | 5.0 | Friction specifically for cloth (0–80) |
absorption | float | 0.0 | Fraction of effector force absorbed (0–1) |
permeability | float | 0.0 | Chance particle passes through mesh (0–1) |
stickiness | float | 0.0 | Surface stickiness on collision (0–10) |
thickness_outer | float | 0.02 | Outer face thickness (0.001–1) |
thickness_inner | float | 0.25 | Inner face thickness for soft body (0.001–1) |
use_particle_kill | bool | False | Kill particles on contact |
use_culling | bool | False | Improve penetration recovery |
Force Fields
Force fields are configured via obj.field (FieldSettings). Create a dedicated force field empty with bpy.ops.object.effector_add(type=...).
FieldSettings (obj.field)
| Property | Type | Default | Description |
|---|---|---|---|
type | enum | 'NONE' | Force field type (see table below) |
strength | float | 0.0 | Force magnitude (−inf to inf) |
use_max_distance | bool | False | Enable maximum distance falloff |
distance_max | float | 0.0 | Maximum working distance |
use_min_distance | bool | False | Enable minimum distance |
distance_min | float | 0.0 | Minimum distance (force clamped here) |
use_radial_max | bool | False | Radial maximum distance |
radial_max | float | 0.0 | Radial maximum |
falloff_type | enum | 'SPHERE' | Distance falloff shape: 'SPHERE', 'TUBE', 'CONE' |
falloff_power | float | 2.0 | Falloff exponent |
flow | float | 0.0 | Convert force into air-flow velocity |
wind_factor | float | 0.0 | Force reduction parallel to surface (0–1) |
noise | float | 0.0 | Noise amount for turbulence (0–10) |
size | float | 0.0 | Size of turbulence |
seed | int | 1 | Random seed for noise (1–128) |
linear_drag | float | 0.0 | Velocity-proportional drag |
quadratic_drag | float | 0.0 | Velocity-squared drag |
inflow | float | 0.0 | Inwards component for vortex |
Force Field Types
type | Description |
|---|---|
'FORCE' | Radial attraction/repulsion from origin |
'WIND' | Constant directional force along Z-axis |
'VORTEX' | Spiral force around Z-axis |
'TURBULENCE' | Random noise-based force field |
'MAGNETIC' | Force based on velocity direction (like magnetism) |
'DRAG' | Velocity-damping field (linear_drag, quadratic_drag) |
'CHARGE' | Repulsion between similarly-charged objects |
'LENNARDJ' | Lennard-Jones (attraction at distance, repulsion close) |
'TEXTURE' | Force driven by a texture map |
'GUIDE' | Curve-based particle guidance |
'BOID' | Boid rule field (for boid physics) |
'FLUID_FLOW' | Derived from a fluid simulation velocity field |
Effector Weights
Each simulation (cloth, soft body, particles, fluid) exposes an effector_weights object to scale per-category force-field influence:
# Example: particle system effector weights
ps = obj.particle_systems[0].settings
ps.effector_weights.gravity = 0.5 # reduce gravity to 50%
ps.effector_weights.wind = 1.0 # full wind effect
ps.effector_weights.vortex = 0.0 # ignore vortex fieldsPython API Mapping
import bpy
# Add Collision modifier to a mesh
coll_obj = bpy.data.objects["Ground"]
mod = coll_obj.modifiers.new(name="Collision", type='COLLISION')
mod.settings.cloth_friction = 20.0
mod.settings.damping = 0.8
# Create a Wind force field
bpy.ops.object.effector_add(type='WIND', location=(0, 0, 2))
wind = bpy.context.active_object
wind.field.strength = 5.0
wind.field.noise = 1.5 # add turbulence to wind
# Create a Vortex
bpy.ops.object.effector_add(type='VORTEX', location=(0, 0, 0))
vortex = bpy.context.active_object
vortex.field.strength = 3.0
vortex.field.inflow = 0.5
# Create a Force field with falloff
bpy.ops.object.effector_add(type='FORCE', location=(0, 0, 0))
ff = bpy.context.active_object
ff.field.strength = -10.0 # negative = attraction
ff.field.use_max_distance = True
ff.field.distance_max = 5.0
ff.field.falloff_power = 2.0Notes
- Collision modifier must be before any deforming modifier in the stack to detect the correct shape
- Force fields apply to all simulations in the scene by default; use Effector Weights or Collection constraints to restrict influence
'WIND'blows along the object's local −Z axis; rotate the empty to change wind direction'TURBULENCE'uses thenoise,size, andseedproperties;'WIND'useswind_factorfor surface-parallel reduction- For cloth, force fields only work when the cloth has no bake (or during interactive simulation playback)
Related
- cloth.md
- soft-body.md
- particles.md
- rigid-body.md
Fluid Simulation (Mantaflow)
Blender's fluid system (since 2.82) is powered by Mantaflow. Three modifier roles define the simulation: Domain (volume where simulation occurs), Flow (fluid emitter/inflow/outflow), and Effector (collision or guide object).
Overview
- All objects use a single Fluid modifier (
type='FLUID'); the role is set viamod.fluid_type - Domain supports
'LIQUID'(FLIP solver) and'GAS'(smoke/fire) - Baking is modular: particles, mesh, and noise can be baked independently
Key Settings
Domain — mod.domain_settings (FluidDomainSettings)
| Property | Type | Default | Description |
|---|---|---|---|
domain_type | enum | 'LIQUID' | 'LIQUID' or 'GAS' |
resolution_max | int | 32 | Maximum grid resolution (6–10000) |
cache_type | enum | 'MODULAR' | 'REPLAY', 'MODULAR', 'ALL' |
cache_directory | str | "//" | Path to cache files |
cache_frame_start | int | 1 | First frame of bake range |
cache_frame_end | int | 250 | Last frame of bake range |
cache_data_format | enum | 'UNI' | 'UNI', 'OPENVDB', 'RAW' |
cache_resumable | bool | False | Save intermediate states for resumable bake |
time_scale | float | 1.0 | Simulation speed multiplier |
simulation_method | enum | 'FLIP' | 'FLIP' (splashy) / 'APIC' (stable) |
gravity | Vector | (0,0,−9.81) | Gravity direction and magnitude |
Gas-specific (domain_type = 'GAS')
| Property | Type | Default | Description |
|---|---|---|---|
alpha | float | −0.001 | Smoke buoyancy based on density (−5 to 5) |
beta | float | 0.1 | Smoke buoyancy based on heat (−5 to 5) |
vorticity | float | 0.0 | Rotational turbulence intensity |
dissolve_speed | int | 25 | Frames for smoke to fully dissolve (1–10000) |
use_dissolve_smoke | bool | False | Enable smoke dissolving over time |
Liquid-specific (domain_type = 'LIQUID')
| Property | Type | Default | Description |
|---|---|---|---|
flip_ratio | float | 0.97 | PIC/FLIP blending (0 = smooth PIC, 1 = splashy FLIP) |
particle_scale | float | 1.0 | Particle radius scale |
use_mesh | bool | False | Generate a mesh from the liquid particles |
mesh_concave_upper | float | 3.5 | Upper concavity for mesh generation |
Flow — mod.flow_settings (FluidFlowSettings)
| Property | Type | Default | Description |
|---|---|---|---|
flow_type | enum | 'LIQUID' | 'SMOKE', 'FIRE', 'BOTH', 'LIQUID' |
flow_behavior | enum | 'INFLOW' | 'INFLOW', 'OUTFLOW', 'GEOMETRY' |
use_inflow | bool | True | Enable/disable this flow source |
density | float | 1.0 | Emission density/strength (0–10) |
smoke_color | Color | (0.7, 0.7, 0.7) | Color of emitted smoke |
surface_distance | float | 1.5 | Emission height above mesh surface |
velocity_factor | float | 1.0 | Multiplier on source object velocity (−100–100) |
velocity_normal | float | 0.0 | Normal directional velocity component |
subframes | int | 0 | Extra samples between frames for fast flows (0–200) |
particle_size | float | 1.0 | Particle size in simulation cells (for particle flows) |
Effector — mod.effector_settings (FluidEffectorSettings)
| Property | Type | Default | Description |
|---|---|---|---|
effector_type | enum | 'COLLISION' | 'COLLISION' (deflect fluid) / 'GUIDE' (direct flow) |
use_effector | bool | True | Enable this effector |
surface_distance | float | 0.0 | Extra surface margin (0–10) |
velocity_factor | float | 1.0 | Obstacle velocity multiplier (−100–100) |
guide_mode | enum | 'MAXIMUM' | Guide velocity blending: 'OVERRIDE', 'MAXIMUM', 'MINIMUM', 'AVERAGED' |
subframes | int | 0 | Extra samples for fast-moving effectors |
Python API Mapping
import bpy
# --- Domain ---
domain_obj = bpy.data.objects["FluidDomain"]
mod = domain_obj.modifiers.new(name="Fluid", type='FLUID')
mod.fluid_type = 'DOMAIN'
ds = mod.domain_settings
ds.domain_type = 'LIQUID'
ds.resolution_max = 64
ds.cache_directory = "//cache/fluid"
ds.cache_type = 'ALL'
# --- Flow (inflow) ---
flow_obj = bpy.data.objects["FluidFlow"]
mod_f = flow_obj.modifiers.new(name="Fluid", type='FLUID')
mod_f.fluid_type = 'FLOW'
fs = mod_f.flow_settings
fs.flow_type = 'LIQUID'
fs.flow_behavior = 'INFLOW'
# --- Effector (collision) ---
eff_obj = bpy.data.objects["Collider"]
mod_e = eff_obj.modifiers.new(name="Fluid", type='FLUID')
mod_e.fluid_type = 'EFFECTOR'
mod_e.effector_settings.effector_type = 'COLLISION'
# Bake (requires context with domain object active)
bpy.ops.fluid.bake_all()Notes
- All objects that participate in the simulation must be inside the Domain object's bounding box
cache_type = 'MODULAR'allows baking particles, mesh, and noise separately;'ALL'bakes everything at once- Gas simulations (smoke/fire) require
domain_type = 'GAS'; liquid and gas cannot coexist in one domain use_mesh = True(liquid) generates a surface mesh from particles using a marching-cubes-like algorithm — bake mesh separately after baking particles- OpenVDB (
cache_data_format = 'OPENVDB') gives smaller cache files with lossless compression
Related
- collision-forces.md
- rigid-body.md
Particle System
Blender's particle system supports two modes: Emitter (dynamically spawned particles) and Hair (static or groomed strands). Each object can have multiple particle systems stacked as modifiers.
Overview
- Add via
obj.modifiers.new(name="ParticleSystem", type='PARTICLE_SYSTEM') - The new modifier exposes
mod.particle_system(ParticleSystem) - Simulation settings are in
psys.settings(ParticleSettings) - Multiple systems stack on the same object
Key Settings
ParticleSettings (psys.settings)
Emission
| Property | Type | Default | Description |
|---|---|---|---|
type | enum | 'EMITTER' | 'EMITTER' or 'HAIR' |
count | int | 1000 | Total number of particles |
frame_start | float | 1.0 | First frame of emission |
frame_end | float | 200.0 | Last frame of emission |
lifetime | float | 50.0 | Life span of particles in frames |
lifetime_random | float | 0.0 | Random variation of lifetime (0–1) |
emit_from | enum | 'FACE' | Emission source: 'VERT', 'FACE', 'VOLUME' |
use_even_distribution | bool | True | Distribute evenly across faces by area |
Velocity
| Property | Type | Default | Description |
|---|---|---|---|
normal_factor | float | 0.0 | Initial velocity along surface normal |
tangent_factor | float | 0.0 | Initial velocity along surface tangent |
object_factor | float | 0.0 | Factor of object velocity added to particles |
factor_random | float | 0.0 | Random velocity multiplier |
Physics
| Property | Type | Default | Description |
|---|---|---|---|
physics_type | enum | 'NEWTON' | 'NEWTON', 'KEYED', 'BOIDS', 'FLUID' |
mass | float | 1.0 | Particle mass |
particle_size | float | 0.05 | Particle display / collision size |
use_dynamic_rotation | bool | False | Rotate particles during simulation |
integrator | enum | 'MIDPOINT' | ODE solver: 'EULER', 'MIDPOINT', 'RK4' |
timestep | float | 0.04 | Simulation time step |
Rendering
| Property | Type | Default | Description |
|---|---|---|---|
render_type | enum | 'HALO' | 'NONE', 'HALO', 'LINE', 'PATH', 'OBJECT', 'COLLECTION' |
instance_object | Object | None | Object to instance at each particle (render_type 'OBJECT') |
instance_collection | Collection | None | Collection for instancing (render_type 'COLLECTION') |
Children
| Property | Type | Default | Description |
|---|---|---|---|
child_type | enum | 'NONE' | 'NONE', 'SIMPLE', 'INTERPOLATED' |
child_percent | int | 10 | Number of children per parent |
rendered_child_count | int | 100 | Children rendered per parent |
child_length | float | 1.0 | Relative child strand length (0–1) |
Hair-specific
| Property | Type | Default | Description |
|---|---|---|---|
hair_length | float | 0.1 | Length of hair strands (m) |
hair_step | int | 5 | Number of keys per hair strand |
use_hair_bspline | bool | False | Smooth strands with B-spline interpolation |
Python API Mapping
Emitter System
import bpy
obj = bpy.context.active_object
# Add particle system modifier
mod = obj.modifiers.new(name="EmitterPS", type='PARTICLE_SYSTEM')
psys = mod.particle_system
ps = psys.settings
# Emitter configuration
ps.type = 'EMITTER'
ps.count = 5000
ps.lifetime = 80
ps.frame_start = 1
ps.frame_end = 100
ps.emit_from = 'FACE'
# Initial velocity
ps.normal_factor = 2.0
ps.factor_random = 0.5
# Children (optional, for emitter)
ps.child_type = 'SIMPLE'
ps.child_percent = 5
ps.rendered_child_count = 50Hair System
The Hair system is a separate particle system (or a new modifier slot). The snippet below adds a second modifier; do not run it in the same context as the Emitter snippet above without intending to stack two systems.
import bpy
obj = bpy.context.active_object
# Add a separate particle system modifier for hair
mod = obj.modifiers.new(name="HairPS", type='PARTICLE_SYSTEM')
psys = mod.particle_system
ps = psys.settings
# Hair configuration
ps.type = 'HAIR'
ps.hair_length = 0.5
ps.hair_step = 8
# Children (interpolated works well for hair)
ps.child_type = 'INTERPOLATED'Notes
effector_weightsonParticleSettingscontrols which force fields affect the system (see collision-forces.md)- Hair dynamics (cloth-like simulation) is enabled via
ps.use_hair_dynamics = True; this exposes aClothSettings-like subobject - For GPU rendering with Cycles,
render_type = 'PATH'is required for hair strands - Each modifier slot can use a different
ParticleSettingsdata-block (reusable viabpy.data.particles)
Related
- collision-forces.md
- cloth.md
Physics — Index
| Name | Description | Path |
|---|---|---|
| Rigid Body | Bullet-based rigid body dynamics — Active/Passive types, collision shapes, constraints, and baking | rigid-body.md |
| Cloth Simulation | Cloth modifier for fabric simulation — stiffness, damping, pin groups, and collision | cloth.md |
| Soft Body | Deformable mesh simulation — goal forces, edge springs, and self-collision | soft-body.md |
| Particle System | Emitter and Hair particle systems — emission, physics, children, and force field interaction | particles.md |
| Fluid Simulation (Mantaflow) | Domain/Flow/Effector fluid simulation — Liquid, Gas (smoke/fire), cache, and baking | fluid.md |
| Collision & Force Fields | Collision modifier and Force Fields (Wind, Vortex, Turbulence, Magnetic, etc.) with effector weights | collision-forces.md |
Rigid Body
Rigid body simulation in Blender uses the Bullet physics engine. Objects are added to the Rigid Body World and simulated as non-deforming bodies. Active objects are driven by the simulation; Passive objects are controlled by animation.
Overview
- Rigid Body World is created automatically on first
rigidbody.object_add() - Simulation is stored in a point cache and can be baked to keyframes
- Constraints connect rigid body objects (hinge, slider, generic spring, etc.)
Key Settings
Rigid Body World (bpy.context.scene.rigidbody_world)
| Property | Type | Default | Description |
|---|---|---|---|
enabled | bool | True | Enable simulation evaluation |
collection | Collection | — | Objects participating in simulation |
substeps_per_frame | int | 10 | Simulation steps per frame (1–32767) |
solver_iterations | int | 10 | Constraint solver iterations (1–1000) |
time_scale | float | 1.0 | Simulation speed multiplier (0–100) |
use_split_impulse | bool | False | Reduce velocity build-up on collision |
Rigid Body Object (obj.rigid_body)
| Property | Type | Default | Description |
|---|---|---|---|
type | enum | 'ACTIVE' | 'ACTIVE' (sim-driven) / 'PASSIVE' (anim-driven) |
mass | float | 1.0 | Object mass in kg (0.001–inf) |
friction | float | 0.5 | Surface resistance (0–inf) |
restitution | float | 0.0 | Bounciness (0 = no bounce, 1 = elastic) |
linear_damping | float | 0.04 | Linear velocity loss over time (0–1) |
angular_damping | float | 0.1 | Angular velocity loss over time (0–1) |
collision_shape | enum | 'CONVEX_HULL' | Shape used for collision detection |
collision_margin | float | 0.04 | Collision threshold distance (0–1) |
collision_collections | bitmask | — | Which collision groups the body belongs to |
enabled | bool | True | Actively participates in simulation |
kinematic | bool | False | Allow animation system control |
use_deactivation | bool | True | Allow sleeping when below velocity threshold |
deactivate_linear_velocity | float | 0.4 | Linear threshold for sleep |
deactivate_angular_velocity | float | 0.5 | Angular threshold for sleep |
Collision Shapes
| Value | Description |
|---|---|
'BOX' | Axis-aligned bounding box |
'SPHERE' | Bounding sphere |
'CAPSULE' | Capsule shape |
'CYLINDER' | Cylinder shape |
'CONE' | Cone shape |
'CONVEX_HULL' | Convex hull of mesh (fast, approximate) |
'MESH' | Exact mesh (Passive only for concave) |
'COMPOUND' | Child objects define sub-shapes |
Python API Mapping
import bpy
# Create Rigid Body World
bpy.ops.rigidbody.world_add()
# Add active rigid body to selected object
bpy.ops.rigidbody.object_add(type='ACTIVE')
obj = bpy.context.active_object
rb = obj.rigid_body
# Configure properties
rb.mass = 5.0
rb.friction = 0.8
rb.restitution = 0.3
rb.collision_shape = 'CONVEX_HULL'
# Passive collider (floor) — must be a SEPARATE object, made active first
bpy.ops.mesh.primitive_plane_add(size=20, location=(0, 0, -2))
floor = bpy.context.active_object
bpy.ops.rigidbody.object_add(type='PASSIVE') # applies to `floor` (now active)
floor.rigid_body.friction = 1.0
# Bake all physics to cache
bpy.ops.ptcache.bake_all(bake=True)
# Free bake
bpy.ops.ptcache.free_bake_all()
# Bake to keyframes
bpy.ops.rigidbody.bake_to_keyframes(frame_start=1, frame_end=250, step=1)Notes
'MESH'collision shape is only stable forPASSIVEbodies; use'CONVEX_HULL'forACTIVEbodies with complex geometrykinematicmode lets you animate a body while still generating collision contacts- Baking writes simulation data to a
.blend-internal cache; re-bake after changing settings substeps_per_frameandsolver_iterationsdirectly trade accuracy for performance
Related
- collision-forces.md
Soft Body
Soft body simulation makes meshes deform elastically under forces. The Soft Body modifier (type='SOFT_BODY') exposes settings via SoftBodySettings. Three main subsystems: Goal (attraction to rest pose), Edges (spring network), and Self Collision.
Overview
- Add via
obj.modifiers.new(name="Softbody", type='SOFT_BODY') - Settings accessed via
mod.settings(SoftBodySettings) - Point cache via
mod.point_cache
Key Settings
Goal — vertices attracted to animated position
| Property | Type | Default | Description |
|---|---|---|---|
use_goal | bool | True | Enable goal forces |
goal_default | float | 0.7 | Default goal weight for all vertices (0–1) |
goal_min | float | 0.0 | Minimum vertex group weight scaling (0–1) |
goal_max | float | 1.0 | Maximum vertex group weight scaling (0–1) |
goal_spring | float | 0.5 | Goal spring stiffness (0–0.999) |
goal_friction | float | 0.0 | Damping on goal forces (0–50) |
vertex_group_goal | str | "" | Vertex group driving per-vertex goal weight |
Edges — spring network
| Property | Type | Default | Description |
|---|---|---|---|
use_edges | bool | True | Use edges as springs |
pull | float | 0.5 | Spring stiffness when stretched (0–0.999) |
push | float | 0.5 | Spring stiffness when compressed (0–0.999) |
bend | float | 0.0 | Bending spring stiffness (0–10) |
damping | float | 0.5 | Spring damping (0–10) |
use_stiff_quads | bool | False | Add diagonal springs to quads |
shear | float | 0.0 | Shear resistance for quad diagonals (0–1) |
Self Collision
| Property | Type | Default | Description |
|---|---|---|---|
use_self_collision | bool | False | Enable vertex-to-vertex self collision |
ball_size | float | 0.49 | Collision ball radius factor (–10 to 10) |
ball_stiff | float | 0.5 | Ball spring stiffness (0.001–100) |
ball_damp | float | 0.5 | Blending factor to inelastic collision (0–1) |
Aerodynamics & General
| Property | Type | Default | Description |
|---|---|---|---|
aerodynamics_type | enum | 'SIMPLE' | 'SIMPLE' (drag) / 'LIFT_FORCE' (lift + drag) |
aero | float | 0.0 | Amount of aerodynamic force (0–30000) |
mass | float | 1.0 | Uniform vertex mass (0–50000) |
speed | float | 1.0 | Adjusts simulation speed (0.01–100) |
Python API Mapping
import bpy
obj = bpy.context.active_object
# Add Soft Body modifier
mod = obj.modifiers.new(name="Softbody", type='SOFT_BODY')
sb = mod.settings
# Goal: pin the top vertices
sb.use_goal = True
sb.vertex_group_goal = "Top" # vertex group: weight 1 = pinned
sb.goal_spring = 0.8
sb.goal_friction = 5.0
# Edge springs
sb.use_edges = True
sb.pull = 0.8
sb.push = 0.8
sb.bend = 0.5
sb.damping = 1.0
# Self collision
sb.use_self_collision = True
sb.ball_size = 0.3
sb.ball_stiff = 1.0
# Bake
bpy.ops.ptcache.bake_all(bake=True)Notes
use_goal = Falsewithuse_edges = Trueproduces a fully jelly-like object; combining both gives elastic cloth-like behaviour- Self collision is computationally expensive — keep
ball_sizesmall and only enable when needed goal_min/goal_maxrescale the vertex group weights: a vertex with weight 0.5 maps togoal_min + 0.5 * (goal_max - goal_min)- Aerodynamics requires
use_edges = Trueto function
Related
- cloth.md
- collision-forces.md
bpy.app
Provides read-only application metadata (version, build info, runtime flags) and three submodules for event handling (handlers), deferred execution (timers), and localization (translations).
Signature / Usage
import bpy
# Version check
if bpy.app.version >= (4, 0, 0):
print("Running Blender 4+")
# Handler with @persistent (survives file loads)
from bpy.app.handlers import persistent
@persistent
def on_load(*args):
# load_post receives dummy args; read the path from bpy.data
print("File loaded:", bpy.data.filepath)
bpy.app.handlers.load_post.append(on_load)
# Timer: run once after 2 seconds
def my_timer():
print("Timer fired")
return None # returning None unregisters the timer
bpy.app.timers.register(my_timer, first_interval=2.0)Options / Props
Version Attributes
| Attribute | Type | Description |
|---|---|---|
version | tuple[int, int, int] | Blender version as (major, minor, micro) |
version_string | str | Human-readable version, e.g. "5.2.0" |
version_cycle | str | Release status: 'alpha', 'beta', 'rc', 'release' |
version_file | tuple[int, int] | .blend file format compatibility version |
Runtime State Flags
| Attribute | Type | Description |
|---|---|---|
background | bool | True when running headless (--background) |
factory_startup | bool | True with --factory-startup flag |
module | bool | True when Blender runs as a Python module |
portable | bool | True unless built with absolute path references |
debug | bool | True when --debug flag is active |
debug_python | bool | Debug mode for Python specifically |
File Paths
| Attribute | Type | Description |
|---|---|---|
binary_path | str | Path to the Blender executable |
tempdir | str | Temporary directory for the session |
cachedir | str | Cache directory (temp fallback if unavailable) |
bpy.app.handlers
Event callback lists. Append callables to subscribe; remove to unsubscribe.
| Handler List | Trigger |
|---|---|
frame_change_pre / frame_change_post | Before/after each frame change |
depsgraph_update_pre / depsgraph_update_post | Before/after dependency graph update |
load_pre / load_post / load_post_fail | File load events |
save_pre / save_post / save_post_fail | File save events |
render_pre / render_post / render_complete / render_cancel | Render lifecycle |
render_init / render_write / render_stats | Render sub-events |
undo_pre / undo_post / redo_pre / redo_post | Undo/redo events |
annotation_pre / annotation_post | Grease Pencil annotation events |
animation_playback_pre / animation_playback_post | Playback start/stop |
object_bake_pre / object_bake_complete / object_bake_cancel | Baking events |
composite_pre / composite_post / composite_cancel | Compositing events |
exit_pre | Before Blender quits |
`@persistent` decorator: by default handlers are freed on file load. Decorate with @persistent from bpy.app.handlers to keep them active across file loads.
from bpy.app.handlers import persistent
@persistent
def my_handler(scene):
print("Frame:", scene.frame_current)
bpy.app.handlers.frame_change_pre.append(my_handler)
# Remove:
bpy.app.handlers.frame_change_pre.remove(my_handler)bpy.app.timers
| Function | Signature | Description |
|---|---|---|
register(fn, *, first_interval=0, persistent=False) | -> None | Register fn to be called after first_interval seconds. fn returns None to stop or a float seconds until next call. |
unregister(fn) | -> None | Remove a registered timer. |
is_registered(fn) | -> bool | Check if fn is currently registered. |
def make_countdown(start):
state = {"n": start}
def tick():
print(state["n"])
if state["n"] <= 0:
return None # unregister the timer
state["n"] -= 1
return 1.0 # run again in 1 second
return tick
bpy.app.timers.register(make_countdown(5))bpy.app.translations
| Attribute / Function | Description |
|---|---|
locale | Active locale string (empty if i18n disabled) |
contexts | Predefined context constants (Operator, UI_Events_KeyMaps, id_mesh, …) |
register(module_name, translations_dict) | Register addon translations. translations_dict: {locale: {(ctx, msg): translation}} |
unregister(module_name) | Remove registered translations |
pgettext_iface(msg, ctx='*') | Translate a UI label string |
pgettext_tip(msg, ctx='*') | Translate a tooltip string |
Notes
- Handler callbacks for
frame_change_pre/postduring rendering are called from the render thread, while viewport updates occur on a different thread. Modifying scene data in these handlers without locking can cause crashes — lock the interface before rendering if handlers mutate data. - Timer functions run on the main thread between UI events; they are safe for data modifications but should complete quickly to avoid UI stutter.
bpy.app.timers.registerwithpersistent=Truepreserves the timer across file loads, analogous to@persistentfor handlers.- The
driver_namespacedict (bpy.app.driver_namespace) is reset on file load; populate it in aload_posthandler or via a registered text data-block.
Related
- bpy.context
- bpy.utils
- bpy.info
bpy.context
Provides read-only access to the active window, area, scene, and selection state. Values depend on which editor area is currently active. All context values are read-only; modify them via the data API or by running operators.
Signature / Usage
import bpy
# Common access patterns
obj = bpy.context.active_object
selected = bpy.context.selected_objects
scene = bpy.context.scene
mode = bpy.context.mode # e.g. 'OBJECT', 'EDIT_MESH'
# Temporary context override
with bpy.context.temp_override(area=my_area):
bpy.ops.view3d.view_all()Options / Props
Core Context Attributes
| Attribute | Type | Description |
|---|---|---|
active_object | `Object \ | None` |
selected_objects | Sequence[Object] | All currently selected objects |
scene | Scene | The active scene |
view_layer | ViewLayer | The active view layer |
mode | str | Current mode enum: 'OBJECT', 'EDIT_MESH', 'POSE', etc. |
area | `Area \ | None` |
space_data | `Space \ | None` |
region | `Region \ | None` |
window | `Window \ | None` |
screen | `Screen \ | None` |
workspace | `WorkSpace \ | None` |
Object Collection Attributes
| Attribute | Type | Description |
|---|---|---|
visible_objects | Sequence[Object] | All visible objects in the view layer |
selectable_objects | Sequence[Object] | Objects that can be selected |
editable_objects | Sequence[Object] | Objects that can be edited |
selected_editable_objects | Sequence[Object] | Selected objects that can be edited |
objects_in_mode | Sequence[Object] | Objects in the current mode (e.g., all mesh objects in Edit Mode) |
Bone / Pose Attributes
| Attribute | Type | Description |
|---|---|---|
active_bone | `EditBone \ | Bone` |
active_pose_bone | PoseBone | Active pose bone |
selected_bones | Sequence[EditBone] | Selected bones in Edit Mode |
selected_pose_bones | Sequence[PoseBone] | Selected pose bones |
Methods
| Method | Description |
|---|---|
temp_override(**kwargs) | Context manager; temporarily replaces context members and restores on exit |
evaluated_depsgraph_get() | Returns an up-to-date Depsgraph with modifiers and animations applied |
copy() | Returns current context members as a plain dict |
path_resolve(path, coerce=True) | Resolves a property from an RNA path string |
Notes
- Read-only constraint: Assigning to
bpy.context.active_object = objraisesAttributeError. Usebpy.context.view_layer.objects.active = objinstead. - Area-dependent members: Some attributes (e.g.,
space_data) are only available when the appropriate editor area is active. Accessing them outside that context returnsNoneor raises an error. - temp_override usage: Pass keyword arguments matching
bpy.types.Contextattribute names. Commonly used to supply a validareaorregionwhen calling operators from a script:
# Force operator to run in a VIEW_3D area
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
with bpy.context.temp_override(area=area):
bpy.ops.view3d.view_all()
break- Background mode:
area,space_data, andregionareNonewhen Blender runs headless (--background). Operators that require a window context will fail.
Related
- bpy.data
- bpy.ops
- bpy.types
bpy.data
The primary interface for accessing and managing all Blender data-blocks from Python. Backed by bpy.types.BlendData, it provides typed collections for every ID type in a .blend file.
Signature / Usage
import bpy
# Iterate all objects
for obj in bpy.data.objects:
print(obj.name)
# Key-based lookup
cube = bpy.data.objects["Cube"]
# Safe lookup
mat = bpy.data.materials.get("MyMaterial") # None if missing
# Create a new data-block
mesh = bpy.data.meshes.new(name="MyMesh")
# Remove a data-block
bpy.data.meshes.remove(mesh)
# Current .blend file path
print(bpy.data.filepath)Options / Props
Main Collections
| Attribute | Type | Description |
|---|---|---|
actions | BlendDataActions | Action data-blocks |
armatures | BlendDataArmatures | Armature data-blocks |
brushes | BlendDataBrushes | Brush data-blocks |
cameras | BlendDataCameras | Camera data-blocks |
collections | BlendDataCollections | Collection data-blocks |
curves | BlendDataCurves | Curve data-blocks |
fonts | BlendDataFonts | Vector font data-blocks |
grease_pencils | BlendDataGreasePencilsV3 | Grease Pencil data-blocks |
hair_curves | BlendDataHairCurves | Hair curve data-blocks |
images | BlendDataImages | Image data-blocks |
lights | BlendDataLights | Light data-blocks |
materials | BlendDataMaterials | Material data-blocks |
meshes | BlendDataMeshes | Mesh data-blocks |
node_groups | BlendDataNodeTrees | Node group data-blocks |
objects | BlendDataObjects | Object data-blocks |
scenes | BlendDataScenes | Scene data-blocks |
texts | BlendDataTexts | Text data-blocks |
worlds | BlendDataWorlds | World data-blocks |
File Attributes
| Attribute | Type | Description |
|---|---|---|
filepath | str | Absolute path of the current .blend file |
is_dirty | bool | True if there are unsaved changes |
is_saved | bool | True if the file has been saved at least once |
Collection Methods (common to all collections)
| Method | Description |
|---|---|
new(name) | Create a new data-block with the given name |
remove(datablock, do_unlink=True) | Delete a data-block; do_unlink removes all references first |
get(name, default=None) | Safe key lookup; returns default if not found |
find(name) | Returns the index of the named data-block, or -1 |
keys() / values() / items() | Standard dict-like iteration |
Notes
- Data-blocks with zero users are not automatically deleted; call
.remove()explicitly or usedo_unlink=True. - Modifying a data-block directly (e.g.,
mesh.vertices) requires the object not to be in Edit Mode — usebpy.ops.object.mode_set(mode='OBJECT')first. - Linked data-blocks (from external
.blendfiles) appear in the same collections but havelibraryset to aLibraryobject. They cannot be edited directly without making them local. - Library overrides allow editing linked data: use
datablock.override_create(remap_local_usages=True).
Related
- bpy.context
- bpy.types
- bpy.ops
bpy.ops
Provides access to Blender's operator system. Operators are organized into submodules by category (e.g., bpy.ops.object, bpy.ops.mesh). All arguments must be passed as keyword arguments.
Signature / Usage
import bpy
# Basic call
bpy.ops.object.shade_smooth()
# With keyword arguments
bpy.ops.mesh.subdivide(number_cuts=3, smoothness=0.5)
# Poll before calling to avoid errors
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
# With execution context (triggers invoke path / shows dialog)
bpy.ops.object.collection_instance_add('INVOKE_DEFAULT')
# Context override via temp_override
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
with bpy.context.temp_override(area=area):
bpy.ops.object.delete()
breakOptions / Props
Operator Call Signature
bpy.ops.<category>.<name>([execution_context, undo,] **kwargs)| Positional Arg | Type | Description |
|---|---|---|
execution_context | str enum | Controls whether invoke() or execute() is called (default: 'EXEC_DEFAULT') |
undo | bool | Whether to push an undo step; overrides operator's default |
Execution Context Values
| Value | Description |
|---|---|
'EXEC_DEFAULT' | Run execute() only (default) |
'INVOKE_DEFAULT' | Run invoke() — may show dialogs, use mouse position |
'INVOKE_REGION_WIN' | Invoke in the window region |
'EXEC_REGION_WIN' | Execute in the window region |
'INVOKE_AREA' / 'EXEC_AREA' | Invoke/execute in the active area |
'INVOKE_SCREEN' / 'EXEC_SCREEN' | Invoke/execute at screen level |
Return Values
Operators return a set containing one or more status strings:
| Value | Description |
|---|---|
{'FINISHED'} | Operator completed successfully; undo step pushed |
{'CANCELLED'} | Aborted; no changes, no undo step |
{'RUNNING_MODAL'} | Operator is running modally (e.g., interactive transform) |
{'PASS_THROUGH'} | Event was not handled; passed to next handler |
Major Operator Categories
| Submodule | Example Operators |
|---|---|
bpy.ops.object | select_all, delete, duplicate, mode_set, transform_apply |
bpy.ops.mesh | subdivide, extrude_region, flip_normals, separate, merge |
bpy.ops.edit | Undo/redo, clipboard |
bpy.ops.armature | bone_primitive_add, select_all, switch_direction |
bpy.ops.render | render, render_animation, opengl |
bpy.ops.transform | translate, rotate, resize, edge_slide |
bpy.ops.node | add_node, link, select_all |
bpy.ops.wm | open_mainfile, save_mainfile, append, link |
Notes
- All kwargs only: Operators accept no positional arguments for properties —
bpy.ops.mesh.subdivide(3)raisesTypeError. - Error reporting: If the operator logs errors internally, a
RuntimeErroris raised after execution even if it returns{'FINISHED'}. Wrap calls intry/except RuntimeErrorwhen errors are expected. - Poll failures: Calling an operator when
poll()returnsFalseraisesRuntimeError: Operator bpy.ops.xxx.yyy.poll() failed. Use.poll()to check first. - Context requirements: Many operators require a specific area type (e.g.,
VIEW_3D) or mode. Usebpy.context.temp_override()to supply the correct context from a script. - Operators vs direct API: Prefer direct data manipulation (
obj.location.x = 1.0) over operators for performance and reliability in scripts. Operators carry overhead and context constraints. Use operators when undo integration or user-facing side effects are required.
Related
- bpy.context
- bpy.types
- bpy.data
Python API Core
| Name | Description | Path |
|---|---|---|
| bpy.data | Access to all ID data-blocks in the .blend file (meshes, objects, materials, scenes, …) | bpy-data.md |
| bpy.context | Read-only window-manager and editor context; active object, selection, scene, area | bpy-context.md |
| bpy.ops | Operator execution interface — call built-in and registered operators from Python | bpy-ops.md |
| bpy.types | All RNA-registered types: data-blocks, UI base classes (Panel/Operator/Menu), nodes | bpy-types.md |
| bpy.props | Property type constructors for extending Blender data via RNA | bpy-props.md |
| bpy.utils | Class registration, resource paths, icon preview management, name utilities | bpy-utils.md |
| bpy.app | App version/build info, event handlers, timers, and translation support | bpy-app.md |
| bpy.info | Quickstart, API overview, best practices, and common gotchas | bpy-info.md |