rapture_20260709020441.png



「切り離し (独立)」ボタンを押したときに、表示されているすべてのオブジェクト(メイン球・穴平面・球冠・穴球体・円錐)を1つのオブジェクトに結合(一体化) するように修正
切り離し時に オブジェクト一体して
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 2, 8),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

# 初期値 (分割数)
SEG_U_MAIN = 64
SEG_V_MAIN = 32
SEG_CIRCLE = 64
SEG_U_SUB = 32
SEG_V_SUB = 16

# 初期値 (サイズ・色など)
SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0) 

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0

PLANE_COLOR = (0.9, 0.9, 0.9, 1.0)       
CAP_COLOR = (0.2, 0.8, 0.4, 1.0)         
CENTER_SPHERE_RADIUS = 1.0
CENTER_SPHERE_COLOR = (0.9, 0.1, 0.1, 1.0) 

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_PLANES = f"{OBJ_NAME}_Planes"
NAME_CAPS = f"{OBJ_NAME}_Caps"
NAME_CENTER_SPHERES = f"{OBJ_NAME}_CenterSpheres"
NAME_CONE = f"{OBJ_NAME}_Cone"

MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"
MAT_PLANE = f"{PREFIX_NAME}_Mat_Plane"
MAT_CAP = f"{PREFIX_NAME}_Mat_Cap"
MAT_CENTER_SPHERE = f"{PREFIX_NAME}_Mat_Center_Sphere"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"

PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群
# ==========================================

def ensure_layer_visibility(context, coll_name):
    def traverse(layer_coll):
        if layer_coll.collection.name == coll_name:
            return layer_coll
        for child in layer_coll.children:
            res = traverse(child)
            if res:
                return res
        return None
    lc = traverse(context.view_layer.layer_collection)
    if lc:
        lc.exclude = False
        lc.hide_viewport = False

def get_or_create_collection(coll_name, context):
    coll = bpy.data.collections.get(coll_name)
    if not coll:
        coll = bpy.data.collections.new(coll_name)
    if coll.name not in context.scene.collection.children.keys():
        try:
            context.scene.collection.children.link(coll)
        except Exception:
            pass
    ensure_layer_visibility(context, coll_name)
    return coll

def get_or_create_mesh_object(obj_name, coll):
    obj = bpy.data.objects.get(obj_name)
    if not obj:
        mesh = bpy.data.meshes.new(obj_name)
        obj = bpy.data.objects.new(obj_name, mesh)
        try:
            coll.objects.link(obj)
        except Exception:
            pass
    else:
        if obj.name not in coll.objects:
            try:
                coll.objects.link(obj)
            except Exception:
                pass
        if not obj.data or type(obj.data).__name__ != 'Mesh':
            mesh = bpy.data.meshes.new(obj_name)
            obj.data = mesh
    return obj

def ensure_material(mat_name):
    mat = bpy.data.materials.get(mat_name)
    if not mat:
        mat = bpy.data.materials.new(mat_name)
        mat.use_nodes = True
    return mat

def assign_material(obj, mat, slot_index=0):
    if len(obj.data.materials) <= slot_index:
        while len(obj.data.materials) <= slot_index:
            obj.data.materials.append(None)
    obj.data.materials[slot_index] = mat

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    
    # 透過表示のための設定(Eevee等)
    if hasattr(mat, "blend_method"):
        mat.blend_method = 'BLEND'
    if hasattr(mat, "shadow_method"):
        mat.shadow_method = 'HASHED'

    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            # 透明度(Alpha)の適用
            if "Alpha" in node.inputs:
                node.inputs["Alpha"].default_value = color[3] 
            break
    return mat

def apply_smooth_shade(bm, use_smooth):
    for face in bm.faces:
        face.smooth = use_smooth

# --- メッシュ生成 ---
def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * dist
            
            bmesh.ops.create_cone(
                bm, cap_ends=True, segments=segments,
                radius1=hole_radius, radius2=hole_radius, depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
            
    apply_smooth_shade(bm, use_smooth)
    bm.to_mesh(mesh)
    bm.free()

def create_planes_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
    bm = bmesh.new()
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * dist
            mat_transform = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            
            geom = bmesh.ops.create_circle(
                bm, segments=segments, radius=hole_radius, matrix=mat_transform
            )
            verts = geom['verts']
            if len(verts) >= 3:
                try:
                    bm.faces.new(verts)
                except Exception:
                    try:
                        bmesh.ops.contextual_create(bm, geom=verts)
                    except:
                        pass
                        
    apply_smooth_shade(bm, use_smooth)
    bm.to_mesh(mesh)
    bm.free()

def create_center_spheres_mesh(mesh, sphere_radius, lat_count, lon_count, center_radius, hole_radius, seg_u, seg_v, use_smooth, mode):
    bm = bmesh.new()
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    # 1. テンプレートとなる1つの球体(または半球)を作成
    bmesh.ops.create_uvsphere(bm, u_segments=seg_u, v_segments=seg_v, radius=center_radius)
    
    if mode == 'OUTER':
        # Z < 0 の頂点を削除(外側半球を残す)
        bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.0001], context='VERTS')
        # 切断面のフタを閉じる
        bmesh.ops.holes_fill(bm, edges=[e for e in bm.edges if e.is_boundary])
    elif mode == 'INNER':
        # Z > 0 の頂点を削除(内側半球を残す)
        bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z > 0.0001], context='VERTS')
        bmesh.ops.holes_fill(bm, edges=[e for e in bm.edges if e.is_boundary])
        
    base_geom = bm.verts[:] + bm.edges[:] + bm.faces[:]

    # 2. テンプレートを複製して各位置に配置
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            pos = dir_vec * dist
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            
            # 回転と移動の行列
            mat = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            
            # 複製して変形
            res = bmesh.ops.duplicate(bm, geom=base_geom)
            new_verts = [elem for elem in res['geom'] if isinstance(elem, bmesh.types.BMVert)]
            bmesh.ops.transform(bm, matrix=mat, verts=new_verts)
            
    # 3. 最初に作成したテンプレート本体(原点にあるもの)を削除
    bmesh.ops.delete(bm, geom=base_geom, context='VERTS')
    
    # 法線の再計算とスムーズシェード適用
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    apply_smooth_shade(bm, use_smooth)
    
    bm.to_mesh(mesh)
    bm.free()

def setup_base_objects(context):
    main_coll = get_or_create_collection(COLLECTION_MAIN, context)
    sub_coll = get_or_create_collection(COLLECTION_SUB, context)
    
    mat_sphere = ensure_material(MAT_SPHERE)
    mat_sphere_inner = ensure_material(MAT_SPHERE_INNER)
    mat_plane = ensure_material(MAT_PLANE)
    mat_cap = ensure_material(MAT_CAP)
    mat_center = ensure_material(MAT_CENTER_SPHERE)
    mat_cone = ensure_material(MAT_CONE)
            
    # 1. メイン球体
    obj_sphere = get_or_create_mesh_object(NAME_SPHERE, main_coll)
    assign_material(obj_sphere, mat_sphere, 0)
    assign_material(obj_sphere, mat_sphere_inner, 1)
    
    mod_solid = obj_sphere.modifiers.get("Solidify") or obj_sphere.modifiers.new(name="Solidify", type='SOLIDIFY')
    mod_solid.material_offset = 1     
    mod_bool = obj_sphere.modifiers.get("Boolean") or obj_sphere.modifiers.new(name="Boolean", type='BOOLEAN')
    mod_bool.operation = 'DIFFERENCE'
    mod_bool.solver = 'EXACT'
        
    # 2. カッター
    obj_cutters = get_or_create_mesh_object(NAME_CUTTERS, sub_coll)
    obj_cutters.display_type = 'WIRE'
    obj_cutters.hide_render = True
    obj_cutters.hide_viewport = True
    
    mod_bool.object = obj_cutters 

    # 3. 穴平面
    obj_planes = get_or_create_mesh_object(NAME_PLANES, main_coll)
    assign_material(obj_planes, mat_plane, 0)
        
    # 4. 球冠
    obj_caps = get_or_create_mesh_object(NAME_CAPS, main_coll)
    assign_material(obj_caps, mat_cap, 0)
    
    c_mod_solid = obj_caps.modifiers.get("Solidify") or obj_caps.modifiers.new(name="Solidify", type='SOLIDIFY')
    c_mod_bool = obj_caps.modifiers.get("Boolean") or obj_caps.modifiers.new(name="Boolean", type='BOOLEAN')
    c_mod_bool.operation = 'INTERSECT'
    c_mod_bool.solver = 'EXACT'
    c_mod_bool.object = obj_cutters
        
    # 5. 中心の球体
    obj_center = get_or_create_mesh_object(NAME_CENTER_SPHERES, main_coll)
    assign_material(obj_center, mat_center, 0)

    # 6. 円錐
    obj_cone = get_or_create_mesh_object(NAME_CONE, main_coll)
    assign_material(obj_cone, mat_cone, 0)

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    # --- 球体 ---
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    if obj_sphere and obj_sphere.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        obj_sphere.hide_viewport = not props.show_sphere
        obj_sphere.hide_render = not props.show_sphere
        
        update_material_color(MAT_SPHERE, props.sphere_color)
        update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
            
    # --- カッター ---
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    if obj_cutters and obj_cutters.type == 'MESH':
        create_cutters_mesh(
            obj_cutters.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius,
            props.segments_circle, props.use_smooth_shade
        )
        obj_cutters.location = props.sphere_location
        obj_cutters.rotation_euler = props.sphere_rotation

    # --- 穴平面 ---
    obj_planes = bpy.data.objects.get(NAME_PLANES)
    if obj_planes and obj_planes.type == 'MESH':
        create_planes_mesh(
            obj_planes.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius,
            props.segments_circle, props.use_smooth_shade
        )
        obj_planes.location = props.sphere_location
        obj_planes.rotation_euler = props.sphere_rotation
        obj_planes.hide_viewport = not props.show_plane
        obj_planes.hide_render = not props.show_plane
        
        update_material_color(MAT_PLANE, props.plane_color)

    # --- 球冠(フタ) ---
    obj_caps = bpy.data.objects.get(NAME_CAPS)
    if obj_caps and obj_caps.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_caps.data)
        bm.free()
        
        mod_solid = obj_caps.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_caps.location = props.sphere_location
        obj_caps.rotation_euler = props.sphere_rotation
        obj_caps.hide_viewport = not props.show_cap
        obj_caps.hide_render = not props.show_cap
        
        update_material_color(MAT_CAP, props.cap_color)

    # --- 穴球体 (中心球体) ---
    obj_center_spheres = bpy.data.objects.get(NAME_CENTER_SPHERES)
    if obj_center_spheres and obj_center_spheres.type == 'MESH':
        create_center_spheres_mesh(
            obj_center_spheres.data, props.sphere_radius,
            props.hole_latitude, props.hole_longitude, 
            props.center_sphere_radius, props.hole_radius,
            props.segments_sub_u, props.segments_sub_v, props.use_smooth_shade,
            props.center_sphere_mode
        )
        obj_center_spheres.location = props.sphere_location
        obj_center_spheres.rotation_euler = props.sphere_rotation
        obj_center_spheres.hide_viewport = not props.show_center_sphere
        obj_center_spheres.hide_render = not props.show_center_sphere
        
        update_material_color(MAT_CENTER_SPHERE, props.center_sphere_color)
        
    # --- 中央円錐 ---
    obj_cone = bpy.data.objects.get(NAME_CONE)
    if obj_cone and obj_cone.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_cone(bm, cap_ends=True, segments=props.segments_circle, radius1=props.cone_radius, radius2=0, depth=props.cone_depth)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_cone.data)
        bm.free()
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

