File size: 5,707 Bytes
94a0de3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""生成无需外部贴图的演示场景。

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))