bl_info = {
    "name": "zionad_ [ 骨格立方体 + 等間隔球体 ]",
    "author": "zionad_",
    "version": (2, 5, 5),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "表面・骨格別の色設定ツールと、超軽量な等間隔インスタンス球体配置ツール(ランダム色対応)",
    "category": "3D View",
}

import bpy
import bmesh
import math
import webbrowser
import random

# ===================================
# 共通定義 (作成基本ベース)
# ===================================
ADDON_TITLE = "骨格立方体"
ADDON_CATEGORY = "骨格立方体"
PREFIX_VAL = "basd20260829"
PREFIX = PREFIX_VAL.lower()

# ===================================
# リンク定義 (統合版)
# ===================================
LINK_NAME_1 = "338 Sphere Cam"
LINK_URL_1 = "<https://app.notion.com/p/338-Sphere-Cam-3c0347b3454a80abbb61ff2cd585792c>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"
LINK_NAME_4 = "背後霊カメラ 最新版リンク"
LINK_URL_4 = "<https://app.notion.com/p/Cam-blender-39f347b3454a806490a6f0107c2347dd>"
LINK_NAME_5 = "blender コンテナ 20260713"
LINK_URL_5 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_6 = "骨格立方体"
LINK_URL_6 = "<https://app.notion.com/p/351-2017-3cb347b3454a8086b15fec2f69408bd5>"

# ===================================
# 定数 (骨格立方体固有)
# ===================================
INIT_COL_NAME = "Dice_Collection"
INIT_SUB_COL_NAME = "Skeleton_Cubes"
INIT_OBJ_NAME = "Skeleton_Dice"
INIT_SHAPE = 'CUBE'
INIT_SIZE = 60.0000
INIT_THICKNESS = 1.0000
INIT_RES = 32
INIT_POS = (0.0000, 0.0000, 0.0000)
INIT_ROT = (0.0000, 0.0000, 0.0000)
INIT_SHOW_FRAME = True
INIT_SHOW_FACES = True
INIT_CLR_FRAME = (0.0081, 0.3691, 0.0692, 1.0000)
INIT_CLR_FACE = (0.1761, 0.3179, 0.3123, 0.1272)

# ===================================
# 定数 (球体配置固有)
# ===================================
INIT_SPHERE_OBJ_NAME = "Arranged_Spheres"
INIT_SPHERE_PATTERN = 'CUBE'
INIT_SPHERE_COUNT = (3, 3, 3)
INIT_SPHERE_LENGTH = (40.0000, 40.0000, 40.0000)
INIT_SPHERE_SIZE = 2.0000
INIT_SPHERE_POS = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_ROT = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_COLOR = (0.3340, 0.8000, 0.9000, 0.5000)
INIT_SHOW_SPHERES = True
INIT_SPHERE_RANDOM_COLOR = False

# ===================================
# コアロジック (骨格立方体)
# ===================================
def create_advanced_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm_final = bmesh.new()
    
    # --- 骨格フレームの生成 ---
    if props.show_frame:
        bm_base = bmesh.new()
        if props.shape == 'CUBE':
            bmesh.ops.create_cube(bm_base, size=props.size)
        else:
            bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=props.size/2)
            
        curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.fill_mode = 'FULL'
        curve_data.bevel_depth = props.thick / 2
        curve_data.bevel_resolution = props.res
        
        for edge in bm_base.edges:
            v1, v2 = edge.verts[0].co, edge.verts[1].co
            polyline = curve_data.splines.new('POLY')
            polyline.points.add(1)
            polyline.points[0].co = (*v1, 1)
            polyline.points[1].co = (*v2, 1)
            
        temp_frame_obj = bpy.data.objects.new("temp_frame", curve_data)
        context.collection.objects.link(temp_frame_obj)
        depsgraph = context.evaluated_depsgraph_get()
        mesh_frame = bpy.data.meshes.new_from_object(temp_frame_obj.evaluated_get(depsgraph))
        
        bpy.data.objects.remove(temp_frame_obj)
        bpy.data.curves.remove(curve_data)
        
        bm_final.from_mesh(mesh_frame)
        for face in bm_final.faces:
            face.material_index = 0
        bpy.data.meshes.remove(mesh_frame)
        bm_base.free()
    
    # --- 表面(面)の生成 ---
    if props.show_faces:
        bm_face = bmesh.new()
        if props.shape == 'CUBE':
            bmesh.ops.create_cube(bm_face, size=props.size)
        else:
            bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=props.size/2)
            
        for face in bm_face.faces:
            face.material_index = 1
        temp_face_mesh = bpy.data.meshes.new("temp_face")
        bm_face.to_mesh(temp_face_mesh)
        bm_final.from_mesh(temp_face_mesh)
        bpy.data.meshes.remove(temp_face_mesh)
        bm_face.free()
        
    mesh_out = bpy.data.meshes.new(props.obj_name)
    bm_final.to_mesh(mesh_out)
    bm_final.free()
    
    return mesh_out

def update_skeleton_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.obj_name) and obj.type == 'MESH':
            obj.location = props.pos
            obj.rotation_euler = [math.radians(a) for a in props.rot]
            
            new_mesh = create_advanced_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            while len(obj.data.materials) < 2:
                obj.data.materials.append(None)
                
            if not obj.material_slots[0].material:
                obj.material_slots[0].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Frame")
            mat_frame = obj.material_slots[0].material
            mat_frame.use_nodes = True
            bsdf_frame = mat_frame.node_tree.nodes.get("Principled BSDF")
            if bsdf_frame:
                if 'Base Color' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Base Color'].default_value = props.color_frame
                if 'Alpha' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Alpha'].default_value = props.color_frame[3]

            if not obj.material_slots[1].material:
                obj.material_slots[1].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Face")
            mat_face = obj.material_slots[1].material
            mat_face.use_nodes = True
            bsdf_face = mat_face.node_tree.nodes.get("Principled BSDF")
            if bsdf_face:
                if 'Base Color' in bsdf_face.inputs:
                    bsdf_face.inputs['Base Color'].default_value = props.color_face
                if 'Alpha' in bsdf_face.inputs:
                    bsdf_face.inputs['Alpha'].default_value = props.color_face[3]

# ===================================
# コアロジック (等間隔 球体配置 - インスタンス化)
# ===================================
def create_spheres_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm = bmesh.new()
    
    if props.show_spheres:
        count_x = props.sphere_count[0]
        count_y = props.sphere_count[1]
        count_z = props.sphere_count[2] if props.sphere_pattern == 'CUBE' else 1
        
        len_x = props.sphere_length[0]
        len_y = props.sphere_length[1]
        len_z = props.sphere_length[2]
        
        step_x = len_x / (count_x - 1) if count_x > 1 else 0
        step_y = len_y / (count_y - 1) if count_y > 1 else 0
        step_z = len_z / (count_z - 1) if count_z > 1 else 0
        
        start_x = -len_x / 2.0 if count_x > 1 else 0
        start_y = -len_y / 2.0 if count_y > 1 else 0
        start_z = -len_z / 2.0 if count_z > 1 else 0
        
        for z in range(count_z):
            for y in range(count_y):
                for x in range(count_x):
                    pos = (start_x + x * step_x, start_y + y * step_y, start_z + z * step_z)
                    bm.verts.new(pos)
                
    mesh_out = bpy.data.meshes.new(props.sphere_obj_name + "_Points")
    bm.to_mesh(mesh_out)
    bm.free()
    
    return mesh_out