# ==========================================
# 4. プロパティ定義
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating:
        return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    
    # 品質設定
    use_smooth_shade: bpy.props.BoolProperty(name="スムーズシェード適用", default=True, update=trigger_update)
    segments_main_u: bpy.props.IntProperty(name="メイン球 分割 U", default=SEG_U_MAIN, min=3, soft_max=128, update=trigger_update)
    segments_main_v: bpy.props.IntProperty(name="メイン球 分割 V", default=SEG_V_MAIN, min=2, soft_max=64, update=trigger_update)
    segments_circle: bpy.props.IntProperty(name="穴・円錐 分割数", default=SEG_CIRCLE, min=3, soft_max=128, update=trigger_update)
    segments_sub_u: bpy.props.IntProperty(name="穴球体 分割 U", default=SEG_U_SUB, min=3, soft_max=64, update=trigger_update)
    segments_sub_v: bpy.props.IntProperty(name="穴球体 分割 V", default=SEG_V_SUB, min=2, soft_max=32, update=trigger_update)

    show_sphere: bpy.props.BoolProperty(name="球体を表示", default=True, update=trigger_update)
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="表面色 (Aで透明度)", subtype='COLOR', default=SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色 (Aで透明度)", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    show_plane: bpy.props.BoolProperty(name="穴平面を表示", default=True, update=trigger_update)
    plane_color: bpy.props.FloatVectorProperty(name="穴平面色 (Aで透明度)", subtype='COLOR', default=PLANE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)

    show_cap: bpy.props.BoolProperty(name="球冠(フタ)を表示", default=True, update=trigger_update)
    cap_color: bpy.props.FloatVectorProperty(name="球冠色 (Aで透明度)", subtype='COLOR', default=CAP_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)

    # 穴球体のプロパティ
    show_center_sphere: bpy.props.BoolProperty(name="穴球体を表示", default=True, update=trigger_update)
    center_sphere_mode: bpy.props.EnumProperty(
        name="表示範囲",
        items=[
            ('FULL', "全球 (両方)", "球全体を表示します"),
            ('OUTER', "外側半球のみ (凸)", "外側に膨らむ半球のみ表示します"),
            ('INNER', "内側半球のみ (凹)", "内側に凹む半球のみ表示します"),
        ],
        default='FULL',
        update=trigger_update
    )
    center_sphere_radius: bpy.props.FloatProperty(name="半径", default=CENTER_SPHERE_RADIUS, min=0.01, update=trigger_update)
    center_sphere_color: bpy.props.FloatVectorProperty(name="色 (Aで透明度)", subtype='COLOR', default=CENTER_SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色 (Aで透明度)", subtype='COLOR', default=CONE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================

class ADDON_OT_sync_center_radius(bpy.types.Operator):
    """穴球体の半径を「穴平面の半径」に合わせます"""
    bl_idname = f"{PREFIX_NAME}.sync_center_radius"
    bl_label = "穴半径と同期"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, PROP_ID)
        props.center_sphere_radius = props.hole_radius
        return {'FINISHED'}

class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        try:
            setup_base_objects(context)
            update_meshes_and_props(context)
            self.report({'INFO'}, "地球儀を生成しました。")
        except Exception as e:
            self.report({'ERROR'}, f"生成エラー: {str(e)}")
            print(f"Globe Addon Error: {e}")
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_PLANES, NAME_CAPS, NAME_CENTER_SPHERES, NAME_CONE]:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll:
                coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        is_updating = True
        try:
            # メッシュ品質の初期化
            props.use_smooth_shade = True
            props.segments_main_u = SEG_U_MAIN
            props.segments_main_v = SEG_V_MAIN
            props.segments_circle = SEG_CIRCLE
            props.segments_sub_u = SEG_U_SUB
            props.segments_sub_v = SEG_V_SUB

            # その他の初期化
            props.show_sphere = True
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_inner_color = SPHERE_INNER_COLOR
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.show_plane = True
            props.plane_color = PLANE_COLOR
            
            props.show_cap = True
            props.cap_color = CAP_COLOR
            
            props.show_center_sphere = True
            props.center_sphere_mode = 'FULL'
            props.center_sphere_radius = CENTER_SPHERE_RADIUS
            props.center_sphere_color = CENTER_SPHERE_COLOR
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        try:
            update_meshes_and_props(context)
        except Exception as e:
            self.report({'ERROR'}, f"エラー: {e}")
            
        return {'FINISHED'}

class ADDON_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        def delay_unregister():
            unregister()
            return None
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()

        # ② メッシュ品質設定
        box = layout.box()
        box.label(text="② メッシュ品質設定", icon='MODIFIER')
        box.prop(props, "use_smooth_shade")
        row = box.row()
        row.prop(props, "segments_main_u")
        row.prop(props, "segments_main_v")
        row = box.row()
        row.prop(props, "segments_circle")
        row = box.row()
        row.prop(props, "segments_sub_u")
        row.prop(props, "segments_sub_v")
        
        # ③ 球体設定
        box = layout.box()
        box.label(text="③ 球体設定", icon='MESH_UVSPHERE')
        row = box.row()
        row.prop(props, "show_sphere", text="表示する")
        if props.show_sphere:
            box.prop(props, "sphere_radius")
            box.prop(props, "sphere_thickness")
            box.prop(props, "sphere_color")
            box.prop(props, "sphere_inner_color")
            box.prop(props, "sphere_location")
            box.prop(props, "sphere_rotation")
        
        # ④ 穴窓設定
        box = layout.box()
        box.label(text="④ 穴窓設定 (配置)", icon='OUTLINER_OB_MESH')
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        # ⑤ 穴平面設定
        box = layout.box()
        box.label(text="⑤ 穴平面設定", icon='MESH_CIRCLE')
        row = box.row()
        row.prop(props, "show_plane", text="表示する")
        if props.show_plane:
            row.prop(props, "plane_color", text="色")

        # ⑥ 球冠設定
        box = layout.box()
        box.label(text="⑥ 球冠(フタ)設定", icon='MESH_ICOSPHERE')
        row = box.row()
        row.prop(props, "show_cap", text="表示する")
        if props.show_cap:
            row.prop(props, "cap_color", text="色")

        # ⑦ 穴球体設定
        box = layout.box()
        box.label(text="⑦ 穴球体 設定", icon='LIGHT_POINT')
        row = box.row()
        row.prop(props, "show_center_sphere", text="表示する")
        if props.show_center_sphere:
            box.prop(props, "center_sphere_mode")
            
            row = box.row(align=True)
            row.prop(props, "center_sphere_radius")
            # 半径同期ボタン
            row.operator(f"{PREFIX_NAME}.sync_center_radius", text="", icon='LINKED')
            
            box.prop(props, "center_sphere_color")

        # ⑧ 円錐設定
        box = layout.box()
        box.label(text="⑧ 中央の円錐設定", icon='MESH_CONE')
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑨ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑩ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_sync_center_radius,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()
、「全球」「外側半球(外に膨らむ)」「内側半球(内に凹む)」 
穴につける球体半径を 穴平面の半径にするボタンを作って
それと この穴球体の 大きな球体の外側半球だけ表示 内側だけ表示 両方表示
選択できるようにして
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 2, 7),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

