アルファ型 ロンドン点 エクアドル点 20260630

rapture_20260630211640.png

bl_info = {
    "name": "Alpha Globe Generator Pro (Blender 5 Optimized)",
    "author": "Your Name",
    "version": (1, 0),
    "blender": (4, 0, 0), # Blender 4.0以降および5.x系に最適化
    "location": "View3D > Sidebar (Nパネル)",
    "description": "地球儀中心点C0固定で、緯線上をロンドン点と赤道点が回転するアニメーションモデルを生成",
    "category": "Object",
}

import bpy
import math
import mathutils
import webbrowser

# =========================================================================
# 【基本設定】
# =========================================================================
TAB_NAME    = "アルファ地球儀"         # Nパネルに表示されるタブ名
PREFIX_NAME = "alpha_globe"            # 内部データ・演算子用のプレフィックス

# --- リンク設定 ---
ADDON_LINKS = [
    {
        "label": "地球儀 緯度経度 xyz",
        "url": "<https://note.com/zionadmillion/n/na0b25ce45488>",
        "icon": "URL"
    },
    {
        "label": "最新 posfie",
        "url": "<https://posfie.com/@timekagura/t/zionad2022?sort=0>",
        "icon": "URL"
    }
]

# --- HTMLの色設定に準拠した初期カラー ---
INIT_C0_COLOR  = (0.60, 0.64, 0.71)  # #9aa3b5 (グレー系)
INIT_LON_COLOR = (0.89, 0.47, 0.35)  # #e3795a (オレンジ系)
INIT_EQ_COLOR  = (0.36, 0.79, 0.65)  # #5dcaa5 (グリーン系)
INIT_TRI_COLOR = (0.53, 0.53, 0.50)  # #888780 (線分用グレー)

# =========================================================================
# 【プロパティ定義】
# =========================================================================
class AlphaGlobeProperties(bpy.types.PropertyGroup):
    """UIからの入力値を保持するプロパティグループ"""
    
    # トランスフォーム
    loc_x: bpy.props.FloatProperty(name="位置 X", default=0.0)
    loc_y: bpy.props.FloatProperty(name="位置 Y", default=0.0)
    loc_z: bpy.props.FloatProperty(name="位置 Z", default=0.0)
    rot_x: bpy.props.FloatProperty(name="X軸", default=0.0, subtype='ANGLE')
    rot_y: bpy.props.FloatProperty(name="Y軸", default=0.0, subtype='ANGLE')
    rot_z: bpy.props.FloatProperty(name="Z軸", default=0.0, subtype='ANGLE')
    
    # サイズとアニメーション
    radius: bpy.props.FloatProperty(name="基準半径 (R)", default=1.0, min=0.1)
    frames_per_rev: bpy.props.IntProperty(name="1周のフレーム数", default=240, min=1, description="回転アニメーションの速度。少ないほど速くなります")
    line_thick: bpy.props.FloatProperty(name="線の太さ", default=0.008, min=0.001)
    point_size: bpy.props.FloatProperty(name="点のサイズ", default=0.04, min=0.001)
    
    # カラー
    c0_color: bpy.props.FloatVectorProperty(name="C0色", subtype='COLOR', default=INIT_C0_COLOR, min=0.0, max=1.0)
    eq_color: bpy.props.FloatVectorProperty(name="赤道色", subtype='COLOR', default=INIT_EQ_COLOR, min=0.0, max=1.0)
    lon_color: bpy.props.FloatVectorProperty(name="ロンドン色", subtype='COLOR', default=INIT_LON_COLOR, min=0.0, max=1.0)
    tri_color: bpy.props.FloatVectorProperty(name="線分色", subtype='COLOR', default=INIT_TRI_COLOR, min=0.0, max=1.0)

