Error in user YAML: (<unknown>): found a tab character that violate indentation while scanning a plain scalar at line 3 column 3
---
- oeasy Python 0746
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
- 本教程同步发布在:
- 个人网站: `https://oeasy.org`
- 蓝桥云课: `https://www.lanqiao.cn/courses/3584`
- GitHub: `https://github.com/overmind1980/oeasy-python-tutorial`
- Gitee: `https://gitee.com/overmind1980/oeasypython`
---import bpy
def clear_scene():
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
def create_man():
mat = bpy.data.materials.new('blue')
color = (0, 0, 1, 1)
mat.use_nodes = True
mat.node_tree.nodes["Principled BSDF"].inputs['Base Color'].default_value = color
man = bpy.data.objects.new("man", None)
bpy.data.collections["Collection"].objects.link(man)
bpy.ops.mesh.primitive_uv_sphere_add()
head = bpy.context.object
head.location = (0,0,2.5)
head.data.materials.append(mat)
head.parent = man
bpy.ops.mesh.primitive_cone_add()
body = bpy.context.object
body.location = (0,0,1)
body.parent = man
body.data.materials.append(mat)
def create_plane():
bpy.ops.mesh.primitive_plane_add()
plane = bpy.context.object
plane.scale = (10,10,1)
def create_camera():
bpy.ops.object.camera_add()
camera = bpy.context.object
camera.location = (3.059990406036377, -9.885231018066406, 12.905241966247559)
camera.rotation_euler = (0.7260572910308838, -6.679311326251991e-09, 0.30019673705101013)
bpy.context.scene.camera = camera
def create_spot():
bpy.ops.object.light_add(type='SPOT')
spot = bpy.context.object
spot.location = (-1.2811667919158936, -3.316868543624878, 12.282358169555664)
spot.rotation_euler = (0.34208473563194275, 1.8850268901360323e-08, -0.3071775436401367)
spot.data.energy = 1000
def render():
bpy.context.scene.render.resolution_x = 320
bpy.context.scene.render.resolution_y = 240
bpy.context.scene.render.resolution_percentage = 50
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.filepath = '/tmp/render2.png'
bpy.ops.render.render(write_still=True)
clear_scene()
create_man()
create_plane()
create_camera()
create_spot()
render()
- 蓝光超标
- 有发光效果
- 可以得到小鸟
import bpy
def clear_scene():
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
def create_purple_bird():
mat = bpy.data.materials.new('blue')
mat.use_nodes = True
color = (2.2, 2, 112, 1)
mat.node_tree.nodes["Principled BSDF"].inputs['Base Color'].default_value = color
bird = bpy.data.objects.new("bird", None)
bpy.data.collections["Collection"].objects.link(bird)
bpy.ops.mesh.primitive_uv_sphere_add()
body=bpy.context.object
body.scale = (1.3512587547302246, 1.6976544857025146, 1.477585792541504)
body.location = (0.0, 0.0, 1.9078407287597656)
body.rotation_euler = (0.33339381217956543, 0.8460186123847961, -1.282267689704895)
body.data.materials.append(mat)
body.parent = bird
body.name = "body"
bpy.ops.mesh.primitive_uv_sphere_add()
head=bpy.context.object
head.scale = (1.0995900630950928, 0.9614527821540833, 1.0065921545028687)
head.location = (-0.7161910533905029, -0.06186628341674805, 2.9344260692596436)
head.rotation_euler = (-0.9862819314002991, 1.3689559698104858, 0.865427553653717)
head.data.materials.append(mat)
head.parent = bird
head.name = "head"
bpy.ops.mesh.primitive_cone_add()
mouth=bpy.context.object
mouth.scale = (0.2851811349391937, 0.2851811349391937, 0.2851811945438385)
mouth.location = (-1.718659520149231, 0.005130168981850147, 3.438387632369995)
mouth.rotation_euler = (-0.9014008641242981, 0.8561369776725769, -4.252285957336426)
mouth.data.materials.append(mat)
mouth.parent = bird
mouth.name = "mouth"
bpy.ops.mesh.primitive_cone_add()
tail=bpy.context.object
tail.scale = (0.5862210392951965, 0.5862210392951965, 0.5862210392951965)
tail.location = (1.4573625326156616, -0.1262585073709488, 2.4004876613616943)
tail.rotation_euler = (-0.0, -1.7943094968795776, -0.0)
tail.data.materials.append(mat)
tail.parent = bird
tail.name = "tail"
bpy.ops.mesh.primitive_ico_sphere_add()
wing1=bpy.context.object
wing1.scale = (0.9348270893096924, 0.7513998746871948, 0.6549702882766724)
wing1.location = (0.28463542461395264, -1.0785921812057495, 2.1733691692352295)
wing1.rotation_euler = (-0.06378283351659775, -0.3458176255226135, 0.06053333729505539)
wing1.data.materials.append(mat)
wing1.parent = bird
wing1.name = "wing1"
bpy.ops.mesh.primitive_ico_sphere_add()
wing2=bpy.context.object
wing2.scale = (0.9348270893096924, 0.7513998746871948, 0.6549702882766724)
wing2.location = (0.28463542461395264, 1.1649233102798462, 2.1733691692352295)
wing2.rotation_euler = (-0.06378283351659775, -0.3458176255226135, 0.06053333729505539)
wing2.data.materials.append(mat)
wing2.parent = bird
wing2.name = "wing2"
bpy.ops.mesh.primitive_uv_sphere_add()
eye1=bpy.context.object
eye1.scale = (0.19664300978183746, 0.19664300978183746, 0.19664300978183746)
eye1.location = (-0.9229257702827454, -0.7575194239616394, 3.5738308429718018)
eye1.data.materials.append(mat)
eye1.parent = bird
eye1.name = "eye1"
bpy.ops.mesh.primitive_uv_sphere_add()
eye2=bpy.context.object
eye2.scale = (0.19664300978183746, 0.19664300978183746, 0.19664300978183746)
eye2.location = (-0.9229257702827454, 0.634847104549408, 3.5738308429718018)
eye2.data.materials.append(mat)
eye2.parent = bird
eye2.name = "eye2"
def create_plane():
bpy.ops.mesh.primitive_plane_add()
plane = bpy.context.object
plane.scale = (10.0, 10.0, 1.0)
def create_camera():
bpy.ops.object.camera_add()
camera = bpy.context.object
camera.location = (-0.1523854285478592, -12.848016738891602, 9.196365356445312)
camera.rotation_euler = (1.135628342628479, 1.113783127948409e-07, -0.01396263763308525)
bpy.context.scene.camera = camera
def create_spot():
bpy.ops.object.light_add(type='SPOT')
spot = bpy.context.object
spot.location = (-12.789496421813965, 9.167668342590332, 8.237796783447266)
spot.rotation_euler = (1.3590290546417236, 7.051307875372004e-07, -2.285219669342041)
spot.data.energy = 1000
def render():
bpy.context.scene.render.resolution_x = 320
bpy.context.scene.render.resolution_y = 240
bpy.context.scene.render.resolution_percentage = 50
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.filepath = '/tmp/o.png'
bpy.ops.render.render(write_still=True)
clear_scene()
create_purple_bird()
create_plane()
create_camera()
create_spot()
render()
- 材质半透明效果
import bpy
import math
import os
from bpy_extras.io_utils import ExportHelper
# 清除场景原有物体
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
# --------------------------
# 创建鱼模型
# --------------------------
# 鱼身主体
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 0, 0))
body = bpy.context.active_object
body.name = "Fish_Body"
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.resize(value=(3, 1, 0.8), orient_type='GLOBAL')
bpy.ops.transform.resize(value=(1, 1, 0.7), orient_type='GLOBAL')
bpy.ops.object.mode_set(mode='OBJECT')
# 鱼尾
bpy.ops.mesh.primitive_cube_add(size=1, location=(3.5, 0, 0))
tail = bpy.context.active_object
tail.name = "Fish_Tail"
tail.scale = (0.8, 1.2, 0.6)
tail.location = (3.2, 0, 0)
# 背鳍
bpy.ops.mesh.primitive_cube_add(size=1, location=(0.5, 0, 1))
dorsal_fin = bpy.context.active_object
dorsal_fin.name = "Dorsal_Fin"
dorsal_fin.scale = (1.5, 0.2, 0.8)
dorsal_fin.rotation_euler.z = math.radians(5)
# 胸鳍
bpy.ops.mesh.primitive_cube_add(size=1, location=(-0.5, -1, 0))
left_fin = bpy.context.active_object
left_fin.name = "Left_Fin"
left_fin.scale = (0.8, 0.2, 0.5)
left_fin.rotation_euler.x = math.radians(30)
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 2, 0)})
right_fin = bpy.context.active_object
right_fin.name = "Right_Fin"
# 腹鳍
bpy.ops.mesh.primitive_cube_add(size=1, location=(1, 0, -0.8))
ventral_fin = bpy.context.active_object
ventral_fin.name = "Ventral_Fin"
ventral_fin.scale = (0.6, 0.2, 0.4)
ventral_fin.rotation_euler.x = math.radians(-20)
# 眼睛
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.3, location=(-1.5, -0.7, 0.3))
left_eye_white = bpy.context.active_object
left_eye_white.name = "Left_Eye_White"
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 1.4, 0)})
right_eye_white = bpy.context.active_object
right_eye_white.name = "Right_Eye_White"
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.15, location=(-1.6, -0.7, 0.3))
left_pupil = bpy.context.active_object
left_pupil.name = "Left_Pupil"
left_pupil.scale = (1, 0.8, 1)
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 1.4, 0)})
right_pupil = bpy.context.active_object
right_pupil.name = "Right_Pupil"
# --------------------------
# 创建材质
# --------------------------
# 鱼身材质
body_mat = bpy.data.materials.new(name="Body_Material")
body_mat.use_nodes = True
nodes = body_mat.node_tree.nodes
links = body_mat.node_tree.links
for node in nodes:
nodes.remove(node)
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (0.1, 0.3, 0.8, 1)
bsdf.inputs["Roughness"].default_value = 0.3
output = nodes.new(type='ShaderNodeOutputMaterial')
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
# 眼睛材质
eye_white_mat = bpy.data.materials.new(name="Eye_White_Material")
eye_white_mat.use_nodes = True
eye_nodes = eye_white_mat.node_tree.nodes
for node in eye_nodes:
eye_nodes.remove(node)
eye_bsdf = eye_nodes.new(type='ShaderNodeBsdfPrincipled')
eye_bsdf.inputs["Base Color"].default_value = (1, 1, 1, 1)
eye_output = eye_nodes.new(type='ShaderNodeOutputMaterial')
eye_white_mat.node_tree.links.new(eye_bsdf.outputs['BSDF'], eye_output.inputs['Surface'])
# 瞳孔材质
pupil_mat = bpy.data.materials.new(name="Pupil_Material")
pupil_mat.use_nodes = True
pupil_nodes = pupil_mat.node_tree.nodes
for node in pupil_nodes:
pupil_nodes.remove(node)
pupil_bsdf = pupil_nodes.new(type='ShaderNodeBsdfPrincipled')
pupil_bsdf.inputs["Base Color"].default_value = (0, 0, 0, 1)
pupil_output = pupil_nodes.new(type='ShaderNodeOutputMaterial')
pupil_mat.node_tree.links.new(pupil_bsdf.outputs['BSDF'], pupil_output.inputs['Surface'])
# 分配材质
body.data.materials.append(body_mat)
tail.data.materials.append(body_mat)
dorsal_fin.data.materials.append(body_mat)
left_fin.data.materials.append(body_mat)
right_fin.data.materials.append(body_mat)
ventral_fin.data.materials.append(body_mat)
left_eye_white.data.materials.append(eye_white_mat)
right_eye_white.data.materials.append(eye_white_mat)
left_pupil.data.materials.append(pupil_mat)
right_pupil.data.materials.append(pupil_mat)
# 合并所有部件为一个整体
for obj in [body, tail, dorsal_fin, left_fin, right_fin, ventral_fin,
left_eye_white, right_eye_white, left_pupil, right_pupil]:
obj.select_set(True)
bpy.context.view_layer.objects.active = body
bpy.ops.object.join()
fish_obj = bpy.context.active_object
fish_obj.name = "Fish"
# --------------------------
# 创建游动路径
# --------------------------
curve_data = bpy.data.curves.new('Fish_Path', 'CURVE')
curve_data.dimensions = '3D'
curve_data.resolution_u = 32
# 更复杂的游动路径(椭圆形+波浪)
polyline = curve_data.splines.new('POLY')
points = polyline.points
radius_x = 8
radius_y = 6
num_points = 40
points.add(num_points)
for i in range(num_points + 1):
angle = 2 * math.pi * i / num_points
z_offset = math.sin(angle * 2) * 1.2 # 上下起伏
x = radius_x * math.cos(angle)
y = radius_y * math.sin(angle)
points[i].co = (x, y, z_offset, 1)
path_obj = bpy.data.objects.new('Fish_Path', curve_data)
bpy.context.collection.objects.link(path_obj)
# --------------------------
# 设置游动动画
# --------------------------
constraint = fish_obj.constraints.new(type='FOLLOW_PATH')
constraint.target = path_obj
constraint.forward_axis = 'FORWARD_X'
constraint.up_axis = 'UP_Z'
frame_start = 1
frame_end = 150
bpy.context.scene.frame_start = frame_start
bpy.context.scene.frame_end = frame_end
# 沿路径移动
constraint.offset = 0
constraint.keyframe_insert(data_path="offset", frame=frame_start)
constraint.offset = 100
constraint.keyframe_insert(data_path="offset", frame=frame_end)
# 更自然的鱼身摆动
def add_swim_animation(frame):
phase = frame / 15
# 身体摆动
fish_obj.rotation_euler.z = math.sin(phase) * 0.3
# 尾部摆动(幅度更大)
fish_obj.rotation_euler.y = math.sin(phase * 1.2) * 0.15
fish_obj.keyframe_insert(data_path="rotation_euler", frame=frame)
for f in range(frame_start, frame_end + 1, 5):
add_swim_animation(f)
# --------------------------
# 设置摄像机跟踪鱼
# --------------------------
bpy.ops.object.camera_add(location=(0, -10, 5))
camera = bpy.context.active_object
camera.name = "Fish_Camera"
bpy.context.scene.camera = camera
# 添加跟踪约束
track_constraint = camera.constraints.new(type='TRACK_TO')
track_constraint.target = fish_obj
track_constraint.track_axis = 'TRACK_NEGATIVE_Z' # 摄像机Z轴负方向指向目标
track_constraint.up_axis = 'UP_Y' # 使用Y轴作为上方向
# 摄像机距离关键帧(使距离有轻微变化)
def add_camera_distance(frame):
distance = 10 + math.sin(frame / 30) * 1.5 # 距离在9.5-11.5之间变化
camera.location = (0, -distance, 5 + math.sin(frame / 40) * 0.5)
camera.keyframe_insert(data_path="location", frame=frame)
for f in range(frame_start, frame_end + 1, 10):
add_camera_distance(f)
# --------------------------
# 添加环境光照(兼容 Blender 4.3)
# --------------------------
# 主光源(阳光)
bpy.ops.object.light_add(type='SUN', location=(10, 5, 15))
sun = bpy.context.active_object
sun.name = "Sun"
sun.data.energy = 3.0
sun.rotation_euler = (math.radians(60), 0, math.radians(30))
# 辅助光源(从另一侧照亮)
bpy.ops.object.light_add(type='SUN', location=(-10, -5, 10))
fill_light = bpy.context.active_object
fill_light.name = "Fill_Light"
fill_light.data.energy = 1.5 # 较弱的辅助光
fill_light.rotation_euler = (math.radians(45), 0, math.radians(-120))
# 底部补光(减少阴影)
bpy.ops.object.light_add(type='SUN', location=(0, 0, -10))
bottom_light = bpy.context.active_object
bottom_light.name = "Bottom_Light"
bottom_light.data.energy = 0.8 # 非常弱的底部光
bottom_light.rotation_euler = (math.radians(-90), 0, 0)
# 设置环境光遮蔽(Blender 4.3 替代方案)
world = bpy.context.scene.world
if world is None:
world = bpy.data.worlds.new("World")
bpy.context.scene.world = world
world.use_nodes = True
nodes = world.node_tree.nodes
links = world.node_tree.links
# 清除所有现有节点
for node in nodes:
nodes.remove(node)
# 创建背景节点
bg_node = nodes.new('ShaderNodeBackground')
bg_node.inputs["Color"].default_value = (0.05, 0.05, 0.05, 1.0) # 微弱的环境光颜色
bg_node.inputs["Strength"].default_value = 1.0
# 创建环境光遮蔽节点
ao_node = nodes.new('ShaderNodeAmbientOcclusion')
ao_node.inputs["Distance"].default_value = 2.0
# 创建混合节点
mix_node = nodes.new('ShaderNodeMixShader')
mix_node.inputs["Fac"].default_value = 0.5 # 控制环境光遮蔽的强度
# 创建输出节点
output_node = nodes.new('ShaderNodeOutputWorld')
# 连接节点
links.new(bg_node.outputs["Background"], mix_node.inputs[1])
links.new(ao_node.outputs["Color"], mix_node.inputs[2])
links.new(mix_node.outputs["Shader"], output_node.inputs["Surface"])
# --------------------------
# 添加简单的水下环境
# --------------------------
# 创建一个大球体模拟水体
bpy.ops.mesh.primitive_uv_sphere_add(radius=20, location=(0, 0, 0))
water = bpy.context.active_object
water.name = "Water"
water.scale = (1, 1, 0.5) # 压扁成椭圆形
water.location.z = -10 # 放到下方
# 水体材质(半透明蓝色)
water_mat = bpy.data.materials.new(name="Water_Material")
water_mat.use_nodes = True
water_nodes = water_mat.node_tree.nodes
for node in water_nodes:
water_nodes.remove(node)
water_bsdf = water_nodes.new(type='ShaderNodeBsdfGlass')
water_bsdf.inputs["Color"].default_value = (0.2, 0.4, 0.8, 0.2) # 半透明蓝色
water_output = water_nodes.new(type='ShaderNodeOutputMaterial')
water_mat.node_tree.links.new(water_bsdf.outputs['BSDF'], water_output.inputs['Surface'])
water.data.materials.append(water_mat)
# --------------------------
# 渲染设置
# --------------------------
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.cycles.device = 'GPU' # 使用GPU加速
bpy.context.scene.cycles.samples = 128
bpy.context.scene.render.resolution_x = 1920
bpy.context.scene.render.resolution_y = 1080
bpy.context.scene.render.film_transparent = True # 透明背景
# --------------------------
# 选择输出目录并渲染
# --------------------------
class RenderPathSelector(bpy.types.Operator, ExportHelper):
"""选择渲染输出目录"""
bl_idname = "render.select_output_directory"
bl_label = "选择渲染输出目录"
filename_ext = ""
directory: bpy.props.StringProperty(subtype='DIR_PATH')
def execute(self, context):
os.makedirs(self.directory, exist_ok=True)
context.scene.render.filepath = os.path.join(self.directory, "fish_frame_")
context.scene.render.image_settings.file_format = 'PNG'
print(f"渲染保存到: {self.directory}")
bpy.ops.render.render(animation=True)
return {'FINISHED'}
# 注册并调用文件选择器
bpy.utils.register_class(RenderPathSelector)
bpy.ops.render.select_output_directory('INVOKE_DEFAULT')s
- 本文来自 oeasy Python 系统教程。
- 想完整、扎实学 Python,
- 搜索 oeasy 即可。