# 初期値 (分割数)
SEG_U_MAIN = 64
SEG_V_MAIN = 32
SEG_CIRCLE = 64
SEG_U_SUB = 32
SEG_V_SUB = 16

# 初期値 (サイズ・色など)
SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0) 

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0

PLANE_COLOR = (0.9, 0.9, 0.9, 1.0)       
CAP_COLOR = (0.2, 0.8, 0.4, 1.0)         
CENTER_SPHERE_RADIUS = 0.5
CENTER_SPHERE_COLOR = (0.9, 0.1, 0.1, 1.0) 

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_PLANES = f"{OBJ_NAME}_Planes"
NAME_CAPS = f"{OBJ_NAME}_Caps"
NAME_CENTER_SPHERES = f"{OBJ_NAME}_CenterSpheres"
NAME_CONE = f"{OBJ_NAME}_Cone"

MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"
MAT_PLANE = f"{PREFIX_NAME}_Mat_Plane"
MAT_CAP = f"{PREFIX_NAME}_Mat_Cap"
MAT_CENTER_SPHERE = f"{PREFIX_NAME}_Mat_Center_Sphere"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"

PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群
# ==========================================

def ensure_layer_visibility(context, coll_name):
    def traverse(layer_coll):
        if layer_coll.collection.name == coll_name:
            return layer_coll
        for child in layer_coll.children:
            res = traverse(child)
            if res:
                return res
        return None
    lc = traverse(context.view_layer.layer_collection)
    if lc:
        lc.exclude = False
        lc.hide_viewport = False

def get_or_create_collection(coll_name, context):
    coll = bpy.data.collections.get(coll_name)
    if not coll:
        coll = bpy.data.collections.new(coll_name)
    if coll.name not in context.scene.collection.children.keys():
        try:
            context.scene.collection.children.link(coll)
        except Exception:
            pass
    ensure_layer_visibility(context, coll_name)
    return coll

def get_or_create_mesh_object(obj_name, coll):
    obj = bpy.data.objects.get(obj_name)
    if not obj:
        mesh = bpy.data.meshes.new(obj_name)
        obj = bpy.data.objects.new(obj_name, mesh)
        try:
            coll.objects.link(obj)
        except Exception:
            pass
    else:
        if obj.name not in coll.objects:
            try:
                coll.objects.link(obj)
            except Exception:
                pass
        if not obj.data or type(obj.data).__name__ != 'Mesh':
            mesh = bpy.data.meshes.new(obj_name)
            obj.data = mesh
    return obj

def ensure_material(mat_name):
    mat = bpy.data.materials.get(mat_name)
    if not mat:
        mat = bpy.data.materials.new(mat_name)
        mat.use_nodes = True
    return mat

def assign_material(obj, mat, slot_index=0):
    if len(obj.data.materials) <= slot_index:
        while len(obj.data.materials) <= slot_index:
            obj.data.materials.append(None)
    obj.data.materials[slot_index] = mat

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    
    # 透過表示のための設定(Eevee等)
    if hasattr(mat, "blend_method"):
        mat.blend_method = 'BLEND'
    if hasattr(mat, "shadow_method"):
        mat.shadow_method = 'HASHED'

    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            # 透明度(Alpha)の適用
            if "Alpha" in node.inputs:
                node.inputs["Alpha"].default_value = color[3] 
            break
    return mat

def apply_smooth_shade(bm, use_smooth):
    # スムーズシェードの適用/解除
    for face in bm.faces:
        face.smooth = use_smooth

# --- メッシュ生成 ---
def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * dist
            
            bmesh.ops.create_cone(
                bm, cap_ends=True, segments=segments,
                radius1=hole_radius, radius2=hole_radius, depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
            
    apply_smooth_shade(bm, use_smooth)
    bm.to_mesh(mesh)
    bm.free()

def create_planes_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
    bm = bmesh.new()
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * dist
            mat_transform = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            
            geom = bmesh.ops.create_circle(
                bm, segments=segments, radius=hole_radius, matrix=mat_transform
            )
            verts = geom['verts']
            if len(verts) >= 3:
                try:
                    bm.faces.new(verts)
                except Exception:
                    try:
                        bmesh.ops.contextual_create(bm, geom=verts)
                    except:
                        pass
                        
    apply_smooth_shade(bm, use_smooth)
    bm.to_mesh(mesh)
    bm.free()

def create_center_spheres_mesh(mesh, sphere_radius, lat_count, lon_count, center_radius, hole_radius, seg_u, seg_v, use_smooth):
    bm = bmesh.new()
    dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            pos = dir_vec * dist
            
            bmesh.ops.create_uvsphere(
                bm, u_segments=seg_u, v_segments=seg_v, radius=center_radius,
                matrix=Matrix.Translation(pos)
            )
            
    apply_smooth_shade(bm, use_smooth)
    bm.to_mesh(mesh)
    bm.free()

def setup_base_objects(context):
    main_coll = get_or_create_collection(COLLECTION_MAIN, context)
    sub_coll = get_or_create_collection(COLLECTION_SUB, context)
    
    mat_sphere = ensure_material(MAT_SPHERE)
    mat_sphere_inner = ensure_material(MAT_SPHERE_INNER)
    mat_plane = ensure_material(MAT_PLANE)
    mat_cap = ensure_material(MAT_CAP)
    mat_center = ensure_material(MAT_CENTER_SPHERE)
    mat_cone = ensure_material(MAT_CONE)
            
    # 1. メイン球体
    obj_sphere = get_or_create_mesh_object(NAME_SPHERE, main_coll)
    assign_material(obj_sphere, mat_sphere, 0)
    assign_material(obj_sphere, mat_sphere_inner, 1)
    
    mod_solid = obj_sphere.modifiers.get("Solidify") or obj_sphere.modifiers.new(name="Solidify", type='SOLIDIFY')
    mod_solid.material_offset = 1     
    mod_bool = obj_sphere.modifiers.get("Boolean") or obj_sphere.modifiers.new(name="Boolean", type='BOOLEAN')
    mod_bool.operation = 'DIFFERENCE'
    mod_bool.solver = 'EXACT'
        
    # 2. カッター
    obj_cutters = get_or_create_mesh_object(NAME_CUTTERS, sub_coll)
    obj_cutters.display_type = 'WIRE'
    obj_cutters.hide_render = True
    obj_cutters.hide_viewport = True
    
    mod_bool.object = obj_cutters 

    # 3. 穴平面
    obj_planes = get_or_create_mesh_object(NAME_PLANES, main_coll)
    assign_material(obj_planes, mat_plane, 0)
        
    # 4. 球冠
    obj_caps = get_or_create_mesh_object(NAME_CAPS, main_coll)
    assign_material(obj_caps, mat_cap, 0)
    
    c_mod_solid = obj_caps.modifiers.get("Solidify") or obj_caps.modifiers.new(name="Solidify", type='SOLIDIFY')
    c_mod_bool = obj_caps.modifiers.get("Boolean") or obj_caps.modifiers.new(name="Boolean", type='BOOLEAN')
    c_mod_bool.operation = 'INTERSECT'
    c_mod_bool.solver = 'EXACT'
    c_mod_bool.object = obj_cutters
        
    # 5. 中心の球体
    obj_center = get_or_create_mesh_object(NAME_CENTER_SPHERES, main_coll)
    assign_material(obj_center, mat_center, 0)

    # 6. 円錐
    obj_cone = get_or_create_mesh_object(NAME_CONE, main_coll)
    assign_material(obj_cone, mat_cone, 0)

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    # --- 球体 ---
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    if obj_sphere and obj_sphere.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        obj_sphere.hide_viewport = not props.show_sphere
        obj_sphere.hide_render = not props.show_sphere
        
        update_material_color(MAT_SPHERE, props.sphere_color)
        update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
            
    # --- カッター ---
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    if obj_cutters and obj_cutters.type == 'MESH':
        create_cutters_mesh(
            obj_cutters.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius,
            props.segments_circle, props.use_smooth_shade
        )
        obj_cutters.location = props.sphere_location
        obj_cutters.rotation_euler = props.sphere_rotation

    # --- 穴平面 ---
    obj_planes = bpy.data.objects.get(NAME_PLANES)
    if obj_planes and obj_planes.type == 'MESH':
        create_planes_mesh(
            obj_planes.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius,
            props.segments_circle, props.use_smooth_shade
        )
        obj_planes.location = props.sphere_location
        obj_planes.rotation_euler = props.sphere_rotation
        obj_planes.hide_viewport = not props.show_plane
        obj_planes.hide_render = not props.show_plane
        
        update_material_color(MAT_PLANE, props.plane_color)

    # --- 球冠(フタ) ---
    obj_caps = bpy.data.objects.get(NAME_CAPS)
    if obj_caps and obj_caps.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_caps.data)
        bm.free()
        
        mod_solid = obj_caps.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_caps.location = props.sphere_location
        obj_caps.rotation_euler = props.sphere_rotation
        obj_caps.hide_viewport = not props.show_cap
        obj_caps.hide_render = not props.show_cap
        
        update_material_color(MAT_CAP, props.cap_color)

    # --- 中心球体 ---
    obj_center_spheres = bpy.data.objects.get(NAME_CENTER_SPHERES)
    if obj_center_spheres and obj_center_spheres.type == 'MESH':
        create_center_spheres_mesh(
            obj_center_spheres.data, props.sphere_radius,
            props.hole_latitude, props.hole_longitude, 
            props.center_sphere_radius, props.hole_radius,
            props.segments_sub_u, props.segments_sub_v, props.use_smooth_shade
        )
        obj_center_spheres.location = props.sphere_location
        obj_center_spheres.rotation_euler = props.sphere_rotation
        obj_center_spheres.hide_viewport = not props.show_center_sphere
        obj_center_spheres.hide_render = not props.show_center_sphere
        
        update_material_color(MAT_CENTER_SPHERE, props.center_sphere_color)
        
    # --- 中央円錐 ---
    obj_cone = bpy.data.objects.get(NAME_CONE)
    if obj_cone and obj_cone.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_cone(bm, cap_ends=True, segments=props.segments_circle, radius1=props.cone_radius, radius2=0, depth=props.cone_depth)
        apply_smooth_shade(bm, props.use_smooth_shade)
        bm.to_mesh(obj_cone.data)
        bm.free()
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

