| name | blender-syntax-modifiers |
| description | Covers Blender modifier stack API including adding/removing modifiers, applying modifiers via operators, evaluated mesh access via depsgraph, Geometry Nodes modifier input identifiers, and common AEC modifiers (Array, Boolean, Solidify). Activates when working with modifiers programmatically, applying modifiers to meshes, or accessing Geometry Nodes inputs. |
| license | MIT |
| compatibility | Designed for Claude Code. Requires Blender 3.x/4.x/5.x with Python. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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
import bpy
obj = bpy.data.objects.get("Wall")
if obj is None:
raise RuntimeError("Object 'Wall' not found")
mod = obj.modifiers.new(name="MyArray", type='ARRAY')
mod.count = 5
mod.relative_offset_displace = (1.0, 0.0, 0.0)
Pattern 2: Remove a Modifier
import bpy
obj = bpy.data.objects.get("Wall")
mod = obj.modifiers.get("MyArray")
if mod is not None:
obj.modifiers.remove(mod)
for mod in obj.modifiers[:]:
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.
import bpy
obj = bpy.data.objects.get("Wall")
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
with bpy.context.temp_override(object=obj, active_object=obj):
bpy.ops.object.modifier_apply(modifier="MyArray")
with bpy.context.temp_override(object=obj, active_object=obj):
for mod in obj.modifiers[:]:
bpy.ops.object.modifier_apply(modifier=mod.name)
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.
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)}")
for v in mesh_eval.vertices:
world_co = obj_eval.matrix_world @ v.co
finally:
obj_eval.to_mesh_clear()
Pattern 5: Geometry Nodes Modifier — Assign and Configure Inputs
import bpy
obj = bpy.data.objects.get("Building")
mod = obj.modifiers.new(name="GeoNodes", type='NODES')
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
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")
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
bpy.context.view_layer.update()
for item in mod.node_group.interface.items_tree:
if item.item_type == 'SOCKET' and item.in_out == 'INPUT':
identifier = item.identifier
name = item.name
socket_type = item.socket_type
current_value = mod.get(identifier)
print(f"{name} ({socket_type}): {identifier} = {current_value}")
Pattern 6: Reorder Modifiers
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)
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
import bpy
obj = bpy.data.objects.get("Column")
mod = obj.modifiers.new(name="ColumnArray", type='ARRAY')
mod.use_relative_offset = True
mod.relative_offset_displace = (2.0, 0.0, 0.0)
mod.count = 10
mod.use_constant_offset = True
mod.constant_offset_displace = (3.0, 0.0, 0.0)
mod.fit_type = 'FIT_LENGTH'
mod.fit_length = 30.0
mod.fit_type = 'FIT_CURVE'
mod.curve = bpy.data.objects.get("ArrayPath")
Boolean Modifier — Wall Openings
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'
mod.object = opening
mod.solver = 'FAST'
opening.hide_set(True)
opening.hide_render = True
Solidify Modifier — Wall Thickness
import bpy
obj = bpy.data.objects.get("WallSurface")
mod = obj.modifiers.new(name="WallThickness", type='SOLIDIFY')
mod.thickness = 0.3
mod.offset = -1.0
mod.use_even_offset = True
mod.use_quality_normals = True
Copy Modifier Settings Between Objects
import bpy
source = bpy.data.objects.get("SourceWall")
target = bpy.data.objects.get("TargetWall")
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
import bpy
obj = bpy.data.objects.get("Wall")
mod = obj.modifiers.get("MyArray")
if mod is not None:
print(f"Found modifier: {mod.name}, type: {mod.type}")
has_boolean = any(m.type == 'BOOLEAN' for m in obj.modifiers)
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