blender-syntax-modifiers

NEVER apply modifiers while the object is in Edit Mode — modifier_apply requires Object Mode. Switch with bpy.ops.object.mode_set(mode='OBJECT') first.

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "blender-syntax-modifiers"
description: "NEVER apply modifiers while the object is in Edit Mode — modifier_apply requires Object Mode. Switch with bpy.ops.object.mode_set(mode='OBJECT') first."
license: "MIT"
---

# blender-syntax-modifiers

## Quick Reference

### Critical Warnings

**NEVER** apply modifiers while the object is in Edit Mode — `modifier_apply` requires Object Mode. Switch with `bpy.ops.object.mode_set(mode='OBJECT')` first.

**NEVER** use dict context overrides for `modifier_apply` in Blender 4.0+ — use `context.temp_override()` instead. Dict overrides raise `TypeError`.

**NEVER** read `obj.data.vertices` expecting post-modifier results — use `obj.evaluated_get(depsgraph).to_mesh()` to access evaluated mesh data.

**NEVER** forget `obj_eval.to_mesh_clear()` after `obj_eval.to_mesh()` — this causes memory leaks. ALWAYS use a `try/finally` block.

**NEVER** assume Geometry Nodes modifier input identifiers match socket names — identifiers are auto-generated as `Socket_N`. ALWAYS look up identifiers via `modifier.node_group.interface.items_tree`.

**NEVER** modify the evaluated mesh — it is read-only. Modify the original object, then let the depsgraph re-evaluate.

### Modifier Type Decision Tree

```
Need to add geometry procedurally?
├── Repeat geometry in a pattern → ARRAY
├── Combine/subtract meshes → BOOLEAN
├── Add thickness to surfaces → SOLIDIFY
├── Subdivide for smoothness → SUBSURF
├── Symmetry across an axis → MIRROR
├── Edge beveling → BEVEL
├── Custom node-based logic → NODES (Geometry Nodes)
└── Reduce polygon count → DECIMATE

Need deformation?
├── Skeleton-based → ARMATURE
├── Surface projection → SHRINKWRAP
├── Volume-based deform → MESH_DEFORM
├── Grid-based deform → LATTICE
└── Along a path → CURVE
```

### AEC-Relevant Modifiers

| Modifier | AEC Use Case | Type String |
|----------|-------------|-------------|
| Array | Repetitive elements (columns, railings, facade panels) | `'ARRAY'` |
| Boolean | Wall openings, structural cutouts, MEP penetrations | `'BOOLEAN'` |
| Solidify | Thickness to imported 2D geometry, wall shells | `'SOLIDIFY'` |
| Geometry Nodes | Parametric components, distribution, rule-based generation | `'NODES'` |
| Mirror | Symmetric buildings, reflected floor plans | `'MIRROR'` |
| Bevel | Edge finishing for architectural details | `'BEVEL'` |

---

## Essential Patterns

### Pattern 1: Add a Modifier

```python
# Blender 3.x/4.x/5.x: add modifier to an object
import bpy

obj = bpy.data.objects.get("Wall")
if obj is None:
    raise RuntimeError("Object 'Wall' not found")

# Add modifier: returns the new Modifier object
mod = obj.modifiers.new(name="MyArray", type='ARRAY')
# name: display name (Blender may append .001 if duplicate)
# type: modifier type enum string (see table above)

# Configure modifier properties
mod.count = 5
mod.relative_offset_displace = (1.0, 0.0, 0.0)
```

### Pattern 2: Remove a Modifier

```python
# Blender 3.x/4.x/5.x: remove modifier by reference or name
import bpy

obj = bpy.data.objects.get("Wall")

# Remove by reference
mod = obj.modifiers.get("MyArray")
if mod is not None:
    obj.modifiers.remove(mod)

# Remove all modifiers
for mod in obj.modifiers[:]:  # Copy list — modifiers change during removal
    obj.modifiers.remove(mod)
```

### Pattern 3: Apply a Modifier (Version-Critical)

Applying a modifier permanently bakes its effect into the mesh. This is an IRREVERSIBLE operation. ALWAYS requires Object Mode.