# ==========================================
# 4. プロパティ定義
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating:
        return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    
    # 品質設定
    use_smooth_shade: bpy.props.BoolProperty(name="スムーズシェード適用", default=True, update=trigger_update)
    segments_main_u: bpy.props.IntProperty(name="メイン球 分割 U", default=SEG_U_MAIN, min=3, soft_max=128, update=trigger_update)
    segments_main_v: bpy.props.IntProperty(name="メイン球 分割 V", default=SEG_V_MAIN, min=2, soft_max=64, update=trigger_update)
    segments_circle: bpy.props.IntProperty(name="穴・円錐 分割数", default=SEG_CIRCLE, min=3, soft_max=128, update=trigger_update)
    segments_sub_u: bpy.props.IntProperty(name="中心球体 分割 U", default=SEG_U_SUB, min=3, soft_max=64, update=trigger_update)
    segments_sub_v: bpy.props.IntProperty(name="中心球体 分割 V", default=SEG_V_SUB, min=2, soft_max=32, update=trigger_update)

    show_sphere: bpy.props.BoolProperty(name="球体を表示", default=True, update=trigger_update)
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="表面色 (Aで透明度)", subtype='COLOR', default=SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色 (Aで透明度)", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    show_plane: bpy.props.BoolProperty(name="穴平面を表示", default=True, update=trigger_update)
    plane_color: bpy.props.FloatVectorProperty(name="穴平面色 (Aで透明度)", subtype='COLOR', default=PLANE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)

    show_cap: bpy.props.BoolProperty(name="球冠(フタ)を表示", default=True, update=trigger_update)
    cap_color: bpy.props.FloatVectorProperty(name="球冠色 (Aで透明度)", subtype='COLOR', default=CAP_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)

    show_center_sphere: bpy.props.BoolProperty(name="中心球体を表示", default=True, update=trigger_update)
    center_sphere_radius: bpy.props.FloatProperty(name="中心球体 半径", default=CENTER_SPHERE_RADIUS, min=0.01, update=trigger_update)
    center_sphere_color: bpy.props.FloatVectorProperty(name="中心球体色 (Aで透明度)", subtype='COLOR', default=CENTER_SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色 (Aで透明度)", subtype='COLOR', default=CONE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        try:
            setup_base_objects(context)
            update_meshes_and_props(context)
            self.report({'INFO'}, "地球儀を生成しました。")
        except Exception as e:
            self.report({'ERROR'}, f"生成エラー: {str(e)}")
            print(f"Globe Addon Error: {e}")
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_PLANES, NAME_CAPS, NAME_CENTER_SPHERES, NAME_CONE]:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll:
                coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        is_updating = True
        try:
            # メッシュ品質の初期化
            props.use_smooth_shade = True
            props.segments_main_u = SEG_U_MAIN
            props.segments_main_v = SEG_V_MAIN
            props.segments_circle = SEG_CIRCLE
            props.segments_sub_u = SEG_U_SUB
            props.segments_sub_v = SEG_V_SUB

            # その他の初期化
            props.show_sphere = True
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_inner_color = SPHERE_INNER_COLOR
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.show_plane = True
            props.plane_color = PLANE_COLOR
            
            props.show_cap = True
            props.cap_color = CAP_COLOR
            
            props.show_center_sphere = True
            props.center_sphere_radius = CENTER_SPHERE_RADIUS
            props.center_sphere_color = CENTER_SPHERE_COLOR
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        try:
            update_meshes_and_props(context)
        except Exception as e:
            self.report({'ERROR'}, f"エラー: {e}")
            
        return {'FINISHED'}

class ADDON_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        def delay_unregister():
            unregister()
            return None
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()

        # ② メッシュ品質設定
        box = layout.box()
        box.label(text="② メッシュ品質設定", icon='MODIFIER')
        box.prop(props, "use_smooth_shade")
        row = box.row()
        row.prop(props, "segments_main_u")
        row.prop(props, "segments_main_v")
        row = box.row()
        row.prop(props, "segments_circle")
        row = box.row()
        row.prop(props, "segments_sub_u")
        row.prop(props, "segments_sub_v")
        
        # ③ 球体設定
        box = layout.box()
        box.label(text="③ 球体設定", icon='MESH_UVSPHERE')
        row = box.row()
        row.prop(props, "show_sphere", text="表示する")
        if props.show_sphere:
            box.prop(props, "sphere_radius")
            box.prop(props, "sphere_thickness")
            box.prop(props, "sphere_color")
            box.prop(props, "sphere_inner_color")
            box.prop(props, "sphere_location")
            box.prop(props, "sphere_rotation")
        
        # ④ 穴窓設定
        box = layout.box()
        box.label(text="④ 穴窓設定 (配置)", icon='OUTLINER_OB_MESH')
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        # ⑤ 穴平面設定
        box = layout.box()
        box.label(text="⑤ 穴平面設定", icon='MESH_CIRCLE')
        row = box.row()
        row.prop(props, "show_plane", text="表示する")
        if props.show_plane:
            row.prop(props, "plane_color", text="色")

        # ⑥ 球冠設定
        box = layout.box()
        box.label(text="⑥ 球冠(フタ)設定", icon='MESH_ICOSPHERE')
        row = box.row()
        row.prop(props, "show_cap", text="表示する")
        if props.show_cap:
            row.prop(props, "cap_color", text="色")

        # ⑦ 中心球体設定
        box = layout.box()
        box.label(text="⑦ 中心球体設定", icon='LIGHT_POINT')
        row = box.row()
        row.prop(props, "show_center_sphere", text="表示する")
        if props.show_center_sphere:
            box.prop(props, "center_sphere_radius")
            box.prop(props, "center_sphere_color")

        # ⑧ 円錐設定
        box = layout.box()
        box.label(text="⑧ 中央の円錐設定", icon='MESH_CONE')
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑨ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑩ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()
球体や オブジェクトの分割数を上げる
色の透明度設定 できるよういする
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 2, 4),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

# 初期値
SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0) 

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0

PLANE_COLOR = (0.9, 0.9, 0.9, 1.0)       # 穴平面(フラットな円面)の色
CAP_COLOR = (0.2, 0.8, 0.4, 1.0)         # 球冠(フタ)の色
CENTER_SPHERE_RADIUS = 0.5
CENTER_SPHERE_COLOR = (0.9, 0.1, 0.1, 1.0) # 中心球体の色

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_PLANES = f"{OBJ_NAME}_Planes"
NAME_CAPS = f"{OBJ_NAME}_Caps"
NAME_CENTER_SPHERES = f"{OBJ_NAME}_CenterSpheres"
NAME_CONE = f"{OBJ_NAME}_Cone"

MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"
MAT_PLANE = f"{PREFIX_NAME}_Mat_Plane"
MAT_CAP = f"{PREFIX_NAME}_Mat_Cap"
MAT_CENTER_SPHERE = f"{PREFIX_NAME}_Mat_Center_Sphere"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"

PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群 (堅牢な生成処理)
# ==========================================

def ensure_layer_visibility(context, coll_name):
    # コレクションが誤って非表示・無効化されている場合は強制的にオンにする
    def traverse(layer_coll):
        if layer_coll.collection.name == coll_name:
            return layer_coll
        for child in layer_coll.children:
            res = traverse(child)
            if res:
                return res
        return None
    lc = traverse(context.view_layer.layer_collection)
    if lc:
        lc.exclude = False
        lc.hide_viewport = False

def get_or_create_collection(coll_name, context):
    coll = bpy.data.collections.get(coll_name)
    if not coll:
        coll = bpy.data.collections.new(coll_name)
    if coll.name not in context.scene.collection.children.keys():
        try:
            context.scene.collection.children.link(coll)
        except Exception:
            pass
    ensure_layer_visibility(context, coll_name)
    return coll

def get_or_create_mesh_object(obj_name, coll):
    obj = bpy.data.objects.get(obj_name)
    if not obj:
        mesh = bpy.data.meshes.new(obj_name)
        obj = bpy.data.objects.new(obj_name, mesh)
        try:
            coll.objects.link(obj)
        except Exception:
            pass
    else:
        if obj.name not in coll.objects:
            try:
                coll.objects.link(obj)
            except Exception:
                pass
        if not obj.data or type(obj.data).__name__ != 'Mesh':
            mesh = bpy.data.meshes.new(obj_name)
            obj.data = mesh
    return obj

