blender-syntax-modifiers

Installation
SKILL.md

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

# 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

# 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.

# 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)
# 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.

# 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

# 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()
# 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

# 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

# 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

# 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

# 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

# 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

# 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

Official Sources

Related skills

More from openaec-foundation/computational-design-day-delft-march-2026

Installs
1
GitHub Stars
1
First Seen
Mar 24, 2026