

あなたは優秀なプロのBlenderモデラーです。
Pythonスクリプトを使って、モデリングを作成したいと思います。練習・学習を補助してください。
ユーザーはBlender歴は半年ほど。
Blenderバージョンは4.2.11LTSを使います。
スクリプトに関する知識はほぼありません。
初心者にもわかりやすくステップバイステップで教えてください。
Pythonスクリプトでモデリングをする、とはどういうことか理解できるようなモデル選定とテーマ選定をお願いします。



了解しました!
ここからは、Blender初心者でも「Pythonスクリプトでモデリングができるってこういうことか!」と実感できるように、以下の方針で進めていきます。
最近「AIを絡めればBlenderがもっとはかどる!」なんて話があちこちで飛び交っています。
でも実際のところ「具体的に何を、どうやってやればいいの?」って思ってました。
いろいろ情報は出てきますが、何を信じればいいのか。
だったら自分で試そうじゃないか
と、いうわけで―――



おしんちゃんがやって!



了解なり!
ChatGPTに丸投げしつつ、本当にAIだけでアドオンが組めるのか試してみました。
テーマも内容も全部丸投げ。スクリプトなんて作ったことなし。
検証スタート
この記事では、プロンプト→完成コード→動作確認まで、
すべて書き残しています。
うまくいったところもうまくいかなかったところもすべてさらけ出します///
「AI活用って実際どうなの?」と気になっている人の、リアルな参考になれば幸いです。
🔰まず最初に:Pythonでモデリングってどういうこと?
通常はマウスやショートカットで立体を作っていきますが、Pythonスクリプトを使えば「コードを書くことでモデリング」ができます。
つまり、同じ形を繰り返し生成したり、手動では大変な配置を一瞬で行えたりするのが強みです。



ほう。なるほど。
つまりどういうことだ?
🧱テーマ:「ランダムに立方体を並べて、街みたいにする」
これは超入門にぴったりなテーマです。
- 立方体(Cube)なら簡単に生成できる
- 位置・サイズをPythonで調整できる
- 複雑な知識なしで「おおっ、できた!」という実感が得られる



ほんとかな。
君は割と嘘をつくし。
✅ステップ1:最初の立方体を生成するスクリプト
- Blenderを開き、新規→スクリプト(Scripting)タブに移動
- 新しいスクリプトファイルを開き、以下のコードを貼ってください:
import bpy
# すべてのオブジェクトを削除
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# 立方体を原点に追加
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 1))
📌このコードの意味:
bpy
= BlenderのPython APIprimitive_cube_add
= 立方体を追加するコマンドlocation=(0, 0, 1)
= 位置(Z方向に1上げる)





おお。
確かにできた…。
✅ステップ2:立方体をたくさん並べる
以下のコードで、ランダムな位置・高さに立方体を10個配置します:
import bpy
import random
# 全削除
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# 立方体を10個生成
for i in range(10):
x = random.uniform(-10, 10)
y = random.uniform(-10, 10)
height = random.uniform(1, 5)
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, height / 2))
bpy.context.object.scale.z = height / 2
📌ポイント:
random.uniform
でランダムな数値を作成scale.z
で高さを調整- Z座標を半分にして地面に立たせる(中心が原点のため)





おお。
ちゃんとできた感あるね。
🎨 ステップ3:立方体にランダムな色をつける
下記のコードでは、立方体を生成すると同時に、ランダムな色のマテリアルを設定します。
import bpy
import random
# 全削除
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
def create_random_color_material(name):
# マテリアル作成
mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
r = random.random()
g = random.random()
b = random.random()
bsdf.inputs['Base Color'].default_value = (r, g, b, 1)
return mat
# 立方体を10個生成
for i in range(10):
x = random.uniform(-10, 10)
y = random.uniform(-10, 10)
height = random.uniform(1, 5)
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, height / 2))
cube = bpy.context.object
cube.scale.z = height / 2
# マテリアル作成&割り当て
mat = create_random_color_material(f"Mat_{i}")
if cube.data.materials:
cube.data.materials[0] = mat
else:
cube.data.materials.append(mat)
🧠 補足解説(ざっくりでOK)
bpy.data.materials.new(name)
→ 新しいマテリアルを作るmat.use_nodes = True
→ ノードベースのマテリアルに切り替えbsdf.inputs['Base Color'].default_value
→ ベースカラーをRGBで設定cube.data.materials.append(mat)
→ モデルにマテリアルを適用