def ensure_material(mat_name):
    mat = bpy.data.materials.get(mat_name)
    if not mat:
        mat = bpy.data.materials.new(mat_name)
        mat.use_nodes = True
    return mat

def assign_material(obj, mat, slot_index=0):
    if len(obj.data.materials) <= slot_index:
        while len(obj.data.materials) <= slot_index:
            obj.data.materials.append(None)
    obj.data.materials[slot_index] = mat

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            break
    return mat

# --- メッシュ生成 ---
def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_cone(
                bm, cap_ends=True, segments=16,
                radius1=hole_radius, radius2=hole_radius, depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
    bm.to_mesh(mesh)
    bm.free()

def create_planes_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius):
    bm = bmesh.new()
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * sphere_radius
            mat_transform = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            
            geom = bmesh.ops.create_circle(
                bm, segments=32, radius=hole_radius, matrix=mat_transform
            )
            verts = geom['verts']
            if len(verts) >= 3:
                try:
                    bm.faces.new(verts)
                except Exception:
                    try:
                        bmesh.ops.contextual_create(bm, geom=verts)
                    except:
                        pass
    bm.to_mesh(mesh)
    bm.free()

def create_center_spheres_mesh(mesh, sphere_radius, lat_count, lon_count, center_radius):
    bm = bmesh.new()
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_uvsphere(
                bm, u_segments=16, v_segments=8, radius=center_radius,
                matrix=Matrix.Translation(pos)
            )
    bm.to_mesh(mesh)
    bm.free()

def setup_base_objects(context):
    main_coll = get_or_create_collection(COLLECTION_MAIN, context)
    sub_coll = get_or_create_collection(COLLECTION_SUB, context)
    
    mat_sphere = ensure_material(MAT_SPHERE)
    mat_sphere_inner = ensure_material(MAT_SPHERE_INNER)
    mat_plane = ensure_material(MAT_PLANE)
    mat_cap = ensure_material(MAT_CAP)
    mat_center = ensure_material(MAT_CENTER_SPHERE)
    mat_cone = ensure_material(MAT_CONE)
            
    # 1. メイン球体
    obj_sphere = get_or_create_mesh_object(NAME_SPHERE, main_coll)
    assign_material(obj_sphere, mat_sphere, 0)
    assign_material(obj_sphere, mat_sphere_inner, 1)
    
    mod_solid = obj_sphere.modifiers.get("Solidify") or obj_sphere.modifiers.new(name="Solidify", type='SOLIDIFY')
    mod_solid.material_offset = 1     
    mod_bool = obj_sphere.modifiers.get("Boolean") or obj_sphere.modifiers.new(name="Boolean", type='BOOLEAN')
    mod_bool.operation = 'DIFFERENCE'
    mod_bool.solver = 'EXACT'  # 安全かつ高精度な EXACT を使用
        
    # 2. カッター
    obj_cutters = get_or_create_mesh_object(NAME_CUTTERS, sub_coll)
    obj_cutters.display_type = 'WIRE'
    obj_cutters.hide_render = True
    obj_cutters.hide_viewport = True
    
    mod_bool.object = obj_cutters 

    # 3. 穴平面
    obj_planes = get_or_create_mesh_object(NAME_PLANES, main_coll)
    assign_material(obj_planes, mat_plane, 0)
        
    # 4. 球冠
    obj_caps = get_or_create_mesh_object(NAME_CAPS, main_coll)
    assign_material(obj_caps, mat_cap, 0)
    
    c_mod_solid = obj_caps.modifiers.get("Solidify") or obj_caps.modifiers.new(name="Solidify", type='SOLIDIFY')
    c_mod_bool = obj_caps.modifiers.get("Boolean") or obj_caps.modifiers.new(name="Boolean", type='BOOLEAN')
    c_mod_bool.operation = 'INTERSECT'
    c_mod_bool.solver = 'EXACT'
    c_mod_bool.object = obj_cutters
        
    # 5. 中心の球体
    obj_center = get_or_create_mesh_object(NAME_CENTER_SPHERES, main_coll)
    assign_material(obj_center, mat_center, 0)

    # 6. 円錐
    obj_cone = get_or_create_mesh_object(NAME_CONE, main_coll)
    assign_material(obj_cone, mat_cone, 0)

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    if obj_sphere and obj_sphere.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        obj_sphere.hide_viewport = not props.show_sphere
        obj_sphere.hide_render = not props.show_sphere
        
        update_material_color(MAT_SPHERE, props.sphere_color)
        update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
            
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    if obj_cutters and obj_cutters.type == 'MESH':
        create_cutters_mesh(
            obj_cutters.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius
        )
        obj_cutters.location = props.sphere_location
        obj_cutters.rotation_euler = props.sphere_rotation

    obj_planes = bpy.data.objects.get(NAME_PLANES)
    if obj_planes and obj_planes.type == 'MESH':
        create_planes_mesh(
            obj_planes.data, props.sphere_radius, 
            props.hole_latitude, props.hole_longitude, props.hole_radius
        )
        obj_planes.location = props.sphere_location
        obj_planes.rotation_euler = props.sphere_rotation
        obj_planes.hide_viewport = not props.show_plane
        obj_planes.hide_render = not props.show_plane
        
        update_material_color(MAT_PLANE, props.plane_color)

    obj_caps = bpy.data.objects.get(NAME_CAPS)
    if obj_caps and obj_caps.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_caps.data)
        bm.free()
        
        mod_solid = obj_caps.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_caps.location = props.sphere_location
        obj_caps.rotation_euler = props.sphere_rotation
        obj_caps.hide_viewport = not props.show_cap
        obj_caps.hide_render = not props.show_cap
        
        update_material_color(MAT_CAP, props.cap_color)

    obj_center_spheres = bpy.data.objects.get(NAME_CENTER_SPHERES)
    if obj_center_spheres and obj_center_spheres.type == 'MESH':
        create_center_spheres_mesh(
            obj_center_spheres.data, props.sphere_radius,
            props.hole_latitude, props.hole_longitude, props.center_sphere_radius
        )
        obj_center_spheres.location = props.sphere_location
        obj_center_spheres.rotation_euler = props.sphere_rotation
        obj_center_spheres.hide_viewport = not props.show_center_sphere
        obj_center_spheres.hide_render = not props.show_center_sphere
        
        update_material_color(MAT_CENTER_SPHERE, props.center_sphere_color)
        
    obj_cone = bpy.data.objects.get(NAME_CONE)
    if obj_cone and obj_cone.type == 'MESH':
        bm = bmesh.new()
        bmesh.ops.create_cone(bm, cap_ends=True, segments=32, radius1=props.cone_radius, radius2=0, depth=props.cone_depth)
        bm.to_mesh(obj_cone.data)
        bm.free()
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

# ==========================================
# 4. プロパティ定義
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating:
        return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    show_sphere: bpy.props.BoolProperty(name="球体を表示", default=True, update=trigger_update)
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="表面色", subtype='COLOR', default=SPHERE_COLOR, size=4, update=trigger_update)
    sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, update=trigger_update)
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    show_plane: bpy.props.BoolProperty(name="穴平面を表示", default=True, update=trigger_update)
    plane_color: bpy.props.FloatVectorProperty(name="穴平面色", subtype='COLOR', default=PLANE_COLOR, size=4, update=trigger_update)

    show_cap: bpy.props.BoolProperty(name="球冠(フタ)を表示", default=True, update=trigger_update)
    cap_color: bpy.props.FloatVectorProperty(name="球冠色", subtype='COLOR', default=CAP_COLOR, size=4, update=trigger_update)

    show_center_sphere: bpy.props.BoolProperty(name="中心球体を表示", default=True, update=trigger_update)
    center_sphere_radius: bpy.props.FloatProperty(name="中心球体 半径", default=CENTER_SPHERE_RADIUS, min=0.01, update=trigger_update)
    center_sphere_color: bpy.props.FloatVectorProperty(name="中心球体色", subtype='COLOR', default=CENTER_SPHERE_COLOR, size=4, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=CONE_COLOR, size=4, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # 万が一エラーが起きても必ずポップアップで原因を表示する
        try:
            setup_base_objects(context)
            update_meshes_and_props(context)
            self.report({'INFO'}, "地球儀を生成しました。")
        except Exception as e:
            self.report({'ERROR'}, f"生成エラー: {str(e)}")
            print(f"Globe Addon Error: {e}")
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_PLANES, NAME_CAPS, NAME_CENTER_SPHERES, NAME_CONE]:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll:
                coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        is_updating = True
        try:
            props.show_sphere = True
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_inner_color = SPHERE_INNER_COLOR
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.show_plane = True
            props.plane_color = PLANE_COLOR
            
            props.show_cap = True
            props.cap_color = CAP_COLOR
            
            props.show_center_sphere = True
            props.center_sphere_radius = CENTER_SPHERE_RADIUS
            props.center_sphere_color = CENTER_SPHERE_COLOR
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        try:
            update_meshes_and_props(context)
        except Exception as e:
            self.report({'ERROR'}, f"エラー: {e}")
            
        return {'FINISHED'}