def setup_sphere_material(obj, props):
    if not obj.data.materials:
        obj.data.materials.append(bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Spheres"))
    mat = obj.data.materials[0]
    
    mat.diffuse_color = props.sphere_color
    mat.use_nodes = True
    
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()
    
    out_node = nodes.new('ShaderNodeOutputMaterial')
    out_node.location = (300, 0)
    
    bsdf = nodes.new('ShaderNodeBsdfPrincipled')
    bsdf.location = (0, 0)
    links.new(bsdf.outputs[0], out_node.inputs[0])
    
    # 透過設定
    if 'Alpha' in bsdf.inputs:
        bsdf.inputs['Alpha'].default_value = props.sphere_color[3]
        
    if props.sphere_use_random_color:
        obj_info = nodes.new('ShaderNodeObjectInfo')
        obj_info.location = (-400, 0)
        
        # 虹色生成のためのColorRamp設定
        color_ramp = nodes.new('ShaderNodeValToRGB')
        color_ramp.location = (-200, 0)
        color_ramp.color_ramp.color_mode = 'HSV'
        color_ramp.color_ramp.hue_interpolation = 'CW'
        
        elements = color_ramp.color_ramp.elements
        elements[0].position = 0.0
        elements[0].color = (1.0, 0.0, 0.0, 1.0)
        elements[-1].position = 1.0
        elements[-1].color = (1.0, 0.0, 0.0, 1.0)
        
        # GNインスタンスで確実にランダム値を取得するため「Random」ソケットを使用
        links.new(obj_info.outputs['Random'], color_ramp.inputs['Fac'])
        links.new(color_ramp.outputs['Color'], bsdf.inputs['Base Color'])
    else:
        if 'Base Color' in bsdf.inputs:
            bsdf.inputs['Base Color'].default_value = props.sphere_color
            
    return mat

def setup_sphere_instancing(obj, props):
    mod_name = "Sphere_Instancer"
    mod = obj.modifiers.get(mod_name)
    if not mod:
        mod = obj.modifiers.new(mod_name, 'NODES')
        
    if not mod.node_group:
        node_group = bpy.data.node_groups.new(mod_name + "_Tree", 'GeometryNodeTree')
        mod.node_group = node_group
        
        node_group.interface.new_socket(name="Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
        node_group.interface.new_socket(name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
            
        nodes = node_group.nodes
        links = node_group.links
        
        in_node = nodes.new('NodeGroupInput')
        out_node = nodes.new('NodeGroupOutput')
        
        sphere_node = nodes.new('GeometryNodeMeshIcoSphere')
        sphere_node.inputs['Subdivisions'].default_value = 2
        
        mat_node = nodes.new('GeometryNodeSetMaterial')
        inst_node = nodes.new('GeometryNodeInstanceOnPoints')
        
        links.new(in_node.outputs[0], inst_node.inputs['Points'])
        links.new(sphere_node.outputs['Mesh'], mat_node.inputs['Geometry'])
        links.new(mat_node.outputs['Geometry'], inst_node.inputs['Instance'])
        links.new(inst_node.outputs[0], out_node.inputs[0])
        
    nodes = mod.node_group.nodes
    
    sphere_node = next((n for n in nodes if n.type == 'MESH_ICO_SPHERE'), None)
    if sphere_node:
        sphere_node.inputs['Radius'].default_value = props.sphere_size
        
    mat = setup_sphere_material(obj, props)
    mat_node = next((n for n in nodes if n.type == 'SET_MATERIAL'), None)
    if mat_node:
        try:
            mat_node.inputs['Material'].default_value = mat
        except:
            for inp in mat_node.inputs:
                if inp.type == 'MATERIAL':
                    inp.default_value = mat
                    break

def update_spheres_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.sphere_obj_name) and obj.type == 'MESH':
            obj.location = props.sphere_pos
            obj.rotation_euler = [math.radians(a) for a in props.sphere_rot]
            
            new_mesh = create_spheres_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            setup_sphere_instancing(obj, props)

# ===================================
# 共通クラス群
# ===================================
class OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    def execute(self, context):
        if self.url: webbrowser.open(self.url)
        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'}

def get_rv3d(context):
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            return a.spaces.active.region_3d
    return None

class OT_reset_view(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_view"
    bl_label = "選択物を中央にする"
    def execute(self, context):
        rv3d = get_rv3d(context)
        if rv3d and context.active_object:
            rv3d.view_location = context.active_object.location
        return {'FINISHED'}

class OT_set_grid_scale(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_set_grid_scale"
    bl_label = "グリッドスケール設定"
    scale_value: bpy.props.FloatProperty()
    
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.overlay.grid_scale = self.scale_value
        return {'FINISHED'}

# ===================================
# アドオン固有オペレーター
# ===================================
class OT_create_skeleton(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_skeleton"
    bl_label = "骨格を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_advanced_geometry(context)
        obj = bpy.data.objects.new(f"{props.obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_skeleton_live(self, context)
        return {'FINISHED'}

class OT_detach_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_object"
    bl_label = "アドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.obj_name):
            obj.name = f"Fixed_Dice_{random.randint(100,999)}"
            self.report({'INFO'}, "独立させました")
        return {'FINISHED'}

class OT_reset_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_pos"
    bl_label = "位置を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_rot"
    bl_label = "回転を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.rot = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_create_spheres(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_spheres"
    bl_label = "等間隔球体を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_spheres_geometry(context)
        obj = bpy.data.objects.new(f"{props.sphere_obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_spheres_live(self, context)
        return {'FINISHED'}

class OT_detach_sphere_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_sphere_object"
    bl_label = "球体をアドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.sphere_obj_name):
            obj.name = f"Fixed_Spheres_{random.randint(100,999)}"
            self.report({'INFO'}, "球体を独立させました(インスタンス状態と色は維持されます)")
        return {'FINISHED'}

class OT_reset_sphere_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_pos"
    bl_label = "球体位置を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_sphere_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_rot"
    bl_label = "球体回転を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_rot = (0.0, 0.0, 0.0)
        return {'FINISHED'}

# ===================================
# 設定コピーオペレーター
# ===================================
class OT_copy_settings(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_copy_settings"
    bl_label = "現在の最新値をコピー"
    bl_description = "骨格と球体のすべての設定値をクリップボードにコピーします"
    
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        text = (
            "# ===================================\n"
            "# 定数 (骨格立方体固有)\n"
            "# ===================================\n"
            f'INIT_COL_NAME = "{props.col_name}"\n'
            f'INIT_SUB_COL_NAME = "{props.sub_col_name}"\n'
            f'INIT_OBJ_NAME = "{props.obj_name}"\n\n'
            f"INIT_SHAPE = '{props.shape}'\n"
            f"INIT_SIZE = {props.size:.4f}\n"
            f"INIT_THICKNESS = {props.thick:.4f}\n"
            f"INIT_RES = {props.res}\n"
            f"INIT_POS = ({props.pos[0]:.4f}, {props.pos[1]:.4f}, {props.pos[2]:.4f})\n"
            f"INIT_ROT = ({props.rot[0]:.4f}, {props.rot[1]:.4f}, {props.rot[2]:.4f})\n"
            f"INIT_SHOW_FRAME = {props.show_frame}\n"
            f"INIT_SHOW_FACES = {props.show_faces}\n"
            f"INIT_CLR_FRAME = ({props.color_frame[0]:.4f}, {props.color_frame[1]:.4f}, {props.color_frame[2]:.4f}, {props.color_frame[3]:.4f})\n"
            f"INIT_CLR_FACE = ({props.color_face[0]:.4f}, {props.color_face[1]:.4f}, {props.color_face[2]:.4f}, {props.color_face[3]:.4f})\n"
            
            f"\n# ===================================\n"
            f"# 定数 (球体配置固有)\n"
            f"# ===================================\n"
            f"INIT_SPHERE_OBJ_NAME = '{props.sphere_obj_name}'\n"
            f"INIT_SPHERE_PATTERN = '{props.sphere_pattern}'\n"
            f"INIT_SPHERE_COUNT = ({props.sphere_count[0]}, {props.sphere_count[1]}, {props.sphere_count[2]})\n"
            f"INIT_SPHERE_LENGTH = ({props.sphere_length[0]:.4f}, {props.sphere_length[1]:.4f}, {props.sphere_length[2]:.4f})\n"
            f"INIT_SPHERE_SIZE = {props.sphere_size:.4f}\n"
            f"INIT_SPHERE_POS = ({props.sphere_pos[0]:.4f}, {props.sphere_pos[1]:.4f}, {props.sphere_pos[2]:.4f})\n"
            f"INIT_SPHERE_ROT = ({props.sphere_rot[0]:.4f}, {props.sphere_rot[1]:.4f}, {props.sphere_rot[2]:.4f})\n"
            f"INIT_SHOW_SPHERES = {props.show_spheres}\n"
            f"INIT_SPHERE_COLOR = ({props.sphere_color[0]:.4f}, {props.sphere_color[1]:.4f}, {props.sphere_color[2]:.4f}, {props.sphere_color[3]:.4f})\n"
            f"INIT_SPHERE_RANDOM_COLOR = {props.sphere_use_random_color}\n"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "すべての設定値をクリップボードにコピーしました")
        return {'FINISHED'}

# ===================================
# プロパティグループ
# ===================================
class Properties_Addon(bpy.types.PropertyGroup):
    show_frame: bpy.props.BoolProperty(name="骨格", default=INIT_SHOW_FRAME, update=update_skeleton_live)
    show_faces: bpy.props.BoolProperty(name="面", default=INIT_SHOW_FACES, update=update_skeleton_live)
    show_spheres: bpy.props.BoolProperty(name="球体", default=INIT_SHOW_SPHERES, update=update_spheres_live)

    col_name: bpy.props.StringProperty(name="コレクション", default=INIT_COL_NAME)
    sub_col_name: bpy.props.StringProperty(name="サブコレクション", default=INIT_SUB_COL_NAME)
    
    obj_name: bpy.props.StringProperty(name="Object名", default=INIT_OBJ_NAME)
    shape: bpy.props.EnumProperty(
        items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], 
        name="Shape", default=INIT_SHAPE, update=update_skeleton_live
    )
    size: bpy.props.FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
    thick: bpy.props.FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
    res: bpy.props.IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
    pos: bpy.props.FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
    rot: bpy.props.FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
    
    color_frame: bpy.props.FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
    color_face: bpy.props.FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)

    sphere_obj_name: bpy.props.StringProperty(name="Sphere Object名", default=INIT_SPHERE_OBJ_NAME)
    sphere_pattern: bpy.props.EnumProperty(
        items=[('PLANE', "指定平面", ""), ('CUBE', "指定立方体空間", "")], 
        name="配置パターン", default=INIT_SPHERE_PATTERN, update=update_spheres_live
    )
    sphere_count: bpy.props.IntVectorProperty(name="配置数 (X,Y,Z)", default=INIT_SPHERE_COUNT, min=1, max=500, update=update_spheres_live)
    sphere_length: bpy.props.FloatVectorProperty(name="全体の長さ (X,Y,Z)", default=INIT_SPHERE_LENGTH, min=0.0, update=update_spheres_live)
    sphere_size: bpy.props.FloatProperty(name="球体サイズ", default=INIT_SPHERE_SIZE, min=0.01, update=update_spheres_live)
    sphere_pos: bpy.props.FloatVectorProperty(name="Sphere Pos", default=INIT_SPHERE_POS, update=update_spheres_live)
    sphere_rot: bpy.props.FloatVectorProperty(name="Sphere Rot", default=INIT_SPHERE_ROT, update=update_spheres_live)
    sphere_color: bpy.props.FloatVectorProperty(name="色/透明度", subtype='COLOR', size=4, default=INIT_SPHERE_COLOR, min=0.0, max=1.0, update=update_spheres_live)
    sphere_use_random_color: bpy.props.BoolProperty(name="色をランダムにする", default=INIT_SPHERE_RANDOM_COLOR, update=update_spheres_live)

# ===================================
# パネル構成
# ===================================
class PT_main(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
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択物を中央にする", icon='CENTER_ONLY')

class PT_grid(bpy.types.Panel):
    bl_label = "Grid 設定"
    bl_idname = f"{PREFIX.upper()}_PT_grid"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    
    def draw(self, context):
        layout = self.layout
        
        layout.label(text="グリッドスケール:")
        row = layout.row(align=True)
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="1").scale_value = 1.0
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="10").scale_value = 10.0
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="100").scale_value = 100.0
        
        layout.separator()
        if context.space_data and context.space_data.type == 'VIEW_3D':
            layout.prop(context.space_data.overlay, "grid_scale", text="任意の数値")

class PT_creation(bpy.types.Panel):
    bl_label = "骨格立方体 調整"
    bl_idname = f"{PREFIX.upper()}_PT_creation"
    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_skeleton", icon='MESH_CUBE')
        layout.separator()

        box = layout.box()
        box.label(text="表示 / 非表示 トグル:", icon='HIDE_OFF')
        row = box.row(align=True)
        row.prop(props, "show_frame", toggle=True)
        row.prop(props, "show_faces", toggle=True)
        
        layout.separator()
        
        if props.show_frame or props.show_faces:
            col = layout.column(align=True)
            col.prop(props, "col_name")
            col.prop(props, "sub_col_name")
            col.prop(props, "obj_name")
            
            layout.separator()
            col = layout.column(align=True)
            col.prop(props, "shape", text="基本形状")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_pos_head = col.row(align=True)
            row_pos_head.label(text="位置:")
            row_pos_head.operator(f"object.{PREFIX}_reset_pos", text="", icon='FILE_REFRESH')
            col.prop(props, "pos", index=0, text="X")
            col.prop(props, "pos", index=1, text="Y")
            col.prop(props, "pos", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_rot_head = col.row(align=True)
            row_rot_head.label(text="回転:")
            row_rot_head.operator(f"object.{PREFIX}_reset_rot", text="", icon='FILE_REFRESH')
            col.prop(props, "rot", index=0, text="X")
            col.prop(props, "rot", index=1, text="Y")
            col.prop(props, "rot", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            col.prop(props, "size", text="サイズ")
            col.prop(props, "thick", text="骨格の太さ")
            col.prop(props, "res", text="分割数")
            
            layout.separator()
            
            row = layout.row(align=True)
            if props.show_frame:
                row.prop(props, "color_frame", text="骨格の色")
            if props.show_faces:
                row.prop(props, "color_face", text="表面の色")
            
        layout.separator()
        if obj and obj.name.startswith(props.obj_name):
            layout.operator(f"object.{PREFIX}_detach_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した骨格を選択して調整", icon='INFO')

class PT_spheres(bpy.types.Panel):
    bl_label = "等間隔 球体配置 調整"
    bl_idname = f"{PREFIX.upper()}_PT_spheres"
    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_spheres", icon='MESH_UVSPHERE')
        layout.separator()

        box = layout.box()
        box.label(text="表示 / 非表示 トグル:", icon='HIDE_OFF')
        box.prop(props, "show_spheres", toggle=True)
        
        if props.show_spheres:
            box.separator()
            box.prop(props, "sphere_use_random_color", text="色をランダムで球体1つ1つ変える", icon='SHADING_TEXTURE')
            
            layout.separator()
            
            col = layout.column(align=True)
            col.prop(props, "sphere_obj_name")
            col.prop(props, "sphere_pattern")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_pos_head = col.row(align=True)
            row_pos_head.label(text="全体位置:")
            row_pos_head.operator(f"object.{PREFIX}_reset_sphere_pos", text="", icon='FILE_REFRESH')
            col.prop(props, "sphere_pos", index=0, text="X")
            col.prop(props, "sphere_pos", index=1, text="Y")
            col.prop(props, "sphere_pos", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_rot_head = col.row(align=True)
            row_rot_head.label(text="全体回転:")
            row_rot_head.operator(f"object.{PREFIX}_reset_sphere_rot", text="", icon='FILE_REFRESH')
            col.prop(props, "sphere_rot", index=0, text="X")
            col.prop(props, "sphere_rot", index=1, text="Y")
            col.prop(props, "sphere_rot", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            col.label(text="全体の長さ (空間サイズ):")
            col.prop(props, "sphere_length", index=0, text="X")
            col.prop(props, "sphere_length", index=1, text="Y")
            if props.sphere_pattern == 'CUBE':
                col.prop(props, "sphere_length", index=2, text="Z")
                
            col.separator()
            
            col.label(text="配置数 (何個配置するか):")
            col.prop(props, "sphere_count", index=0, text="X")
            col.prop(props, "sphere_count", index=1, text="Y")
            if props.sphere_pattern == 'CUBE':
                col.prop(props, "sphere_count", index=2, text="Z")
                
            layout.separator()
            col = layout.column(align=True)
            col.prop(props, "sphere_size", text="球体の大きさ")
            
            if props.sphere_use_random_color:
                col.prop(props, "sphere_color", text="全体の透明度調整用 (色はランダム)")
            else:
                col.prop(props, "sphere_color", text="単一色 / 透明度")

        layout.separator()
        if obj and obj.name.startswith(props.sphere_obj_name):
            layout.operator(f"object.{PREFIX}_detach_sphere_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した球体を選択して調整", icon='INFO')

class PT_copy(bpy.types.Panel):
    bl_label = "設定のエクスポート"
    bl_idname = f"{PREFIX.upper()}_PT_copy"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.operator(f"object.{PREFIX}_copy_settings", text="現在の最新値をすべてコピー", icon='COPYDOWN')

class PT_links(bpy.types.Panel):
    bl_label = "Links"
    bl_idname = f"{PREFIX.upper()}_PT_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        if LINK_NAME_1 and LINK_URL_1: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_1, icon='URL').url = LINK_URL_1
        if LINK_NAME_2 and LINK_URL_2: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_URL_2, icon='URL').url = LINK_URL_2
        if LINK_NAME_3 and LINK_URL_3: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_3, icon='URL').url = LINK_URL_3
        if LINK_NAME_4 and LINK_URL_4: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_4, icon='URL').url = LINK_URL_4
        if LINK_NAME_5 and LINK_URL_5: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_5, icon='URL').url = LINK_URL_5
        if LINK_NAME_6 and LINK_URL_6: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_6, icon='URL').url = LINK_URL_6

class PT_remove(bpy.types.Panel):
    bl_label = "Addon Remove"
    bl_idname = f"{PREFIX.upper()}_PT_remove"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# register() / unregister()
# ===================================
classes = [
    OT_open_url,
    OT_remove_addon,
    Properties_Addon,
    OT_reset_view,
    OT_set_grid_scale,
    
    OT_create_skeleton,
    OT_detach_object,
    OT_reset_pos,
    OT_reset_rot,
    
    OT_create_spheres,
    OT_detach_sphere_object,
    OT_reset_sphere_pos,
    OT_reset_sphere_rot,
    
    OT_copy_settings,
    
    PT_main,
    PT_grid,
    PT_creation,
    PT_spheres,
    PT_copy,   # ここでLinksの上に配置
    PT_links,
    PT_remove
]

def register():
    for c in classes:
        c.__name__ = f"{PREFIX_VAL}_{c.__name__}"
        bpy.utils.register_class(c)
        
    setattr(bpy.types.Scene, f"{PREFIX}_props", bpy.props.PointerProperty(type=Properties_Addon))
    
    def apply_initial_settings():
        if bpy.context.window_manager:
            for window in bpy.context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D':
                                space.show_region_ui = True
                                space.shading.type = 'MATERIAL'
        return None

    bpy.app.timers.register(apply_initial_settings, first_interval=0.1)

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

if __name__ == "__main__":
    try: unregister()
    except: pass
    register()
bl_info = {
    "name": "zionad_ [ 骨格立方体 + 等間隔球体 ]",
    "author": "zionad_",
    "version": (2, 5, 1),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "表面・骨格別の色設定ツールと、超軽量な等間隔インスタンス球体配置ツール(ランダム色対応)",
    "category": "3D View",
}

import bpy
import bmesh
import math
import webbrowser
import random

# ===================================
# 共通定義 (作成基本ベース)
# ===================================
ADDON_TITLE = "骨格立方体"
ADDON_CATEGORY = "骨格立方体"
PREFIX_VAL = "basd20260829"
PREFIX = PREFIX_VAL.lower()

# ===================================
# リンク定義 (統合版)
# ===================================
LINK_NAME_1 = "338 Sphere Cam"
LINK_URL_1 = "<https://app.notion.com/p/338-Sphere-Cam-3c0347b3454a80abbb61ff2cd585792c>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"
LINK_NAME_4 = "背後霊カメラ 最新版リンク"
LINK_URL_4 = "<https://app.notion.com/p/Cam-blender-39f347b3454a806490a6f0107c2347dd>"
LINK_NAME_5 = "blender コンテナ 20260713"
LINK_URL_5 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_6 = "骨格立方体"
LINK_URL_6 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"

# ===================================
# 定数 (骨格立方体固有)
# ===================================
INIT_COL_NAME = "Dice_Collection"
INIT_SUB_COL_NAME = "Skeleton_Cubes"
INIT_OBJ_NAME = "Skeleton_Dice"
INIT_SHAPE = 'CUBE'
INIT_SIZE = 60.0000
INIT_THICKNESS = 1.0000
INIT_RES = 32
INIT_POS = (0.0000, 0.0000, 0.0000)
INIT_ROT = (0.0000, 0.0000, 0.0000)
INIT_SHOW_FRAME = True
INIT_SHOW_FACES = True
INIT_CLR_FRAME = (0.0081, 0.3691, 0.0692, 1.0000)
INIT_CLR_FACE = (0.1761, 0.3179, 0.3123, 0.1272)

# ===================================
# 定数 (球体配置固有)
# ===================================
INIT_SPHERE_OBJ_NAME = "Arranged_Spheres"
INIT_SPHERE_PATTERN = 'CUBE'
INIT_SPHERE_COUNT = (3, 3, 3)
INIT_SPHERE_LENGTH = (40.0000, 40.0000, 40.0000)
INIT_SPHERE_SIZE = 2.0000
INIT_SPHERE_POS = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_ROT = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_COLOR = (0.3340, 0.8000, 0.9000, 0.5000)
INIT_SHOW_SPHERES = True
INIT_SPHERE_RANDOM_COLOR = False

# ===================================
# コアロジック (骨格立方体)
# ===================================
def create_advanced_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm_final = bmesh.new()
    
    # --- 骨格フレームの生成 ---
    if props.show_frame:
        bm_base = bmesh.new()
        if props.shape == 'CUBE':
            bmesh.ops.create_cube(bm_base, size=props.size)
        else:
            bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=props.size/2)
            
        curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.fill_mode = 'FULL'
        curve_data.bevel_depth = props.thick / 2
        curve_data.bevel_resolution = props.res
        
        for edge in bm_base.edges:
            v1, v2 = edge.verts[0].co, edge.verts[1].co
            polyline = curve_data.splines.new('POLY')
            polyline.points.add(1)
            polyline.points[0].co = (*v1, 1)
            polyline.points[1].co = (*v2, 1)
            
        temp_frame_obj = bpy.data.objects.new("temp_frame", curve_data)
        context.collection.objects.link(temp_frame_obj)
        depsgraph = context.evaluated_depsgraph_get()
        mesh_frame = bpy.data.meshes.new_from_object(temp_frame_obj.evaluated_get(depsgraph))
        
        bpy.data.objects.remove(temp_frame_obj)
        bpy.data.curves.remove(curve_data)
        
        bm_final.from_mesh(mesh_frame)
        for face in bm_final.faces:
            face.material_index = 0
        bpy.data.meshes.remove(mesh_frame)
        bm_base.free()
    
    # --- 表面(面)の生成 ---
    if props.show_faces:
        bm_face = bmesh.new()
        if props.shape == 'CUBE':
            bmesh.ops.create_cube(bm_face, size=props.size)
        else:
            bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=props.size/2)
            
        for face in bm_face.faces:
            face.material_index = 1
        temp_face_mesh = bpy.data.meshes.new("temp_face")
        bm_face.to_mesh(temp_face_mesh)
        bm_final.from_mesh(temp_face_mesh)
        bpy.data.meshes.remove(temp_face_mesh)
        bm_face.free()
        
    mesh_out = bpy.data.meshes.new(props.obj_name)
    bm_final.to_mesh(mesh_out)
    bm_final.free()
    
    return mesh_out

def update_skeleton_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.obj_name) and obj.type == 'MESH':
            obj.location = props.pos
            obj.rotation_euler = [math.radians(a) for a in props.rot]
            
            new_mesh = create_advanced_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            while len(obj.data.materials) < 2:
                obj.data.materials.append(None)
                
            # Blender 5.x Material Settings for Frame
            if not obj.material_slots[0].material:
                obj.material_slots[0].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Frame")
            mat_frame = obj.material_slots[0].material
            mat_frame.use_nodes = True
            bsdf_frame = mat_frame.node_tree.nodes.get("Principled BSDF")
            if bsdf_frame:
                if 'Base Color' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Base Color'].default_value = props.color_frame
                if 'Alpha' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Alpha'].default_value = props.color_frame[3]

            # Blender 5.x Material Settings for Face
            if not obj.material_slots[1].material:
                obj.material_slots[1].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Face")
            mat_face = obj.material_slots[1].material
            mat_face.use_nodes = True
            bsdf_face = mat_face.node_tree.nodes.get("Principled BSDF")
            if bsdf_face:
                if 'Base Color' in bsdf_face.inputs:
                    bsdf_face.inputs['Base Color'].default_value = props.color_face
                if 'Alpha' in bsdf_face.inputs:
                    bsdf_face.inputs['Alpha'].default_value = props.color_face[3]