ん?一個しか出てこないし、
色もついてないぞ?
やっぱりこういうことあるよね。
しっかり指摘してやるか。


🛠️ 修正版スクリプト(ローカライズに依存しない書き方)
import bpy
import random
# ① シーンをまっさらに
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
# ② ランダム色マテリアルを安全に作る関数
def create_random_color_material(name):
mat = bpy.data.materials.new(name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# 「プリンシプルBSDF」を型 (=type) で探す -----------
bsdf = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf is None: # 万一ない場合は新規生成
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
# マテリアル出力ノードを取得(日本語UI対策)
m_out = nodes.get("Material Output") or nodes.get("マテリアル出力")
if m_out and not bsdf.outputs[0].is_linked: # 未接続ならリンク
links.new(bsdf.outputs[0], m_out.inputs[0])
# ランダムで Base Color を設定
bsdf.inputs['Base Color'].default_value = (
random.random(), random.random(), random.random(), 1
)
return mat
# ③ 立方体を10個配置してマテリアル適用 ------------------
for i in range(10):
x, y = (random.uniform(-10, 10) for _ in range(2))
height = random.uniform(1, 5)
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, height / 2))
cube = bpy.context.object
cube.scale.z = height / 2
mat = create_random_color_material(f"Mat_{i}")
cube.data.materials.clear()
cube.data.materials.append(mat)





おお、できた。
なるほど、日本語UI使ってたせいね。
エラーメッセージから、しっかりと修正案を出してくるところすげえな。
🏗 ステップ4:格子状にビル街を自動生成するスクリプト
import bpy
import random
# -----------------------------
# 0. シーンを初期化(全削除)
# -----------------------------
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
# -----------------------------
# 1. パラメータ設定
# -----------------------------
GRID_COUNT = 10 # X,Y 方向のビル数(10 → 10×10=100 個)
SPACING = 2.2 # ビル間隔(大きいほどスキマが広がる)
MIN_H, MAX_H = 1, 6 # ビル高さの範囲
# -----------------------------
# 2. ランダム色マテリアル作成関数
# (UI が日本語でも壊れない書き方)
# -----------------------------
def make_color_material(name, rgba):
mat = bpy.data.materials.new(name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Principled BSDF を type で取得 / なければ新規作成
bsdf = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf is None:
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
# マテリアル出力ノード(日本語 UI 対応)
m_out = nodes.get("Material Output") or nodes.get("マテリアル出力")
if m_out and not bsdf.outputs[0].is_linked:
mat.node_tree.links.new(bsdf.outputs[0], m_out.inputs[0])
bsdf.inputs["Base Color"].default_value = rgba
return mat
# 色テーブル(高さに応じて使い分け)
MAT_LOW = make_color_material("Low_Building", (0.2, 0.6, 1.0, 1)) # 青
MAT_MID = make_color_material("Mid_Building", (1.0, 0.8, 0.2, 1)) # 黄
MAT_HIGH = make_color_material("High_Building", (0.9, 0.2, 0.2, 1)) # 赤
def pick_mat_by_height(h):
if h > (MAX_H * 0.66):
return MAT_HIGH
elif h > (MAX_H * 0.33):
return MAT_MID
else:
return MAT_LOW
# -----------------------------
# 3. グリッド状にビルを配置
# -----------------------------
offset = -(GRID_COUNT - 1) * SPACING / 2 # 原点中心に並べるためのずらし量
for ix in range(GRID_COUNT):
for iy in range(GRID_COUNT):
# 座標計算
x = offset + ix * SPACING
y = offset + iy * SPACING
height = random.uniform(MIN_H, MAX_H)
# 立方体追加(位置はビルの中心が原点なので高さ/2 持ち上げる)
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, height / 2))
cube = bpy.context.object
cube.scale.z = height / 2 # Z 方向だけ拡大して高さにする
# マテリアル割り当て
cube.data.materials.clear()
cube.data.materials.append(pick_mat_by_height(height))
🔍 実行後のチェックポイント
- テキストエディタに貼り付け → ▶ ボタンで実行
- ビューポートを マテリアルプレビュー に切り替える
- 原点を中心に 10 × 10 のビル街が出現
- 低いビル → 青
- 中くらい → 黄
- 高いビル → 赤





