bl_info = {
    "name": "zionad_ [ 穴あき球体ツール・プロ ]",
    "author": "zionad_",
    "version": (3, 1, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "円錐で球体をくり抜き、内側と外側の色を個別に設定。切り離し・設定コピー機能付き",
    "category": "3D View",
}

import bpy
import bmesh
import math
import random

# ===================================
# 共通定義
# ===================================
ADDON_TITLE = "穴あき球体"
ADDON_CATEGORY = "穴あき球体"
PREFIX_VAL = "pierced_sphere_v3"
PREFIX = PREFIX_VAL.lower()

# ===================================
# 定数 (初期値)
# ===================================
INIT_OBJ_NAME = "Pierced_Sphere"
INIT_SPHERE_SIZE = 10.0
INIT_CONE_ANGLE = 45.0
INIT_CONE_LENGTH = 15.0
INIT_COLOR_OUTER = (0.1, 0.1, 0.1, 1.0) # 外側:黒
INIT_COLOR_INNER = (1.0, 0.2, 0.0, 1.0) # 内側:オレンジ

# ===================================
# コアロジック (更新系)
# ===================================

def update_geometry(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    obj = context.active_object
    
    # アドオンが管理しているオブジェクトかチェック
    if not obj or not obj.name.startswith(props.obj_name):
        return

    # 1. 球体メッシュの更新
    bm = bmesh.new()
    bmesh.ops.create_uvsphere(bm, u_segments=64, v_segments=32, radius=props.sphere_size)
    for face in bm.faces:
        face.material_index = 0
        
    new_mesh = bpy.data.meshes.new(props.obj_name)
    bm.to_mesh(new_mesh)
    bm.free()
    
    old_mesh = obj.data
    obj.data = new_mesh
    if old_mesh: bpy.data.meshes.remove(old_mesh)

    # 2. カッター(円錐)の更新
    cutter_name = f"{obj.name}_Cutter"
    cutter = bpy.data.objects.get(cutter_name)
    if not cutter:
        cutter_mesh = bpy.data.meshes.new(cutter_name)
        cutter = bpy.data.objects.new(cutter_name, cutter_mesh)
        context.collection.objects.link(cutter)
        cutter.hide_viewport = True
        cutter.hide_render = True
    
    bm_cone = bmesh.new()
    rad_angle = math.radians(props.cone_angle / 2)
    radius = props.cone_length * math.tan(rad_angle)
    
    bmesh.ops.create_cone(
        bm_cone, cap_ends=True, segments=64, 
        radius1=radius, radius2=0, depth=props.cone_length
    )
    # 頂点を0,0,0にする移動
    bmesh.ops.translate(bm_cone, vec=(0, 0, props.cone_length/2), verts=bm_cone.verts)
    
    for face in bm_cone.faces:
        face.material_index = 1
        
    bm_cone.to_mesh(cutter.data)
    bm_cone.free()
    
    # 3. モディファイア設定
    mod_name = "Hole_Boolean"
    mod = obj.modifiers.get(mod_name) or obj.modifiers.new(mod_name, 'BOOLEAN')
    mod.operation = 'DIFFERENCE'
    mod.object = cutter
    mod.solver = 'EXACT'

    # 4. マテリアル更新
    setup_materials(obj, props)

def setup_materials(obj, props):
    while len(obj.data.materials) < 2:
        obj.data.materials.append(None)
    
    def get_or_create_mat(name_suffix, color):
        mat_name = f"{PREFIX_VAL}_{name_suffix}"
        mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
        mat.use_nodes = True
        bsdf = mat.node_tree.nodes.get("Principled BSDF")
        if bsdf:
            bsdf.inputs['Base Color'].default_value = color
            if 'Alpha' in bsdf.inputs:
                bsdf.inputs['Alpha'].default_value = color[3]
        mat.blend_method = 'BLEND' if color[3] < 1.0 else 'OPAQUE'
        return mat

    obj.data.materials[0] = get_or_create_mat("Outer", props.color_outer)
    obj.data.materials[1] = get_or_create_mat("Inner", props.color_inner)

# ===================================
# オペレーター
# ===================================

class OT_create_pierced_sphere(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_sphere"
    bl_label = "穴あき球体を生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh = bpy.data.meshes.new(props.obj_name)
        obj = bpy.data.objects.new(f"{props.obj_name}_{random.randint(100,999)}", mesh)
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        update_geometry(self, context)
        return {'FINISHED'}

class OT_detach_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach"
    bl_label = "アドオンから切り離す"
    bl_description = "名前を変更し、形状を確定させて独立させます"

    def execute(self, context):
        obj = context.active_object
        if obj:
            # ブーリアンを適用してメッシュを確定させる
            for mod in obj.modifiers:
                if mod.type == 'BOOLEAN':
                    bpy.ops.object.modifier_apply(modifier=mod.name)
            
            # カッターを削除
            cutter_name = f"{obj.name}_Cutter"
            cutter = bpy.data.objects.get(cutter_name)
            if cutter:
                bpy.data.objects.remove(cutter, do_unlink=True)
            
            # 名前を変更して管理外にする
            obj.name = f"Fixed_Sphere_{random.randint(100,999)}"
            self.report({'INFO'}, "オブジェクトを独立させました")
        return {'FINISHED'}

class OT_copy_settings(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_copy_settings"
    bl_label = "設定値をコピー"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        text = (
            f"INIT_OBJ_NAME = '{props.obj_name}'\n"
            f"INIT_SPHERE_SIZE = {props.sphere_size:.4f}\n"
            f"INIT_CONE_ANGLE = {props.cone_angle:.4f}\n"
            f"INIT_CONE_LENGTH = {props.cone_length:.4f}\n"
            f"INIT_COLOR_OUTER = ({props.color_outer[0]:.4f}, {props.color_outer[1]:.4f}, {props.color_outer[2]:.4f}, {props.color_outer[3]:.4f})\n"
            f"INIT_COLOR_INNER = ({props.color_inner[0]:.4f}, {props.color_inner[1]:.4f}, {props.color_inner[2]:.4f}, {props.color_inner[3]:.4f})\n"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "初期値用コードをコピーしました")
        return {'FINISHED'}

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

# ===================================
# UI / プロパティ
# ===================================

class Properties_Addon(bpy.types.PropertyGroup):
    obj_name: bpy.props.StringProperty(name="Object名ベース", default=INIT_OBJ_NAME)
    sphere_size: bpy.props.FloatProperty(name="球体サイズ", default=INIT_SPHERE_SIZE, min=0.1, update=update_geometry)
    cone_angle: bpy.props.FloatProperty(name="穴の角度", default=INIT_CONE_ANGLE, min=1.0, max=179.0, update=update_geometry)
    cone_length: bpy.props.FloatProperty(name="穴の長さ", default=INIT_CONE_LENGTH, min=0.1, update=update_geometry)
    color_outer: bpy.props.FloatVectorProperty(name="外側の色", subtype='COLOR', size=4, default=INIT_COLOR_OUTER, min=0.0, max=1.0, update=update_geometry)
    color_inner: bpy.props.FloatVectorProperty(name="内側の色", subtype='COLOR', size=4, default=INIT_COLOR_INNER, min=0.0, max=1.0, update=update_geometry)

class PT_main_panel(bpy.types.Panel):
    bl_label = "穴あき球体ツール"
    bl_idname = f"{PREFIX.upper()}_PT_main"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        
        # 生成ボタン
        layout.operator(f"object.{PREFIX}_create_sphere", icon='MESH_UVSPHERE')
        
        layout.separator()
        
        # パラメータ設定
        box = layout.box()
        box.label(text="形状・色設定", icon='PREFERENCES')
        box.prop(props, "sphere_size")
        box.prop(props, "cone_angle")
        box.prop(props, "cone_length")
        box.separator()
        box.prop(props, "color_outer")
        box.prop(props, "color_inner")

        layout.separator()

        # 切離しボタン(現在のアクティブオブジェクトが対象の場合のみ有効)
        if obj and obj.name.startswith(props.obj_name):
            layout.operator(f"object.{PREFIX}_detach", icon='UNLINKED')
        else:
            layout.label(text="※生成したオブジェクトを選択して調整", icon='INFO')

class PT_system_panel(bpy.types.Panel):
    bl_label = "システム"
    bl_idname = f"{PREFIX.upper()}_PT_system"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_options = {'DEFAULT_CLOSED'}
    
    def draw(self, context):
        layout = self.layout
        layout.operator(f"object.{PREFIX}_copy_settings", icon='COPYDOWN')
        layout.separator()
        layout.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# 登録・解除
# ===================================

classes = [
    Properties_Addon,
    OT_create_pierced_sphere,
    OT_detach_object,
    OT_copy_settings,
    OT_remove_addon,
    PT_main_panel,
    PT_system_panel,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    setattr(bpy.types.Scene, f"{PREFIX}_props", bpy.props.PointerProperty(type=Properties_Addon))

def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    if hasattr(bpy.types.Scene, f"{PREFIX}_props"):
        delattr(bpy.types.Scene, f"{PREFIX}_props")

if __name__ == "__main__":
    register()














bl_info = {
    "name": "zionad_ [ 穴あき球体ツール・プロ ]",
    "author": "zionad_",
    "version": (3, 1, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "円錐で球体をくり抜き、内側と外側の色を個別に設定。切り離し・設定コピー機能付き",
    "category": "3D View",
}

import bpy
import bmesh
import math
import random

# ===================================
# 共通定義
# ===================================
ADDON_TITLE = "穴あき球体"
ADDON_CATEGORY = "穴あき球体"
PREFIX_VAL = "pierced_sphere_v3"
PREFIX = PREFIX_VAL.lower()

# ===================================
# 定数 (初期値)
# ===================================
INIT_OBJ_NAME = "Pierced_Sphere"
INIT_SPHERE_SIZE = 10.0
INIT_CONE_ANGLE = 45.0
INIT_CONE_LENGTH = 15.0
INIT_COLOR_OUTER = (0.1, 0.1, 0.1, 1.0) # 外側:黒
INIT_COLOR_INNER = (1.0, 0.2, 0.0, 1.0) # 内側:オレンジ

# ===================================
# コアロジック (更新系)
# ===================================

def update_geometry(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    obj = context.active_object
    
    # アドオンが管理しているオブジェクトかチェック
    if not obj or not obj.name.startswith(props.obj_name):
        return

    # 1. 球体メッシュの更新
    bm = bmesh.new()
    bmesh.ops.create_uvsphere(bm, u_segments=64, v_segments=32, radius=props.sphere_size)
    for face in bm.faces:
        face.material_index = 0
        
    new_mesh = bpy.data.meshes.new(props.obj_name)
    bm.to_mesh(new_mesh)
    bm.free()
    
    old_mesh = obj.data
    obj.data = new_mesh
    if old_mesh: bpy.data.meshes.remove(old_mesh)

    # 2. カッター(円錐)の更新
    cutter_name = f"{obj.name}_Cutter"
    cutter = bpy.data.objects.get(cutter_name)
    if not cutter:
        cutter_mesh = bpy.data.meshes.new(cutter_name)
        cutter = bpy.data.objects.new(cutter_name, cutter_mesh)
        context.collection.objects.link(cutter)
        cutter.hide_viewport = True
        cutter.hide_render = True
    
    bm_cone = bmesh.new()
    rad_angle = math.radians(props.cone_angle / 2)
    radius = props.cone_length * math.tan(rad_angle)
    
    bmesh.ops.create_cone(
        bm_cone, cap_ends=True, segments=64, 
        radius1=radius, radius2=0, depth=props.cone_length
    )
    # 頂点を0,0,0にする移動
    bmesh.ops.translate(bm_cone, vec=(0, 0, props.cone_length/2), verts=bm_cone.verts)
    
    for face in bm_cone.faces:
        face.material_index = 1
        
    bm_cone.to_mesh(cutter.data)
    bm_cone.free()
    
    # 3. モディファイア設定
    mod_name = "Hole_Boolean"
    mod = obj.modifiers.get(mod_name) or obj.modifiers.new(mod_name, 'BOOLEAN')
    mod.operation = 'DIFFERENCE'
    mod.object = cutter
    mod.solver = 'EXACT'

    # 4. マテリアル更新
    setup_materials(obj, props)

def setup_materials(obj, props):
    while len(obj.data.materials) < 2:
        obj.data.materials.append(None)
    
    def get_or_create_mat(name_suffix, color):
        mat_name = f"{PREFIX_VAL}_{name_suffix}"
        mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
        mat.use_nodes = True
        bsdf = mat.node_tree.nodes.get("Principled BSDF")
        if bsdf:
            bsdf.inputs['Base Color'].default_value = color
            if 'Alpha' in bsdf.inputs:
                bsdf.inputs['Alpha'].default_value = color[3]
        mat.blend_method = 'BLEND' if color[3] < 1.0 else 'OPAQUE'
        return mat

    obj.data.materials[0] = get_or_create_mat("Outer", props.color_outer)
    obj.data.materials[1] = get_or_create_mat("Inner", props.color_inner)

# ===================================
# オペレーター
# ===================================

class OT_create_pierced_sphere(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_sphere"
    bl_label = "穴あき球体を生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh = bpy.data.meshes.new(props.obj_name)
        obj = bpy.data.objects.new(f"{props.obj_name}_{random.randint(100,999)}", mesh)
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        update_geometry(self, context)
        return {'FINISHED'}

class OT_detach_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach"
    bl_label = "アドオンから切り離す"
    bl_description = "名前を変更し、形状を確定させて独立させます"

    def execute(self, context):
        obj = context.active_object
        if obj:
            # ブーリアンを適用してメッシュを確定させる
            for mod in obj.modifiers:
                if mod.type == 'BOOLEAN':
                    bpy.ops.object.modifier_apply(modifier=mod.name)
            
            # カッターを削除
            cutter_name = f"{obj.name}_Cutter"
            cutter = bpy.data.objects.get(cutter_name)
            if cutter:
                bpy.data.objects.remove(cutter, do_unlink=True)
            
            # 名前を変更して管理外にする
            obj.name = f"Fixed_Sphere_{random.randint(100,999)}"
            self.report({'INFO'}, "オブジェクトを独立させました")
        return {'FINISHED'}

class OT_copy_settings(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_copy_settings"
    bl_label = "設定値をコピー"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        text = (
            f"INIT_OBJ_NAME = '{props.obj_name}'\n"
            f"INIT_SPHERE_SIZE = {props.sphere_size:.4f}\n"
            f"INIT_CONE_ANGLE = {props.cone_angle:.4f}\n"
            f"INIT_CONE_LENGTH = {props.cone_length:.4f}\n"
            f"INIT_COLOR_OUTER = ({props.color_outer[0]:.4f}, {props.color_outer[1]:.4f}, {props.color_outer[2]:.4f}, {props.color_outer[3]:.4f})\n"
            f"INIT_COLOR_INNER = ({props.color_inner[0]:.4f}, {props.color_inner[1]:.4f}, {props.color_inner[2]:.4f}, {props.color_inner[3]:.4f})\n"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "初期値用コードをコピーしました")
        return {'FINISHED'}

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

# ===================================
# UI / プロパティ
# ===================================

class Properties_Addon(bpy.types.PropertyGroup):
    obj_name: bpy.props.StringProperty(name="Object名ベース", default=INIT_OBJ_NAME)
    sphere_size: bpy.props.FloatProperty(name="球体サイズ", default=INIT_SPHERE_SIZE, min=0.1, update=update_geometry)
    cone_angle: bpy.props.FloatProperty(name="穴の角度", default=INIT_CONE_ANGLE, min=1.0, max=179.0, update=update_geometry)
    cone_length: bpy.props.FloatProperty(name="穴の長さ", default=INIT_CONE_LENGTH, min=0.1, update=update_geometry)
    color_outer: bpy.props.FloatVectorProperty(name="外側の色", subtype='COLOR', size=4, default=INIT_COLOR_OUTER, min=0.0, max=1.0, update=update_geometry)
    color_inner: bpy.props.FloatVectorProperty(name="内側の色", subtype='COLOR', size=4, default=INIT_COLOR_INNER, min=0.0, max=1.0, update=update_geometry)

class PT_main_panel(bpy.types.Panel):
    bl_label = "穴あき球体ツール"
    bl_idname = f"{PREFIX.upper()}_PT_main"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        
        # 生成ボタン
        layout.operator(f"object.{PREFIX}_create_sphere", icon='MESH_UVSPHERE')
        
        layout.separator()
        
        # パラメータ設定
        box = layout.box()
        box.label(text="形状・色設定", icon='PREFERENCES')
        box.prop(props, "sphere_size")
        box.prop(props, "cone_angle")
        box.prop(props, "cone_length")
        box.separator()
        box.prop(props, "color_outer")
        box.prop(props, "color_inner")

        layout.separator()

        # 切離しボタン(現在のアクティブオブジェクトが対象の場合のみ有効)
        if obj and obj.name.startswith(props.obj_name):
            layout.operator(f"object.{PREFIX}_detach", icon='UNLINKED')
        else:
            layout.label(text="※生成したオブジェクトを選択して調整", icon='INFO')

class PT_system_panel(bpy.types.Panel):
    bl_label = "システム"
    bl_idname = f"{PREFIX.upper()}_PT_system"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_options = {'DEFAULT_CLOSED'}
    
    def draw(self, context):
        layout = self.layout
        layout.operator(f"object.{PREFIX}_copy_settings", icon='COPYDOWN')
        layout.separator()
        layout.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# 登録・解除
# ===================================

classes = [
    Properties_Addon,
    OT_create_pierced_sphere,
    OT_detach_object,
    OT_copy_settings,
    OT_remove_addon,
    PT_main_panel,
    PT_system_panel,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    setattr(bpy.types.Scene, f"{PREFIX}_props", bpy.props.PointerProperty(type=Properties_Addon))

def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    if hasattr(bpy.types.Scene, f"{PREFIX}_props"):
        delattr(bpy.types.Scene, f"{PREFIX}_props")

if __name__ == "__main__":
    register()