# ===================================
# コアロジック (等間隔 球体配置 - インスタンス化)
# ===================================
def create_spheres_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm = bmesh.new()
    
    # 非表示設定の場合は頂点を生成しない(超軽量化)
    if props.show_spheres:
        count_x = props.sphere_count[0]
        count_y = props.sphere_count[1]
        count_z = props.sphere_count[2] if props.sphere_pattern == 'CUBE' else 1
        
        len_x = props.sphere_length[0]
        len_y = props.sphere_length[1]
        len_z = props.sphere_length[2]
        
        step_x = len_x / (count_x - 1) if count_x > 1 else 0
        step_y = len_y / (count_y - 1) if count_y > 1 else 0
        step_z = len_z / (count_z - 1) if count_z > 1 else 0
        
        start_x = -len_x / 2.0 if count_x > 1 else 0
        start_y = -len_y / 2.0 if count_y > 1 else 0
        start_z = -len_z / 2.0 if count_z > 1 else 0
        
        for z in range(count_z):
            for y in range(count_y):
                for x in range(count_x):
                    pos = (start_x + x * step_x, start_y + y * step_y, start_z + z * step_z)
                    bm.verts.new(pos)
                
    mesh_out = bpy.data.meshes.new(props.sphere_obj_name + "_Points")
    bm.to_mesh(mesh_out)
    bm.free()
    
    return mesh_out