class ADDON_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        def delay_unregister():
            unregister()
            return None
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()
        
        # ③ 球体設定
        box = layout.box()
        box.label(text="③ 球体設定", icon='MESH_UVSPHERE')
        row = box.row()
        row.prop(props, "show_sphere", text="表示する")
        if props.show_sphere:
            box.prop(props, "sphere_radius")
            box.prop(props, "sphere_thickness")
            box.prop(props, "sphere_color")
            box.prop(props, "sphere_inner_color")
            box.prop(props, "sphere_location")
            box.prop(props, "sphere_rotation")
        
        # ④ 穴窓設定
        box = layout.box()
        box.label(text="④ 穴窓設定 (配置)", icon='OUTLINER_OB_MESH')
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        # ⑤ 穴平面設定
        box = layout.box()
        box.label(text="⑤ 穴平面設定", icon='MESH_CIRCLE')
        row = box.row()
        row.prop(props, "show_plane", text="表示する")
        if props.show_plane:
            row.prop(props, "plane_color", text="色")

        # ⑥ 球冠設定
        box = layout.box()
        box.label(text="⑥ 球冠(フタ)設定", icon='MESH_ICOSPHERE')
        row = box.row()
        row.prop(props, "show_cap", text="表示する")
        if props.show_cap:
            row.prop(props, "cap_color", text="色")

        # ⑦ 中心球体設定
        box = layout.box()
        box.label(text="⑦ 中心球体設定", icon='LIGHT_POINT')
        row = box.row()
        row.prop(props, "show_center_sphere", text="表示する")
        if props.show_center_sphere:
            box.prop(props, "center_sphere_radius")
            box.prop(props, "center_sphere_color")

        # ⑧ 円錐設定
        box = layout.box()
        box.label(text="⑧ 中央の円錐設定", icon='MESH_CONE')
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑨ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑩ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()

円平面 表示されず
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 0, 5),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目 (ここだけを変更して使い回す)
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0)

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0

# --- 今回追加した要素の初期値 ---
HOLE_RIM_COLOR = (0.2, 0.8, 0.2, 1.0)          # 円面(穴の断面)の色
HOLE_CAP_COLOR = (0.8, 0.8, 0.2, 1.0)          # 球冠(フタ)の色
HOLE_CENTER_SPHERE_COLOR = (0.9, 0.1, 0.9, 1.0)# 中心の球体の色
HOLE_CENTER_SPHERE_RADIUS = 0.5                # 中心の球体の半径

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数 (同じ文字列を繰り返さないための派生変数)
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_CONE = f"{OBJ_NAME}_Cone"
NAME_CAPS = f"{OBJ_NAME}_Caps"
NAME_CENTER_SPHERES = f"{OBJ_NAME}_CenterSpheres"

MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"
MAT_SPHERE_RIM = f"{PREFIX_NAME}_Mat_Sphere_Rim"
MAT_CAPS = f"{PREFIX_NAME}_Mat_Caps"
MAT_CENTER_SPHERES = f"{PREFIX_NAME}_Mat_CenterSpheres"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"
PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群 (堅牢な生成と更新)
# ==========================================

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    
    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            break
    return mat

def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_cone(
                bm, cap_ends=True, segments=16,
                radius1=hole_radius, radius2=hole_radius, depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
            
    bm.to_mesh(mesh)
    bm.free()

def create_center_spheres_mesh(mesh, sphere_radius, lat_count, lon_count, center_radius):
    bm = bmesh.new()
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_uvsphere(
                bm, u_segments=16, v_segments=8, radius=center_radius,
                matrix=Matrix.Translation(pos)
            )
            
    bm.to_mesh(mesh)
    bm.free()

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    obj_caps = bpy.data.objects.get(NAME_CAPS)
    obj_center_spheres = bpy.data.objects.get(NAME_CENTER_SPHERES)
    obj_cone = bpy.data.objects.get(NAME_CONE)
    
    if obj_sphere:
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            mod_solid.use_rim = props.show_hole_rim  # 断面の表示切り替え
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        
        update_material_color(MAT_SPHERE, props.sphere_color)
        update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
        update_material_color(MAT_SPHERE_RIM, props.hole_rim_color)
            
    if obj_cutters:
        create_cutters_mesh(
            obj_cutters.data, 
            props.sphere_radius, props.hole_latitude, props.hole_longitude, props.hole_radius
        )
        
    if obj_caps:
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_caps.data)
        bm.free()
        
        mod_solid = obj_caps.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_caps.location = props.sphere_location
        obj_caps.rotation_euler = props.sphere_rotation
        
        obj_caps.hide_viewport = not props.show_hole_cap
        obj_caps.hide_render = not props.show_hole_cap
        
        update_material_color(MAT_CAPS, props.hole_cap_color)
        
    if obj_center_spheres:
        create_center_spheres_mesh(
            obj_center_spheres.data,
            props.sphere_radius, props.hole_latitude, props.hole_longitude, props.hole_center_sphere_radius
        )
        obj_center_spheres.location = props.sphere_location
        obj_center_spheres.rotation_euler = props.sphere_rotation
        
        obj_center_spheres.hide_viewport = not props.show_hole_center_sphere
        obj_center_spheres.hide_render = not props.show_hole_center_sphere
        
        update_material_color(MAT_CENTER_SPHERES, props.hole_center_sphere_color)
        
    if obj_cone:
        bm = bmesh.new()
        bmesh.ops.create_cone(
            bm, cap_ends=True, segments=32, radius1=props.cone_radius, radius2=0, depth=props.cone_depth
        )
        bm.to_mesh(obj_cone.data)
        bm.free()
        
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

def setup_base_objects(context):
    for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
        if c_name not in bpy.data.collections:
            new_c = bpy.data.collections.new(c_name)
            context.scene.collection.children.link(new_c)
            
    main_coll = bpy.data.collections.get(COLLECTION_MAIN)
    sub_coll = bpy.data.collections.get(COLLECTION_SUB)
    
    mat_list = [MAT_SPHERE, MAT_SPHERE_INNER, MAT_SPHERE_RIM, MAT_CAPS, MAT_CENTER_SPHERES, MAT_CONE]
    for mat_name in mat_list:
        if mat_name not in bpy.data.materials:
            mat = bpy.data.materials.new(mat_name)
            mat.use_nodes = True
            
    # 1. メイン球体
    if NAME_SPHERE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_SPHERE)
        obj = bpy.data.objects.new(NAME_SPHERE, mesh)
        main_coll.objects.link(obj)
        
        mod_solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
        mod_bool = obj.modifiers.new(name="Boolean", type='BOOLEAN')
        mod_bool.operation = 'DIFFERENCE'
        mod_bool.solver = 'FLOAT'
        
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    if obj_sphere:
        if len(obj_sphere.data.materials) < 3:
            obj_sphere.data.materials.clear()
            obj_sphere.data.materials.append(bpy.data.materials[MAT_SPHERE])
            obj_sphere.data.materials.append(bpy.data.materials[MAT_SPHERE_INNER])
            obj_sphere.data.materials.append(bpy.data.materials[MAT_SPHERE_RIM])
        else:
            obj_sphere.data.materials[0] = bpy.data.materials[MAT_SPHERE]
            obj_sphere.data.materials[1] = bpy.data.materials[MAT_SPHERE_INNER]
            obj_sphere.data.materials[2] = bpy.data.materials[MAT_SPHERE_RIM]
            
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.material_offset = 1      # 裏面
            mod_solid.material_offset_rim = 2  # 断面
        
    # 2. カッター(穴あけ用)
    if NAME_CUTTERS not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CUTTERS)
        obj = bpy.data.objects.new(NAME_CUTTERS, mesh)
        sub_coll.objects.link(obj)
        obj.display_type = 'WIRE'
        obj.hide_render = True
        obj.hide_viewport = True
        
    # 3. 球冠(フタ)
    if NAME_CAPS not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CAPS)
        obj = bpy.data.objects.new(NAME_CAPS, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_CAPS])
        
        mod_bool = obj.modifiers.new(name="Boolean", type='BOOLEAN')
        mod_bool.operation = 'INTERSECT' # カッターの内側のみ抽出
        mod_bool.solver = 'FLOAT'
        mod_solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
        
    # 4. 中心の球体
    if NAME_CENTER_SPHERES not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CENTER_SPHERES)
        obj = bpy.data.objects.new(NAME_CENTER_SPHERES, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_CENTER_SPHERES])
        
    # 5. 円錐
    if NAME_CONE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CONE)
        obj = bpy.data.objects.new(NAME_CONE, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_CONE])
        
    # ブーリアン参照の確実な割り当て
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    obj_caps = bpy.data.objects.get(NAME_CAPS)
    if obj_sphere and obj_cutters:
        mod_bool = obj_sphere.modifiers.get("Boolean")
        if mod_bool: mod_bool.object = obj_cutters
    if obj_caps and obj_cutters:
        mod_bool = obj_caps.modifiers.get("Boolean")
        if mod_bool: mod_bool.object = obj_cutters