おー
📸 ステップ5:街を画像としてレンダリングして保存する
import bpy
import random
import os
# -----------------------------
# 0. シーン初期化
# -----------------------------
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
# -----------------------------
# 1. 街パラメータ設定
# -----------------------------
GRID_COUNT = 10
SPACING = 2.2
MIN_H, MAX_H = 1, 6
def make_color_material(name, rgba):
mat = bpy.data.materials.new(name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
bsdf = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf is None:
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
m_out = nodes.get("Material Output") or nodes.get("マテリアル出力")
if m_out and not bsdf.outputs[0].is_linked:
mat.node_tree.links.new(bsdf.outputs[0], m_out.inputs[0])
bsdf.inputs["Base Color"].default_value = rgba
return mat
MAT_LOW = make_color_material("Low", (0.2, 0.6, 1.0, 1))
MAT_MID = make_color_material("Mid", (1.0, 0.8, 0.2, 1))
MAT_HIGH = make_color_material("High", (0.9, 0.2, 0.2, 1))
def pick_mat_by_height(h):
if h > MAX_H * 0.66:
return MAT_HIGH
elif h > MAX_H * 0.33:
return MAT_MID
else:
return MAT_LOW
offset = -(GRID_COUNT - 1) * SPACING / 2
for ix in range(GRID_COUNT):
for iy in range(GRID_COUNT):
x = offset + ix * SPACING
y = offset + iy * SPACING
h = random.uniform(MIN_H, MAX_H)
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, h / 2))
cube = bpy.context.object
cube.scale.z = h / 2
cube.data.materials.clear()
cube.data.materials.append(pick_mat_by_height(h))
# -----------------------------
# 2. カメラ追加・配置
# -----------------------------
cam_data = bpy.data.cameras.new("Camera")
cam = bpy.data.objects.new("Camera", cam_data)
bpy.context.collection.objects.link(cam)
bpy.context.scene.camera = cam
cam.location = (0, -30, 25)
cam.rotation_euler = (1.1, 0, 0) # 俯瞰視点に調整
# -----------------------------
# 3. ライト追加・配置
# -----------------------------
light_data = bpy.data.lights.new(name="Light", type='SUN')
light = bpy.data.objects.new(name="Light", object_data=light_data)
bpy.context.collection.objects.link(light)
light.location = (20, -20, 30)
# -----------------------------
# 4. レンダー設定&保存
# -----------------------------
scene = bpy.context.scene
scene.render.engine = 'CYCLES' # または 'BLENDER_EEVEE'
scene.cycles.device = 'GPU' # GPUがあるなら
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = os.path.join(os.path.expanduser("~"), "rendered_city.png")
# 保存レンダリング実行
bpy.ops.render.render(write_still=True)
✅ 実行後にこうなる
- 原点中心にカラフルな街ができる
- カメラが自動配置され、俯瞰の都市風景を捉える
- ライトも自動でセット
- 画像が あなたのPCのホームフォルダに
rendered_city.png
として保存される





それっぽいのは出来てるけど、保存までされてないな。


✅ 画像を「C:\Users\あなたの名前\Pictures」に保存するスクリプト
import bpy
import os
# ここで保存先を指定(←自分のユーザー名に置き換えてください)
save_path = r"C:\Users\yourname\Pictures\my_city.png" # ←ここ重要!
# 保存先フォルダが無ければ作成(万全対策)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
# レンダー設定
scene = bpy.context.scene
scene.render.engine = 'CYCLES' # または 'BLENDER_EEVEE'
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = save_path
print("保存先パス:", save_path)
# 保存レンダリング実行
bpy.ops.render.render(write_still=True)