def setup_sphere_material(obj, props):
    if not obj.data.materials:
        obj.data.materials.append(bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Spheres"))
    mat = obj.data.materials[0]
    
    # ソリッドモード用のベースカラー
    mat.diffuse_color = props.sphere_color
    mat.use_nodes = True
    
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()
    
    out_node = nodes.new('ShaderNodeOutputMaterial')
    out_node.location = (300, 0)
    
    bsdf = nodes.new('ShaderNodeBsdfPrincipled')
    bsdf.location = (0, 0)
    links.new(bsdf.outputs[0], out_node.inputs[0])
    
    # Blender 5.x 共通透過設定
    if 'Alpha' in bsdf.inputs:
        bsdf.inputs['Alpha'].default_value = props.sphere_color[3]
        
    if props.sphere_use_random_color:
        obj_info = nodes.new('ShaderNodeObjectInfo')
        obj_info.location = (-600, 0)
        
        white_noise = nodes.new('ShaderNodeTexWhiteNoise')
        white_noise.location = (-400, 0)
        links.new(obj_info.outputs['Location'], white_noise.inputs['Vector'])
        
        # 虹色生成のためのColorRamp設定
        color_ramp = nodes.new('ShaderNodeValToRGB')
        color_ramp.location = (-200, 0)
        color_ramp.color_ramp.color_mode = 'HSV'
        color_ramp.color_ramp.hue_interpolation = 'CW'
        
        elements = color_ramp.color_ramp.elements
        elements[0].position = 0.0
        elements[0].color = (1.0, 0.0, 0.0, 1.0)
        elements[-1].position = 1.0
        elements[-1].color = (1.0, 0.0, 0.0, 1.0)
        
        links.new(white_noise.outputs['Value'], color_ramp.inputs['Fac'])
        links.new(color_ramp.outputs['Color'], bsdf.inputs['Base Color'])
    else:
        if 'Base Color' in bsdf.inputs:
            bsdf.inputs['Base Color'].default_value = props.sphere_color
            
    return mat

def setup_sphere_instancing(obj, props):
    mod_name = "Sphere_Instancer"
    mod = obj.modifiers.get(mod_name)
    if not mod:
        mod = obj.modifiers.new(mod_name, 'NODES')
        
    if not mod.node_group:
        node_group = bpy.data.node_groups.new(mod_name + "_Tree", 'GeometryNodeTree')
        mod.node_group = node_group
        
        # Blender 5.x専用 NodeGroup API
        node_group.interface.new_socket(name="Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
        node_group.interface.new_socket(name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
            
        nodes = node_group.nodes
        links = node_group.links
        
        in_node = nodes.new('NodeGroupInput')
        out_node = nodes.new('NodeGroupOutput')
        
        sphere_node = nodes.new('GeometryNodeMeshIcoSphere')
        sphere_node.inputs['Subdivisions'].default_value = 2
        
        mat_node = nodes.new('GeometryNodeSetMaterial')
        inst_node = nodes.new('GeometryNodeInstanceOnPoints')
        
        links.new(in_node.outputs[0], inst_node.inputs['Points'])
        links.new(sphere_node.outputs['Mesh'], mat_node.inputs['Geometry'])
        links.new(mat_node.outputs['Geometry'], inst_node.inputs['Instance'])
        links.new(inst_node.outputs[0], out_node.inputs[0])
        
    nodes = mod.node_group.nodes
    
    sphere_node = next((n for n in nodes if n.type == 'MESH_ICO_SPHERE'), None)
    if sphere_node:
        sphere_node.inputs['Radius'].default_value = props.sphere_size
        
    mat = setup_sphere_material(obj, props)
    mat_node = next((n for n in nodes if n.type == 'SET_MATERIAL'), None)
    if mat_node:
        try:
            mat_node.inputs['Material'].default_value = mat
        except:
            for inp in mat_node.inputs:
                if inp.type == 'MATERIAL':
                    inp.default_value = mat
                    break

def update_spheres_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.sphere_obj_name) and obj.type == 'MESH':
            obj.location = props.sphere_pos
            obj.rotation_euler = [math.radians(a) for a in props.sphere_rot]
            
            new_mesh = create_spheres_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            setup_sphere_instancing(obj, props)

# ===================================
# 共通クラス群
# ===================================
class OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    def execute(self, context):
        if self.url: webbrowser.open(self.url)
        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'}

def get_rv3d(context):
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            return a.spaces.active.region_3d
    return None

class OT_reset_view(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_view"
    bl_label = "選択物を中央にする"
    def execute(self, context):
        rv3d = get_rv3d(context)
        if rv3d and context.active_object:
            rv3d.view_location = context.active_object.location
        return {'FINISHED'}

class OT_set_grid_scale(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_set_grid_scale"
    bl_label = "グリッドスケール設定"
    scale_value: bpy.props.FloatProperty()
    
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.overlay.grid_scale = self.scale_value
        return {'FINISHED'}

# ===================================
# アドオン固有オペレーター
# ===================================
class OT_create_skeleton(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_skeleton"
    bl_label = "骨格を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_advanced_geometry(context)
        obj = bpy.data.objects.new(f"{props.obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_skeleton_live(self, context)
        return {'FINISHED'}

class OT_detach_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_object"
    bl_label = "アドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.obj_name):
            obj.name = f"Fixed_Dice_{random.randint(100,999)}"
            self.report({'INFO'}, "独立させました")
        return {'FINISHED'}

class OT_reset_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_pos"
    bl_label = "位置を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_rot"
    bl_label = "回転を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.rot = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_create_spheres(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_spheres"
    bl_label = "等間隔球体を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_spheres_geometry(context)
        obj = bpy.data.objects.new(f"{props.sphere_obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_spheres_live(self, context)
        return {'FINISHED'}

class OT_detach_sphere_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_sphere_object"
    bl_label = "球体をアドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.sphere_obj_name):
            bpy.ops.object.modifier_apply(modifier="Sphere_Instancer")
            obj.name = f"Fixed_Spheres_{random.randint(100,999)}"
            self.report({'INFO'}, "モディファイアを適用し、球体を独立させました")
        return {'FINISHED'}

class OT_reset_sphere_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_pos"
    bl_label = "球体位置を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_sphere_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_rot"
    bl_label = "球体回転を一括リセット"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_rot = (0.0, 0.0, 0.0)
        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 = (
            "# ===================================\n"
            "# 定数 (最新値)\n"
            "# ===================================\n"
            f'INIT_SHOW_FRAME = {props.show_frame}\n'
            f'INIT_SHOW_FACES = {props.show_faces}\n'
            f'INIT_SHOW_SPHERES = {props.show_spheres}\n'
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "定数定義コードをクリップボードにコピーしました")
        return {'FINISHED'}

# ===================================
# プロパティグループ
# ===================================
class Properties_Addon(bpy.types.PropertyGroup):
    show_frame: bpy.props.BoolProperty(name="骨格", default=INIT_SHOW_FRAME, update=update_skeleton_live)
    show_faces: bpy.props.BoolProperty(name="面", default=INIT_SHOW_FACES, update=update_skeleton_live)
    show_spheres: bpy.props.BoolProperty(name="球体", default=INIT_SHOW_SPHERES, update=update_spheres_live)

    col_name: bpy.props.StringProperty(name="コレクション", default=INIT_COL_NAME)
    sub_col_name: bpy.props.StringProperty(name="サブコレクション", default=INIT_SUB_COL_NAME)
    
    obj_name: bpy.props.StringProperty(name="Object名", default=INIT_OBJ_NAME)
    shape: bpy.props.EnumProperty(
        items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], 
        name="Shape", default=INIT_SHAPE, update=update_skeleton_live
    )
    size: bpy.props.FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
    thick: bpy.props.FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
    res: bpy.props.IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
    pos: bpy.props.FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
    rot: bpy.props.FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
    
    color_frame: bpy.props.FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
    color_face: bpy.props.FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)

    sphere_obj_name: bpy.props.StringProperty(name="Sphere Object名", default=INIT_SPHERE_OBJ_NAME)
    sphere_pattern: bpy.props.EnumProperty(
        items=[('PLANE', "指定平面", ""), ('CUBE', "指定立方体空間", "")], 
        name="配置パターン", default=INIT_SPHERE_PATTERN, update=update_spheres_live
    )
    sphere_count: bpy.props.IntVectorProperty(name="配置数 (X,Y,Z)", default=INIT_SPHERE_COUNT, min=1, max=500, update=update_spheres_live)
    sphere_length: bpy.props.FloatVectorProperty(name="全体の長さ (X,Y,Z)", default=INIT_SPHERE_LENGTH, min=0.0, update=update_spheres_live)
    sphere_size: bpy.props.FloatProperty(name="球体サイズ", default=INIT_SPHERE_SIZE, min=0.01, update=update_spheres_live)
    sphere_pos: bpy.props.FloatVectorProperty(name="Sphere Pos", default=INIT_SPHERE_POS, update=update_spheres_live)
    sphere_rot: bpy.props.FloatVectorProperty(name="Sphere Rot", default=INIT_SPHERE_ROT, update=update_spheres_live)
    sphere_color: bpy.props.FloatVectorProperty(name="色/透明度", subtype='COLOR', size=4, default=INIT_SPHERE_COLOR, min=0.0, max=1.0, update=update_spheres_live)
    sphere_use_random_color: bpy.props.BoolProperty(name="色をランダムにする", default=INIT_SPHERE_RANDOM_COLOR, update=update_spheres_live)

# ===================================
# パネル構成
# ===================================
class PT_main(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
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択物を中央にする", icon='CENTER_ONLY')

class PT_creation(bpy.types.Panel):
    bl_label = "骨格立方体 調整"
    bl_idname = f"{PREFIX.upper()}_PT_creation"
    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_skeleton", icon='MESH_CUBE')
        layout.separator()

        # ここを正しいアイコン名 (HIDE_OFF) に修正
        box = layout.box()
        box.label(text="表示 / 非表示 トグル:", icon='HIDE_OFF')
        row = box.row(align=True)
        row.prop(props, "show_frame", toggle=True)
        row.prop(props, "show_faces", toggle=True)
        
        layout.separator()
        
        # もし両方非表示なら詳細設定を隠す(UIをスッキリさせる)
        if props.show_frame or props.show_faces:
            col = layout.column(align=True)
            col.prop(props, "col_name")
            col.prop(props, "sub_col_name")
            col.prop(props, "obj_name")
            
            layout.separator()
            col = layout.column(align=True)
            col.prop(props, "shape", text="基本形状")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_pos_head = col.row(align=True)
            row_pos_head.label(text="位置:")
            row_pos_head.operator(f"object.{PREFIX}_reset_pos", text="", icon='FILE_REFRESH')
            col.prop(props, "pos", index=0, text="X")
            col.prop(props, "pos", index=1, text="Y")
            col.prop(props, "pos", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_rot_head = col.row(align=True)
            row_rot_head.label(text="回転:")
            row_rot_head.operator(f"object.{PREFIX}_reset_rot", text="", icon='FILE_REFRESH')
            col.prop(props, "rot", index=0, text="X")
            col.prop(props, "rot", index=1, text="Y")
            col.prop(props, "rot", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            col.prop(props, "size", text="サイズ")
            col.prop(props, "thick", text="骨格の太さ")
            col.prop(props, "res", text="分割数")
            
            layout.separator()
            
            row = layout.row(align=True)
            if props.show_frame:
                row.prop(props, "color_frame", text="骨格の色")
            if props.show_faces:
                row.prop(props, "color_face", text="表面の色")
            
            layout.separator()
            if obj and obj.name.startswith(props.obj_name):
                layout.operator(f"object.{PREFIX}_detach_object", icon='UNLINKED')
            else:
                layout.label(text="※生成した骨格を選択して調整", icon='INFO')
                
            layout.separator()
            layout.operator(f"object.{PREFIX}_copy_settings", icon='COPYDOWN')

class PT_spheres(bpy.types.Panel):
    bl_label = "等間隔 球体配置 調整"
    bl_idname = f"{PREFIX.upper()}_PT_spheres"
    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_spheres", icon='MESH_UVSPHERE')
        layout.separator()

        # ここを正しいアイコン名 (HIDE_OFF) に修正
        box = layout.box()
        box.label(text="表示 / 非表示 トグル:", icon='HIDE_OFF')
        box.prop(props, "show_spheres", toggle=True)
        
        # 非表示の時はUIを隠してスッキリさせる
        if props.show_spheres:
            box.separator()
            box.prop(props, "sphere_use_random_color", text="色をランダムで球体1つ1つ変える", icon='SHADING_TEXTURE')
            
            layout.separator()
            
            col = layout.column(align=True)
            col.prop(props, "sphere_obj_name")
            col.prop(props, "sphere_pattern")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_pos_head = col.row(align=True)
            row_pos_head.label(text="全体位置:")
            row_pos_head.operator(f"object.{PREFIX}_reset_sphere_pos", text="", icon='FILE_REFRESH')
            col.prop(props, "sphere_pos", index=0, text="X")
            col.prop(props, "sphere_pos", index=1, text="Y")
            col.prop(props, "sphere_pos", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            row_rot_head = col.row(align=True)
            row_rot_head.label(text="全体回転:")
            row_rot_head.operator(f"object.{PREFIX}_reset_sphere_rot", text="", icon='FILE_REFRESH')
            col.prop(props, "sphere_rot", index=0, text="X")
            col.prop(props, "sphere_rot", index=1, text="Y")
            col.prop(props, "sphere_rot", index=2, text="Z")
            
            layout.separator()
            
            col = layout.column(align=True)
            col.label(text="全体の長さ (空間サイズ):")
            col.prop(props, "sphere_length", index=0, text="X")
            col.prop(props, "sphere_length", index=1, text="Y")
            if props.sphere_pattern == 'CUBE':
                col.prop(props, "sphere_length", index=2, text="Z")
                
            col.separator()
            
            col.label(text="配置数 (何個配置するか):")
            col.prop(props, "sphere_count", index=0, text="X")
            col.prop(props, "sphere_count", index=1, text="Y")
            if props.sphere_pattern == 'CUBE':
                col.prop(props, "sphere_count", index=2, text="Z")
                
            layout.separator()
            col = layout.column(align=True)
            col.prop(props, "sphere_size", text="球体の大きさ")
            
            # ランダムカラーのON/OFFで色のラベル表記を変更
            if props.sphere_use_random_color:
                col.prop(props, "sphere_color", text="全体の透明度調整用 (色はランダム)")
            else:
                col.prop(props, "sphere_color", text="単一色 / 透明度")

            layout.separator()
            if obj and obj.name.startswith(props.sphere_obj_name):
                layout.operator(f"object.{PREFIX}_detach_sphere_object", icon='UNLINKED')
            else:
                layout.label(text="※生成した球体を選択して調整", icon='INFO')

class PT_links(bpy.types.Panel):
    bl_label = "Links"
    bl_idname = f"{PREFIX.upper()}_PT_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        if LINK_NAME_1 and LINK_URL_1: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_1, icon='URL').url = LINK_URL_1
        if LINK_NAME_2 and LINK_URL_2: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_URL_2, icon='URL').url = LINK_URL_2
        if LINK_NAME_3 and LINK_URL_3: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_3, icon='URL').url = LINK_URL_3
        if LINK_NAME_4 and LINK_URL_4: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_4, icon='URL').url = LINK_URL_4
        if LINK_NAME_5 and LINK_URL_5: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_5, icon='URL').url = LINK_URL_5
        if LINK_NAME_6 and LINK_URL_6: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_6, icon='URL').url = LINK_URL_6

class PT_remove(bpy.types.Panel):
    bl_label = "Addon Remove"
    bl_idname = f"{PREFIX.upper()}_PT_remove"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# register() / unregister()
# ===================================
classes = [
    OT_open_url,
    OT_remove_addon,
    Properties_Addon,
    OT_reset_view,
    OT_set_grid_scale,
    
    OT_create_skeleton,
    OT_detach_object,
    OT_reset_pos,
    OT_reset_rot,
    
    OT_create_spheres,
    OT_detach_sphere_object,
    OT_reset_sphere_pos,
    OT_reset_sphere_rot,
    
    OT_copy_settings,
    
    PT_main,
    PT_creation,
    PT_spheres,
    PT_links,
    PT_remove
]

def register():
    for c in classes:
        c.__name__ = f"{PREFIX_VAL}_{c.__name__}"
        bpy.utils.register_class(c)
        
    setattr(bpy.types.Scene, f"{PREFIX}_props", bpy.props.PointerProperty(type=Properties_Addon))
    
    def apply_initial_settings():
        if bpy.context.window_manager:
            for window in bpy.context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D':
                                space.show_region_ui = True
                                space.shading.type = 'MATERIAL'
        return None

    bpy.app.timers.register(apply_initial_settings, first_interval=0.1)

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

if __name__ == "__main__":
    try: unregister()
    except: pass
    register()
bl_info = {
    "name": "zionad_ [ 骨格立方体 + 等間隔球体 ]",
    "author": "zionad_",
    "version": (2, 4, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "表面・骨格別の色設定ツールと、超軽量な等間隔インスタンス球体配置ツール(ランダム色対応)",
    "category": "3D View",
}

import bpy
import bmesh
import math
import webbrowser
import random

# ===================================
# 共通定義 (作成基本ベース)
# ===================================
ADDON_TITLE = "骨格立方体"
ADDON_CATEGORY = "骨格立方体"
PREFIX_VAL = "basd20260829"
PREFIX = PREFIX_VAL.lower()

# ===================================
# リンク定義 (統合版)
# ===================================
LINK_NAME_1 = "338 Sphere Cam"
LINK_URL_1 = "<https://app.notion.com/p/338-Sphere-Cam-3c0347b3454a80abbb61ff2cd585792c>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"
LINK_NAME_4 = "背後霊カメラ 最新版リンク"
LINK_URL_4 = "<https://app.notion.com/p/Cam-blender-39f347b3454a806490a6f0107c2347dd>"
LINK_NAME_5 = "blender コンテナ 20260713"
LINK_URL_5 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_6 = "骨格立方体"
LINK_URL_6 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"

# ===================================
# 定数 (骨格立方体固有)
# ===================================
INIT_COL_NAME = "Dice_Collection"
INIT_SUB_COL_NAME = "Skeleton_Cubes"
INIT_OBJ_NAME = "Skeleton_Dice"
INIT_SHAPE = 'CUBE'
INIT_SIZE = 60.0000
INIT_THICKNESS = 1.0000
INIT_RES = 32
INIT_POS = (0.0000, 0.0000, 0.0000)
INIT_ROT = (0.0000, 0.0000, 0.0000)
INIT_SHOW_FACES = True
INIT_CLR_FRAME = (0.0081, 0.3691, 0.0692, 1.0000)
INIT_CLR_FACE = (0.1761, 0.3179, 0.3123, 0.1272)

# ===================================
# 定数 (球体配置固有)
# ===================================
INIT_SPHERE_OBJ_NAME = "Arranged_Spheres"
INIT_SPHERE_PATTERN = 'CUBE'
INIT_SPHERE_COUNT = (3, 3, 3)
INIT_SPHERE_LENGTH = (40.0000, 40.0000, 40.0000)
INIT_SPHERE_SIZE = 2.0000
INIT_SPHERE_POS = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_ROT = (0.0000, 0.0000, 0.0000)
INIT_SPHERE_COLOR = (0.3340, 0.8000, 0.9000, 0.5000)
INIT_SPHERE_RANDOM_COLOR = False

# ===================================
# コアロジック (骨格立方体)
# ===================================
def create_advanced_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm_base = bmesh.new()
    
    if props.shape == 'CUBE':
        bmesh.ops.create_cube(bm_base, size=props.size)
    else:
        bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=props.size/2)
        
    curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
    curve_data.dimensions = '3D'
    curve_data.fill_mode = 'FULL'
    curve_data.bevel_depth = props.thick / 2
    curve_data.bevel_resolution = props.res
    
    for edge in bm_base.edges:
        v1, v2 = edge.verts[0].co, edge.verts[1].co
        polyline = curve_data.splines.new('POLY')
        polyline.points.add(1)
        polyline.points[0].co = (*v1, 1)
        polyline.points[1].co = (*v2, 1)
        
    temp_frame_obj = bpy.data.objects.new("temp_frame", curve_data)
    context.collection.objects.link(temp_frame_obj)
    depsgraph = context.evaluated_depsgraph_get()
    mesh_frame = bpy.data.meshes.new_from_object(temp_frame_obj.evaluated_get(depsgraph))
    
    bpy.data.objects.remove(temp_frame_obj)
    bpy.data.curves.remove(curve_data)
    
    bm_final = bmesh.new()
    bm_final.from_mesh(mesh_frame)
    for face in bm_final.faces:
        face.material_index = 0
    bpy.data.meshes.remove(mesh_frame)
    
    if props.show_faces:
        bm_face = bmesh.new()
        if props.shape == 'CUBE':
            bmesh.ops.create_cube(bm_face, size=props.size)
        else:
            bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=props.size/2)
            
        for face in bm_face.faces:
            face.material_index = 1
        temp_face_mesh = bpy.data.meshes.new("temp_face")
        bm_face.to_mesh(temp_face_mesh)
        bm_final.from_mesh(temp_face_mesh)
        bpy.data.meshes.remove(temp_face_mesh)
        bm_face.free()
        
    mesh_out = bpy.data.meshes.new(props.obj_name)
    bm_final.to_mesh(mesh_out)
    bm_final.free()
    bm_base.free()
    
    return mesh_out

def update_skeleton_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.obj_name) and obj.type == 'MESH':
            obj.location = props.pos
            obj.rotation_euler = [math.radians(a) for a in props.rot]
            
            new_mesh = create_advanced_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            while len(obj.data.materials) < 2:
                obj.data.materials.append(None)
                
            # Blender 5.x Material Settings for Frame
            if not obj.material_slots[0].material:
                obj.material_slots[0].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Frame")
            mat_frame = obj.material_slots[0].material
            mat_frame.use_nodes = True
            bsdf_frame = mat_frame.node_tree.nodes.get("Principled BSDF")
            if bsdf_frame:
                if 'Base Color' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Base Color'].default_value = props.color_frame
                if 'Alpha' in bsdf_frame.inputs:
                    bsdf_frame.inputs['Alpha'].default_value = props.color_frame[3]

            # Blender 5.x Material Settings for Face
            if not obj.material_slots[1].material:
                obj.material_slots[1].material = bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Face")
            mat_face = obj.material_slots[1].material
            mat_face.use_nodes = True
            bsdf_face = mat_face.node_tree.nodes.get("Principled BSDF")
            if bsdf_face:
                if 'Base Color' in bsdf_face.inputs:
                    bsdf_face.inputs['Base Color'].default_value = props.color_face
                if 'Alpha' in bsdf_face.inputs:
                    bsdf_face.inputs['Alpha'].default_value = props.color_face[3]

# ===================================
# コアロジック (等間隔 球体配置 - インスタンス化)
# ===================================
def create_spheres_geometry(context):
    props = getattr(context.scene, f"{PREFIX}_props")
    bm = bmesh.new()
    
    count_x = props.sphere_count[0]
    count_y = props.sphere_count[1]
    count_z = props.sphere_count[2] if props.sphere_pattern == 'CUBE' else 1
    
    len_x = props.sphere_length[0]
    len_y = props.sphere_length[1]
    len_z = props.sphere_length[2]
    
    step_x = len_x / (count_x - 1) if count_x > 1 else 0
    step_y = len_y / (count_y - 1) if count_y > 1 else 0
    step_z = len_z / (count_z - 1) if count_z > 1 else 0
    
    start_x = -len_x / 2.0 if count_x > 1 else 0
    start_y = -len_y / 2.0 if count_y > 1 else 0
    start_z = -len_z / 2.0 if count_z > 1 else 0
    
    for z in range(count_z):
        for y in range(count_y):
            for x in range(count_x):
                pos = (start_x + x * step_x, start_y + y * step_y, start_z + z * step_z)
                bm.verts.new(pos)
                
    mesh_out = bpy.data.meshes.new(props.sphere_obj_name + "_Points")
    bm.to_mesh(mesh_out)
    bm.free()
    
    return mesh_out

def setup_sphere_material(obj, props):
    if not obj.data.materials:
        obj.data.materials.append(bpy.data.materials.new(name=f"{PREFIX_VAL}_Mat_Spheres"))
    mat = obj.data.materials[0]
    
    # ソリッドモード用のベースカラー
    mat.diffuse_color = props.sphere_color
    mat.use_nodes = True
    
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()
    
    out_node = nodes.new('ShaderNodeOutputMaterial')
    out_node.location = (300, 0)
    
    bsdf = nodes.new('ShaderNodeBsdfPrincipled')
    bsdf.location = (0, 0)
    links.new(bsdf.outputs[0], out_node.inputs[0])
    
    # Blender 5.x 共通透過設定
    if 'Alpha' in bsdf.inputs:
        bsdf.inputs['Alpha'].default_value = props.sphere_color[3]
        
    if props.sphere_use_random_color:
        obj_info = nodes.new('ShaderNodeObjectInfo')
        obj_info.location = (-600, 0)
        
        white_noise = nodes.new('ShaderNodeTexWhiteNoise')
        white_noise.location = (-400, 0)
        links.new(obj_info.outputs['Location'], white_noise.inputs['Vector'])
        
        # 虹色生成のためのColorRamp設定
        color_ramp = nodes.new('ShaderNodeValToRGB')
        color_ramp.location = (-200, 0)
        color_ramp.color_ramp.color_mode = 'HSV'
        color_ramp.color_ramp.hue_interpolation = 'CW' # 時計回りで色相を一周
        
        elements = color_ramp.color_ramp.elements
        elements[0].position = 0.0
        elements[0].color = (1.0, 0.0, 0.0, 1.0) # 赤
        elements[-1].position = 1.0
        elements[-1].color = (1.0, 0.0, 0.0, 1.0) # 赤(1周して同じ色に戻る)
        
        links.new(white_noise.outputs['Value'], color_ramp.inputs['Fac'])
        links.new(color_ramp.outputs['Color'], bsdf.inputs['Base Color'])
    else:
        if 'Base Color' in bsdf.inputs:
            bsdf.inputs['Base Color'].default_value = props.sphere_color
            
    return mat

def setup_sphere_instancing(obj, props):
    mod_name = "Sphere_Instancer"
    mod = obj.modifiers.get(mod_name)
    if not mod:
        mod = obj.modifiers.new(mod_name, 'NODES')
        
    if not mod.node_group:
        node_group = bpy.data.node_groups.new(mod_name + "_Tree", 'GeometryNodeTree')
        mod.node_group = node_group
        
        # Blender 5.x専用 NodeGroup API
        node_group.interface.new_socket(name="Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
        node_group.interface.new_socket(name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
            
        nodes = node_group.nodes
        links = node_group.links
        
        in_node = nodes.new('NodeGroupInput')
        out_node = nodes.new('NodeGroupOutput')
        
        sphere_node = nodes.new('GeometryNodeMeshIcoSphere')
        sphere_node.inputs['Subdivisions'].default_value = 2
        
        mat_node = nodes.new('GeometryNodeSetMaterial')
        inst_node = nodes.new('GeometryNodeInstanceOnPoints')
        
        # 確実に色が反映されるよう、プリミティブ生成直後にマテリアルを設定
        links.new(in_node.outputs[0], inst_node.inputs['Points'])
        links.new(sphere_node.outputs['Mesh'], mat_node.inputs['Geometry'])
        links.new(mat_node.outputs['Geometry'], inst_node.inputs['Instance'])
        links.new(inst_node.outputs[0], out_node.inputs[0])
        
    nodes = mod.node_group.nodes
    
    sphere_node = next((n for n in nodes if n.type == 'MESH_ICO_SPHERE'), None)
    if sphere_node:
        sphere_node.inputs['Radius'].default_value = props.sphere_size
        
    mat = setup_sphere_material(obj, props)
    mat_node = next((n for n in nodes if n.type == 'SET_MATERIAL'), None)
    if mat_node:
        try:
            mat_node.inputs['Material'].default_value = mat
        except:
            for inp in mat_node.inputs:
                if inp.type == 'MATERIAL':
                    inp.default_value = mat
                    break

def update_spheres_live(self, context):
    props = getattr(context.scene, f"{PREFIX}_props")
    for obj in context.scene.objects:
        if obj.name.startswith(props.sphere_obj_name) and obj.type == 'MESH':
            obj.location = props.sphere_pos
            obj.rotation_euler = [math.radians(a) for a in props.sphere_rot]
            
            new_mesh = create_spheres_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            setup_sphere_instancing(obj, props)

# ===================================
# 共通クラス群
# ===================================
class OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    def execute(self, context):
        if self.url: webbrowser.open(self.url)
        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'}

def get_rv3d(context):
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            return a.spaces.active.region_3d
    return None

class OT_reset_view(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_view"
    bl_label = "選択物を中央にする"
    def execute(self, context):
        rv3d = get_rv3d(context)
        if rv3d and context.active_object:
            rv3d.view_location = context.active_object.location
        return {'FINISHED'}

class OT_set_grid_scale(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_set_grid_scale"
    bl_label = "グリッドスケール設定"
    scale_value: bpy.props.FloatProperty()
    
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.overlay.grid_scale = self.scale_value
        return {'FINISHED'}

# ===================================
# アドオン固有オペレーター (骨格立方体)
# ===================================
class OT_create_skeleton(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_skeleton"
    bl_label = "骨格を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_advanced_geometry(context)
        obj = bpy.data.objects.new(f"{props.obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_skeleton_live(self, context)
        return {'FINISHED'}

class OT_detach_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_object"
    bl_label = "アドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.obj_name):
            obj.name = f"Fixed_Dice_{random.randint(100,999)}"
            self.report({'INFO'}, "独立させました")
        return {'FINISHED'}

class OT_reset_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_pos"
    bl_label = "位置を一括リセット"
    bl_description = "位置(Pos)を (0, 0, 0) にリセットします"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_rot"
    bl_label = "回転を一括リセット"
    bl_description = "回転(Rot)を (0, 0, 0) にリセットします"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.rot = (0.0, 0.0, 0.0)
        return {'FINISHED'}

# ===================================
# アドオン固有オペレーター (球体配置用)
# ===================================
class OT_create_spheres(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_create_spheres"
    bl_label = "等間隔球体を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        mesh_data = create_spheres_geometry(context)
        obj = bpy.data.objects.new(f"{props.sphere_obj_name}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(props.col_name) or bpy.data.collections.new(props.col_name)
        if main_col.name not in context.scene.collection.children:
            context.scene.collection.children.link(main_col)
        sub_col = bpy.data.collections.get(props.sub_col_name) or bpy.data.collections.new(props.sub_col_name)
        if sub_col.name not in main_col.children:
            main_col.children.link(sub_col)
        sub_col.objects.link(obj)
        
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_spheres_live(self, context)
        return {'FINISHED'}

class OT_detach_sphere_object(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_detach_sphere_object"
    bl_label = "球体をアドオンから切り離す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        obj = context.active_object
        if obj and obj.name.startswith(props.sphere_obj_name):
            bpy.ops.object.modifier_apply(modifier="Sphere_Instancer")
            obj.name = f"Fixed_Spheres_{random.randint(100,999)}"
            self.report({'INFO'}, "モディファイアを適用し、球体を独立させました")
        return {'FINISHED'}

class OT_reset_sphere_pos(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_pos"
    bl_label = "球体位置を一括リセット"
    bl_description = "位置(Pos)を (0, 0, 0) にリセットします"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_pos = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class OT_reset_sphere_rot(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_reset_sphere_rot"
    bl_label = "球体回転を一括リセット"
    bl_description = "回転(Rot)を (0, 0, 0) にリセットします"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.sphere_rot = (0.0, 0.0, 0.0)
        return {'FINISHED'}

# ===================================
# 設定コピーオペレーター
# ===================================
class OT_copy_settings(bpy.types.Operator):
    bl_idname = f"object.{PREFIX}_copy_settings"
    bl_label = "現在の最新値をコピー"
    bl_description = "コードに直接貼り付けられる形式でクリップボードにコピーします"
    
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        text = (
            "# ===================================\n"
            "# 定数 (骨格立方体固有)\n"
            "# ===================================\n"
            f'INIT_COL_NAME = "{props.col_name}"\n'
            f'INIT_SUB_COL_NAME = "{props.sub_col_name}"\n'
            f'INIT_OBJ_NAME = "{props.obj_name}"\n\n'
            f"INIT_SHAPE = '{props.shape}'\n"
            f"INIT_SIZE = {props.size:.4f}\n"
            f"INIT_THICKNESS = {props.thick:.4f}\n"
            f"INIT_RES = {props.res}\n"
            f"INIT_POS = ({props.pos[0]:.4f}, {props.pos[1]:.4f}, {props.pos[2]:.4f})\n"
            f"INIT_ROT = ({props.rot[0]:.4f}, {props.rot[1]:.4f}, {props.rot[2]:.4f})\n"
            f"INIT_SHOW_FACES = {props.show_faces}\n"
            f"INIT_CLR_FRAME = ({props.color_frame[0]:.4f}, {props.color_frame[1]:.4f}, {props.color_frame[2]:.4f}, {props.color_frame[3]:.4f})\n"
            f"INIT_CLR_FACE = ({props.color_face[0]:.4f}, {props.color_face[1]:.4f}, {props.color_face[2]:.4f}, {props.color_face[3]:.4f})\n"
            
            f"\n# ===================================\n"
            f"# 定数 (球体配置固有)\n"
            f"# ===================================\n"
            f"INIT_SPHERE_OBJ_NAME = '{props.sphere_obj_name}'\n"
            f"INIT_SPHERE_PATTERN = '{props.sphere_pattern}'\n"
            f"INIT_SPHERE_COUNT = ({props.sphere_count[0]}, {props.sphere_count[1]}, {props.sphere_count[2]})\n"
            f"INIT_SPHERE_LENGTH = ({props.sphere_length[0]:.4f}, {props.sphere_length[1]:.4f}, {props.sphere_length[2]:.4f})\n"
            f"INIT_SPHERE_SIZE = {props.sphere_size:.4f}\n"
            f"INIT_SPHERE_POS = ({props.sphere_pos[0]:.4f}, {props.sphere_pos[1]:.4f}, {props.sphere_pos[2]:.4f})\n"
            f"INIT_SPHERE_ROT = ({props.sphere_rot[0]:.4f}, {props.sphere_rot[1]:.4f}, {props.sphere_rot[2]:.4f})\n"
            f"INIT_SPHERE_COLOR = ({props.sphere_color[0]:.4f}, {props.sphere_color[1]:.4f}, {props.sphere_color[2]:.4f}, {props.sphere_color[3]:.4f})\n"
            f"INIT_SPHERE_RANDOM_COLOR = {props.sphere_use_random_color}\n"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "定数定義コードをクリップボードにコピーしました")
        return {'FINISHED'}

# ===================================
# プロパティグループ
# ===================================
class Properties_Addon(bpy.types.PropertyGroup):
    col_name: bpy.props.StringProperty(name="コレクション", default=INIT_COL_NAME)
    sub_col_name: bpy.props.StringProperty(name="サブコレクション", default=INIT_SUB_COL_NAME)
    
    # 骨格用
    obj_name: bpy.props.StringProperty(name="Object名", default=INIT_OBJ_NAME)
    shape: bpy.props.EnumProperty(
        items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], 
        name="Shape", default=INIT_SHAPE, update=update_skeleton_live
    )
    size: bpy.props.FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
    thick: bpy.props.FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
    res: bpy.props.IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
    pos: bpy.props.FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
    rot: bpy.props.FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
    
    show_faces: bpy.props.BoolProperty(name="Show Faces", default=INIT_SHOW_FACES, update=update_skeleton_live)
    color_frame: bpy.props.FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
    color_face: bpy.props.FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)

    # 球体用
    sphere_obj_name: bpy.props.StringProperty(name="Sphere Object名", default=INIT_SPHERE_OBJ_NAME)
    sphere_pattern: bpy.props.EnumProperty(
        items=[('PLANE', "指定平面", ""), ('CUBE', "指定立方体空間", "")], 
        name="配置パターン", default=INIT_SPHERE_PATTERN, update=update_spheres_live
    )
    sphere_count: bpy.props.IntVectorProperty(name="配置数 (X,Y,Z)", default=INIT_SPHERE_COUNT, min=1, max=500, update=update_spheres_live)
    sphere_length: bpy.props.FloatVectorProperty(name="全体の長さ (X,Y,Z)", default=INIT_SPHERE_LENGTH, min=0.0, update=update_spheres_live)
    sphere_size: bpy.props.FloatProperty(name="球体サイズ", default=INIT_SPHERE_SIZE, min=0.01, update=update_spheres_live)
    sphere_pos: bpy.props.FloatVectorProperty(name="Sphere Pos", default=INIT_SPHERE_POS, update=update_spheres_live)
    sphere_rot: bpy.props.FloatVectorProperty(name="Sphere Rot", default=INIT_SPHERE_ROT, update=update_spheres_live)
    sphere_color: bpy.props.FloatVectorProperty(name="色/透明度", subtype='COLOR', size=4, default=INIT_SPHERE_COLOR, min=0.0, max=1.0, update=update_spheres_live)
    sphere_use_random_color: bpy.props.BoolProperty(name="色をランダムにする", default=INIT_SPHERE_RANDOM_COLOR, update=update_spheres_live)

# ===================================
# パネル構成
# ===================================
class PT_main(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
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択物を中央にする", icon='CENTER_ONLY')

class PT_grid(bpy.types.Panel):
    bl_label = "Grid 設定"
    bl_idname = f"{PREFIX.upper()}_PT_grid"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    
    def draw(self, context):
        layout = self.layout
        
        layout.label(text="グリッドスケール:")
        row = layout.row(align=True)
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="1").scale_value = 1.0
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="10").scale_value = 10.0
        row.operator(f"view3d.{PREFIX}_set_grid_scale", text="100").scale_value = 100.0
        
        layout.separator()
        if context.space_data and context.space_data.type == 'VIEW_3D':
            layout.prop(context.space_data.overlay, "grid_scale", text="任意の数値")

class PT_creation(bpy.types.Panel):
    bl_label = "骨格立方体 調整"
    bl_idname = f"{PREFIX.upper()}_PT_creation"
    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_skeleton", icon='MESH_CUBE')
        layout.separator()
        
        col = layout.column(align=True)
        col.prop(props, "col_name")
        col.prop(props, "sub_col_name")
        col.prop(props, "obj_name")
        
        layout.separator()
        col = layout.column(align=True)
        col.prop(props, "shape", text="基本形状")
        
        layout.separator()
        
        col = layout.column(align=True)
        row_pos_head = col.row(align=True)
        row_pos_head.label(text="位置:")
        row_pos_head.operator(f"object.{PREFIX}_reset_pos", text="", icon='FILE_REFRESH')
        col.prop(props, "pos", index=0, text="X")
        col.prop(props, "pos", index=1, text="Y")
        col.prop(props, "pos", index=2, text="Z")
        
        layout.separator()
        
        col = layout.column(align=True)
        row_rot_head = col.row(align=True)
        row_rot_head.label(text="回転:")
        row_rot_head.operator(f"object.{PREFIX}_reset_rot", text="", icon='FILE_REFRESH')
        col.prop(props, "rot", index=0, text="X")
        col.prop(props, "rot", index=1, text="Y")
        col.prop(props, "rot", index=2, text="Z")
        
        layout.separator()
        
        col = layout.column(align=True)
        col.prop(props, "size", text="サイズ")
        col.prop(props, "thick", text="骨格の太さ")
        col.prop(props, "res", text="分割数")
        
        layout.separator()
        layout.prop(props, "show_faces", text="表面を表示する", icon='FACESEL')
        
        row = layout.row()
        row.prop(props, "color_frame", text="骨格の色")
        if props.show_faces:
            row.prop(props, "color_face", text="表面の色")
        
        layout.separator()
        if obj and obj.name.startswith(props.obj_name):
            layout.operator(f"object.{PREFIX}_detach_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した骨格を選択して調整", icon='INFO')
            
        layout.separator()
        layout.operator(f"object.{PREFIX}_copy_settings", icon='COPYDOWN')

class PT_spheres(bpy.types.Panel):
    bl_label = "等間隔 球体配置 調整"
    bl_idname = f"{PREFIX.upper()}_PT_spheres"
    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_spheres", icon='MESH_UVSPHERE')
        layout.separator()
        
        col = layout.column(align=True)
        col.prop(props, "sphere_obj_name")
        col.prop(props, "sphere_pattern")
        
        layout.separator()
        
        # 位置
        col = layout.column(align=True)
        row_pos_head = col.row(align=True)
        row_pos_head.label(text="全体位置:")
        row_pos_head.operator(f"object.{PREFIX}_reset_sphere_pos", text="", icon='FILE_REFRESH')
        col.prop(props, "sphere_pos", index=0, text="X")
        col.prop(props, "sphere_pos", index=1, text="Y")
        col.prop(props, "sphere_pos", index=2, text="Z")
        
        layout.separator()
        
        # 回転
        col = layout.column(align=True)
        row_rot_head = col.row(align=True)
        row_rot_head.label(text="全体回転:")
        row_rot_head.operator(f"object.{PREFIX}_reset_sphere_rot", text="", icon='FILE_REFRESH')
        col.prop(props, "sphere_rot", index=0, text="X")
        col.prop(props, "sphere_rot", index=1, text="Y")
        col.prop(props, "sphere_rot", index=2, text="Z")
        
        layout.separator()
        
        # 長さ指定
        col = layout.column(align=True)
        col.label(text="全体の長さ (空間サイズ):")
        col.prop(props, "sphere_length", index=0, text="X")
        col.prop(props, "sphere_length", index=1, text="Y")
        if props.sphere_pattern == 'CUBE':
            col.prop(props, "sphere_length", index=2, text="Z")
            
        col.separator()
        
        # 配置数指定
        col.label(text="配置数 (何個配置するか):")
        col.prop(props, "sphere_count", index=0, text="X")
        col.prop(props, "sphere_count", index=1, text="Y")
        if props.sphere_pattern == 'CUBE':
            col.prop(props, "sphere_count", index=2, text="Z")
            
        layout.separator()
        col = layout.column(align=True)
        col.prop(props, "sphere_size", text="球体の大きさ")
        col.prop(props, "sphere_use_random_color", text="色をランダムにする", icon='SHADING_TEXTURE')
        col.prop(props, "sphere_color", text="単一色/透明度")

        layout.separator()
        if obj and obj.name.startswith(props.sphere_obj_name):
            layout.operator(f"object.{PREFIX}_detach_sphere_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した球体を選択して調整", icon='INFO')

class PT_links(bpy.types.Panel):
    bl_label = "Links"
    bl_idname = f"{PREFIX.upper()}_PT_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        if LINK_NAME_1 and LINK_URL_1: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_1, icon='URL').url = LINK_URL_1
        if LINK_NAME_2 and LINK_URL_2: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_URL_2, icon='URL').url = LINK_URL_2
        if LINK_NAME_3 and LINK_URL_3: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_3, icon='URL').url = LINK_URL_3
        if LINK_NAME_4 and LINK_URL_4: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_4, icon='URL').url = LINK_URL_4
        if LINK_NAME_5 and LINK_URL_5: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_5, icon='URL').url = LINK_URL_5
        if LINK_NAME_6 and LINK_URL_6: layout.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_6, icon='URL').url = LINK_URL_6

class PT_remove(bpy.types.Panel):
    bl_label = "Addon Remove"
    bl_idname = f"{PREFIX.upper()}_PT_remove"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# register() / unregister()
# ===================================
classes = [
    OT_open_url,
    OT_remove_addon,
    Properties_Addon,
    OT_reset_view,
    OT_set_grid_scale,
    
    # 骨格用
    OT_create_skeleton,
    OT_detach_object,
    OT_reset_pos,
    OT_reset_rot,
    
    # 球体用
    OT_create_spheres,
    OT_detach_sphere_object,
    OT_reset_sphere_pos,
    OT_reset_sphere_rot,
    
    OT_copy_settings,
    
    # パネル
    PT_main,
    PT_grid,
    PT_creation,
    PT_spheres,
    PT_links,
    PT_remove
]

def register():
    for c in classes:
        c.__name__ = f"{PREFIX_VAL}_{c.__name__}"
        bpy.utils.register_class(c)
        
    setattr(bpy.types.Scene, f"{PREFIX}_props", bpy.props.PointerProperty(type=Properties_Addon))
    
    def apply_initial_settings():
        # アドオン登録後、ビューポートをマテリアルプレビューに自動変更
        if bpy.context.window_manager:
            for window in bpy.context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D':
                                space.show_region_ui = True
                                space.shading.type = 'MATERIAL'
        return None

    bpy.app.timers.register(apply_initial_settings, first_interval=0.1)

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

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