Newer
Older
InforSystem / blend / gravity_simulation_ui_txt
import bpy
import numpy as np

# シーンに新規FloatProperty「gravity_strength」を登録する関数
def register_props():
    bpy.types.Scene.gravity_strength = bpy.props.FloatProperty(
        name="重力定数 G",       # UIに表示される名前
        default=1.0,             # デフォルト値
        min=0.1,                 # 最小値
        max=5.0,                 # 最大値
        step=0.1,                # ステップ幅(UIでの操作用)
        precision=2              # 小数点以下の桁数
    )

# 軌道計算とアニメーション作成を行う関数
def calculate_orbit(context):
    G = context.scene.gravity_strength  # UIで設定した重力定数を取得
    M = 1000                            # 太陽の質量(任意の単位)
    pos = np.array([8.0, 0.0])         # 地球の初期位置(x=8, y=0)
    vel = np.array([0.0, 10.0])        # 地球の初期速度(x=0, y=10)
    dt = 0.01                         # タイムステップ
    steps = 400                       # アニメーションの総フレーム数
    positions = []                    # 軌道位置を記録するリスト(今は使っていません)

    # 既存のオブジェクトを全て選択して削除(シーンを初期化)
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)

    # --- 太陽オブジェクトの作成 ---
    bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 0, 0))
    sun = bpy.context.object
    sun.name = "Sun"

    # 太陽のマテリアルを作成(Emissionシェーダーで光らせ、テクスチャを貼る)
    mat = bpy.data.materials.new("SunMat")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links

    # 初期ノードを全削除
    for node in nodes:
        nodes.remove(node)

    # 画像テクスチャノードを作成し、テクスチャ画像を読み込む
    tex_image = nodes.new(type='ShaderNodeTexImage')
    img_path = "/Users/saitokaren/koeki/git/InforSystem/blend/texture/sun_texture.jpg"
    tex_image.image = bpy.data.images.load(img_path)

    # Emissionノード(光を発するマテリアル)
    emission = nodes.new(type='ShaderNodeEmission')

    # マテリアル出力ノード
    material_output = nodes.new(type='ShaderNodeOutputMaterial')

    # ノードの見やすい配置(任意)
    tex_image.location = (-300, 0)
    emission.location = (0, 0)
    material_output.location = (300, 0)

    # 画像の色をEmissionノードの色入力に接続
    links.new(tex_image.outputs['Color'], emission.inputs['Color'])

    # Emissionノードの出力をMaterial Outputに接続
    links.new(emission.outputs['Emission'], material_output.inputs['Surface'])

    # 作成したマテリアルを太陽に割り当てる
    sun.data.materials.append(mat)

    # --- 光源の作成 ---
    # 光源は別物。SUNタイプのライトを作成し、太陽から斜め45度の方向に配置
    bpy.ops.object.light_add(type='SUN', location=(0, 0, 0))
    light = bpy.context.object
    light.data.energy = 3               # 光の強さ(数値が大きいほど明るい)
    # 光が斜めから当たるように角度を設定(135度=太陽から見て斜め上方向)
    light.rotation_euler = (np.radians(135), 0, 0)

    # --- 地球オブジェクトの作成 ---
    bpy.ops.mesh.primitive_uv_sphere_add(radius=0.3, location=(pos[0], pos[1], 0))
    earth = bpy.context.object
    earth.name = "Earth"

    # 地球のマテリアル作成(Principled BSDFでテクスチャを貼る)
    mat = bpy.data.materials.new("EarthMat")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links

    # 初期ノード削除
    for node in nodes:
        nodes.remove(node)

    # 地球テクスチャ読み込み
    tex_image = nodes.new(type='ShaderNodeTexImage')
    img_path = "/Users/saitokaren/koeki/git/InforSystem/blend/texture/earth_texture.jpg"
    tex_image.image = bpy.data.images.load(img_path)

    # Principled BSDF(物理ベースシェーダー)
    bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')

    # マテリアル出力ノード
    material_output = nodes.new(type='ShaderNodeOutputMaterial')

    # 接続:テクスチャの色 → ベースカラー入力
    links.new(tex_image.outputs['Color'], bsdf.inputs['Base Color'])

    # 接続:BSDF出力 → マテリアル出力
    links.new(bsdf.outputs['BSDF'], material_output.inputs['Surface'])

    # 地球にマテリアル割り当て
    earth.data.materials.append(mat)

    # --- アニメーション設定 ---
    scene = context.scene
    scene.frame_start = 1
    scene.frame_end = steps
    rotation_z_earth = 0.0
    rotation_z_sun = 0.0

    for frame in range(1, steps + 1):
        # 地球に働く重力加速度の計算(単純な万有引力)
        r_vec = -pos
        dist = np.linalg.norm(r_vec)
        acc = G * M * r_vec / dist**3

        # 速度と位置の更新(Euler法)
        vel += acc * dt
        pos += vel * dt
        positions.append(pos.copy())

        # 地球の位置更新とキーフレーム挿入
        earth.location = (pos[0], pos[1], 0)
        earth.keyframe_insert(data_path="location", frame=frame)

        # 地球の自転角度を増やし、回転を設定・キーフレーム挿入
        rotation_z_earth += 0.1
        earth.rotation_euler = (0, 0, rotation_z_earth)
        earth.keyframe_insert(data_path="rotation_euler", frame=frame)

        # 太陽の自転(地球より遅く回転)
        rotation_z_sun += 0.05
        sun.rotation_euler = (0, 0, rotation_z_sun)
        sun.keyframe_insert(data_path="rotation_euler", frame=frame)

# UIパネル定義
class GravityOnlyPanel(bpy.types.Panel):
    bl_label = "重力定数で軌道観察"
    bl_idname = "VIEW3D_PT_gravity_only"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = '重力定数'

    def draw(self, context):
        layout = self.layout
        # 重力定数スライダー表示
        layout.prop(context.scene, "gravity_strength")
        # 再計算ボタン
        layout.operator("orbit.recalc", text="再計算")
        # アニメーション再生ボタン
        layout.operator("screen.animation_play", text="▶")

# 再計算オペレータ(ボタン押したときに軌道再計算)
class RecalcOrbitOperator(bpy.types.Operator):
    bl_idname = "orbit.recalc"
    bl_label = "軌道を再計算"

    def execute(self, context):
        calculate_orbit(context)
        self.report({'INFO'}, "軌道を再計算しました")
        return {'FINISHED'}

# クラス登録リスト
classes = [GravityOnlyPanel, RecalcOrbitOperator]

# アドオン登録関数
def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    register_props()

# アドオン解除関数
def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.gravity_strength

if __name__ == "__main__":
    register()
    calculate_orbit(bpy.context)