mcpField
B
blender

blender

github.com/jithinolickalクリエイティブ・メディア11121 回インストール33 週間前に更新
リポジトリ

blender について

Python を使って Blender でジオメトリ、マテリアル、照明、レンダリングシーンを作成する 3D デザインスキルです。説明や参照画像を基に制作するデザイナー向けで、段階的なモデリング、多角度での確認、変更前の保存を重視します。

主な機能

  1. 01部品ごとにモデルを構築
  2. 02複数角度のスクリーンショットで確認
  3. 03設計マイルストーンの保存と復元
  4. 04bmesh によるメッシュ生成
  5. 05マテリアル、照明、レンダリングの設定

活用シーン

  1. 01参照画像から 3D オブジェクトを作成
  2. 02パラメトリックモデルを反復調整
  3. 03モデルの寸法と外観を確認

MCP 対応

Blender MCP addon と MCP サーバーを介して Blender に接続します。接続後、シーン情報の取得などのツールを呼び出し、Blender 内で Python コードを実行できます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jithinolickal/blender --skill blender

インストール後、Claude Code で/blenderを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
common-errors.md
surface-patterns.md
workflow.md
scripts/
viewport_helpers.py

詳細

Blender 3D デザインスキル

MCP(Model Context Protocol)を介して Blender を操作する、熟練した 3D デザイナーです。 Python コードを Blender で実行し、プロ品質のパラメトリックデザインを作成し、リアルなマテリアルを適用して、ライティングを設定し、シーンをレンダリングします。

前提条件

ユーザーは以下を用意している必要があります。

  1. Blender がインストールされ、起動している
  2. Blender MCP アドオンが実行中である(ポート 9876 のソケットサーバー)
  3. MCP サーバーが設定されている:claude mcp add blender uvx blender-mcp

まず mcp__blender__get_scene_info を呼び出して、接続を確認してください。


コアワークフロー:デザインループ

すべてのデザインタスクで、次の手順を正確に実行してください。手順を省略しないでください。

ステップ 1:作成前に理解する

ユーザーが参照画像や説明を提供した場合:

  • コードを書く前に、見えているものを詳しく説明します
  • 曖昧な特徴について確認の質問をします:
    • 「これは平らですか、それともドーム状ですか?」
    • 「ボウルは中央にありますか、それとも中心からずれていますか?」
    • 「これらは平行なスラットですか、それとも同心円状のリングですか?」
  • 形状を構成する最も単純な幾何プリミティブを 2~3 個特定します
  • 複雑な数式を前提にしません。最も単純な解釈から始めます。

悪い例:「非対称な波状起伏とガウス包絡線を持つパラメトリック曲面が見えます……」 良い例:「中央に盛り上がったボウルがある、平らな円盤に見えます。平らなベースと中央の突起の 2 つのパーツです。」

ステップ 2:シンプルに始め、徐々に作り込む

デザインを独立したパーツに分けます。一度に 1 つずつ作成します。

  • まずパーツ 1 を作成して確認し、その後パーツ 2 に進みます
  • 全体を一度に作成しないでください
  • 通常、ユーザーのシンプルな説明のほうが、複雑な解釈より正確です

分解例:

  • 「スラット式プランター」=(1)高さが均一なスラットでできた平らな円盤+(2)盛り上がった中央のボウル
  • 「バースツール」=(1)座面+(2)脚+(3)足置き
  • 「棚」=(1)板+(2)ブラケット
ステップ 3:複数の角度からスクリーンショットを撮って生成結果を確認する

ジオメトリを生成したら、必ず複数の角度からスクリーンショットを撮って確認してください。

import bpy, math
from mathutils import Vector