# =========================================================================
# 【オペレーター:生成処理】
# =========================================================================
class ALPHA_OT_create_globe(bpy.types.Operator):
    """アルファ地球儀モデル一式を生成します"""
    bl_idname = f"mesh.{PREFIX_NAME.lower()}_create"
    bl_label = "アルファ地球儀を生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
        
        # --- コレクション自動連番作成 ---
        set_count = 1
        while bpy.data.collections.get(f"アルファ地球儀_{set_count}"):
            set_count += 1
            
        coll = bpy.data.collections.new(f"アルファ地球儀_{set_count}")
        context.scene.collection.children.link(coll)
        
        def move_to_coll(obj):
            for c in obj.users_collection: c.objects.unlink(obj)
            coll.objects.link(obj)

        def create_mat(name, color):
            mat = bpy.data.materials.new(name=name)
            mat.use_nodes = True
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf: # Blender 4/5 共通仕様
                bsdf.inputs["Base Color"].default_value = (color[0], color[1], color[2], 1.0)
            return mat

        # マテリアル一括生成
        mat_c0 = create_mat(f"{PREFIX_NAME}_C0_{set_count}", props.c0_color)
        mat_eq = create_mat(f"{PREFIX_NAME}_EQ_{set_count}", props.eq_color)
        mat_lon = create_mat(f"{PREFIX_NAME}_LON_{set_count}", props.lon_color)
        mat_tri = create_mat(f"{PREFIX_NAME}_TRI_{set_count}", props.tri_color)

        R = props.radius
        t = props.line_thick
        p = props.point_size
        
        # 緯度経度の固定値(HTML準拠)
        LAT_LONDON = 51.5
        LON_EQUATOR = -78.5
        
        lat_lon_rad = math.radians(LAT_LONDON)
        lon_eq_rad = math.radians(LON_EQUATOR)

        # ----------------------------------------------------------------
        # 1. Base Empty (全ての親)
        bpy.ops.object.empty_add(type='ARROWS', location=(0,0,0))
        base_empty = context.active_object
        base_empty.name = f"AlphaGlobe_Base_{set_count}"
        base_empty["globe_prefix"] = PREFIX_NAME
        base_empty["alpha_part"] = "BASE"
        move_to_coll(base_empty)

        # ----------------------------------------------------------------
        # 2. C0 球体 (中心点・固定)
        bpy.ops.mesh.primitive_uv_sphere_add(radius=p * 1.5, segments=32, ring_count=16, location=(0,0,0))
        c0 = context.active_object
        c0.name = f"C0_Center_{set_count}"
        c0.data.materials.append(mat_c0)
        c0.parent = base_empty
        c0["globe_prefix"] = PREFIX_NAME
        c0["alpha_part"] = "C0"
        move_to_coll(c0)

        # ----------------------------------------------------------------
        # 3. 赤道緯線 (Torus・固定)
        bpy.ops.mesh.primitive_torus_add(major_radius=R, minor_radius=t/2, major_segments=64, minor_segments=16, location=(0,0,0))
        eq_line = context.active_object
        eq_line.name = f"Equator_Line_{set_count}"
        eq_line.data.materials.append(mat_eq)
        eq_line.parent = base_empty
        eq_line["globe_prefix"] = PREFIX_NAME
        eq_line["alpha_part"] = "EQ"
        move_to_coll(eq_line)

        # ----------------------------------------------------------------
        # 4. ロンドン緯線 (Torus・固定)
        r_lon = R * math.cos(lat_lon_rad)
        z_lon = R * math.sin(lat_lon_rad)
        bpy.ops.mesh.primitive_torus_add(major_radius=r_lon, minor_radius=t/2, major_segments=64, minor_segments=16, location=(0,0,z_lon))
        lon_line = context.active_object
        lon_line.name = f"London_Line_{set_count}"
        lon_line.data.materials.append(mat_lon)
        lon_line.parent = base_empty
        lon_line["globe_prefix"] = PREFIX_NAME
        lon_line["alpha_part"] = "LON"
        move_to_coll(lon_line)

        # ----------------------------------------------------------------
        # 5. 回転ローター (Empty・Z軸ドライバー駆動)
        bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0,0,0))
        rotor = context.active_object
        rotor.name = f"Rotor_Anim_{set_count}"
        rotor.parent = base_empty
        rotor["globe_prefix"] = PREFIX_NAME
        rotor["alpha_part"] = "ROTOR"
        move_to_coll(rotor)

        # ドライバー設定 (再生するとフレームに連動して回転)
        fcurve = rotor.driver_add('rotation_euler', 2) # インデックス2 = Z軸
        driver = fcurve.driver
        driver.type = 'SCRIPTED'
        driver.expression = f"(frame / {props.frames_per_rev}) * {2 * math.pi}"

        # ----------------------------------------------------------------
        # 6. 赤道点 (Sphere・ローターの子)
        x_eq = R * math.cos(lon_eq_rad)
        y_eq = R * math.sin(lon_eq_rad)
        bpy.ops.mesh.primitive_uv_sphere_add(radius=p, segments=32, ring_count=16, location=(x_eq, y_eq, 0))
        eq_pt = context.active_object
        eq_pt.name = f"Equator_Point_{set_count}"
        eq_pt.data.materials.append(mat_eq)
        eq_pt.parent = rotor
        eq_pt["globe_prefix"] = PREFIX_NAME
        eq_pt["alpha_part"] = "EQ"
        move_to_coll(eq_pt)

        # ----------------------------------------------------------------
        # 7. ロンドン点 (Sphere・ローターの子)
        bpy.ops.mesh.primitive_uv_sphere_add(radius=p, segments=32, ring_count=16, location=(r_lon, 0, z_lon))
        lon_pt = context.active_object
        lon_pt.name = f"London_Point_{set_count}"
        lon_pt.data.materials.append(mat_lon)
        lon_pt.parent = rotor
        lon_pt["globe_prefix"] = PREFIX_NAME
        lon_pt["alpha_part"] = "LON"
        move_to_coll(lon_pt)

        # ----------------------------------------------------------------
        # 8. 三角形の辺 (Mesh + Wireframe Modifier・ローターの子)
        mesh = bpy.data.meshes.new(f"TriangleMesh_{set_count}")
        verts = [ (0, 0, 0), (r_lon, 0, z_lon), (x_eq, y_eq, 0) ]
        edges = [(0,1), (0,2), (1,2)] # C0-Lon, C0-Eq, Lon-Eq の3本の線分
        mesh.from_pydata(verts, edges, [])
        tri_obj = bpy.data.objects.new(f"Triangle_Edges_{set_count}", mesh)
        tri_obj.data.materials.append(mat_tri)
        tri_obj.parent = rotor
        tri_obj["globe_prefix"] = PREFIX_NAME
        tri_obj["alpha_part"] = "TRIANGLE"
        coll.objects.link(tri_obj)
        
        # ワイヤーフレームモディファイアで線に太さを持たせる
        mod = tri_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
        mod.thickness = t

        # ----------------------------------------------------------------
        # 全体の位置・回転適用 (Base Emptyを移動・回転)
        rot_mat = mathutils.Euler((props.rot_x, props.rot_y, props.rot_z), 'XYZ').to_matrix().to_4x4()
        loc_mat = mathutils.Matrix.Translation((props.loc_x, props.loc_y, props.loc_z))
        base_empty.matrix_world = loc_mat @ rot_mat @ base_empty.matrix_world

        context.view_layer.objects.active = base_empty
        base_empty.select_set(True)
        context.view_layer.update()

        self.report({'INFO'}, f"アルファ地球儀を生成しました (コレクション: {coll.name})")
        return {'FINISHED'}

