通过 Python bpy API 实现全面的 Blender 自动化,涵盖程序化建模、材质节点、灯光、渲染和资产生成,包含已验证的齿轮/...
Invoke when the user wants to:
bash
# Install Blender
apt-get install blender # Linux (Debian/Ubuntu)
# Or: brew install blender # macOS
# Verify
blender --version
bash
blender --background --python script.py
python
import bpy, math
# Clear scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for mat in list(bpy.data.materials):
bpy.data.materials.remove(mat)
python
def create_gear(name, radius=2.0, teeth=16, thickness=0.8):
"""Create a gear with teeth and center hole"""
# Base cylinder
bpy.ops.mesh.primitive_cylinder_add(
vertices=teeth * 4,
radius=radius,
depth=thickness,
location=(0, 0, 0)
)
gear = bpy.context.object
gear.name = name
# Edit mode: select vertices at tooth positions
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
for v in gear.data.vertices:
angle = math.atan2(v.co.y, v.co.x)
tooth_angle = 2 * math.pi / teeth
angle_diff = abs((angle % tooth_angle) - tooth_angle / 2)
if angle_diff < tooth_angle * 0.35:
v.select = True
# Extrude and scale for teeth
bpy.ops.mesh.extrude_region_move(
TRANSFORM_OT_translate={"value": (0, 0, 0)}
)
bpy.ops.transform.resize(
value=((radius + 0.4) / radius,) * 2 + (1,),
orient_type='GLOBAL'
)
bpy.ops.object.mode_set(mode='OBJECT')
# Center hole via Boolean
bpy.ops.mesh.primitive_cylinder_add(
vertices=32, radius=0.5,
depth=thickness * 1.5, location=(0, 0, 0)
)
cutter = bpy.context.object
bool_mod = gear.modifiers.new(name="Hole", type='BOOLEAN')
bool_mod.operation = 'DIFFERENCE'
bool_mod.object = cutter
bpy.context.view_layer.objects.active = gear
gear.select_set(True)
bpy.ops.object.modifier_apply(modifier="Hole")
bpy.data.objects.remove(cutter, do_unlink=True)
# Modifier stack
bevel = gear.modifiers.new(name="Bevel", type='BEVEL')
bevel.width = 0.05; bevel.segments = 2; bevel.limit_method = 'ANGLE'
subdiv = gear.modifiers.new(name="Subdivision", type='SUBSURF')
subdiv.levels = 1; subdiv.render_levels = 2
return gear
python
def create_metal_material(name, base_color, metallic=0.85, roughness=0.25,
noise_scale=30.0, use_brushed=True):
"""Create a procedural metal material with optional brushed effect"""
mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
output = nodes.new(type='ShaderNodeOutputMaterial')
output.location = (400, 0)
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
bsdf.location = (0, 0)
bsdf.inputs['Base Color'].default_value = base_color
bsdf.inputs['Metallic'].default_value = metallic
bsdf.inputs['Roughness'].default_value = roughness
if use_brushed:
tex = nodes.new(type='ShaderNodeTexCoord')
tex.location = (-400, 100)
noise = nodes.new(type='ShaderNodeTexNoise')
noise.location = (-200, 0)
noise.inputs['Scale'].default_value = noise_scale
noise.inputs['Detail'].default_value = 2.0
ramp = nodes.new(type='ShaderNodeValToRGB')
ramp.location = (0, 100)
ramp.color_ramp.elements[0].color = (
base_color[0]*0.8, base_color[1]*0.8, base_color[2]*0.8, 1.0)
ramp.color_ramp.elements[1].color = (
base_color[0]*1.1, base_color[1]*1.1, base_color[2]*1.1, 1.0)
links.new(tex.outputs['Object'], noise.inputs['Vector'])
links.new(noise.outputs['Fac'], ramp.inputs['Fac'])
links.new(ramp.outputs['Color'], bsdf.inputs['Base Color'])
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
return mat
python
def setup_lighting(base_intensity=600):
"""Standard 3-point lighting: key, fill, rim"""
# Key light (main)
key = bpy.ops.object.light_add(
type='AREA', location=(5, -4, 6),
rotation=(0.8, 0, 0.7))
key = bpy.context.object
key.data.energy = base_intensity
key.data.size = 4
# Fill light
fill = bpy.ops.object.light_add(
type='AREA', location=(-4, 3, 3),
rotation=(0.5, 0, -1.0))
fill = bpy.context.object
fill.data.energy = base_intensity * 0.5
fill.data.size = 3
# Rim/back light
rim = bpy.ops.object.light_add(
type='AREA', location=(0, 5, 5),
rotation=(0.5, 0, 1.57))
rim = bpy.context.object
rim.data.energy = base_intensity * 0.4
rim.data.size = 2
python
def setup_camera(location=(5.5, -4.5, 3.5), target=(0, 0, 0)):
bpy.ops.object.camera_add(location=location)
cam = bpy.context.object
# Point camera at target dynamically using quaternions (mathutils)
direction = mathutils.Vector(target) - cam.location
rot_quat = direction.to_track_quat('-Z', 'Y')
cam.rotation_euler = rot_quat.to_euler()
bpy.context.scene.camera = cam
return cam
python
def render(output_path="/tmp/render.png", engine='CYCLES',
width=1080, height=1080, samples=64):
scene = bpy.context.scene
scene.render.engine = engine
scene.render.resolution_x = width
scene.render.resolution_y = height
scene.render.filepath = output_path
scene.render.image_settings.file_format = 'PNG'
if engine == 'CYCLES':
scene.cycles.samples = samples
scene.cycles.use_denoising = True
# Configure Metal GPU on macOS if available
try:
bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'METAL'
bpy.context.preferences.addons['cycles'].preferences.get_devices()
for d in bpy.context.preferences.addons['cycles'].preferences.devices:
if d.type == 'METAL':
d.use = True
except Exception:
pass
bpy.ops.render.render(write_still=True)
python
def create_glass_material(name="Glass", color=(0.9, 0.95, 1.0, 1.0), roughness=0.1, ior=1.309):
"""Create refractive glass or ice material. IOR: 1.309 (ice), 1.5 (glass)"""
mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
output = nodes.new(type='ShaderNodeOutputMaterial')
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
bsdf.inputs['Base Color'].default_value = color
bsdf.inputs['Roughness'].default_value = roughness
bsdf.inputs['IOR'].default_value = ior
# Enable transparency (Transmission)
if 'Transmission Weight' in bsdf.inputs:
bsdf.inputs['Transmission Weight'].default_value = 1.0
elif 'Transmission' in bsdf.inputs:
bsdf.inputs['Transmission'].default_value = 1.0
return mat
When carving grooves or features into a mesh:
EXACT solver delete the geometry (vertex count becomes 0). Carve individual loops one-by-one instead.EXACT solver to fail and skip subtraction.curve_data.use_fill_caps = True before conversion. Combine with a Subdivision Surface modifier for clean rounded tips.For perfect geometry and flawless renderings on regular solids (like spheres), define seams directly in the shader nodes using object coordinates instead of modifying geometry:
SMOOTHERSTEP mask from $[w, w+s]$ to $[0, 1]$. Use this mask to mix Colors (Black vs. Orange), Roughness, and normal Height inputs into a single Bump node.When creating models intended for physical 3D printing (e.g. FDM/SLA):
Real-World Units: Explicitly set the scene scale. Usually, 1 Blender unit represents 1 cm or 1 mm: python
scene = bpy.context.scene
scene.unit_settings.system = 'METRIC'
scene.unit_settings.scale_length = 0.01 # 1 unit = 1 cm (Pop Mart standard)
Hollow/Wall Thickness: 3D models cannot have zero-thickness surfaces. Use the Solidify modifier to give surfaces a physical thickness (typically 1.2mm to 2.0mm):
python
solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
solid.thickness = 0.12 # 1.2 mm (if 1 unit = 1 cm)
solid.offset = -1.0 # Solidify inward
Poly Count Optimization: Large vertex counts slow down slicers. Use a Decimate modifier to reduce poly count prior to export:
python
decimate = obj.modifiers.new(name="Decimate", type='DECIMATE')
decimate.ratio = 0.15 # Keep 15% of faces
Watertight STL Export: Ensure geometry is manifold, then export using standard operators: python
# Blender 4.0+ uses standard wm operators for STL export
bpy.ops.wm.stl_export(filepath="model.stl", export_selected=True)
# Older Blender (<4.0):
# bpy.ops.export_mesh.stl(filepath="model.stl", use_selection=True)
To align procedural elements to concept art (front/side/top views), programmatically load images as background planes:
python
def load_reference_image(filepath, name="ReferenceImage", location=(0, 0, 0), rotation=(1.5708, 0, 0)):
# Create an empty object of type IMAGE
bpy.ops.object.empty_add(type='IMAGE', location=location, rotation=rotation)
empty = bpy.context.object
empty.name = name
# Load and assign the image data
try:
img = bpy.data.images.load(filepath)
empty.data = img
empty.empty_display_size = 5.0 # scale size
# Optional: set opacity
empty.use_empty_image_alpha = True
empty.empty_image_depth = 'BACK' # display behind mesh
except Exception as e:
print(f"Error loading reference image: {e}")
EngineBest ForNotesBLENDER_EEVEEFast preview, real-timeNo denoiser needed, good for quick checksCYCLESPhotorealisticEssential for glass reflection and refraction. Use samples=64 or 128 with Denoising.
'FLOAT' (called 'FAST' in Blender <4.0), ensure cutter overlapping boundaries, or perform Boolean carving one-by-one.curve_data.use_fill_caps = True.Roughness=0.3, Metallic=0.85), a high-contrast rim/back light, and use the CYCLES render engine.use_nodes will be removed as nodes are always enabled. Check compatibility but safely use it for current versions.