"""生成无需外部贴图的演示场景。 blender --background --factory-startup --python scripts/create_demo.py -- --output demo.blend """ from __future__ import annotations import argparse import math import sys from pathlib import Path import bpy from mathutils import Vector def material(name, color, metallic=0.0, roughness=0.4): value = bpy.data.materials.new(name) value.diffuse_color = (*color, 1) value.use_nodes = True shader = value.node_tree.nodes.get("Principled BSDF") shader.inputs["Base Color"].default_value = (*color, 1) shader.inputs["Metallic"].default_value = metallic shader.inputs["Roughness"].default_value = roughness return value def smooth(obj): for polygon in obj.data.polygons: polygon.use_smooth = True def aim(obj, target): obj.rotation_euler = (Vector(target) - obj.location).to_track_quat("-Z", "Y").to_euler() def area(name, location, energy, color, size, target=(0, 0, 1)): bpy.ops.object.light_add(type="AREA", location=location) obj = bpy.context.object obj.name = name obj.data.energy = energy obj.data.color = color obj.data.shape = "DISK" obj.data.size = size aim(obj, target) return obj def build(output: Path): bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.name = "Studio" scene.render.engine = "CYCLES" scene.cycles.samples = 64 scene.cycles.use_denoising = True scene.render.resolution_x = 960 scene.render.resolution_y = 540 scene.render.resolution_percentage = 100 scene.render.fps = 24 scene.frame_start = 1 scene.frame_end = 120 scene.render.image_settings.file_format = "PNG" scene.world = bpy.data.worlds.new("Studio World") scene.world.use_nodes = True scene.world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.055, 0.085, 0.13, 1) scene.world.node_tree.nodes["Background"].inputs["Strength"].default_value = 0.3 try: scene.view_settings.view_transform = "AgX" except TypeError: pass jade = material("Jade ceramic", (0.05, 0.42, 0.32), metallic=0.24, roughness=0.2) gold = material("Champagne metal", (0.8, 0.55, 0.2), metallic=0.88, roughness=0.24) cream = material("Warm porcelain", (0.76, 0.68, 0.55), roughness=0.28) navy = material("Midnight plinth", (0.023, 0.054, 0.085), metallic=0.2, roughness=0.35) floor = material("Studio floor", (0.055, 0.085, 0.11), roughness=0.45) bpy.ops.mesh.primitive_plane_add(size=200) bpy.context.object.name = "Floor" bpy.context.object.data.materials.append(floor) bpy.ops.mesh.primitive_cylinder_add(vertices=96, radius=1.85, depth=0.32, location=(0, 0, 0.16)) plinth = bpy.context.object plinth.name = "Display plinth" plinth.data.materials.append(navy) bevel = plinth.modifiers.new("Soft edges", "BEVEL") bevel.width = 0.08 bevel.segments = 4 smooth(plinth) bpy.ops.mesh.primitive_monkey_add(location=(0.0, 0, 1.45)) hero = bpy.context.object hero.name = "Jade Suzanne" hero.rotation_euler.z = math.radians(-12) hero.data.materials.append(jade) subdivision = hero.modifiers.new("Sculpted surface", "SUBSURF") subdivision.levels = 2 subdivision.render_levels = 2 smooth(hero) bpy.ops.mesh.primitive_torus_add(major_segments=96, minor_segments=24, location=(0, 0.25, 1.6), rotation=(math.radians(76), 0.2, 0), major_radius=1.52, minor_radius=0.052) ring = bpy.context.object ring.name = "Golden orbit" ring.data.materials.append(gold) smooth(ring) for name, loc, radius, mat in [ ("Porcelain sphere", (1.23, -0.63, 0.63), 0.31, cream), ("Golden sphere", (-1.16, -0.67, 0.59), 0.27, gold), ("Small jade sphere", (0.7, 0.9, 0.5), 0.18, jade), ]: bpy.ops.mesh.primitive_uv_sphere_add(segments=48, ring_count=24, radius=radius, location=loc) obj = bpy.context.object obj.name = name obj.data.materials.append(mat) smooth(obj) area("Large warm key", (3, -4, 6), 1100, (1.0, 0.84, 0.67), 4) area("Soft cool fill", (-4, -1, 3), 800, (0.50, 0.76, 1.0), 3) area("Golden rim", (1, 4, 5), 1500, (1.0, 0.65, 0.28), 2.5) bpy.ops.object.empty_add(type="PLAIN_AXES", location=(0, 0, 1.12)) target = bpy.context.object target.name = "Camera target" bpy.ops.object.camera_add(location=(5.4, -7.5, 4.2)) camera = bpy.context.object camera.name = "OrbitCamera" camera.data.lens = 52 camera.data.dof.use_dof = True camera.data.dof.focus_object = target camera.data.dof.aperture_fstop = 6.3 aim(camera, target.location) track = camera.constraints.new("TRACK_TO") track.target = target track.track_axis = "TRACK_NEGATIVE_Z" track.up_axis = "UP_Y" scene.camera = camera # 逐帧记录相机位置,不使用驱动器或自动脚本。 for frame in range(1, 121): angle = math.radians(-68 + (frame - 1) * 70 / 119) camera.location = (8.9 * math.cos(angle), 8.9 * math.sin(angle), 4.0) camera.keyframe_insert(data_path="location", frame=frame) scene.frame_set(1) output.parent.mkdir(parents=True, exist_ok=True) bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False) print(f"已创建演示场景:{output.resolve()}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--output", default="demo.blend") args = parser.parse_args(sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []) build(Path(args.output))