# =========================================================================
# 【オペレーター:表示トグル・その他】
# =========================================================================
class ALPHA_OT_toggle_visibility(bpy.types.Operator):
    """パーツの表示/非表示を切り替えます"""
    bl_idname = f"object.{PREFIX_NAME.lower()}_toggle_vis"
    bl_label = "表示切替"
    bl_options = {'REGISTER', 'UNDO'}
    
    part_type: bpy.props.StringProperty() 
    
    def execute(self, context):
        target_objs = [o for o in context.scene.objects if o.get("globe_prefix") == PREFIX_NAME and o.get("alpha_part") == self.part_type]
        if not target_objs: 
            return {'CANCELLED'}
        new_state = not target_objs[0].hide_viewport
        for o in target_objs: 
            o.hide_viewport = new_state
        return {'FINISHED'}

class ALPHA_OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX_NAME.lower()}_open_url"
    bl_label = "URLを開く"
    url: bpy.props.StringProperty(default="")
    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class ALPHA_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX_NAME.lower()}_remove_addon"
    bl_label = "アドオン削除"
    def execute(self, context):
        unregister()
        return {'FINISHED'}

# =========================================================================
# 【パネルUI】
# =========================================================================
class ALPHA_PT_main_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_main_panel"
    bl_label = "アルファ地球儀 (生成・設定)"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")

        box = layout.box()
        box.scale_y = 1.3
        box.operator(f"mesh.{PREFIX_NAME.lower()}_create", text="アニメーションモデルを生成", icon='PLAY')
        
        box = layout.box()
        box.label(text="基本設定", icon='PREFERENCES')
        col = box.column(align=True)
        col.prop(props, "radius")
        col.prop(props, "frames_per_rev")
        col.prop(props, "line_thick")
        col.prop(props, "point_size")
        
        def draw_part(parent, title, icon, color_prop, part_type):
            b = parent.box()
            b.label(text=title, icon=icon)
            r = b.row(align=True)
            r.prop(props, color_prop, text="")
            r.operator(f"object.{PREFIX_NAME.lower()}_toggle_vis", text="表示切替", icon='HIDE_OFF').part_type = part_type

        draw_part(layout, "C0 (中心点)", 'DOT', "c0_color", 'C0')
        draw_part(layout, "赤道 (緯線・点)", 'MESH_TORUS', "eq_color", 'EQ')
        draw_part(layout, "ロンドン (緯線・点)", 'MESH_TORUS', "lon_color", 'LON')
        draw_part(layout, "三角形の辺 (点線相当)", 'MESH_DATA', "tri_color", 'TRIANGLE')