```python
# Blender 3.2+/4.x/5.x: apply modifier using temp_override
import bpy

obj = bpy.data.objects.get("Wall")

# Ensure Object Mode
if bpy.context.mode != 'OBJECT':
    bpy.ops.object.mode_set(mode='OBJECT')

# Apply a single modifier
with bpy.context.temp_override(object=obj, active_object=obj):
    bpy.ops.object.modifier_apply(modifier="MyArray")

# Apply ALL modifiers (copy list: stack changes during apply)
with bpy.context.temp_override(object=obj, active_object=obj):
    for mod in obj.modifiers[:]:
        bpy.ops.object.modifier_apply(modifier=mod.name)
```

```python
# Blender < 3.2 ONLY (legacy: do NOT use for new code)
override = bpy.context.copy()
override['object'] = obj
override['active_object'] = obj
bpy.ops.object.modifier_apply(override, modifier="MyArray")
```

### Pattern 4: Evaluated Mesh via Depsgraph (Preview Without Applying)

Use this to read the post-modifier mesh WITHOUT permanently applying modifiers.

```python
# Blender 3.x/4.x/5.x: read evaluated mesh data
import bpy

obj = bpy.data.objects.get("Wall")
depsgraph = bpy.context.evaluated_depsgraph_get()
obj_eval = obj.evaluated_get(depsgraph)
mesh_eval = obj_eval.to_mesh()

try:
    print(f"Original verts: {len(obj.data.vertices)}")
    print(f"Evaluated verts: {len(mesh_eval.vertices)}")

    # Read vertex positions in world space
    for v in mesh_eval.vertices:
        world_co = obj_eval.matrix_world @ v.co
        # process world_co...
finally:
    obj_eval.to_mesh_clear()  # ALWAYS clean up
```

### Pattern 5: Geometry Nodes Modifier: Assign and Configure Inputs

```python
# Blender 4.0+: assign node group and set input values
import bpy

obj = bpy.data.objects.get("Building")
mod = obj.modifiers.new(name="GeoNodes", type='NODES')

# Assign an existing node group
node_group = bpy.data.node_groups.get("AEC_WallGenerator")
if node_group is None:
    raise RuntimeError("Node group 'AEC_WallGenerator' not found")
mod.node_group = node_group

# Find input identifiers by name (REQUIRED: identifiers are NOT names)
def get_gn_input_identifier(modifier, input_name):
    """Return the Socket_N identifier for a named GN input."""
    for item in modifier.node_group.interface.items_tree:
        if item.item_type == 'SOCKET' and item.in_out == 'INPUT':
            if item.name == input_name:
                return item.identifier
    raise KeyError(f"Input '{input_name}' not found in node group")

# Set input values using identifiers
height_id = get_gn_input_identifier(mod, "Height")
mod[height_id] = 3.5

thickness_id = get_gn_input_identifier(mod, "Thickness")
mod[thickness_id] = 0.2

# Force scene update after changing GN inputs
bpy.context.view_layer.update()
```

```python
# Blender 4.0+: list all GN modifier inputs
for item in mod.node_group.interface.items_tree:
    if item.item_type == 'SOCKET' and item.in_out == 'INPUT':
        identifier = item.identifier   # e.g., "Socket_2"
        name = item.name               # e.g., "Height"
        socket_type = item.socket_type  # e.g., "NodeSocketFloat"
        current_value = mod.get(identifier)
        print(f"{name} ({socket_type}): {identifier} = {current_value}")
```

### Pattern 6: Reorder Modifiers

```python
# Blender 4.0+/5.x: move modifier to specific position
import bpy

obj = bpy.data.objects.get("Wall")
with bpy.context.temp_override(object=obj):
    bpy.ops.object.modifier_move_to_index(modifier="Boolean", index=0)

# Blender 3.x/4.x/5.x: move up/down by one position
with bpy.context.temp_override(object=obj):
    bpy.ops.object.modifier_move_up(modifier="Boolean")
    bpy.ops.object.modifier_move_down(modifier="Array")
```

---

## Common Operations

### Array Modifier: Repetitive AEC Elements

```python
# Blender 3.x/4.x/5.x: create array of columns
import bpy

obj = bpy.data.objects.get("Column")
mod = obj.modifiers.new(name="ColumnArray", type='ARRAY')

# Fixed count
mod.use_relative_offset = True
mod.relative_offset_displace = (2.0, 0.0, 0.0)  # Spacing in object-relative units
mod.count = 10

# Constant offset (absolute units)
mod.use_constant_offset = True
mod.constant_offset_displace = (3.0, 0.0, 0.0)  # 3m spacing in scene units

# Fit to length
mod.fit_type = 'FIT_LENGTH'
mod.fit_length = 30.0  # Total span in scene units

# Fit to curve
mod.fit_type = 'FIT_CURVE'
mod.curve = bpy.data.objects.get("ArrayPath")
```