おおおおお。出来てる!
🧩 ステップ6:街生成スクリプトを“アドオン化”しよう!
目的:
スクリプトを「アドオン(プラグイン)」として登録し、
BlenderのUI上に「街を生成するボタン」を作れるようにします。
✅ 完成イメージ
- メニューに「都市生成」というタブが追加
- ワンクリックで街が生成されるボタンが表示
.py
ファイルとして保存すれば再利用・配布も可能
📋 スクリプト(アドオン版)
bl_info = {
"name": "City Generator",
"blender": (4, 2, 0),
"category": "Object",
"author": "おしんちゃん",
"version": (1, 0),
"description": "シンプルなビル街をワンクリックで生成します",
}
import bpy
import random
class OBJECT_OT_generate_city(bpy.types.Operator):
bl_idname = "object.generate_city"
bl_label = "都市を生成"
bl_description = "ビル街を自動で生成します"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
GRID = 10
SPACING = 2.2
MIN_H, MAX_H = 1, 6
offset = -(GRID - 1) * SPACING / 2
def mat_color(name, color):
mat = bpy.data.materials.get(name)
if not mat:
mat = bpy.data.materials.new(name)
mat.use_nodes = True
bsdf = next((n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf:
bsdf.inputs['Base Color'].default_value = color
return mat
MAT_LOW = mat_color("Low", (0.2, 0.6, 1.0, 1))
MAT_MID = mat_color("Mid", (1.0, 0.8, 0.2, 1))
MAT_HIGH = mat_color("High", (0.9, 0.2, 0.2, 1))
def pick_mat(h):
if h > MAX_H * 0.66:
return MAT_HIGH
elif h > MAX_H * 0.33:
return MAT_MID
else:
return MAT_LOW
for x in range(GRID):
for y in range(GRID):
h = random.uniform(MIN_H, MAX_H)
px = offset + x * SPACING
py = offset + y * SPACING
bpy.ops.mesh.primitive_cube_add(size=1, location=(px, py, h / 2))
cube = bpy.context.object
cube.scale.z = h / 2
cube.data.materials.clear()
cube.data.materials.append(pick_mat(h))
return {'FINISHED'}
class VIEW3D_PT_city_panel(bpy.types.Panel):
bl_label = "都市生成ツール"
bl_idname = "VIEW3D_PT_city_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "都市生成"
def draw(self, context):
layout = self.layout
layout.operator("object.generate_city", icon='MOD_BUILD')
def register():
bpy.utils.register_class(OBJECT_OT_generate_city)
bpy.utils.register_class(VIEW3D_PT_city_panel)
def unregister():
bpy.utils.unregister_class(OBJECT_OT_generate_city)
bpy.utils.unregister_class(VIEW3D_PT_city_panel)
if __name__ == "__main__":
register()
🔧 Blender内部から保存する手順(初心者向け)
- **上部メニューから「テキスト」→「新規」**をクリック
→ 空のスクリプトエディタが開きます - そこにアドオンコードをコピペします(前回のアドオンコード)
- **「テキスト」→「名前をつけて保存」**をクリック
→ 保存ダイアログが出ます





で…で…でで…
できてるー!?
🧪 ステップ8:スライダーでパラメータ調整できる UI を追加
✅ 今回追加する機能
目的:
「建物数」「建物の最小高さ・最大高さ」などをUIで変更できるようにします。
これにより、毎回スクリプトを編集せずに都市を自在に生成可能になります。
UI項目 | 説明 |
---|---|
建物数(グリッド数) | 5〜30の範囲で選択可(5×5〜30×30) |
最小高さ | 各ビルの最小高さ(例:1m) |
最大高さ | 各ビルの最大高さ(例:8m) |
bl_info = {
"name": "City Generator UI + Save Dialog",
"author": "おしんちゃん",
"version": (1, 5),
"blender": (4, 2, 0),
"location": "View3D > サイドバー > 都市生成",
"description": "UIから都市を生成し、保存ダイアログでPNG出力できます",
"category": "Object",
}
import bpy
import random
import os
from bpy.props import IntProperty, FloatProperty, StringProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from datetime import datetime
# ----------------------------------------------------------------
# プロパティグループ(スライダー保持)
# ----------------------------------------------------------------
class CityGenProperties(PropertyGroup):
grid_count: IntProperty(name="グリッド数", default=10, min=5, max=30)
min_height: FloatProperty(name="最小高さ", default=1.0, min=0.5, max=10.0)
max_height: FloatProperty(name="最大高さ", default=6.0, min=0.5, max=20.0)
# ----------------------------------------------------------------
# オペレーター:都市生成と保存ダイアログ
# ----------------------------------------------------------------
class OBJECT_OT_generate_city_with_dialog(Operator):
bl_idname = "object.generate_city_with_dialog"
bl_label = "都市生成+画像保存"
bl_options = {'REGISTER', 'UNDO'}
filepath: StringProperty(
name="保存先",
description="PNG保存パス",
subtype='FILE_PATH',
default="//city_render.png"
)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
props = context.scene.citygen_props
grid = props.grid_count
min_h = min(props.min_height, props.max_height)
max_h = max(props.min_height, props.max_height)
spacing = 2.2
offset = -(grid - 1) * spacing / 2
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
def mat_color(name, rgba):
mat = bpy.data.materials.get(name)
if mat: return mat
mat = bpy.data.materials.new(name)
mat.use_nodes = True
bsdf = next((n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED'), None)
bsdf.inputs['Base Color'].default_value = rgba
return mat
MAT_LOW = mat_color("Low" , (0.2, 0.6, 1.0, 1))
MAT_MID = mat_color("Mid" , (1.0, 0.8, 0.2, 1))
MAT_HIGH = mat_color("High", (0.9, 0.2, 0.2, 1))
def pick_mat(h):
if h > max_h * 0.66: return MAT_HIGH
if h > max_h * 0.33: return MAT_MID
return MAT_LOW
for ix in range(grid):
for iy in range(grid):
h = random.uniform(min_h, max_h)
x = offset + ix * spacing
y = offset + iy * spacing
bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, h/2))
cube = bpy.context.object
cube.scale.z = h / 2
cube.data.materials.clear()
cube.data.materials.append(pick_mat(h))
cam_data = bpy.data.cameras.new("CityCam")
cam = bpy.data.objects.new("CityCam", cam_data)
context.collection.objects.link(cam)
context.scene.camera = cam
cam.location = (0, -30, 25)
cam.rotation_euler = (1.1, 0, 0)
sun_data = bpy.data.lights.new("CitySun", type='SUN')
sun = bpy.data.objects.new("CitySun", sun_data)
context.collection.objects.link(sun)
sun.location = (20, -20, 30)
scene = context.scene
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.render.image_settings.file_format = 'PNG'
path = bpy.path.abspath(self.filepath)
if path.endswith(os.sep) or os.path.isdir(path):
dt = datetime.now().strftime("%Y_%m_%d_%H_%M")
path = os.path.join(path, f"city_render_{dt}.png")
os.makedirs(os.path.dirname(path), exist_ok=True)
scene.render.filepath = path
bpy.ops.render.render(write_still=True)
self.report({'INFO'}, f"画像を保存しました: {path}")
return {'FINISHED'}
# ----------------------------------------------------------------
# パネル:UI表示
# ----------------------------------------------------------------
class VIEW3D_PT_city_ui_panel(Panel):
bl_label = "都市生成ツール"
bl_idname = "VIEW3D_PT_city_ui_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "都市生成"
def draw(self, context):
layout = self.layout
props = context.scene.citygen_props
layout.prop(props, "grid_count")
layout.prop(props, "min_height")
layout.prop(props, "max_height")
layout.operator("object.generate_city_with_dialog", icon='IMAGE_DATA')
# ----------------------------------------------------------------
# 登録 / 解除
# ----------------------------------------------------------------
classes = (
CityGenProperties,
OBJECT_OT_generate_city_with_dialog,
VIEW3D_PT_city_ui_panel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.citygen_props = PointerProperty(type=CityGenProperties)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.citygen_props
if __name__ == "__main__":
register()





少しやり取りしたけど、ちゃんとできた。
すごいな。知識なくてもなんとなく試すことができた!
まとめ
というわけで、
「AIに丸投げして、スクリプト作成は出来るか」というチャレンジは
うまくいきました!🎉
細かい修正は必要なのと、知識がないとどこがダメでどこを修正すればいいのか、って言うのが全然わからなかったけど、繰り返しやればコツはつかめそうな感覚はありました。
これ極めれば、販売できるアドオン作れるんじゃないか…?
今まで敬遠していたスクリプトもChatGPTに聞きながら進めるのは有効だと思いました。
みなさんもぜひお試しください!