class ALPHA_PT_transform_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_transform_panel"
    bl_label = "全体の位置・回転"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")

        box_rot = layout.box()
        box_rot.label(text="全体の回転 (度)", icon='ORIENTATION_GIMBAL')
        col_rot = box_rot.column(align=True)
        col_rot.prop(props, "rot_x")
        col_rot.prop(props, "rot_y")
        col_rot.prop(props, "rot_z")

        box_loc = layout.box()
        box_loc.label(text="生成位置", icon='OBJECT_ORIGIN')
        col_loc = box_loc.column(align=True)
        col_loc.prop(props, "loc_x")
        col_loc.prop(props, "loc_y")
        col_loc.prop(props, "loc_z")

class ALPHA_PT_link_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_link_panel"
    bl_label = "リンク"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_order = 100

    def draw(self, context):
        layout = self.layout
        for link in ADDON_LINKS:
            op = layout.operator(f"wm.{PREFIX_NAME.lower()}_open_url", text=link["label"], icon=link.get("icon", "URL"))
            op.url = link["url"]

class ALPHA_PT_remove_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_remove_panel"
    bl_label = "アドオン削除"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_order = 101
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        layout.operator(f"wm.{PREFIX_NAME.lower()}_remove_addon", text="アドオン削除", icon='CANCEL')

# =========================================================================
# 【登録処理】
# =========================================================================
classes = [
    AlphaGlobeProperties,
    ALPHA_OT_create_globe,
    ALPHA_OT_toggle_visibility,
    ALPHA_OT_open_url,
    ALPHA_OT_remove_addon,
    ALPHA_PT_main_panel,
    ALPHA_PT_transform_panel,
    ALPHA_PT_link_panel,
    ALPHA_PT_remove_panel,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props", bpy.props.PointerProperty(type=AlphaGlobeProperties))

def unregister():
    if hasattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props"):
        delattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props")
    for cls in reversed(classes):
        try: bpy.utils.unregister_class(cls)
        except: pass

if __name__ == "__main__":
    try: unregister()
    except: pass
    register()