# ==========================================
# 4. プロパティ定義とリアルタイム更新制御
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating: return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="表面色", subtype='COLOR', default=SPHERE_COLOR, size=4, update=trigger_update)
    sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, update=trigger_update)
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    show_hole_rim: bpy.props.BoolProperty(name="円面を表示", default=True, update=trigger_update)
    hole_rim_color: bpy.props.FloatVectorProperty(name="円面色", subtype='COLOR', default=HOLE_RIM_COLOR, size=4, update=trigger_update)
    
    show_hole_cap: bpy.props.BoolProperty(name="球冠(フタ)を表示", default=False, update=trigger_update)
    hole_cap_color: bpy.props.FloatVectorProperty(name="球冠色", subtype='COLOR', default=HOLE_CAP_COLOR, size=4, update=trigger_update)
    
    show_hole_center_sphere: bpy.props.BoolProperty(name="中心球体を表示", default=False, update=trigger_update)
    hole_center_sphere_radius: bpy.props.FloatProperty(name="中心球体 半径", default=HOLE_CENTER_SPHERE_RADIUS, min=0.01, update=trigger_update)
    hole_center_sphere_color: bpy.props.FloatVectorProperty(name="中心球体 色", subtype='COLOR', default=HOLE_CENTER_SPHERE_COLOR, size=4, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=CONE_COLOR, size=4, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        setup_base_objects(context)
        update_meshes_and_props(context)
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        obj_list = [NAME_SPHERE, NAME_CUTTERS, NAME_CONE, NAME_CAPS, NAME_CENTER_SPHERES]
        for obj_name in obj_list:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll: coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        
        is_updating = True
        try:
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_inner_color = SPHERE_INNER_COLOR
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.show_hole_rim = True
            props.hole_rim_color = HOLE_RIM_COLOR
            props.show_hole_cap = False
            props.hole_cap_color = HOLE_CAP_COLOR
            props.show_hole_center_sphere = False
            props.hole_center_sphere_radius = HOLE_CENTER_SPHERE_RADIUS
            props.hole_center_sphere_color = HOLE_CENTER_SPHERE_COLOR
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        update_meshes_and_props(context)
        return {'FINISHED'}

class ADDON_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        def delay_unregister():
            unregister()
            return None
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()
        
        box = layout.box()
        box.label(text="③ 球体設定")
        box.prop(props, "sphere_radius")
        box.prop(props, "sphere_thickness")
        box.prop(props, "sphere_color")
        box.prop(props, "sphere_inner_color")
        box.prop(props, "sphere_location")
        box.prop(props, "sphere_rotation")
        
        box = layout.box()
        box.label(text="④ 穴設定")
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        # 断面(Rim)の設定
        box.separator()
        row_rim = box.row()
        row_rim.prop(props, "show_hole_rim", text="円面(断面)を表示", icon='HIDE_OFF' if props.show_hole_rim else 'HIDE_ON')
        if props.show_hole_rim:
            box.prop(props, "hole_rim_color")
            
        # 球冠(Caps)の設定
        box.separator()
        row_cap = box.row()
        row_cap.prop(props, "show_hole_cap", text="球冠(フタ)を表示", icon='HIDE_OFF' if props.show_hole_cap else 'HIDE_ON')
        if props.show_hole_cap:
            box.prop(props, "hole_cap_color")
            
        # 中心球体の設定
        box.separator()
        row_sph = box.row()
        row_sph.prop(props, "show_hole_center_sphere", text="中心球体を表示", icon='HIDE_OFF' if props.show_hole_center_sphere else 'HIDE_ON')
        if props.show_hole_center_sphere:
            box.prop(props, "hole_center_sphere_radius")
            box.prop(props, "hole_center_sphere_color")
        
        box = layout.box()
        box.label(text="⑤ 円錐設定")
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑥ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑦ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 0, 4),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目 (ここだけを変更して使い回す)
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0)  # 追加: 裏面(内側)のデフォルト色

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数 (同じ文字列を繰り返さないための派生変数)
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_CONE = f"{OBJ_NAME}_Cone"
MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"  # 追加: 裏面(内側)用マテリアル
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"
PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群 (堅牢な生成と更新)
# ==========================================

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    
    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            break
    return mat

def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_cone(
                bm,
                cap_ends=True,
                segments=16,
                radius1=hole_radius,
                radius2=hole_radius,
                depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
            
    bm.to_mesh(mesh)
    bm.free()

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    obj_cone = bpy.data.objects.get(NAME_CONE)
    
    if obj_sphere:
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        
        # 表面と裏面(内側)のマテリアル色をそれぞれ更新
        update_material_color(MAT_SPHERE, props.sphere_color)
        update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
            
    if obj_cutters:
        create_cutters_mesh(
            obj_cutters.data, 
            props.sphere_radius, 
            props.hole_latitude, 
            props.hole_longitude, 
            props.hole_radius
        )
        
    if obj_cone:
        bm = bmesh.new()
        bmesh.ops.create_cone(
            bm, 
            cap_ends=True, 
            segments=32, 
            radius1=props.cone_radius, 
            radius2=0, 
            depth=props.cone_depth
        )
        bm.to_mesh(obj_cone.data)
        bm.free()
        
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

def setup_base_objects(context):
    for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
        if c_name not in bpy.data.collections:
            new_c = bpy.data.collections.new(c_name)
            context.scene.collection.children.link(new_c)
            
    main_coll = bpy.data.collections.get(COLLECTION_MAIN)
    sub_coll = bpy.data.collections.get(COLLECTION_SUB)
    
    # 表面、裏面、円錐の3つのマテリアルを確保
    for mat_name in [MAT_SPHERE, MAT_SPHERE_INNER, MAT_CONE]:
        if mat_name not in bpy.data.materials:
            mat = bpy.data.materials.new(mat_name)
            mat.use_nodes = True
            
    if NAME_SPHERE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_SPHERE)
        obj = bpy.data.objects.new(NAME_SPHERE, mesh)
        main_coll.objects.link(obj)
        
        mod_solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
        mod_bool = obj.modifiers.new(name="Boolean", type='BOOLEAN')
        mod_bool.operation = 'DIFFERENCE'
        mod_bool.solver = 'FLOAT'
        
    # 球体のマテリアルスロットとモディファイアの設定を保証する
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    if obj_sphere:
        # マテリアルスロットを2つ設定(0:表面, 1:裏面)
        if len(obj_sphere.data.materials) < 2:
            obj_sphere.data.materials.clear()
            obj_sphere.data.materials.append(bpy.data.materials[MAT_SPHERE])
            obj_sphere.data.materials.append(bpy.data.materials[MAT_SPHERE_INNER])
        else:
            obj_sphere.data.materials[0] = bpy.data.materials[MAT_SPHERE]
            obj_sphere.data.materials[1] = bpy.data.materials[MAT_SPHERE_INNER]
            
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.material_offset = 1      # 内側の面をスロット1(裏面色)にする
            mod_solid.material_offset_rim = 0  # 穴の断面はスロット0(表面色)にする
        
    if NAME_CUTTERS not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CUTTERS)
        obj = bpy.data.objects.new(NAME_CUTTERS, mesh)
        sub_coll.objects.link(obj)
        obj.display_type = 'WIRE'
        obj.hide_render = True
        obj.hide_viewport = True
        
    if NAME_CONE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CONE)
        obj = bpy.data.objects.new(NAME_CONE, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_CONE])
        
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    if obj_sphere and obj_cutters:
        mod_bool = obj_sphere.modifiers.get("Boolean")
        if mod_bool:
            mod_bool.object = obj_cutters

# ==========================================
# 4. プロパティ定義とリアルタイム更新制御
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating:
        return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="表面色", subtype='COLOR', default=SPHERE_COLOR, size=4, update=trigger_update)
    sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, update=trigger_update) # 追加: 裏面色
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=CONE_COLOR, size=4, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        setup_base_objects(context)
        update_meshes_and_props(context)
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_description = "現在のオブジェクトをアドオンの更新対象から切り離し、静的モデルとして独立させます"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_CONE]:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll:
                coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        
        is_updating = True
        try:
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_inner_color = SPHERE_INNER_COLOR # リセットにも対応
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        update_meshes_and_props(context)
        return {'FINISHED'}

class ADDON_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_description = "このアドオンのパネルをUIから完全に削除(終了)します"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        def delay_unregister():
            unregister()
            return None
        
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()
        
        box = layout.box()
        box.label(text="③ 球体設定")
        box.prop(props, "sphere_radius")
        box.prop(props, "sphere_thickness")
        box.prop(props, "sphere_color")
        box.prop(props, "sphere_inner_color") # UIに裏面色を追加
        box.prop(props, "sphere_location")
        box.prop(props, "sphere_rotation")
        
        box = layout.box()
        box.label(text="④ 穴設定")
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        box = layout.box()
        box.label(text="⑤ 円錐設定")
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑥ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑦ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Globe Addon",
    "author": "AI",
    "version": (1, 0, 3),
    "blender": (5, 0, 0), # Blender 5.x 専用
    "location": "View3D > Sidebar",
    "description": "Parametric Globe Generator for Blender 5.x",
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Matrix, Vector

# ==========================================
# 1. コード先頭の設定項目 (ここだけを変更して使い回す)
# ==========================================

TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"

SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)

LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0  # パネル仕様「穴半径」用の初期値

CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)

# ==========================================
# 2. 内部用定数 (同じ文字列を繰り返さないための派生変数)
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_CONE = f"{OBJ_NAME}_Cone"
MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"
PROP_ID = f"{PREFIX_NAME}_props"

# ==========================================
# 3. ユーティリティ関数群 (堅牢な生成と更新)
# ==========================================