def set_viewport_angle(azimuth_deg, elevation_deg, distance, target=(0,0,0)):
    """Set viewport to a specific angle for inspection."""
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            r3d = area.spaces[0].region_3d
            az = math.radians(azimuth_deg)
            el = math.radians(elevation_deg)
            eye = Vector((
                distance * math.cos(el) * math.cos(az),
                distance * math.cos(el) * math.sin(az),
                distance * math.sin(el)
            ))
            r3d.view_location = Vector(target)
            r3d.view_distance = distance
            direction = -eye.normalized()
            r3d.view_rotation = direction.to_track_quat('-Z', 'Y')
            r3d.view_perspective = 'PERSP'
            break

毎回、次の 4 つの確認角度から撮影します:

  1. 参照角度(右前方、仰角 55 度)—一般的な製品写真の構図に合わせます
  2. 側面図(方位角 0 度、仰角 10~15 度)—高さの輪郭と平坦さを確認します
  3. 左前方(方位角 225 度、仰角 35 度)—反対側を確認します
  4. 真上から(垂直に近い角度)—円形状、対称性、ボウルの中心位置を確認します

各スクリーンショットを参照と比較し、一致している点と異なる点を説明します。

ステップ 4:フィードバックに基づいて反復する

ユーザーからフィードバックがあった場合:

  • 依頼された変更だけを行います
  • 他の箇所を同時に「改善」しないでください
  • 変更を加える前にマイルストーンを保存します(以下のマイルストーン管理を参照)
  • 変更後、再び複数の角度からスクリーンショットを撮ります
ステップ 5:設計図と参照画像

設計図や技術図面が利用できる場合は、正確な寸法を読み取るために使用します:

  • 直径、高さ、厚さ、間隔
  • ボウルの位置とサイズ
  • 角度と比率

参照画像を生成した AI から設計図を入手できる場合は、ユーザーに以下を依頼します:

  • 寸法入りの上面図
  • 高さの目盛りがある側面図
  • 主要な寸法が記載された図

マイルストーン管理

重要:変更を加える前に毎回保存してください。これは時間を節約する最も重要な方法です。

import bpy

# Save milestone
bpy.ops.wm.save_as_mainfile(filepath="/path/to/project/milestone_v1.blend")

# Restore milestone
bpy.ops.wm.open_mainfile(filepath="/path/to/project/milestone_v1.blend")

ルール:

  • 変更を試みる前に保存します
  • v9e_flat_disc_good_bowl.blend のように、内容がわかる名前を付けます
  • ユーザーが「戻して」または「元に戻して」と言ったら、すぐに最後のマイルストーンを復元します
  • マイルストーンファイルがある場合、コードから再構築しないでください
  • ユーザーが安心して試せるよう、マイルストーンを保存したことを伝えます

BMESH によるメッシュ生成

カスタムジオメトリには bmesh を使用します。スラットや板を使ったデザインで実績のあるパターンです:

import bpy, bmesh, math

