| name | blender-syntax-materials |
| description | Use when creating or modifying Blender materials and shader nodes via Python. Prevents the breaking change of using old Principled BSDF input names (e.g., "Subsurface" renamed to "Subsurface Weight" in 4.0). Covers material creation, node tree setup, Principled BSDF input mapping, UV assignment, texture loading, and material slot management. Keywords: material, shader, Principled BSDF, node tree, texture, UV map, material_slot, ShaderNodeBsdfPrincipled, node links, Blender materials, assign material to object, create material from code, change color.
|
| 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-materials
Quick Reference
Critical Warnings
NEVER use Blender 3.x Principled BSDF socket names in 4.0+ code. Sockets were renamed in 4.0 (e.g., "Subsurface" -> "Subsurface Weight"). Using old names raises KeyError.
NEVER use mat.blend_method in Blender 4.2+. This property was removed. Use mat.surface_render_method instead.
NEVER access UV data per-vertex. UV coordinates are stored per-loop (per face corner). Access via uv_layers.data[loop_index].
NEVER call bpy.data.images.load() without check_existing=True. Omitting it creates duplicate image data blocks on repeated loads.
NEVER set colorspace_settings.name incorrectly. Use 'sRGB' for color/diffuse textures and 'Non-Color' for normal maps, roughness, metallic, and other data textures.
ALWAYS set mat.use_nodes = True before accessing mat.node_tree. Without this, node_tree is None.
ALWAYS store the reference returned by bpy.data.materials.new(). Blender may append .001 if the name already exists.
Version Decision Tree
Creating material code?
├── Target Blender 4.0+ ONLY?
│ ├── YES → Use 4.0+ Principled BSDF socket names directly
│ └── NO → Use version-safe helper (see Pattern 3)
│
Setting transparency/alpha blending?
├── Blender 4.2+?
│ ├── YES → mat.surface_render_method = 'BLENDED'
│ └── NO (3.x / 4.0-4.1) → mat.blend_method = 'BLEND'
│
Connecting texture to Principled BSDF?
├── Color texture (diffuse, base color)?
│ └── Connect to "Base Color" input (all versions)
├── Normal map?
│ └── Connect via ShaderNodeNormalMap → "Normal" input
└── Data texture (roughness, metallic, AO)?
└── Set image colorspace to 'Non-Color'
Essential Patterns
Pattern 1: Create and Assign a Material
import bpy
mat = bpy.data.materials.new(name="MyMaterial")
mat.use_nodes = True
obj = bpy.context.active_object
if obj.data.materials:
obj.data.materials[0] = mat
else:
obj.data.materials.append(mat)
Pattern 2: Build a Complete Node Tree
mat = bpy.data.materials.new("NodeMaterial")
mat.use_nodes = True
tree = mat.node_tree
tree.nodes.clear()
principled = tree.nodes.new('ShaderNodeBsdfPrincipled')
principled.location = (0, 0)
output = tree.nodes.new('ShaderNodeOutputMaterial')
output.location = (400, 0)
tree.links.new(principled.outputs["BSDF"], output.inputs["Surface"])
Pattern 3: Version-Safe Principled BSDF Input Access
import bpy
def set_principled_value(node, socket_3x, socket_4x, value):
"""Set Principled BSDF input with version-safe socket name."""
if bpy.app.version >= (4, 0, 0):
socket_name = socket_4x
else:
socket_name = socket_3x
if socket_name in node.inputs:
node.inputs[socket_name].default_value = value
set_principled_value(principled, "Subsurface", "Subsurface Weight", 0.1)
set_principled_value(principled, "Specular", "Specular IOR Level", 0.5)
set_principled_value(principled, "Transmission", "Transmission Weight", 1.0)
set_principled_value(principled, "Coat", "Coat Weight", 0.3)
set_principled_value(principled, "Sheen", "Sheen Weight", 0.1)
set_principled_value(principled, "Emission", "Emission Color", (1, 1, 1, 1))
Pattern 4: Texture Node with UV Mapping
tree = mat.node_tree
tex_coord = tree.nodes.new('ShaderNodeTexCoord')
tex_coord.location = (-800, 0)
mapping = tree.nodes.new('ShaderNodeMapping')
mapping.location = (-600, 0)
tex_node = tree.nodes.new('ShaderNodeTexImage')
tex_node.location = (-400, 0)
img = bpy.data.images.load("/path/to/texture.png", check_existing=True)
tex_node.image = img
tree.links.new(tex_coord.outputs["UV"], mapping.inputs["Vector"])
tree.links.new(mapping.outputs["Vector"], tex_node.inputs["Vector"])
tree.links.new(tex_node.outputs["Color"], principled.inputs["Base Color"])
Pattern 5: Alpha/Transparency Setup (Version-Dependent)
mat.surface_render_method = 'BLENDED'
if bpy.app.version >= (4, 2, 0):
mat.surface_render_method = 'BLENDED'
else:
mat.blend_method = 'BLEND'
Principled BSDF Socket Rename Table (3.x -> 4.0+)
| Blender 3.x Name | Blender 4.0+ Name | Notes |
|---|
Base Color | Base Color | Unchanged |
Subsurface | Subsurface Weight | Renamed |
Subsurface Color | REMOVED | Use Base Color instead |
Specular | Specular IOR Level | Renamed |
Specular Tint | Specular Tint | Type changed: Float -> Color |
Transmission | Transmission Weight | Renamed |
Coat | Coat Weight | Renamed |
Sheen | Sheen Weight | Renamed |
Emission | Emission Color | Renamed |
Metallic | Metallic | Unchanged |
Roughness | Roughness | Unchanged |
IOR | IOR | Unchanged |
Alpha | Alpha | Unchanged |
Normal | Normal | Unchanged |
Common Operations
Assign Material to Specific Faces
import bpy
obj = bpy.context.active_object
mat_a = bpy.data.materials.new("Material_A")
mat_b = bpy.data.materials.new("Material_B")
obj.data.materials.append(mat_a)
obj.data.materials.append(mat_b)
for polygon in obj.data.polygons:
if polygon.center.z > 0:
polygon.material_index = 1
else:
polygon.material_index = 0
Add Material Slot via Operator
import bpy
obj = bpy.context.active_object
bpy.context.view_layer.objects.active = obj
bpy.ops.object.material_slot_add()
obj.material_slots[obj.active_material_index].material = mat
bpy.ops.object.material_slot_remove()
Access Default Principled BSDF
When use_nodes = True is set on a new material, Blender creates a default Principled BSDF and Material Output automatically.
mat = bpy.data.materials.new("QuickMaterial")
mat.use_nodes = True
principled = mat.node_tree.nodes.get("Principled BSDF")
UV Layer Management
mesh = obj.data
for uv_layer in mesh.uv_layers:
print(uv_layer.name)
active_uv = mesh.uv_layers.active
new_uv = mesh.uv_layers.new(name="ProjectionUV")
for loop_idx in range(len(mesh.loops)):
uv = active_uv.data[loop_idx].uv
for loop_idx in range(len(mesh.loops)):
active_uv.data[loop_idx].uv.x *= 2.0
active_uv.data[loop_idx].uv.y *= 2.0
Load and Configure Image Textures
import bpy
img = bpy.data.images.load("/path/to/texture.png", check_existing=True)
img = bpy.data.images.new("BakeTarget", width=2048, height=2048)
tex_node = tree.nodes.new('ShaderNodeTexImage')
tex_node.image = img
tex_node.interpolation = 'Linear'
tex_node.projection = 'FLAT'
tex_node.extension = 'REPEAT'
img.colorspace_settings.name = 'sRGB'
Normal Map Setup
tree = mat.node_tree
normal_tex = tree.nodes.new('ShaderNodeTexImage')
normal_tex.image = bpy.data.images.load("/path/to/normal.png", check_existing=True)
normal_tex.image.colorspace_settings.name = 'Non-Color'
normal_map = tree.nodes.new('ShaderNodeNormalMap')
tree.links.new(normal_tex.outputs["Color"], normal_map.inputs["Color"])
tree.links.new(normal_map.outputs["Normal"], principled.inputs["Normal"])
Remove All Materials from Object
obj.data.materials.clear()
Common Shader Node Types
| Node Type String | Class | Purpose |
|---|
'ShaderNodeBsdfPrincipled' | Principled BSDF | Main PBR shader |
'ShaderNodeOutputMaterial' | Material Output | Final output |
'ShaderNodeTexImage' | Image Texture | Load image files |
'ShaderNodeTexCoord' | Texture Coordinate | UV, Object, Generated coords |
'ShaderNodeMapping' | Mapping | Transform texture coordinates |
'ShaderNodeNormalMap' | Normal Map | Convert image to normal vector |
'ShaderNodeMixShader' | Mix Shader | Blend two shaders |
'ShaderNodeBsdfTransparent' | Transparent BSDF | Transparency shader |
'ShaderNodeBsdfGlass' | Glass BSDF | Glass shader |
'ShaderNodeMath' | Math | Scalar math operations |
'ShaderNodeMixRGB' | Mix (Color) | Blend colors (3.x name) |
'ShaderNodeMix' | Mix | Blend colors/vectors (4.0+) |
'ShaderNodeValToRGB' | Color Ramp | Map value to color gradient |
'ShaderNodeBump' | Bump | Generate bump from height |
'ShaderNodeSeparateXYZ' | Separate XYZ | Split vector components |
'ShaderNodeCombineXYZ' | Combine XYZ | Merge components to vector |
'ShaderNodeTexNoise' | Noise Texture | Procedural noise |
'ShaderNodeTexVoronoi' | Voronoi Texture | Procedural voronoi |
'ShaderNodeGroup' | Node Group | Reusable node group instance |
EEVEE vs Cycles Material Differences
| Property | EEVEE | Cycles | Notes |
|---|
| Transparency | Requires surface_render_method (4.2+) or blend_method (3.x-4.1) | Automatic from Alpha | EEVEE needs explicit config |
| Screen Space Refraction | mat.use_screen_refraction = True | Not needed | EEVEE-specific |
| Backface Culling | mat.use_backface_culling | mat.use_backface_culling | Same API |
| Displacement | mat.cycles.displacement_method | mat.cycles.displacement_method | Cycles-only |
| Shadow Mode | mat.shadow_method (3.x-4.1) | Automatic | EEVEE-specific |
Version-Specific Migration Rules
| Feature | Blender 3.x | Blender 4.0+ | Blender 4.2+ |
|---|
| Principled BSDF sockets | Old names | New names (see table) | Same as 4.0 |
| Alpha blending (EEVEE) | mat.blend_method = 'BLEND' | mat.blend_method = 'BLEND' | mat.surface_render_method = 'BLENDED' |
| Shadow method (EEVEE) | mat.shadow_method | mat.shadow_method | Property restructured |
| Mix node | 'ShaderNodeMixRGB' | 'ShaderNodeMix' (preferred) | 'ShaderNodeMix' |
| Specular Tint type | Float (0.0-1.0) | Color (RGBA) | Color (RGBA) |
Reference Links
Official Sources