bl_info = {
    "name": "zionad_ [ 骨格立方体 ] & Sine Wave",
    "author": "zionad_ & ChatGPT",
    "version": (2, 5, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (N Panel) > 骨格立方体",
    "description": "骨格立方体生成ツールと進行波・時間軌跡のリアルタイム描画ツールの統合アドオン",
    "category": "3D View",
}

# 2026年07月14日 統合版(作成ボタン・パネル構造統一)

import bpy
import math
import webbrowser
import random
from bpy.props import (
    FloatProperty, FloatVectorProperty, StringProperty, 
    IntProperty, EnumProperty, BoolProperty, PointerProperty
)
from bpy.types import Operator, Panel, PropertyGroup

# ==========================================
# 【初期設定・手動変更エリア】 (骨格立方体)
# ==========================================
LINK_NAME_1 = "基本アドオン設定 20260711b"
LINK_URL_1 = "<https://app.notion.com/p/20260711-39af5dacaf438027811dcd877db7c630>"

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 = "blender コンテナ 20260713"
LINK_URL_4 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"

LINK_NAME_5 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"

OBJ_NAME_BASE = "Skeleton_Dice"
COL_NAME = "Dice_Collection"
SUB_COL_NAME = "Skeleton_Cubes"

# 初期数値
INIT_SIZE = 60.0
INIT_THICKNESS = 1.0
INIT_RES = 32           
INIT_POS = (0.0, 0.0, 0.0)
INIT_ROT = (0.0, 0.0, 0.0)
INIT_CLR_FRAME = (0.1, 0.6, 1.0, 1.0) 
INIT_CLR_FACE = (1.0, 1.0, 1.0, 0.3)  

# ==========================================
# 【初期設定・手動変更エリア】 (Sine Wave)
# ==========================================
SINE_OBJ_PREFIX = "SINE_WAVE_"

SINE_AMPLITUDE_DEFAULT = 2.0
SINE_FREQUENCY_DEFAULT = 1.0
SINE_LENGTH_DEFAULT = 10.0
SINE_TIME_MAX_DEFAULT = 10.0
SINE_TIME_DEFAULT = 1.0
SINE_DELAY_DEFAULT = 0.0
SINE_RESOLUTION_DEFAULT = 128
SINE_WAVE_SPEED_X_DEFAULT = 1.0

SINE_LOC_X_DEFAULT = 0.0
SINE_LOC_Y_DEFAULT = 0.0
SINE_LOC_Z_DEFAULT = 0.0

INIT_COLOR  = (0.5, 0.5, 0.5, 0.8)
MAIN_COLOR  = (0.2, 0.6, 1.0, 1.0)
TRAJ1_COLOR = (1.0, 0.8, 0.2, 1.0) 
TRAJ2_COLOR = (1.0, 0.4, 0.8, 1.0) 
TX_COLOR    = (0.2, 1.0, 0.5, 1.0)

# ==========================================
# 【形状生成コア・ロジック】 (骨格立方体)
# ==========================================
def create_advanced_geometry(context):
    scene = context.scene
    import bmesh
    
    bm_base = bmesh.new()
    if scene.dice_shape == 'CUBE':
        bmesh.ops.create_cube(bm_base, size=scene.dice_size)
    else: 
        bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=scene.dice_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 = scene.dice_thick / 2
    curve_data.bevel_resolution = scene.dice_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 scene.dice_show_faces:
        bm_face = bmesh.new()
        if scene.dice_shape == 'CUBE':
            bmesh.ops.create_cube(bm_face, size=scene.dice_size)
        else:
            bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=scene.dice_size/2)
        
        for face in bm_face.faces:
            face.material_index = 1
        bm_face.to_mesh(bpy.data.meshes.new("temp_face")) 
        bm_final.from_mesh(bpy.data.meshes.get("temp_face"))
        bpy.data.meshes.remove(bpy.data.meshes.get("temp_face"))
        bm_face.free()

    mesh_out = bpy.data.meshes.new(OBJ_NAME_BASE)
    bm_final.to_mesh(mesh_out)
    bm_final.free()
    bm_base.free()
    
    return mesh_out