def update_material_color(mat_name, color):
    mat = bpy.data.materials.get(mat_name)
    if not mat or not mat.use_nodes:
        return mat
    
    for node in mat.node_tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            if "Base Color" in node.inputs:
                node.inputs["Base Color"].default_value = color
            break
    return mat

def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius):
    bm = bmesh.new()
    length = sphere_radius * 2.5 
    
    for i in range(1, lat_count + 1):
        theta = math.pi * i / (lat_count + 1)
        for j in range(lon_count):
            phi = 2 * math.pi * j / lon_count
            
            x = math.sin(theta) * math.cos(phi)
            y = math.sin(theta) * math.sin(phi)
            z = math.cos(theta)
            dir_vec = Vector((x, y, z))
            
            rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
            pos = dir_vec * sphere_radius
            
            bmesh.ops.create_cone(
                bm,
                cap_ends=True,
                segments=16,
                radius1=hole_radius,
                radius2=hole_radius,
                depth=length,
                matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
            )
            
    bm.to_mesh(mesh)
    bm.free()

def update_meshes_and_props(context):
    props = getattr(context.scene, PROP_ID)
    
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    obj_cone = bpy.data.objects.get(NAME_CONE)
    
    if obj_sphere:
        bm = bmesh.new()
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=props.sphere_radius)
        bm.to_mesh(obj_sphere.data)
        bm.free()
        
        mod_solid = obj_sphere.modifiers.get("Solidify")
        if mod_solid:
            mod_solid.thickness = props.sphere_thickness
            
        obj_sphere.location = props.sphere_location
        obj_sphere.rotation_euler = props.sphere_rotation
        update_material_color(MAT_SPHERE, props.sphere_color)
            
    if obj_cutters:
        create_cutters_mesh(
            obj_cutters.data, 
            props.sphere_radius, 
            props.hole_latitude, 
            props.hole_longitude, 
            props.hole_radius
        )
        
    if obj_cone:
        bm = bmesh.new()
        bmesh.ops.create_cone(
            bm, 
            cap_ends=True, 
            segments=32, 
            radius1=props.cone_radius, 
            radius2=0, 
            depth=props.cone_depth
        )
        bm.to_mesh(obj_cone.data)
        bm.free()
        
        obj_cone.location = props.cone_location
        obj_cone.rotation_euler = props.cone_rotation
        update_material_color(MAT_CONE, props.cone_color)

def setup_base_objects(context):
    for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
        if c_name not in bpy.data.collections:
            new_c = bpy.data.collections.new(c_name)
            context.scene.collection.children.link(new_c)
            
    main_coll = bpy.data.collections.get(COLLECTION_MAIN)
    sub_coll = bpy.data.collections.get(COLLECTION_SUB)
    
    for mat_name in [MAT_SPHERE, MAT_CONE]:
        if mat_name not in bpy.data.materials:
            mat = bpy.data.materials.new(mat_name)
            mat.use_nodes = True
            
    if NAME_SPHERE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_SPHERE)
        obj = bpy.data.objects.new(NAME_SPHERE, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_SPHERE])
        
        mod_solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
        mod_bool = obj.modifiers.new(name="Boolean", type='BOOLEAN')
        mod_bool.operation = 'DIFFERENCE'
        mod_bool.solver = 'FLOAT'  # Blender 5.x 用
        
    if NAME_CUTTERS not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CUTTERS)
        obj = bpy.data.objects.new(NAME_CUTTERS, mesh)
        sub_coll.objects.link(obj)
        obj.display_type = 'WIRE'
        obj.hide_render = True
        obj.hide_viewport = True
        
    if NAME_CONE not in bpy.data.objects:
        mesh = bpy.data.meshes.new(NAME_CONE)
        obj = bpy.data.objects.new(NAME_CONE, mesh)
        main_coll.objects.link(obj)
        obj.data.materials.append(bpy.data.materials[MAT_CONE])
        
    obj_sphere = bpy.data.objects.get(NAME_SPHERE)
    obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
    if obj_sphere and obj_cutters:
        mod_bool = obj_sphere.modifiers.get("Boolean")
        if mod_bool:
            mod_bool.object = obj_cutters

# ==========================================
# 4. プロパティ定義とリアルタイム更新制御
# ==========================================
is_updating = False

def trigger_update(self, context):
    global is_updating
    if is_updating:
        return
    is_updating = True
    try:
        update_meshes_and_props(context)
    finally:
        is_updating = False

class ADDON_PG_props(bpy.types.PropertyGroup):
    sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
    sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
    sphere_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=SPHERE_COLOR, size=4, update=trigger_update)
    sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
    
    hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
    hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
    hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
    
    cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
    cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
    cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=CONE_COLOR, size=4, update=trigger_update)
    cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
    cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)

# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_create(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.create"
    bl_label = "作成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        setup_base_objects(context)
        update_meshes_and_props(context)
        return {'FINISHED'}

class ADDON_OT_detach(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.detach"
    bl_label = "切り離し (独立)"
    bl_description = "現在のオブジェクトをアドオンの更新対象から切り離し、静的モデルとして独立させます"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # 1. オブジェクトとマテリアルのリネーム
        for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_CONE]:
            obj = bpy.data.objects.get(obj_name)
            if obj:
                obj.name = f"{obj_name}_独立"
                if obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat:
                            new_mat = mat.copy()
                            new_mat.name = f"{mat.name}_独立"
                            obj.data.materials[i] = new_mat

        # 2. コレクションのリネーム
        for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
            coll = bpy.data.collections.get(c_name)
            if coll:
                coll.name = f"{c_name}_独立"

        self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
        return {'FINISHED'}

class ADDON_OT_reset(bpy.types.Operator):
    bl_idname = f"{PREFIX_NAME}.reset"
    bl_label = "初期値へ戻す"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global is_updating
        props = getattr(context.scene, PROP_ID)
        
        is_updating = True
        try:
            props.sphere_radius = SPHERE_RADIUS
            props.sphere_thickness = SPHERE_THICKNESS
            props.sphere_color = SPHERE_COLOR
            props.sphere_location = (0, 0, 0)
            props.sphere_rotation = (0, 0, 0)
            
            props.hole_latitude = LATITUDE_COUNT
            props.hole_longitude = LONGITUDE_COUNT
            props.hole_radius = HOLE_RADIUS
            
            props.cone_radius = CONE_RADIUS
            props.cone_depth = CONE_DEPTH
            props.cone_color = CONE_COLOR
            props.cone_location = (0, 0, 0)
            props.cone_rotation = (0, 0, 0)
        finally:
            is_updating = False
            
        update_meshes_and_props(context)
        return {'FINISHED'}

# 修正箇所: タイマーを利用して実行後に安全にアドオンを解除するように変更
class ADDON_OT_remove_addon(bpy.types.Operator):
    """アドオンを終了し、UIパネルを削除します"""
    bl_idname = f"{PREFIX_NAME}.remove_addon"
    bl_label = "アドオン削除"
    bl_description = "このアドオンのパネルをUIから完全に削除(終了)します"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # UI更新・オペレータ実行中に自身を削除するとエラーになるため
        # 処理完了の少し後(0.1秒後)に登録解除を実行させます。
        def delay_unregister():
            unregister()
            return None
        
        bpy.app.timers.register(delay_unregister, first_interval=0.1)
        self.report({'INFO'}, "アドオンを終了しました")
        return {'FINISHED'}

# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_main"
    bl_label = PANEL_TITLE
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROP_ID)
        
        # ① 選択オブジェクト表示
        layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
        layout.separator()
        
        # ② オブジェクト作成 & 切り離し
        row = layout.row(align=True)
        row.scale_y = 1.2
        row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
        row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
        layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
        layout.separator()
        
        # ③ 球体設定
        box = layout.box()
        box.label(text="③ 球体設定")
        box.prop(props, "sphere_radius")
        box.prop(props, "sphere_thickness")
        box.prop(props, "sphere_color")
        box.prop(props, "sphere_location")
        box.prop(props, "sphere_rotation")
        
        # ④ 穴設定
        box = layout.box()
        box.label(text="④ 穴設定")
        box.prop(props, "hole_latitude")
        box.prop(props, "hole_longitude")
        box.prop(props, "hole_radius")
        
        # ⑤ 円錐設定
        box = layout.box()
        box.label(text="⑤ 円錐設定")
        box.prop(props, "cone_radius")
        box.prop(props, "cone_depth")
        box.prop(props, "cone_color")
        box.prop(props, "cone_location")
        box.prop(props, "cone_rotation")

class ADDON_PT_link(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_link"
    bl_label = "⑥ リンク"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
        op.url = LINK_URL

class ADDON_PT_remove(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME}_pt_remove"
    bl_label = "⑦ アドオン削除"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

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

# ==========================================
# 7. 登録処理
# ==========================================
classes = [
    ADDON_PG_props,
    ADDON_OT_create,
    ADDON_OT_detach,
    ADDON_OT_reset,
    ADDON_OT_remove_addon,
    ADDON_PT_main,
    ADDON_PT_link,
    ADDON_PT_remove,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except RuntimeError:
            pass # 登録解除時のエラーを無視して続行する
    if hasattr(bpy.types.Scene, PROP_ID):
        delattr(bpy.types.Scene, PROP_ID)

if __name__ == "__main__":
    register()