# Code Created : 2024_0525_1400

bl_info = {
    "name": "Skeleton Cube Generator",
    "author": "Your Name",
    "version": (1, 1, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar",
    "description": "Generates a skeletal cube from integrated cylinders and spheres.",
    "category": "3D View",
}

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

# =========================================================
# 共通定義
# =========================================================
ADDON_TITLE = "Skeleton Cube"
PREFIX_VAL = "SKELETON_CUBE"
PREFIX = PREFIX_VAL.lower()
ADDON_CATEGORY = "Skeleton Cube"

# =========================================================
# ユーザー設定パラメータ (初期値)
# =========================================================
DEFAULT_CENTER_X = 0.0
DEFAULT_CENTER_Y = 0.0
DEFAULT_CENTER_Z = 0.0

DEFAULT_ROTATION_X = 0.0
DEFAULT_ROTATION_Y = 0.0
DEFAULT_ROTATION_Z = 0.0

DEFAULT_EDGE_LENGTH = 2.0

# 円柱パラメータ
DEFAULT_CYLINDER_RADIUS = 0.05
DEFAULT_COLOR_R = 0.2
DEFAULT_COLOR_G = 0.6
DEFAULT_COLOR_B = 1.0
DEFAULT_ALPHA = 1.0

# 球体パラメータ
DEFAULT_SHOW_SPHERES = True
DEFAULT_SPHERE_RADIUS = 0.08
DEFAULT_SPH_COLOR_R = 1.0
DEFAULT_SPH_COLOR_G = 0.3
DEFAULT_SPH_COLOR_B = 0.3
DEFAULT_SPH_ALPHA = 1.0

# =========================================================
# リンク定義
# =========================================================
LINK_NAME_2 = "コンテナ builder"
LINK_URL_2 = "<https://note.com/zionad2017/n/ndb6dbdbc844a>"

LINK_NAME_3 = "posfie"
LINK_URL_3 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"

LINK_NAME_4 = "zionadchat"
LINK_URL_4 = "<https://x.com/zionadchat>"

# =========================================================
# マテリアル設定補助関数
# =========================================================
def setup_material(mat_name, color, alpha):
    mat = bpy.data.materials.get(mat_name)
    if not mat:
        mat = bpy.data.materials.new(mat_name)
        mat.use_nodes = True
        
    mat.use_backface_culling = False
    mat.diffuse_color = (color[0], color[1], color[2], alpha)
    
    # 透過設定 (Eevee / Viewport互換)
    if hasattr(mat, "blend_method"):
        mat.blend_method = 'BLEND'
    if hasattr(mat, "shadow_method"):
        mat.shadow_method = 'NONE'

    nodes = mat.node_tree.nodes
    bsdf = nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs['Base Color'].default_value = (color[0], color[1], color[2], alpha)
        if 'Alpha' in bsdf.inputs:
            bsdf.inputs['Alpha'].default_value = alpha
            
    return mat

# =========================================================
# メイン生成・更新ロジック
# =========================================================
def update_skeleton_cube(context):
    props = context.scene.skeleton_cube_props
    
    obj_name = PREFIX_VAL
    mesh_name = f"{PREFIX_VAL}_MESH"
    
    # 1. メッシュの取得・作成
    mesh = bpy.data.meshes.get(mesh_name)
    if not mesh:
        mesh = bpy.data.meshes.new(mesh_name)
    else:
        mesh.clear_geometry()
        
    # 2. オブジェクトの取得・作成
    obj = bpy.data.objects.get(obj_name)
    if not obj:
        obj = bpy.data.objects.new(obj_name, mesh)
        context.collection.objects.link(obj)
        
    # 3. BMeshで 1 Mesh に 円柱 と 球体 を構築
    bm = bmesh.new()
    
    L = props.edge_length
    h = L / 2.0
    r_cyl = props.cylinder_radius
    
    # 立方体の8頂点 (ローカル座標)
    verts_pos = [
        Vector((-h, -h, -h)), Vector((+h, -h, -h)),
        Vector((+h, +h, -h)), Vector((-h, +h, -h)),
        Vector((-h, -h, +h)), Vector((+h, -h, +h)),
        Vector((+h, +h, +h)), Vector((-h, +h, +h))
    ]
    v1, v2, v3, v4, v5, v6, v7, v8 = verts_pos
    
    # 12本の辺の接続ペア
    edges = [
        (v1, v2), (v2, v3), (v3, v4), (v4, v1), # 下側
        (v5, v6), (v6, v7), (v7, v8), (v8, v5), # 上側
        (v1, v5), (v2, v6), (v3, v7), (v4, v8)  # 縦方向
    ]
    
    # --- 円柱の生成 (マテリアルインデックス 0) ---
    for start, end in edges:
        center = (start + end) / 2.0
        direction = (end - start).normalized()
        length = (start - end).length
        
        rot_quat = direction.to_track_quat('Z', 'Y')
        matrix = Matrix.Translation(center) @ rot_quat.to_matrix().to_4x4()
        
        bmesh.ops.create_cone(
            bm, cap_ends=True, cap_tris=False, segments=16,
            radius1=r_cyl, radius2=r_cyl, depth=length, matrix=matrix
        )
        
    # --- 球体の生成 (マテリアルインデックス 1) ---
    if props.show_spheres:
        r_sph = props.sphere_radius
        for v in verts_pos:
            start_face_count = len(bm.faces)
            bmesh.ops.create_uvsphere(
                bm, u_segments=16, v_segments=16, radius=r_sph,
                matrix=Matrix.Translation(v)
            )
            # 生成された球体のFaceにマテリアルインデックス1を割り当て
            for i in range(start_face_count, len(bm.faces)):
                bm.faces[i].material_index = 1
        
    # BMeshのデータをMeshに書き込み
    bm.to_mesh(mesh)
    bm.free()
    mesh.update()
    
    # 4. オブジェクトのTransformを更新
    obj.location = props.loc
    obj.rotation_euler = props.rot
    
    # 5. マテリアルの設定と割り当て
    mat_cyl = setup_material(f"{PREFIX_VAL}_CYL_MAT", props.color, props.alpha)
    mat_sph = setup_material(f"{PREFIX_VAL}_SPH_MAT", props.sphere_color, props.sphere_alpha)
    
    obj.data.materials.clear()
    obj.data.materials.append(mat_cyl) # Index 0: 円柱
    obj.data.materials.append(mat_sph) # Index 1: 球体
        
    # 6. ビューポートの強制更新
    for area in context.screen.areas:
        if area.type == 'VIEW_3D':
            area.tag_redraw()

# =========================================================
# プロパティ・コールバック
# =========================================================
def on_property_update(self, context):
    if self.is_created:
        update_skeleton_cube(context)

class SkeletonCubeProperties(bpy.types.PropertyGroup):
    is_created: bpy.props.BoolProperty(default=False)
    
    # 基本・円柱プロパティ
    loc: bpy.props.FloatVectorProperty(
        name="位置", size=3, default=(DEFAULT_CENTER_X, DEFAULT_CENTER_Y, DEFAULT_CENTER_Z),
        update=on_property_update
    )
    rot: bpy.props.FloatVectorProperty(
        name="回転", size=3, subtype='EULER',
        default=(math.radians(DEFAULT_ROTATION_X), math.radians(DEFAULT_ROTATION_Y), math.radians(DEFAULT_ROTATION_Z)),
        update=on_property_update
    )
    edge_length: bpy.props.FloatProperty(
        name="立方体1辺の長さ", default=DEFAULT_EDGE_LENGTH, min=0.001,
        update=on_property_update
    )
    cylinder_radius: bpy.props.FloatProperty(
        name="円柱半径", default=DEFAULT_CYLINDER_RADIUS, min=0.001,
        update=on_property_update
    )
    color: bpy.props.FloatVectorProperty(
        name="色", subtype='COLOR', size=3, min=0.0, max=1.0,
        default=(DEFAULT_COLOR_R, DEFAULT_COLOR_G, DEFAULT_COLOR_B),
        update=on_property_update
    )
    alpha: bpy.props.FloatProperty(
        name="透明度", default=DEFAULT_ALPHA, min=0.0, max=1.0,
        update=on_property_update
    )
    
    # 球体プロパティ
    show_spheres: bpy.props.BoolProperty(
        name="頂点に球体を表示", default=DEFAULT_SHOW_SPHERES,
        update=on_property_update
    )
    sphere_radius: bpy.props.FloatProperty(
        name="球体半径", default=DEFAULT_SPHERE_RADIUS, min=0.001,
        update=on_property_update
    )
    sphere_color: bpy.props.FloatVectorProperty(
        name="球体色", subtype='COLOR', size=3, min=0.0, max=1.0,
        default=(DEFAULT_SPH_COLOR_R, DEFAULT_SPH_COLOR_G, DEFAULT_SPH_COLOR_B),
        update=on_property_update
    )
    sphere_alpha: bpy.props.FloatProperty(
        name="球体透明度", default=DEFAULT_SPH_ALPHA, min=0.0, max=1.0,
        update=on_property_update
    )

# =========================================================
# Operators
# =========================================================
class SKELETON_CUBE_OT_Create(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create"
    bl_label = "骨格立方体を作成"
    bl_description = "現在の設定で骨格立方体を生成・更新します"

    def execute(self, context):
        props = context.scene.skeleton_cube_props
        props.is_created = True
        update_skeleton_cube(context)
        return {'FINISHED'}

class SKELETON_CUBE_OT_OpenLink(bpy.types.Operator):
    bl_idname = f"{PREFIX}.open_link"
    bl_label = "Open Link"
    url: bpy.props.StringProperty()
    def execute(self, context):
        if self.url:
            webbrowser.open(self.url)
        return {'FINISHED'}

def unregister_delayed():
    try:
        unregister()
    except Exception as e:
        print(f"Skeleton Cube Unregister Error: {e}")
    return None # Timer解除

class SKELETON_CUBE_OT_RemoveAddon(bpy.types.Operator):
    bl_idname = f"{PREFIX}.remove_addon"
    bl_label = "アドオンから切り離す"
    bl_description = "現在の状態を確定し、アドオン機能を解除します。Objectは削除されません。"

    def execute(self, context):
        bpy.app.timers.register(unregister_delayed, first_interval=0.01)
        return {'FINISHED'}

# =========================================================
# Panels
# =========================================================
class SKELETON_CUBE_PT_Main(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.skeleton_cube_props
        
        col = layout.column(align=True)
        col.label(text="中心位置")
        col.prop(props, "loc", index=0, text="X")
        col.prop(props, "loc", index=1, text="Y")
        col.prop(props, "loc", index=2, text="Z")
        
        col.separator()
        col.label(text="回転")
        col.prop(props, "rot", index=0, text="X")
        col.prop(props, "rot", index=1, text="Y")
        col.prop(props, "rot", index=2, text="Z")
        
        col.separator()
        col.prop(props, "edge_length", text="立方体1辺")
        col.prop(props, "cylinder_radius", text="円柱半径")
        
        col.separator()
        col.prop(props, "color", text="円柱の色")
        col.prop(props, "alpha", text="円柱の透明度")
        
        col.separator()
        layout.operator(SKELETON_CUBE_OT_Create.bl_idname, text="骨格立方体を作成", icon='CUBE')

class SKELETON_CUBE_PT_Sphere(bpy.types.Panel):
    bl_label = "頂点球体の設定"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.skeleton_cube_props
        
        col = layout.column(align=True)
        col.prop(props, "show_spheres", text="球体を生成する")
        
        # チェックがオフならグレーアウト
        sub = col.column(align=True)
        sub.enabled = props.show_spheres
        sub.prop(props, "sphere_radius", text="球体の半径")
        sub.prop(props, "sphere_color", text="球体の色")
        sub.prop(props, "sphere_alpha", text="球体の透明度")

class SKELETON_CUBE_PT_Links(bpy.types.Panel):
    bl_label = "リンク"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text="カスタムリンク (未設定)").url = ""
        layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_2).url = LINK_URL_2
        layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_3).url = LINK_URL_3
        layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_4).url = LINK_URL_4