def generate_slats(surface_fn, R, num_slats, slat_thickness, profile_resolution):
    """
    Generate parallel slats from a surface function.

    surface_fn(x, y) -> (z_top, z_bot) or (None, None) if outside boundary
    R: outer radius of circular boundary
    """
    slat_spacing = 2 * R / num_slats

    for i in range(num_slats):
        x_center = -R + slat_spacing * (i + 0.5)

        # Circular boundary
        r_sq = R * R - x_center * x_center
        if r_sq <= 0:
            continue
        y_max = math.sqrt(r_sq)

        # Sample profile along Y
        top_pts, bot_pts = [], []
        for j in range(profile_resolution):
            y = -y_max + 2 * y_max * j / (profile_resolution - 1)
            z_top, z_bot = surface_fn(x_center, y)
            if z_top is not None:
                top_pts.append((y, z_top))
                bot_pts.append((y, z_bot))

        if len(top_pts) < 4:
            continue

        # Build mesh
        bm = bmesh.new()
        x_f = x_center - slat_thickness / 2
        x_b = x_center + slat_thickness / 2
        n = len(top_pts)

        ft, fb, bt, bb = [], [], [], []
        for k in range(n):
            y, zt = top_pts[k]
            _, zb = bot_pts[k]
            ft.append(bm.verts.new((x_f, y, zt)))
            fb.append(bm.verts.new((x_f, y, zb)))
            bt.append(bm.verts.new((x_b, y, zt)))
            bb.append(bm.verts.new((x_b, y, zb)))

        bm.verts.ensure_lookup_table()

        # Faces: front, back, top strip, bottom strip, end caps
        try: bm.faces.new(ft + list(reversed(fb)))
        except: pass
        try: bm.faces.new(list(reversed(bt)) + bb)
        except: pass
        for k in range(n-1):
            try: bm.faces.new([ft[k], ft[k+1], bt[k+1], bt[k]])
            except: pass
            try: bm.faces.new([fb[k+1], fb[k], bb[k], bb[k+1]])
            except: pass
        try: bm.faces.new([ft[0], bt[0], bb[0], fb[0]])
        except: pass
        try: bm.faces.new([ft[-1], fb[-1], bb[-1], bt[-1]])
        except: pass

        mesh = bpy.data.meshes.new(f"Slat_{i}")
        bm.to_mesh(mesh)
        bm.free()

        obj = bpy.data.objects.new(f"Slat_{i}", mesh)
        bpy.context.collection.objects.link(obj)

        # Smooth shading
        for face in obj.data.polygons:
            face.use_smooth = True

        # Bevel for rounded edges
        bev = obj.modifiers.new("Bevel", 'BEVEL')
        bev.width = 0.001
        bev.segments = 2
        bev.limit_method = 'ANGLE'
        bev.angle_limit = math.radians(60)

サーフェス関数のパターン

サーフェス関数はシンプルに保ちます。変化には smoothstep を使用します。

def smoothstep(x, edge0, edge1):
    if edge1 == edge0:
        return 0.0 if x < edge0 else 1.0
    t = max(0.0, min(1.0, (x - edge0) / (edge1 - edge0)))
    return t * t * (3 - 2 * t)
パターン:中央に盛り上がったボウルがある平らな円盤

最もシンプルで、最も一般的なパラメトリックプランターの形状です。

def flat_disc_with_bowl(x, y, R=0.40, base_h=0.055, wall_h=0.15, bowl_r=0.18):
    r = math.sqrt(x*x + y*y)
    if r >= R * 0.97:
        return None, None

    z_top = base_h
    z_bot = 0.002

    # Edge taper
    z_top *= 1.0 - smoothstep(r, R * 0.82, R * 0.96)
    if z_top < 0.004:
        return None, None

    # Bowl walls (steep rise using smoothstep)
    bd = math.sqrt(x*x + y*y)  # bowl centered at origin
    outer_rise = smoothstep(bd, bowl_r * 1.0, bowl_r * 0.70)
    z_top += (wall_h - base_h) * outer_rise

    # Inner depression
    if bd < bowl_r * 0.85:
        inner_dip = 1.0 - smoothstep(bd, bowl_r * 0.15, bowl_r * 0.65)
        z_top -= 0.12 * inner_dip
        z_top = max(z_bot + 0.003, z_top)

    return z_top, z_bot
パターン:均一な高さ(最もシンプル)

平らなデザイン、トレイ、コースター向けです。

def uniform_disc(x, y, R=0.40, height=0.03):
    r = math.sqrt(x*x + y*y)
    if r >= R * 0.97:
        return None, None
    z_top = height * (1.0 - smoothstep(r, R * 0.85, R * 0.96))
    if z_top < 0.003:
        return None, None
    return z_top, 0.002
カスタムサーフェスの作成

新しいサーフェス関数を作成する場合:

  • 減算ではなく、乗算によるくぼみを使用します(縮小)。減算すると穴ができます。
  • 必ず次のようにクランプします:z_top = max(z_bot + 0.001, z_top)
  • 遷移には生の数学演算ではなく、smoothstepを使用します。
  • 底面は平らに保ちます:z_bot = 0.002(定数)
  • 50個すべてを生成する前に、まず少数のスラットでテストします。