def update_skeleton_live(self, context):
    scene = context.scene
    for obj in scene.objects:
        if obj.name.startswith(OBJ_NAME_BASE) and obj.type == 'MESH':
            obj.location = scene.dice_pos
            obj.rotation_euler = [math.radians(a) for a in scene.dice_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="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:
                bsdf_frame.inputs['Base Color'].default_value = scene.dice_color_frame
                if 'Alpha' in bsdf_frame.inputs: bsdf_frame.inputs['Alpha'].default_value = scene.dice_color_frame[3]
            mat_frame.blend_method = 'BLEND'

            if not obj.material_slots[1].material:
                obj.material_slots[1].material = bpy.data.materials.new(name="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:
                bsdf_face.inputs['Base Color'].default_value = scene.dice_color_face
                if 'Alpha' in bsdf_face.inputs: bsdf_face.inputs['Alpha'].default_value = scene.dice_color_face[3]
            mat_face.blend_method = 'BLEND'

# ==========================================
# 【形状生成コア・ロジック】 (Sine Wave)
# ==========================================
def clear_sine_wave_data():
    for obj in list(bpy.data.objects):
        if obj.name.startswith(SINE_OBJ_PREFIX):
            bpy.data.objects.remove(obj, do_unlink=True)
    for crv in list(bpy.data.curves):
        if crv.name.startswith(SINE_OBJ_PREFIX):
            bpy.data.curves.remove(crv)
    for mat in list(bpy.data.materials):
        if mat.name.startswith(SINE_OBJ_PREFIX):
            bpy.data.materials.remove(mat)

def is_sine_wave_exists():
    """サイン波がすでに生成されているかチェック"""
    for obj in bpy.data.objects:
        if obj.name.startswith(SINE_OBJ_PREFIX):
            return True
    return False

def calculate_z_displacement(x, t, props):
    f = props.frequency
    v = props.wave_speed_x
    
    if v == 0.0:
        phase = 2.0 * math.pi * f * (t - props.delay)
    else:
        if props.wave_direction == 'SPACE':
            phase = 2.0 * math.pi * f * (x / v - (t - props.delay))
        else:
            phase = 2.0 * math.pi * f * ((t - props.delay) - x / v)
            
    amp = props.amplitude
    if props.taper_x and props.length > 0.0:
        amp = props.amplitude * (x / props.length)
        
    return amp * math.sin(phase) + SINE_LOC_Z_DEFAULT

def create_curve_object(name, thickness, color, collection):
    curve_data = bpy.data.curves.new(name=name, type='CURVE')
    curve_data.dimensions = '3D'
    curve_data.bevel_depth = thickness
    curve_data.bevel_resolution = 4
    
    spline = curve_data.splines.new(type='POLY')
    obj = bpy.data.objects.new(name, curve_data)
    collection.objects.link(obj)
    
    mat = bpy.data.materials.new(name=f"{name}_Mat")
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs['Base Color'].default_value = color
        if len(color) == 4 and color[3] < 1.0:
            bsdf.inputs['Alpha'].default_value = color[3]
    obj.data.materials.append(mat)
    return spline

def generate_waves(context):
    props = context.scene.sine_wave_props
    clear_sine_wave_data()
    
    coll_name = "SineWave_Collection"
    if coll_name in bpy.data.collections:
        coll = bpy.data.collections[coll_name]
    else:
        coll = bpy.data.collections.new(coll_name)
        context.scene.collection.children.link(coll)

    if props.init_show:
        spline_init = create_curve_object(f"{SINE_OBJ_PREFIX}Init_t0", props.init_thickness, props.init_color, coll)
        spline_init.points.add(props.resolution - 1)
        for i in range(props.resolution):
            x_local = (i / (props.resolution - 1)) * props.length
            y = 0.0 + SINE_LOC_Y_DEFAULT
            z = calculate_z_displacement(x_local, 0.0, props)
            spline_init.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

    if props.main_show:
        spline_main = create_curve_object(f"{SINE_OBJ_PREFIX}Main", props.thickness, props.color_main, coll)
        spline_main.points.add(props.resolution - 1)
        for i in range(props.resolution):
            x_local = (i / (props.resolution - 1)) * props.length
            y = props.time_t + SINE_LOC_Y_DEFAULT
            z = calculate_z_displacement(x_local, props.time_t, props)
            spline_main.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

    if props.traj1_show and props.time_max > 0.0:
        spline_traj1 = create_curve_object(f"{SINE_OBJ_PREFIX}Trajectory1", props.traj1_thickness, props.traj1_color, coll)
        spline_traj1.points.add(props.resolution - 1)
        for i in range(props.resolution):
            tau = (i / (props.resolution - 1)) * props.time_max
            x_local = props.traj1_x
            y = tau + SINE_LOC_Y_DEFAULT
            z = calculate_z_displacement(x_local, tau, props)
            spline_traj1.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

    if props.traj2_show and props.time_max > 0.0:
        spline_traj2 = create_curve_object(f"{SINE_OBJ_PREFIX}Trajectory2", props.traj2_thickness, props.traj2_color, coll)
        spline_traj2.points.add(props.resolution - 1)
        for i in range(props.resolution):
            tau = (i / (props.resolution - 1)) * props.time_max
            x_local = props.traj2_x
            y = tau + SINE_LOC_Y_DEFAULT
            z = calculate_z_displacement(x_local, tau, props)
            spline_traj2.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

    if props.tx_show:
        spline_tx = create_curve_object(f"{SINE_OBJ_PREFIX}Wave_tx", props.tx_thickness, props.tx_color, coll)
        spline_tx.points.add(props.resolution - 1)
        for i in range(props.resolution):
            if props.tx_slope_a == 0.0:
                max_tx_length = props.length
            else:
                max_tx_length = min(props.length, props.time_max / props.tx_slope_a)

            x_local = (i / (props.resolution - 1)) * max_tx_length
            t_local = props.tx_slope_a * x_local
            y = t_local + SINE_LOC_Y_DEFAULT
            z = calculate_z_displacement(x_local, t_local, props)
            spline_tx.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

def update_waves_cb(self, context):
    """プロパティ変更時、すでに波が生成されていればリアルタイム更新する"""
    if context and hasattr(context.scene, 'sine_wave_props'):
        if is_sine_wave_exists():
            generate_waves(context)

# ==========================================
# 【プロパティ定義】 (Sine Wave)
# ==========================================
class SineWaveProperties(PropertyGroup):
    amplitude: FloatProperty(name="最大振幅 (Z軸)", default=SINE_AMPLITUDE_DEFAULT, update=update_waves_cb)
    frequency: FloatProperty(name="時間振動数 f (回/単位時間)", default=SINE_FREQUENCY_DEFAULT, update=update_waves_cb)
    
    length: FloatProperty(name="空間の長さ (X最大)", default=SINE_LENGTH_DEFAULT, update=update_waves_cb)
    time_max: FloatProperty(name="時間の長さ (Y最大)", default=SINE_TIME_MAX_DEFAULT, update=update_waves_cb)
    
    time_t: FloatProperty(
        name="現在の時間 t", 
        default=SINE_TIME_DEFAULT, 
        soft_min=0.0, 
        soft_max=10.0, 
        update=update_waves_cb
    )
    
    wave_speed_x: FloatProperty(name="X方向速度 (v)", default=SINE_WAVE_SPEED_X_DEFAULT, soft_min=-10.0, soft_max=10.0, update=update_waves_cb)
    delay: FloatProperty(name="開始時間の遅れ", default=SINE_DELAY_DEFAULT, update=update_waves_cb)
    resolution: IntProperty(name="解像度", default=SINE_RESOLUTION_DEFAULT, min=2, update=update_waves_cb)
    
    wave_direction: EnumProperty(
        name="波の開始基準",
        items=[
            ('SPACE', "空間 (X軸) で山から開始", "t=0の初期波において、X軸正の方向へ『山』から始まります"),
            ('TIME', "時間 (Y軸) で山から開始", "原点X=0において、時間が進むと『山』から湧き出します")
        ],
        default='SPACE',
        update=update_waves_cb
    )
    
    taper_x: BoolProperty(
        name="X軸方向へ徐々に山を高くする",
        description="X=0で振幅を0にし、Xが進むにつれて山の高さを最大振幅まで徐々に大きくします",
        default=False,
        update=update_waves_cb
    )
    
    init_show: BoolProperty(name="初期波 (t=0)", default=True, update=update_waves_cb)
    init_thickness: FloatProperty(name="初期波の太さ", default=0.05, min=0.0, update=update_waves_cb)
    init_color: FloatVectorProperty(name="初期波の色", subtype='COLOR', size=4, default=INIT_COLOR, min=0.0, max=1.0, update=update_waves_cb)

    main_show: BoolProperty(name="時間波形 (t=t)", default=True, update=update_waves_cb)
    thickness: FloatProperty(name="時間波形の太さ", default=0.15, min=0.0, update=update_waves_cb)
    color_main: FloatVectorProperty(name="時間波形の色", subtype='COLOR', size=4, default=MAIN_COLOR, min=0.0, max=1.0, update=update_waves_cb)
    
    traj1_show: BoolProperty(name="軌跡 1", default=True, update=update_waves_cb)
    traj1_x: FloatProperty(name="軌跡1 X位置", default=0.0, update=update_waves_cb)
    traj1_thickness: FloatProperty(name="軌跡1の太さ", default=0.1, min=0.0, update=update_waves_cb)
    traj1_color: FloatVectorProperty(name="軌跡1の色", subtype='COLOR', size=4, default=TRAJ1_COLOR, min=0.0, max=1.0, update=update_waves_cb)

    traj2_show: BoolProperty(name="軌跡 2", default=True, update=update_waves_cb)
    traj2_x: FloatProperty(name="軌跡2 X位置", default=2.0, update=update_waves_cb)
    traj2_thickness: FloatProperty(name="軌跡2の太さ", default=0.1, min=0.0, update=update_waves_cb)
    traj2_color: FloatVectorProperty(name="軌跡2の色", subtype='COLOR', size=4, default=TRAJ2_COLOR, min=0.0, max=1.0, update=update_waves_cb)

    tx_show: BoolProperty(name="t=ax 波面", default=True, update=update_waves_cb)
    tx_slope_a: FloatProperty(name="係数 (a)", default=1.0, min=0.0, max=1.0, update=update_waves_cb)
    tx_thickness: FloatProperty(name="t=ax 波の太さ", default=0.12, min=0.0, update=update_waves_cb)
    tx_color: FloatVectorProperty(name="t=ax 波の色", subtype='COLOR', size=4, default=TX_COLOR, min=0.0, max=1.0, update=update_waves_cb)

# ==========================================
# 【オペレーター】
# ==========================================

# 骨格立方体用
class DICE_TOOLS_5_OT_Create(Operator):
    bl_idname = "dice_tools_5.create_skeleton"
    bl_label = "骨格を新規生成"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        mesh_data = create_advanced_geometry(context)
        obj = bpy.data.objects.new(f"{OBJ_NAME_BASE}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(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(SUB_COL_NAME) or bpy.data.collections.new(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
        update_skeleton_live(self, context)
        return {'FINISHED'}

class DICE_TOOLS_5_OT_Detach(Operator):
    bl_idname = "dice_tools_5.detach_object"
    bl_label = "アドオンから切り離す"
    def execute(self, context):
        obj = context.active_object
        if obj and obj.name.startswith(OBJ_NAME_BASE):
            obj.name = "Fixed_Dice_" + str(random.randint(100,999))
            self.report({'INFO'}, "独立させました")
        return {'FINISHED'}

# Sine Wave用オペレーター (新規追加)
class SINE_WAVE_OT_Create(Operator):
    bl_idname = "sine_wave.create"
    bl_label = "サイン波を新規生成"
    bl_options = {'REGISTER', 'UNDO'}
    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.shading.type = 'MATERIAL'
        generate_waves(context)
        return {'FINISHED'}

class SINE_WAVE_OT_Clear(Operator):
    bl_idname = "sine_wave.clear"
    bl_label = "サイン波を削除"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        clear_sine_wave_data()
        return {'FINISHED'}

# 共通・管理用
class DICE_TOOLS_5_OT_ViewCenter(Operator):
    bl_idname = "dice_tools_5.view_center"
    bl_label = "選択を中央へ"
    def execute(self, context):
        if context.active_object: bpy.ops.view3d.view_selected()
        return {'FINISHED'}

class DICE_TOOLS_5_OT_OpenLink(Operator):
    bl_idname = "dice_tools_5.open_link"
    bl_label = "URLを開く"
    url: StringProperty()
    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class DICE_TOOLS_5_OT_RemoveAddon(Operator):
    bl_idname = "dice_tools_5.remove_addon_ui"
    bl_label = "アドオンUIを削除"
    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ==========================================
# 【UIパネル】
# ==========================================
# (すべてのパネルを bl_category = "骨格立方体" に統一)

class DICE_TOOLS_5_PT_ViewPanel(Panel):
    bl_label = "ビュー情報"
    bl_idname = "DICE_TOOLS_5_PT_ViewPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"

    def draw(self, context):
        layout = self.layout
        rv3d = context.region_data
        layout.operator("dice_tools_5.view_center", icon='CENTER_ONLY')
        layout.separator()
        layout.label(text="モード: " + ("透視投影" if rv3d.view_perspective == 'PERSP' else "平行投影"))
        box = layout.box()
        loc = rv3d.view_location
        box.label(text=f"中心: X:{loc.x:.1f} Y:{loc.y:.1f} Z:{loc.z:.1f}")
        layout.label(text=f"画面: {context.region.width} x {context.region.height}")
        if rv3d.view_perspective == 'PERSP':
            layout.label(text=f"距離: {rv3d.view_distance:.1f}")
        layout.prop(context.scene, "dice_info_str", text="", icon='COPY_ID')

class DICE_TOOLS_5_PT_CreationPanel(Panel):
    bl_label = "骨格 調整"
    bl_idname = "DICE_TOOLS_5_PT_CreationPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        obj = context.active_object
        
        layout.operator("dice_tools_5.create_skeleton", icon='MESH_CUBE')
        layout.separator()
        
        col = layout.column(align=True)
        col.prop(scene, "dice_shape", text="基本形状")
        col.prop(scene, "dice_pos", text="位置")
        col.prop(scene, "dice_rot", text="回転")
        col.prop(scene, "dice_size", text="サイズ")
        col.prop(scene, "dice_thick", text="骨格の太さ")
        col.prop(scene, "dice_res", text="分割数")
        
        layout.separator()
        layout.prop(scene, "dice_show_faces", text="表面を表示する", icon='FACESEL')
        
        row = layout.row()
        row.prop(scene, "dice_color_frame", text="骨格の色")
        if scene.dice_show_faces:
            row.prop(scene, "dice_color_face", text="表面の色")
        
        layout.separator()
        if obj and obj.name.startswith(OBJ_NAME_BASE):
            layout.operator("dice_tools_5.detach_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した骨格を選択して調整", icon='INFO')

class SINE_WAVE_PT_CreationPanel(Panel):
    bl_label = "サイン波 調整"
    bl_idname = "SINE_WAVE_PT_CreationPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"

    def draw(self, context):
        layout = self.layout
        props = context.scene.sine_wave_props

        # 生成・削除のオペレーターボタン(骨格立方体と同じ構造)
        row = layout.row(align=True)
        row.operator("sine_wave.create", icon='MOD_WAVE')
        row.operator("sine_wave.clear", icon='TRASH')
        layout.separator()

        # 1. 波の形と方向
        box = layout.box()
        box.label(text="波の形・方向の設定", icon='MOD_WAVE')
        box.prop(props, "wave_direction")
        box.prop(props, "taper_x")

        # 2. 基本設定
        box = layout.box()
        box.label(text="物理パラメータ", icon='VIEW_ORTHO')
        box.prop(props, "amplitude")
        box.prop(props, "frequency")
        box.prop(props, "length")
        box.prop(props, "time_max")
        
        box.separator()
        box.prop(props, "time_t", slider=True)
        box.prop(props, "wave_speed_x")
        box.prop(props, "delay")
        box.prop(props, "resolution")
        
        # 3. 波の表示切り替え
        box2 = layout.box()
        box2.label(text="波の表示切り替え (ON/OFF)", icon='RESTRICT_VIEW_OFF')
        
        row1 = box2.row(align=True)
        row1.prop(props, "init_show", toggle=True, text="t=0")
        row1.prop(props, "main_show", toggle=True, text="t=t")
        row1.prop(props, "tx_show", toggle=True, text="t=ax")
        
        row2 = box2.row(align=True)
        row2.prop(props, "traj1_show", toggle=True, text="軌跡1")
        row2.prop(props, "traj2_show", toggle=True, text="軌跡2")

        # 4. 見た目の詳細設定
        box3 = layout.box()
        box3.label(text="色・太さ・位置の設定", icon='MATERIAL')
        
        if props.init_show:
            row_c = box3.row()
            row_c.label(text="初期 (t=0):")
            row_c.prop(props, "init_thickness", text="太さ")
            row_c.prop(props, "init_color", text="")

        if props.main_show:
            row_c = box3.row()
            row_c.label(text=f"現在 (t={props.time_t:.1f}):")
            row_c.prop(props, "thickness", text="太さ")
            row_c.prop(props, "color_main", text="")
            
        if props.traj1_show:
            box3.separator()
            row_c = box3.row(align=True)
            row_c.label(text="軌跡1:")
            row_c.prop(props, "traj1_x", text="X座標")
            row_c.prop(props, "traj1_thickness", text="太さ")
            row_c.prop(props, "traj1_color", text="")

        if props.traj2_show:
            row_c = box3.row(align=True)
            row_c.label(text="軌跡2:")
            row_c.prop(props, "traj2_x", text="X座標")
            row_c.prop(props, "traj2_thickness", text="太さ")
            row_c.prop(props, "traj2_color", text="")

        if props.tx_show:
            box3.separator()
            row_a = box3.row()
            row_a.label(text="波面 (t=ax):")
            row_a.prop(props, "tx_slope_a", text="係数 a", slider=True)
            
            row_c = box3.row()
            row_c.label(text="")
            row_c.prop(props, "tx_thickness", text="太さ")
            row_c.prop(props, "tx_color", text="")

class DICE_TOOLS_5_PT_Footer(Panel):
    bl_label = "リンク・管理"
    bl_idname = "DICE_TOOLS_5_PT_Footer"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        links = [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2), (LINK_NAME_3, LINK_URL_3), (LINK_NAME_4, LINK_URL_4), (LINK_NAME_5, LINK_URL_5)]
        for name, url in links:
            op = layout.operator("dice_tools_5.open_link", text=name, icon='WORLD')
            op.url = url
        layout.separator()
        layout.operator("dice_tools_5.remove_addon_ui", icon='TRASH')

# ==========================================
# 【登録・解除】
# ==========================================
classes = (
    # オペレーター (骨格)
    DICE_TOOLS_5_OT_ViewCenter, 
    DICE_TOOLS_5_OT_Create, 
    DICE_TOOLS_5_OT_Detach,
    DICE_TOOLS_5_OT_OpenLink, 
    DICE_TOOLS_5_OT_RemoveAddon,
    # オペレーター (SineWave)
    SINE_WAVE_OT_Create,
    SINE_WAVE_OT_Clear,
    # プロパティ (SineWave)
    SineWaveProperties,
    # パネル群 (全て骨格立方体タブ内)
    DICE_TOOLS_5_PT_ViewPanel, 
    DICE_TOOLS_5_PT_CreationPanel, 
    SINE_WAVE_PT_CreationPanel,
    DICE_TOOLS_5_PT_Footer,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    
    s = bpy.types.Scene
    
    s.dice_shape = EnumProperty(items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], name="Shape", default='CUBE', update=update_skeleton_live)
    s.dice_size = FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
    s.dice_thick = FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
    s.dice_res = IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
    s.dice_pos = FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
    s.dice_rot = FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
    s.dice_show_faces = BoolProperty(name="Show Faces", default=True, update=update_skeleton_live)
    s.dice_color_frame = FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
    s.dice_color_face = FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)
    s.dice_info_str = StringProperty(name="Info")
    
    s.sine_wave_props = PointerProperty(type=SineWaveProperties)

def unregister():
    for cls in reversed(classes):
        if hasattr(bpy.utils, "unregister_class"):
            try: bpy.utils.unregister_class(cls)
            except: pass
    
    s = bpy.types.Scene
    del s.dice_shape, s.dice_size, s.dice_thick, s.dice_res, s.dice_pos, s.dice_rot
    del s.dice_show_faces, s.dice_color_frame, s.dice_color_face, s.dice_info_str
    
    del s.sine_wave_props
    clear_sine_wave_data()

if __name__ == "__main__":
    register()
bl_info = {
    "name": "zionad_ [ 骨格立方体 ]",
    "author": "zionad_",
    "version": (2, 0, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar > 骨格立方体",
    "description": "表面・骨格別の色設定、表面有無の選択が可能な骨格生成ツール",
    "category": "骨格立方体",
}

# 2026年07月14日 06時35分00秒

import bpy
import math
import webbrowser
import random
from bpy.props import FloatProperty, FloatVectorProperty, StringProperty, IntProperty, EnumProperty, BoolProperty
from bpy.types import Operator, Panel

# ==========================================
# 【初期設定・手動変更エリア】
# ==========================================

LINK_NAME_1 = "基本アドオン設定 20260711b"
LINK_URL_1 = "<https://app.notion.com/p/20260711-39af5dacaf438027811dcd877db7c630>"

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 = "blender コンテナ 20260713"
LINK_URL_4 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"

LINK_NAME_5 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"

OBJ_NAME_BASE = "Skeleton_Dice"
COL_NAME = "Dice_Collection"
SUB_COL_NAME = "Skeleton_Cubes"

# 初期数値
INIT_SIZE = 60.0
INIT_THICKNESS = 1.0
INIT_RES = 32           # 分割数初期値 32
INIT_POS = (0.0, 0.0, 0.0)
INIT_ROT = (0.0, 0.0, 0.0)

# 初期色 (骨格: フレーム用)
INIT_CLR_FRAME = (0.1, 0.6, 1.0, 1.0) # 青系
# 初期色 (表面: 面用)
INIT_CLR_FACE = (1.0, 1.0, 1.0, 0.3)  # 白の透明

# ==========================================
# 【形状生成コア・ロジック】
# ==========================================

def create_advanced_geometry(context):
    """骨格(パイプ)と表面(面)を組み合わせたメッシュを構築"""
    scene = context.scene
    import bmesh
    
    # 1. 元となる形状のデータを作成
    bm_base = bmesh.new()
    if scene.dice_shape == 'CUBE':
        bmesh.ops.create_cube(bm_base, size=scene.dice_size)
    else: # SQUARE
        bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=scene.dice_size/2)
    
    # 2. パイプ骨格の作成
    curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
    curve_data.dimensions = '3D'
    curve_data.fill_mode = 'FULL'
    curve_data.bevel_depth = scene.dice_thick / 2
    curve_data.bevel_resolution = scene.dice_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)
    
    # 3. 結合用のBMesh作成
    bm_final = bmesh.new()
    # 骨格をBMeshに追加 (Material Index 0)
    bm_final.from_mesh(mesh_frame)
    for face in bm_final.faces:
        face.material_index = 0
    bpy.data.meshes.remove(mesh_frame)
    
    # 表面を追加する場合 (Material Index 1)
    if scene.dice_show_faces:
        # 面だけを抽出して追加
        bm_face = bmesh.new()
        if scene.dice_shape == 'CUBE':
            bmesh.ops.create_cube(bm_face, size=scene.dice_size)
        else:
            bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=scene.dice_size/2)
        
        # 既存のBMeshに結合
        for face in bm_face.faces:
            face.material_index = 1
        bm_face.to_mesh(bpy.data.meshes.new("temp_face")) # 変換経由
        bm_final.from_mesh(bpy.data.meshes.get("temp_face"))
        bpy.data.meshes.remove(bpy.data.meshes.get("temp_face"))
        bm_face.free()

    # 4. 最終メッシュ出力
    mesh_out = bpy.data.meshes.new(OBJ_NAME_BASE)
    bm_final.to_mesh(mesh_out)
    bm_final.free()
    bm_base.free()
    
    return mesh_out

def update_skeleton_live(self, context):
    scene = context.scene
    for obj in scene.objects:
        if obj.name.startswith(OBJ_NAME_BASE) and obj.type == 'MESH':
            # 変形
            obj.location = scene.dice_pos
            obj.rotation_euler = [math.radians(a) for a in scene.dice_rot]
            
            # メッシュ更新
            new_mesh = create_advanced_geometry(context)
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh: bpy.data.meshes.remove(old_mesh)
            
            # マテリアル設定 (2スロット)
            while len(obj.data.materials) < 2:
                obj.data.materials.append(None)
            
            # スロット0: 骨格用
            if not obj.material_slots[0].material:
                obj.material_slots[0].material = bpy.data.materials.new(name="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:
                bsdf_frame.inputs['Base Color'].default_value = scene.dice_color_frame
                if 'Alpha' in bsdf_frame.inputs: bsdf_frame.inputs['Alpha'].default_value = scene.dice_color_frame[3]
            mat_frame.blend_method = 'BLEND'

            # スロット1: 表面用
            if not obj.material_slots[1].material:
                obj.material_slots[1].material = bpy.data.materials.new(name="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:
                bsdf_face.inputs['Base Color'].default_value = scene.dice_color_face
                if 'Alpha' in bsdf_face.inputs: bsdf_face.inputs['Alpha'].default_value = scene.dice_color_face[3]
            mat_face.blend_method = 'BLEND'

# ==========================================
# 【オペレーター】
# ==========================================

class DICE_TOOLS_5_OT_Create(Operator):
    bl_idname = "dice_tools_5.create_skeleton"
    bl_label = "骨格を新規生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        mesh_data = create_advanced_geometry(context)
        obj = bpy.data.objects.new(f"{OBJ_NAME_BASE}_{random.randint(100,999)}", mesh_data)
        
        main_col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(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(SUB_COL_NAME) or bpy.data.collections.new(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
        update_skeleton_live(self, context)
        return {'FINISHED'}

class DICE_TOOLS_5_OT_Detach(Operator):
    bl_idname = "dice_tools_5.detach_object"
    bl_label = "アドオンから切り離す"
    def execute(self, context):
        obj = context.active_object
        if obj and obj.name.startswith(OBJ_NAME_BASE):
            obj.name = "Fixed_Dice_" + str(random.randint(100,999))
            self.report({'INFO'}, "独立させました")
        return {'FINISHED'}

class DICE_TOOLS_5_OT_ViewCenter(Operator):
    bl_idname = "dice_tools_5.view_center"
    bl_label = "選択を中央へ"
    def execute(self, context):
        if context.active_object: bpy.ops.view3d.view_selected()
        return {'FINISHED'}

class DICE_TOOLS_5_OT_OpenLink(Operator):
    bl_idname = "dice_tools_5.open_link"
    bl_label = "URLを開く"
    url: StringProperty()
    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class DICE_TOOLS_5_OT_RemoveAddon(Operator):
    bl_idname = "dice_tools_5.remove_addon_ui"
    bl_label = "アドオンUIを削除"
    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ==========================================
# 【UIパネル】
# ==========================================

class DICE_TOOLS_5_PT_ViewPanel(Panel):
    bl_label = "ビュー情報"
    bl_idname = "DICE_TOOLS_5_PT_ViewPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"

    def draw(self, context):
        layout = self.layout
        rv3d = context.region_data
        layout.operator("dice_tools_5.view_center", icon='CENTER_ONLY')
        layout.separator()
        layout.label(text="モード: " + ("透視投影" if rv3d.view_perspective == 'PERSP' else "平行投影"))
        box = layout.box()
        loc = rv3d.view_location
        box.label(text=f"中心: X:{loc.x:.1f} Y:{loc.y:.1f} Z:{loc.z:.1f}")
        layout.label(text=f"画面: {context.region.width} x {context.region.height}")
        if rv3d.view_perspective == 'PERSP':
            layout.label(text=f"距離: {rv3d.view_distance:.1f}")
        layout.prop(context.scene, "dice_info_str", text="", icon='COPY_ID')

class DICE_TOOLS_5_PT_CreationPanel(Panel):
    bl_label = "骨格 調整"
    bl_idname = "DICE_TOOLS_5_PT_CreationPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        obj = context.active_object
        
        layout.operator("dice_tools_5.create_skeleton", icon='MESH_CUBE')
        layout.separator()
        
        col = layout.column(align=True)
        col.prop(scene, "dice_shape", text="基本形状")
        col.prop(scene, "dice_pos", text="位置")
        col.prop(scene, "dice_rot", text="回転")
        col.prop(scene, "dice_size", text="サイズ")
        col.prop(scene, "dice_thick", text="骨格の太さ")
        col.prop(scene, "dice_res", text="分割数")
        
        layout.separator()
        layout.prop(scene, "dice_show_faces", text="表面を表示する", icon='FACESEL')
        
        row = layout.row()
        row.prop(scene, "dice_color_frame", text="骨格の色")
        if scene.dice_show_faces:
            row.prop(scene, "dice_color_face", text="表面の色")
        
        layout.separator()
        if obj and obj.name.startswith(OBJ_NAME_BASE):
            layout.operator("dice_tools_5.detach_object", icon='UNLINKED')
        else:
            layout.label(text="※生成した骨格を選択して調整", icon='INFO')

class DICE_TOOLS_5_PT_Footer(Panel):
    bl_label = "リンク・管理"
    bl_idname = "DICE_TOOLS_5_PT_Footer"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "骨格立方体"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        links = [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2), (LINK_NAME_3, LINK_URL_3), (LINK_NAME_4, LINK_URL_4), (LINK_NAME_5, LINK_URL_5)]
        for name, url in links:
            op = layout.operator("dice_tools_5.open_link", text=name, icon='WORLD')
            op.url = url
        layout.separator()
        layout.operator("dice_tools_5.remove_addon_ui", icon='TRASH')

# ==========================================
# 【登録・解除】
# ==========================================

classes = (
    DICE_TOOLS_5_OT_ViewCenter, DICE_TOOLS_5_OT_Create, DICE_TOOLS_5_OT_Detach,
    DICE_TOOLS_5_OT_OpenLink, DICE_TOOLS_5_OT_RemoveAddon,
    DICE_TOOLS_5_PT_ViewPanel, DICE_TOOLS_5_PT_CreationPanel, DICE_TOOLS_5_PT_Footer,
)

def register():
    for cls in classes: bpy.utils.register_class(cls)
    
    s = bpy.types.Scene
    s.dice_shape = EnumProperty(items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], name="Shape", default='CUBE', update=update_skeleton_live)
    s.dice_size = FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
    s.dice_thick = FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
    s.dice_res = IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
    s.dice_pos = FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
    s.dice_rot = FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
    
    s.dice_show_faces = BoolProperty(name="Show Faces", default=True, update=update_skeleton_live)
    s.dice_color_frame = FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
    s.dice_color_face = FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)
    
    s.dice_info_str = StringProperty(name="Info")

def unregister():
    for cls in reversed(classes):
        if hasattr(bpy.utils, "unregister_class"):
            try: bpy.utils.unregister_class(cls)
            except: pass
    
    s = bpy.types.Scene
    del s.dice_shape, s.dice_size, s.dice_thick, s.dice_res, s.dice_pos, s.dice_rot
    del s.dice_show_faces, s.dice_color_frame, s.dice_color_face, s.dice_info_str

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Sine Wave Generator (Space & Time Trajectory)",
    "author": "ChatGPT",
    "version": (1, 6, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (N Panel)",
    "description": "進行波、x=0の時間軌跡、t=xの波面などを描画します。",
    "category": "3D View",
}

import bpy
import math

# ===================================
# 共通定義 & 初期設定
# ===================================
ADDON_TITLE = "Sine Wave Generator"
PREFIX = "sine_wave"

# 数値
SINE_AMPLITUDE_DEFAULT = 2.0
SINE_FREQUENCY_DEFAULT = 1.0
SINE_LENGTH_DEFAULT = 10.0
SINE_TIME_DEFAULT = 1.0          # デフォルトを t=1.0 に変更
SINE_DELAY_DEFAULT = 0.0
SINE_RESOLUTION_DEFAULT = 128
SINE_WAVE_SPEED_X_DEFAULT = 1.0

# 位置オフセット
SINE_LOC_X_DEFAULT = 0.0
SINE_LOC_Y_DEFAULT = 0.0
SINE_LOC_Z_DEFAULT = 0.0

# デフォルトの色
INIT_COLOR = (0.5, 0.5, 0.5, 0.8)   # t=0 波形 (グレー)
MAIN_COLOR = (0.2, 0.6, 1.0, 1.0)   # t=1 波形 (青)
TRAJ_COLOR = (1.0, 0.8, 0.2, 1.0)   # x=0 軌跡 (黄色)
TX_COLOR   = (0.2, 1.0, 0.5, 1.0)   # t=x 波面 (緑)

# ===================================
# 共通の変位計算関数
# ===================================
def calculate_z_displacement(x, t, props):
    """
    空間(x)と時間(t)から、Z軸方向の変位を計算する共通関数。
    ※ 波の式を (v*t - x) とすることで、t>0 (時間が進む) のとき、
      x=0 における最初の変位が z>0 (山) になります。
    """
    phase_x = props.wave_speed_x * (t - props.delay) - x
    return props.amplitude * math.sin(props.frequency * phase_x) + SINE_LOC_Z_DEFAULT

# ===================================
# プロパティ定義
# ===================================
class SineWaveProperties(bpy.types.PropertyGroup):
    # 基本パラメータ
    amplitude: bpy.props.FloatProperty(name="振幅高さ (Z軸)", default=SINE_AMPLITUDE_DEFAULT)
    frequency: bpy.props.FloatProperty(name="周波数 (k)", default=SINE_FREQUENCY_DEFAULT)
    length: bpy.props.FloatProperty(name="波の長さ (X軸)", default=SINE_LENGTH_DEFAULT, description="x=0 から +X方向への描画距離")
    
    time_t: bpy.props.FloatProperty(name="時間 t (Y座標)", default=SINE_TIME_DEFAULT, description="時間波形を描画する時刻")
    wave_speed_x: bpy.props.FloatProperty(name="X方向速度 (v)", default=SINE_WAVE_SPEED_X_DEFAULT, soft_min=-10.0, soft_max=10.0)
    delay: bpy.props.FloatProperty(name="開始時間の遅れ", default=SINE_DELAY_DEFAULT)
    resolution: bpy.props.IntProperty(name="解像度", default=SINE_RESOLUTION_DEFAULT, min=2)
    
    # 1. 初期波 (t=0)
    init_show: bpy.props.BoolProperty(name="初期波 (t=0) を描画", default=True)
    init_thickness: bpy.props.FloatProperty(name="初期波の太さ", default=0.05, min=0.0)
    init_color: bpy.props.FloatVectorProperty(name="初期波の色", subtype='COLOR', size=4, default=INIT_COLOR, min=0.0, max=1.0)

    # 2. メイン波 (t=t)
    main_show: bpy.props.BoolProperty(name="時間波形 (t=t) を描画", default=True)
    thickness: bpy.props.FloatProperty(name="時間波形の太さ", default=0.15, min=0.0)
    color_main: bpy.props.FloatVectorProperty(name="時間波形の色", subtype='COLOR', size=4, default=MAIN_COLOR, min=0.0, max=1.0)
    
    # 3. 時間軌跡 (x=0)
    traj_show: bpy.props.BoolProperty(name="時間軌跡 (x=0) を描画", default=True)
    traj_thickness: bpy.props.FloatProperty(name="軌跡の太さ", default=0.1, min=0.0)
    traj_color: bpy.props.FloatVectorProperty(name="軌跡の色", subtype='COLOR', size=4, default=TRAJ_COLOR, min=0.0, max=1.0)

    # 4. t=x 波面
    tx_show: bpy.props.BoolProperty(name="t=x 波面を描画", default=True)
    tx_thickness: bpy.props.FloatProperty(name="t=x 波の太さ", default=0.12, min=0.0)
    tx_color: bpy.props.FloatVectorProperty(name="t=x 波の色", subtype='COLOR', size=4, default=TX_COLOR, min=0.0, max=1.0)

# ===================================
# オペレーター (生成処理)
# ===================================
class SINE_WAVE_OT_generate(bpy.types.Operator):
    bl_idname = f"{PREFIX}.generate"
    bl_label = "波と軌跡を生成"

    def create_curve_object(self, name, thickness, color, collection):
        """カーブとマテリアルを生成するヘルパー関数"""
        curve_data = bpy.data.curves.new(name=name, type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.bevel_depth = thickness
        curve_data.bevel_resolution = 4
        
        spline = curve_data.splines.new(type='POLY')
        
        obj = bpy.data.objects.new(name, curve_data)
        collection.objects.link(obj)
        
        mat = bpy.data.materials.new(name=f"{name}_Mat")
        mat.use_nodes = True
        mat.blend_method = 'BLEND'
        bsdf = mat.node_tree.nodes.get("Principled BSDF")
        if bsdf:
            bsdf.inputs['Base Color'].default_value = color
            if len(color) == 4 and color[3] < 1.0:
                bsdf.inputs['Alpha'].default_value = color[3]
        obj.data.materials.append(mat)
        
        return spline

    def execute(self, context):
        props = context.scene.sine_wave_props
        collection = context.collection

        # ------------------------------------------------
        # 1. 初期波形 (t=0 の空間波形: y = 0)
        # ------------------------------------------------
        if props.init_show:
            spline_init = self.create_curve_object("Wave_Init_t0", props.init_thickness, props.init_color, collection)
            spline_init.points.add(props.resolution - 1)
            
            for i in range(props.resolution):
                x_local = (i / (props.resolution - 1)) * props.length # x > 0 基準
                y = 0.0 + SINE_LOC_Y_DEFAULT
                z = calculate_z_displacement(x_local, 0.0, props)
                spline_init.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

        # ------------------------------------------------
        # 2. メインの波 (空間波形: y = time_t)
        # ------------------------------------------------
        if props.main_show:
            spline_main = self.create_curve_object(f"Wave_Main_t{props.time_t:.1f}", props.thickness, props.color_main, collection)
            spline_main.points.add(props.resolution - 1)
            
            for i in range(props.resolution):
                x_local = (i / (props.resolution - 1)) * props.length # x > 0 基準
                y = props.time_t + SINE_LOC_Y_DEFAULT
                z = calculate_z_displacement(x_local, props.time_t, props)
                spline_main.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

        # ------------------------------------------------
        # 3. 時間軌跡 (x=0 の時間軸変位: y = 0 to time_t)
        # ------------------------------------------------
        if props.traj_show and props.time_t > 0.0:
            spline_traj = self.create_curve_object("Trajectory_x0", props.traj_thickness, props.traj_color, collection)
            spline_traj.points.add(props.resolution - 1)
            
            for i in range(props.resolution):
                tau = (i / (props.resolution - 1)) * props.time_t
                x_local = 0.0  # x=0 に固定
                y = tau + SINE_LOC_Y_DEFAULT
                z = calculate_z_displacement(x_local, tau, props)
                spline_traj.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

        # ------------------------------------------------
        # 4. t=x 波面 (xとtが連動する斜めの面上の波: y = x)
        # ------------------------------------------------
        if props.tx_show:
            spline_tx = self.create_curve_object("Wave_t_equals_x", props.tx_thickness, props.tx_color, collection)
            spline_tx.points.add(props.resolution - 1)
            
            for i in range(props.resolution):
                x_local = (i / (props.resolution - 1)) * props.length
                t_local = x_local  # t = x
                y = t_local + SINE_LOC_Y_DEFAULT
                z = calculate_z_displacement(x_local, t_local, props)
                spline_tx.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)

        return {'FINISHED'}

# ===================================
# UI パネル
# ===================================
class SINE_WAVE_PT_panel(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_idname = f"{PREFIX}_PT_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "SineWave"

    def draw(self, context):
        layout = self.layout
        props = context.scene.sine_wave_props

        # 基本設定
        box = layout.box()
        box.label(text="基本設定", icon='VIEW_ORTHO')
        box.prop(props, "amplitude")
        box.prop(props, "frequency")
        box.prop(props, "length")
        box.prop(props, "time_t")
        box.prop(props, "wave_speed_x")
        box.prop(props, "delay")
        box.prop(props, "resolution")
        
        # 各種描画のオンオフと見た目
        box2 = layout.box()
        box2.label(text="波の描画設定", icon='OUTLINER_OB_CURVE')
        
        # 1. 初期波 (t=0)
        box2.prop(props, "init_show", icon='TIME')
        if props.init_show:
            row = box2.row()
            row.prop(props, "init_thickness", text="太さ")
            row.prop(props, "init_color", text="")

        # 2. メイン波 (t=t)
        box2.prop(props, "main_show", text=f"時間波形 (t={props.time_t:.1f}) を描画", icon='MOD_WAVE')
        if props.main_show:
            row = box2.row()
            row.prop(props, "thickness", text="太さ")
            row.prop(props, "color_main", text="")
            
        # 3. 軌跡 (x=0)
        box2.prop(props, "traj_show", icon='ANIM')
        if props.traj_show:
            row = box2.row()
            row.prop(props, "traj_thickness", text="太さ")
            row.prop(props, "traj_color", text="")

        # 4. t=x 波面
        box2.prop(props, "tx_show", icon='CON_TRANSLIKE')
        if props.tx_show:
            row = box2.row()
            row.prop(props, "tx_thickness", text="太さ")
            row.prop(props, "tx_color", text="")
        
        layout.separator()
        row = layout.row()
        row.scale_y = 1.5
        row.operator(SINE_WAVE_OT_generate.bl_idname, text="波と軌跡を作成", icon='PLAY')

def setup_viewport_ui():
    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.shading.type = 'MATERIAL'
                        space.show_region_ui = True
    return None

classes = (
    SineWaveProperties,
    SINE_WAVE_OT_generate,
    SINE_WAVE_PT_panel,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.sine_wave_props = bpy.props.PointerProperty(type=SineWaveProperties)
    bpy.app.timers.register(setup_viewport_ui, first_interval=0.1)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.sine_wave_props

if __name__ == "__main__":
    register()