### Boolean Modifier: Wall Openings

```python
# Blender 3.x/4.x/5.x: create opening in wall
import bpy

wall = bpy.data.objects.get("Wall")
opening = bpy.data.objects.get("WindowOpening")

mod = wall.modifiers.new(name="WindowCut", type='BOOLEAN')
mod.operation = 'DIFFERENCE'     # 'INTERSECT', 'UNION', 'DIFFERENCE'
mod.object = opening             # Cutter object
mod.solver = 'FAST'              # 'FAST' or 'EXACT' (Blender 3.0+)
# EXACT is slower but handles edge cases better (coplanar faces, thin geometry)

# Hide cutter in viewport (common AEC pattern)
opening.hide_set(True)
opening.hide_render = True
```

### Solidify Modifier: Wall Thickness

```python
# Blender 3.x/4.x/5.x: add thickness to planar geometry
import bpy

obj = bpy.data.objects.get("WallSurface")
mod = obj.modifiers.new(name="WallThickness", type='SOLIDIFY')

mod.thickness = 0.3              # Wall thickness in scene units
mod.offset = -1.0                # -1 = outward, 0 = centered, 1 = inward
mod.use_even_offset = True       # Uniform thickness on angled surfaces
mod.use_quality_normals = True   # Better normals on complex geometry
```

### Copy Modifier Settings Between Objects

```python
# Blender 3.x/4.x/5.x: copy all modifiers from source to target
import bpy

source = bpy.data.objects.get("SourceWall")
target = bpy.data.objects.get("TargetWall")

# Use operator to copy modifiers
with bpy.context.temp_override(
    object=target,
    active_object=source,
    selected_objects=[target]
):
    bpy.ops.object.make_links_data(type='MODIFIERS')
```

### Check if Modifier Exists Before Operating

```python
# Blender 3.x/4.x/5.x: safe modifier access
import bpy

obj = bpy.data.objects.get("Wall")

# Check by name
mod = obj.modifiers.get("MyArray")
if mod is not None:
    print(f"Found modifier: {mod.name}, type: {mod.type}")

# Check by type
has_boolean = any(m.type == 'BOOLEAN' for m in obj.modifiers)

# Iterate with type filter
for mod in obj.modifiers:
    if mod.type == 'ARRAY':
        print(f"Array: {mod.name}, count: {mod.count}")
```

---

## Version-Specific Notes

| Feature | Blender 3.x | Blender 4.0+ |
|---------|-------------|--------------|
| Context override for `modifier_apply` | Dict override (deprecated 3.2) | `context.temp_override()` REQUIRED |
| GN node group sockets API | `node_group.inputs.new(...)` | `node_group.interface.new_socket(...)` |
| GN modifier input identifiers | `Input_N` (Blender 3.x) | `Socket_N` (Blender 4.0+) |
| `modifier_move_to_index` | Not available | Available |
| Boolean solver `'EXACT'` | Available (3.0+) | Available |
| `bmesh.from_object()` depsgraph param | Optional | REQUIRED |
| `BVHTree.FromObject()` depsgraph param | Optional | REQUIRED |

---

## Reference Links

- [references/methods.md](references/methods.md) — Complete API signatures for ObjectModifiers, Modifier base, and specific modifier types
- [references/examples.md](references/examples.md) — Working code examples for all common modifier operations
- [references/anti-patterns.md](references/anti-patterns.md) — What NOT to do with modifiers, with explanations

### Official Sources

- https://docs.blender.org/api/current/bpy.types.ObjectModifiers.html
- https://docs.blender.org/api/current/bpy.types.Modifier.html
- https://docs.blender.org/api/current/bpy.types.NodesModifier.html
- https://docs.blender.org/api/current/bpy.types.ArrayModifier.html
- https://docs.blender.org/api/current/bpy.types.BooleanModifier.html
- https://docs.blender.org/api/current/bpy.types.SolidifyModifier.html
- https://docs.blender.org/api/current/bpy.types.Depsgraph.html

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY