Blenderを始めてたった2日。Pythonでここまでできた!?
Pythonは、「データ分析」とか「スクレイピング」みたいな業務で使うことが多かったのですが、
まさかポップコーンが飛び出す3DCGを作れるとは。
ChatGPTに教えてもらったものをベースに、blenderの機能使って簡単に編集したものです
追加事項
・容器の色をしましま模様にする
・平面を追加
・縁にベベルをかける
目標🍿
ポップコーンがはじけ飛んでいる3DCGをpythonで作る
① お、ポップコーンだ。でも容器が‥
まずは、ベースとなる素材を依頼しました
<ChatGPT依頼内容>
アメリカで食べれそうなポップコーンを描いてそれを弾けさせて。blender×pythonコードを書いて欲しい。ポップコーンはリアルを追求させて、ボコボコしていて、サイズもバラバラで、量も大量にして欲しい
全体コードはこちら↓
import bpy
import random
import math
# --------------------------------------
# 0. 初期化:全削除
# --------------------------------------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# --------------------------------------
# 1. 中が凹んだバケツを作成
# --------------------------------------
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=3, depth=3, location=(0, 0, 1.5))
box = bpy.context.active_object
box.name = "PopcornBucket"
# 内側に厚み
solidify = box.modifiers.new(name="Solidify", type='SOLIDIFY')
solidify.thickness = -0.3
solidify.offset = 1
# スムーズに
subsurf = box.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2
# 上部を凹ませる
displace = box.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("BowlShape", type='BLEND')
tex.use_color_ramp = True
tex.progression = 'SPHERICAL'
displace.texture = tex
displace.direction = 'Z'
displace.strength = -0.3
# 丸みを出す
cast = box.modifiers.new(name="Cast", type='CAST')
cast.cast_type = 'SPHERE'
cast.factor = 0.2
# 適用
bpy.ops.object.select_all(action='DESELECT')
box.select_set(True)
bpy.context.view_layer.objects.active = box
bpy.ops.object.convert(target='MESH')
# --------------------------------------
# 2. ポップコーンベース作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"
# ノイズ変形
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4
# スムーズ化
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2
# 適用
bpy.ops.object.select_all(action='DESELECT')
popcorn_base.select_set(True)
bpy.context.view_layer.objects.active = popcorn_base
bpy.ops.object.convert(target='MESH')
# --------------------------------------
# 3. ポップコーン複製(ランダム配置)
# --------------------------------------
num_corns = 120
popcorns = []
for i in range(num_corns):
obj = popcorn_base.copy()
obj.data = popcorn_base.data.copy()
obj.location = (
random.uniform(-2.5, 2.5),
random.uniform(-2.5, 2.5),
random.uniform(0.2, 2.0)
)
scale = random.uniform(0.5, 1.2)
obj.scale = (
scale * random.uniform(0.9, 1.1),
scale * random.uniform(0.9, 1.1),
scale * random.uniform(0.9, 1.2)
)
bpy.context.collection.objects.link(obj)
popcorns.append(obj)
# ベース削除
bpy.data.objects.remove(popcorn_base, do_unlink=True)
# --------------------------------------
# 4. 弾けるアニメーション(半分だけ)
# --------------------------------------
for i, obj in enumerate(popcorns):
if i % 2 == 0:
continue # バケツに残る半分
start = random.randint(1, 20)
end = start + 20
jump_height = random.uniform(2.5, 5.5)
obj.keyframe_insert(data_path="location", frame=start)
obj.location.z += jump_height
obj.keyframe_insert(data_path="location", frame=end)
obj.rotation_euler = (0, 0, 0)
obj.keyframe_insert(data_path="rotation_euler", frame=start)
obj.rotation_euler = (
random.uniform(0, math.pi * 2),
random.uniform(0, math.pi * 2),
random.uniform(0, math.pi * 2)
)
obj.keyframe_insert(data_path="rotation_euler", frame=end)
# --------------------------------------
# 5. マテリアル(ポップコーン色)
# --------------------------------------
mat = bpy.data.materials.new(name="PopcornMat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.8
for obj in popcorns:
if not obj.data.materials:
obj.data.materials.append(mat)
② あれ、貝みたい
容器の形を変化と、弾けなかったポップコーンを表現したく依頼しました
<ChatGPT依頼内容>
箱があってそこから弾け飛ぶイメージ。 箱は、角を取って凹ませてバケツっぽくして欲しい。またポップコーンを弾けさせると同時にちょっとは箱の中に残して欲しい。リアルな箱+弾けポップコーン+残るポップコーン
全体コードはこちら↓
import bpy
import random
import math
# --------------------------------------
# 0. 初期化(全削除)
# --------------------------------------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# --------------------------------------
# 1. 箱を作成(ボウル代わり)
# --------------------------------------
bpy.ops.mesh.primitive_cube_add(size=6, location=(0, 0, 0))
box = bpy.context.active_object
box.name = "PopcornBox"
box.scale.z = 0.5
bpy.ops.object.transform_apply(scale=True)
# 箱を非表示にするならここで box.hide_render = True
# --------------------------------------
# 2. ポップコーンベース作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 0.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"
# ノイズ変形
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4
# サブディビジョン
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2
# 適用
bpy.ops.object.select_all(action='DESELECT')
popcorn_base.select_set(True)
bpy.context.view_layer.objects.active = popcorn_base
bpy.ops.object.convert(target='MESH')
# --------------------------------------
# 3. ランダムに大量複製(箱の中)
# --------------------------------------
num_corns = 120
popcorns = []
for _ in range(num_corns):
obj = popcorn_base.copy()
obj.data = popcorn_base.data.copy()
obj.location = (
random.uniform(-2.5, 2.5),
random.uniform(-2.5, 2.5),
random.uniform(0, 1.0)
)
scale = random.uniform(0.5, 1.2)
obj.scale = (
scale * random.uniform(0.9, 1.1),
scale * random.uniform(0.9, 1.1),
scale * random.uniform(0.9, 1.2)
)
bpy.context.collection.objects.link(obj)
popcorns.append(obj)
# 元は削除
bpy.data.objects.remove(popcorn_base, do_unlink=True)
# --------------------------------------
# 4. 弾けるアニメーション(箱から飛び出す)
# --------------------------------------
for obj in popcorns:
start = random.randint(1, 20)
end = start + 20
jump_height = random.uniform(3.0, 5.5)
# 位置
obj.keyframe_insert(data_path="location", frame=start)
obj.location.z += jump_height
obj.keyframe_insert(data_path="location", frame=end)
# 回転
obj.rotation_euler = (0, 0, 0)
obj.keyframe_insert(data_path="rotation_euler", frame=start)
obj.rotation_euler = (
random.uniform(0, math.pi * 2),
random.uniform(0, math.pi * 2),
random.uniform(0, math.pi * 2)
)
obj.keyframe_insert(data_path="rotation_euler", frame=end)
# --------------------------------------
# 5. マテリアル設定(ポップコーン色)
# --------------------------------------
mat = bpy.data.materials.new(name="PopcornMat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.8
for obj in popcorns:
if not obj.data.materials:
obj.data.materials.append(mat)
③ ダーク感強め
容器っぽくなりましたが、ポップコーンに躍動感がなくなりました
<ChatGPT依頼内容>
(ポップコーンの箱のURL)URLみたいな容器にしてほしい。そこからポップコーンが飛び出るようなアニメーションにして欲しい。
全体コードはこちら↓
import bpy
import random
import math
# --------------------------------------
# 0. 初期化:全削除
# --------------------------------------
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# --------------------------------------
# 1. 紙製ポップコーンボックス作成(赤白ストライプ付き)
# --------------------------------------
# 下が細く、上が広がった箱
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 1))
box = bpy.context.active_object
box.name = "PopcornBox"
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.resize(value=(1.2, 1.2, 1))
bpy.ops.object.mode_set(mode='OBJECT')
# 斜めに広げる(上だけスケール)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.object.mode_set(mode='OBJECT')
mesh = box.data
for v in mesh.vertices:
if v.co.z > 0:
v.co.x *= 1.2
v.co.y *= 1.2
bpy.ops.object.mode_set(mode='OBJECT')
# Solidifyで厚み
solidify = box.modifiers.new(name="Solidify", type='SOLIDIFY')
solidify.thickness = 0.05
solidify.offset = 1
# 赤白ストライプマテリアル
mat = bpy.data.materials.new(name="StripeMaterial")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf = nodes.get("Principled BSDF")
# 既存ノード除去
for node in nodes:
if node.name != "Principled BSDF":
nodes.remove(node)
# テクスチャ座標 → 分離XYZ → 数学(mod)でストライプ作成
tex_coord = nodes.new(type='ShaderNodeTexCoord')
separate_xyz = nodes.new(type='ShaderNodeSeparateXYZ')
math_mod = nodes.new(type='ShaderNodeMath')
math_mod.operation = 'MODULO'
math_mod.inputs[1].default_value = 0.3 # ストライプ幅
color_ramp = nodes.new(type='ShaderNodeValToRGB')
color_ramp.color_ramp.elements[0].color = (1, 0, 0, 1) # 赤
color_ramp.color_ramp.elements[1].color = (1, 1, 1, 1) # 白
# 接続
links.new(tex_coord.outputs['Object'], separate_xyz.inputs[0])
links.new(separate_xyz.outputs['X'], math_mod.inputs[0])
links.new(math_mod.outputs[0], color_ramp.inputs[0])
links.new(color_ramp.outputs[0], bsdf.inputs['Base Color'])
box.data.materials.append(mat)
# --------------------------------------
# 2. ポップコーン作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2
bpy.ops.object.convert(target='MESH')
# --------------------------------------
# 3. ポップコーンをボックス内に複製配置
# --------------------------------------
popcorns = []
num_corns = 120
for i in range(num_corns):
obj = popcorn_base.copy()
obj.data = popcorn_base.data.copy()
obj.location = (
random.uniform(-0.6, 0.6),
random.uniform(-0.6, 0.6),
random.uniform(0.3, 1.8)
)
scale = random.uniform(0.5, 1.0)
obj.scale = (scale, scale, scale * random.uniform(0.8, 1.2))
bpy.context.collection.objects.link(obj)
popcorns.append(obj)
bpy.data.objects.remove(popcorn_base, do_unlink=True)
# --------------------------------------
# 4. 弾けるアニメーション(上方向にジャンプ)
# --------------------------------------
for i, obj in enumerate(popcorns):
if i % 2 == 0:
continue
start = random.randint(1, 20)
end = start + 20
height = random.uniform(2.5, 5)
obj.keyframe_insert(data_path="location", frame=start)
obj.location.z += height
obj.keyframe_insert(data_path="location", frame=end)
obj.rotation_euler = (0, 0, 0)
obj.keyframe_insert(data_path="rotation_euler", frame=start)
obj.rotation_euler = (
random.uniform(0, math.pi),
random.uniform(0, math.pi),
random.uniform(0, math.pi)
)
obj.keyframe_insert(data_path="rotation_euler", frame=end)
# --------------------------------------
# 5. マテリアル(ポップコーン色)
# --------------------------------------
pop_mat = bpy.data.materials.new(name="PopcornMat")
pop_mat.use_nodes = True
bsdf = pop_mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.7
for obj in popcorns:
if not obj.data.materials:
obj.data.materials.append(pop_mat)
④ 完成
お、できた
<ChatGPT依頼内容>
ポップコーンがランダムな位置からジャンプし、頂点で跳ねた後、落下して箱の外に落ちていく感じ。弧を描くように。
ポップコーンにあたる部分はもっといろんなところに散らばる感じで。でも箱の中にはちょっと残っていてリアルな感じで書いて欲しい
全体コードはこちら↓
import bpy
import random
import math
# 0. 初期化
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# 1. 箱作成
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1.2, depth=2.5, location=(0, 0, 1.25))
box = bpy.context.active_object
box.name = "PopcornBox"
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1.0, depth=2.4, location=(0, 0, 1.25))
inner = bpy.context.active_object
inner.name = "PopcornBoxInner"
bool_mod = box.modifiers.new(type='BOOLEAN', name='BooleanInner')
bool_mod.operation = 'DIFFERENCE'
bool_mod.object = inner
bpy.context.view_layer.objects.active = box
bpy.ops.object.modifier_apply(modifier='BooleanInner')
bpy.data.objects.remove(inner, do_unlink=True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.context.tool_settings.use_proportional_edit = True
bpy.context.tool_settings.proportional_edit_falloff = 'SMOOTH'
bpy.ops.transform.resize(value=(1.1, 1.1, 1), orient_type='GLOBAL', proportional_size=2)
bpy.context.tool_settings.use_proportional_edit = False
bpy.ops.object.mode_set(mode='OBJECT')
mat_box = bpy.data.materials.new(name="BoxMat")
mat_box.use_nodes = True
bsdf_box = mat_box.node_tree.nodes.get("Principled BSDF")
bsdf_box.inputs["Base Color"].default_value = (1.0, 1.0, 1.0, 1)
bsdf_box.inputs["Roughness"].default_value = 0.8
box.data.materials.append(mat_box)
# 2. ポップコーンベース作成
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2
bpy.ops.object.convert(target='MESH')
# 3. ポップコーン配置
popcorns = []
num_popcorn = 160
for i in range(num_popcorn):
obj = popcorn_base.copy()
obj.data = popcorn_base.data.copy()
obj.location = (
random.uniform(-0.7, 0.7),
random.uniform(-0.7, 0.7),
random.uniform(0.3, 2.0)
)
scale = random.uniform(0.4, 1.0)
obj.scale = (
scale,
scale,
scale * random.uniform(0.8, 1.2)
)
bpy.context.collection.objects.link(obj)
popcorns.append(obj)
bpy.data.objects.remove(popcorn_base, do_unlink=True)
# 4. ポップコーンアニメーション(弧を描いて箱外に落ちる)
ground_z = 0.0
for obj in popcorns:
start_frame = random.randint(1, 30)
peak_frame = start_frame + random.randint(10, 20)
end_frame = peak_frame + random.randint(15, 25)
base_x, base_y, base_z = obj.location
obj.location = (base_x, base_y, base_z)
obj.keyframe_insert(data_path="location", frame=start_frame)
jump_x = base_x + random.uniform(-1.5, 1.5)
jump_y = base_y + random.uniform(-1.5, 1.5)
jump_z = base_z + random.uniform(4.0, 7.0)
obj.location = (jump_x, jump_y, jump_z)
obj.keyframe_insert(data_path="location", frame=peak_frame)
land_x = jump_x + random.uniform(-1.0, 1.0)
land_y = jump_y + random.uniform(-1.0, 1.0)
land_z = ground_z
obj.location = (land_x, land_y, land_z)
obj.keyframe_insert(data_path="location", frame=end_frame)
obj.rotation_euler = (0, 0, 0)
obj.keyframe_insert(data_path="rotation_euler", frame=start_frame)
rot_end = (
random.uniform(0, math.pi * 6),
random.uniform(0, math.pi * 6),
random.uniform(0, math.pi * 6)
)
obj.rotation_euler = rot_end
obj.keyframe_insert(data_path="rotation_euler", frame=end_frame)
# 5. ポップコーンマテリアル
mat_pop = bpy.data.materials.new(name="PopcornMat")
mat_pop.use_nodes = True
bsdf_pop = mat_pop.node_tree.nodes.get("Principled BSDF")
bsdf_pop.inputs["Base Color"].default_value = (1.0, 0.9, 0.5, 1)
bsdf_pop.inputs["Roughness"].default_value = 0.7
for obj in popcorns:
if not obj.data.materials:
obj.data.materials.append(mat_pop)
# 6. カメラ・ライト・背景
bpy.ops.object.camera_add(location=(0, -7, 5), rotation=(math.radians(75), 0, 0))
bpy.context.scene.camera = bpy.context.active_object
bpy.ops.object.light_add(type='AREA', location=(0, 0, 8))
light = bpy.context.active_object
light.data.energy = 1000
bpy.context.scene.world.use_nodes = True
bg = bpy.context.scene.world.node_tree.nodes["Background"]
bg.inputs[0].default_value = (1, 1, 1, 1)
# 7. レンダリング設定
scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.frame_end = 80
最後に
まだまだ完璧とは言えませんが、Pythonだけでもアニメーションのベースはしっかり作れることが分かりました。
あとはコードの動きをもっと理解して、ロジックを自分で組み立てられるようになれば、さらに表現の幅は広がりそうです。
3DCGの分野にも、Pythonの活躍の場がしっかりある。
そんな可能性を感じたチャレンジでした!