Blender×AIの活用って?まずはスクリプトで検証してみた

おしんちゃん

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

ChatGPT

了解しました!
ここからは、Blender初心者でも「Pythonスクリプトでモデリングができるってこういうことか!」と実感できるように、以下の方針で進めていきます。

最近「AIを絡めればBlenderがもっとはかどる!」なんて話があちこちで飛び交っています。

でも実際のところ「具体的に何を、どうやってやればいいの?」って思ってました。

いろいろ情報は出てきますが、何を信じればいいのか。

だったら自分で試そうじゃないか

と、いうわけで―――

まよちゃん

おしんちゃんがやって!

おしんちゃん

了解なり!

ChatGPTに丸投げしつつ、本当にAIだけでアドオンが組めるのか試してみました。

テーマも内容も全部丸投げ。スクリプトなんて作ったことなし。

検証スタート

この記事では、プロンプト→完成コード→動作確認まで、

すべて書き残しています。

うまくいったところもうまくいかなかったところもすべてさらけ出します///

「AI活用って実際どうなの?」と気になっている人の、リアルな参考になれば幸いです。

目次

🔰まず最初に:Pythonでモデリングってどういうこと?

通常はマウスやショートカットで立体を作っていきますが、Pythonスクリプトを使えば「コードを書くことでモデリング」ができます。
つまり、同じ形を繰り返し生成したり、手動では大変な配置を一瞬で行えたりするのが強みです。

おしんちゃん

ほう。なるほど。
つまりどういうことだ?

🧱テーマ:「ランダムに立方体を並べて、街みたいにする」

これは超入門にぴったりなテーマです。

  • 立方体(Cube)なら簡単に生成できる
  • 位置・サイズをPythonで調整できる
  • 複雑な知識なしで「おおっ、できた!」という実感が得られる
おしんちゃん

ほんとかな。
君は割と嘘をつくし。

✅ステップ1:最初の立方体を生成するスクリプト

  1. Blenderを開き、新規→スクリプト(Scripting)タブに移動
  2. 新しいスクリプトファイルを開き、以下のコードを貼ってください:
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 API
  • primitive_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))

🔍 実行後のチェックポイント

  1. テキストエディタに貼り付け → ▶ ボタンで実行
  2. ビューポートを マテリアルプレビュー に切り替える
  3. 原点を中心に 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内部から保存する手順(初心者向け)

  1. **上部メニューから「テキスト」→「新規」**をクリック
     → 空のスクリプトエディタが開きます
  2. そこにアドオンコードをコピペします(前回のアドオンコード)
  3. **「テキスト」→「名前をつけて保存」**をクリック
     → 保存ダイアログが出ます
おしんちゃん

で…で…でで…
できてるー!?

🧪 ステップ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に聞きながら進めるのは有効だと思いました。

みなさんもぜひお試しください!

よかったらシェアしてね!
  • URLをコピーしました!
目次