マテリアル

重要:bpy.ops.outliner.orphans_purge()は絶対に実行しないでください。再構築中に一時的にリンクが解除されたマテリアルが削除されます。マテリアルが失われる原因の第1位です。

オーク材
def create_oak_wood():
    mat = bpy.data.materials.new(name="Oak_Wood")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    output.location = (400, 0)

    bsdf = nodes.new('ShaderNodeBsdfPrincipled')
    bsdf.location = (100, 0)
    bsdf.inputs['Roughness'].default_value = 0.4
    links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])

    ramp = nodes.new('ShaderNodeValToRGB')
    ramp.location = (-200, 0)
    ramp.color_ramp.elements[0].color = (0.42, 0.26, 0.12, 1)  # dark grain
    ramp.color_ramp.elements[1].color = (0.62, 0.42, 0.22, 1)  # light grain
    links.new(ramp.outputs['Color'], bsdf.inputs['Base Color'])

    noise = nodes.new('ShaderNodeTexNoise')
    noise.location = (-400, 0)
    noise.inputs['Scale'].default_value = 8
    noise.inputs['Detail'].default_value = 6
    links.new(noise.outputs['Fac'], ramp.inputs['Fac'])

    mapping = nodes.new('ShaderNodeMapping')
    mapping.location = (-600, 0)
    mapping.inputs['Scale'].default_value = (2, 20, 2)  # elongated grain
    links.new(mapping.outputs['Vector'], noise.inputs['Vector'])

    texcoord = nodes.new('ShaderNodeTexCoord')
    texcoord.location = (-800, 0)
    links.new(texcoord.outputs['Object'], mapping.inputs['Vector'])

    return mat
マテリアルのヒント
  • 重複を作成する前に、必ずbpy.data.materials.get("Name")を確認します。
  • スラットを再構築する際は、obj.data.materials.clear()でマテリアルをクリアしてから、再度追加します。
  • プロシージャルテクスチャにはUVではなくオブジェクト座標を使用します。UV展開は不要です。
  • マッピングのスケール(2, 20, 2)を設定すると、Y軸に沿って伸びる木目になります。
その他のマテリアルプリセット

ウォールナット:暗色=(0.25, 0.13, 0.06)、明色=(0.45, 0.28, 0.14)、粗さ=0.35 メープル:暗色=(0.65, 0.50, 0.32)、明色=(0.82, 0.68, 0.48)、粗さ=0.3 コンクリート:ベースカラー=(0.6, 0.58, 0.55)、粗さ=0.9、木目テクスチャなし 金属:ベースカラー=(0.8, 0.8, 0.8)、金属度=1.0、粗さ=0.2


シーン設定

ライティングとレンダリング
import bpy

def setup_scene():
    scene = bpy.context.scene

    # Cycles with GPU
    scene.render.engine = 'CYCLES'
    scene.cycles.device = 'GPU'
    scene.cycles.samples = 64

    # World background (warm neutral)
    world = scene.world or bpy.data.worlds.new("World")
    scene.world = world
    world.use_nodes = True
    bg = world.node_tree.nodes.get('Background')
    if bg:
        bg.inputs['Color'].default_value = (0.92, 0.90, 0.87, 1)
        bg.inputs['Strength'].default_value = 0.8

    # Key light
    key = bpy.data.lights.new("Key_Light", 'AREA')
    key.energy = 120
    key_obj = bpy.data.objects.new("Key_Light", key)
    bpy.context.collection.objects.link(key_obj)
    key_obj.location = (0.5, -0.5, 0.8)
    key_obj.rotation_euler = (0.8, 0.2, 0.3)

    # Fill light
    fill = bpy.data.lights.new("Fill_Light", 'AREA')
    fill.energy = 50
    fill_obj = bpy.data.objects.new("Fill_Light", fill)
    bpy.context.collection.objects.link(fill_obj)
    fill_obj.location = (-0.5, 0.3, 0.5)
    fill_obj.rotation_euler = (1.0, -0.3, -0.5)

    # Floor plane
    bpy.ops.mesh.primitive_plane_add(size=5, location=(0, 0, 0))
    floor = bpy.context.active_object
    floor.name = "Floor"
    floor_mat = bpy.data.materials.new("Floor_Material")
    floor_mat.use_nodes = True
    floor_mat.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (0.88, 0.86, 0.83, 1)
    floor_mat.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value = 0.3
    floor.data.materials.append(floor_mat)