class SKELETON_CUBE_PT_Remove(bpy.types.Panel):
    bl_label = "アドオン削除"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        layout.label(text="このアドオンを消去します")
        layout.operator(SKELETON_CUBE_OT_RemoveAddon.bl_idname, text="アドオンから切り離す", icon='CANCEL')

# =========================================================
# Register / Unregister / 起動時設定
# =========================================================
classes = (
    SkeletonCubeProperties,
    SKELETON_CUBE_OT_Create,
    SKELETON_CUBE_OT_OpenLink,
    SKELETON_CUBE_OT_RemoveAddon,
    SKELETON_CUBE_PT_Main,
    SKELETON_CUBE_PT_Sphere,
    SKELETON_CUBE_PT_Links,
    SKELETON_CUBE_PT_Remove,
)

def setup_viewport_on_load():
    """起動時にマテリアルプレビューに変更&サイドバーを開く"""
    context = bpy.context
    if context and context.window_manager:
        for window in 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 # 一度だけ実行してタイマー解除

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.skeleton_cube_props = bpy.props.PointerProperty(type=SkeletonCubeProperties)
    
    # UI構築完了後に安全にビューポート設定を行うためタイマー登録
    bpy.app.timers.register(setup_viewport_on_load, first_interval=0.1)

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

if __name__ == "__main__":
    register()