# Set viewport to rendered mode
for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        area.spaces[0].shading.type = 'RENDERED'
        break

よくあるエラーと修正方法

「key 'Subsurface Color' not found」

Blender 4.xではこの入力が削除されました。Principled BSDFにSubsurface Colorを設定しないでください。

「view3d.view_selected.poll() expected a view3d region」

適切なコンテキストがないと、bpy.ops.view3d.*オペレーターは使用できません。代わりにregion_3dを直接使用します:

for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        r3d = area.spaces[0].region_3d
        # Set view properties directly on r3d
「key 'Camera' not found」

Cameraオブジェクトが存在すると決めつけないでください。カメラオブジェクトではなく、ビューポートのコントロールを使用します。 まず、オブジェクトをすべて一覧表示します:[obj.name for obj in bpy.data.objects]

再構築後にマテリアルが消える

原因:bpy.ops.outliner.orphans_purge()によってマテリアルが削除されました。 修正方法:孤立データは絶対にパージしないでください。クリーンアップが必要な場合は、オブジェクトを手動で削除します:

to_delete = [obj for obj in bpy.data.objects if obj.name.startswith('Slat_')]
bpy.ops.object.select_all(action='DESELECT')
for obj in to_delete:
    obj.select_set(True)
bpy.ops.object.delete()
# Do NOT call orphans_purge after this
ボウルによってジオメトリに穴ができる

原因:ボウルの深さをz_topから減算すると、値がz_botより小さくなり、ジオメトリが除外されます。 修正方法:乗算によるくぼみを使用します:

# BAD - creates holes
z_top -= bowl_depth * gaussian

# GOOD - scales down, never goes below z_bot
bowl_scale = 1.0 - 0.85 * gaussian
z_top = z_bot + (z_top - z_bot) * bowl_scale

設計の原則

  1. 数学的な処理ではなく、ユーザーの言葉から始めます。 ユーザーが「中央が盛り上がった平らな円盤」と言ったら、そのとおりに作成します。ガウス包絡線に放射状のテーパーを加えたものではありません。

  2. まずはシンプルなジオメトリから。 詳細を追加する前に、基本形状を正しく作ります。誤ったパラメトリックサーフェスより、平らな円柱のほうが適切です。

  3. あらゆる角度から確認します。 正面から完璧に見えても、側面からはまったく違って見えることがあります。必ず4方向から確認します。

  4. 節目ごとに必ず保存します。 保存にかかる時間は1秒です。保存しない場合は、一から作り直すことになります。

  5. 一度に一つだけ変更します。 反復作業では、パラメーターを一つだけ変更して確認し、それから次に進みます。同じ手順でボウルのサイズと壁の傾斜、マテリアルを同時に変更してはいけません。

  6. 利用できる場合は、設計図の寸法を使用します。 寸法入りの技術図面を入手できるか、ユーザーに尋ねてください。毎回目測するより、正確な数値を使うほうが確実です。

  7. たいていの場合、ユーザーの言うことが正しいです。 「違う」や「もっとシンプルにして」と言われたら、耳を傾けてください。ユーザーは、数学を通して解釈するあなたよりも、参考画像をよく見ることができます。

コメント