bl_info = {
    "name": "zionad, オブジェクト統合ジェネレーター",
    "author": "zionad & AI Assistant",
    "version": (2, 3, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar",
    "description": "全図形対応、テキストを形状ドロップダウンリストに統合(リアルタイム文字変更対応)",
    "category": "3D View",
}

# 【作成日】 2026年7月12日 

# 【初期設定・定数定義】
ADDON_TITLE = "図形 All-in-One"
PREFIX = "zukkei2026"
DEFAULT_COLLECTION_NAME = "Zion_Collection"
DEFAULT_SUBCOLLECTION_NAME = "Zion_SubCollection"
DEFAULT_OBJECT_NAME = "Zion_Object"
DEFAULT_COLOR = (0.1, 0.6, 0.8, 1.0)
DEFAULT_P1_X = 0.0
DEFAULT_P1_Y = 0.0
DEFAULT_P1_Z = 2.0
DEFAULT_P2_X = 0.0
DEFAULT_P2_Y = 0.0
DEFAULT_P2_Z = 0.0
DEFAULT_R1 = 0.5
DEFAULT_R2 = 1.0
DEFAULT_SEGMENTS = 32

import bpy
import bmesh
import math
import mathutils

PROP_NAME = f"{PREFIX}_props"
CTRL_FLAG = f"is_{PREFIX}_controlled"
MAT_NAME = f"{PREFIX}_Live_Material"
MESH_NAME = f"{PREFIX}_Live_Mesh"

# =========================================================
# コールバック関数 (リアルタイムメッシュ更新)
# =========================================================
def update_controlled_object(self, context):
    obj = context.view_layer.objects.active
    if not obj or obj.get(CTRL_FLAG) != True:
        return
        
    shape = self.shape_enum
    
    # どの図形でもXYZの位置・回転・サイズ(スケール)を適用
    obj.location = self.location
    obj.rotation_euler = self.rotation
    obj.scale = self.scale_vec

    # メッシュ形状再構築
    rebuild_mesh_geometry(obj, shape, self)

    # メッシュ再構築後にマテリアルと色を確実にアサインする
    mat = bpy.data.materials.get(MAT_NAME)
    if not mat:
        mat = bpy.data.materials.new(name=MAT_NAME)
        
    if len(obj.data.materials) == 0:
        obj.data.materials.append(mat)
    elif obj.data.materials[0] != mat:
        obj.data.materials[0] = mat
        
    mat.use_nodes = True
    mat.blend_method = 'BLEND' if self.color[3] < 1.0 else 'OPAQUE'
    
    # ソリッド(ビューポート)モード用カラー
    mat.diffuse_color = self.color
    
    # ノード名ではなくノードタイプで確実にBSDFを取得
    bsdf = next((node for node in mat.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'), None)
    if bsdf:
        if "Base Color" in bsdf.inputs:
            bsdf.inputs["Base Color"].default_value = self.color
        if "Alpha" in bsdf.inputs:
            bsdf.inputs["Alpha"].default_value = self.color[3]

def update_shape_enum(self, context):
    shape = self.shape_enum
    if shape == 'CYLINDER':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 1.0, 1.0
        self.cap_top, self.cap_bottom = True, True
    elif shape == 'CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.0, 1.0
        self.cap_top, self.cap_bottom = False, True
    elif shape == 'TRUNCATED_CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
        self.cap_top, self.cap_bottom = False, False
    elif shape == 'SKEW_CONE':
        self.p1, self.p2 = (1, 1, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
        self.cap_top, self.cap_bottom = False, False
        
    update_controlled_object(self, context)

# =========================================================
# 形状生成ロジック統合 (分割数・テキスト対応)
# =========================================================
def create_text_mesh_data(props):
    font_curve = bpy.data.curves.new(name="TempTextPath", type='FONT')
    font_curve.body = props.input_text if props.input_text else " "
    font_curve.extrude = props.text_extrude
    font_curve.align_x = 'CENTER'
    font_curve.align_y = 'CENTER'
    
    temp_obj = bpy.data.objects.new("TempTextObj", font_curve)
    bpy.context.scene.collection.objects.link(temp_obj)
    
    depsgraph = bpy.context.evaluated_depsgraph_get()
    object_eval = temp_obj.evaluated_get(depsgraph)
    mesh = bpy.data.meshes.new_from_object(object_eval)
    
    bpy.data.objects.remove(temp_obj)
    bpy.data.curves.remove(font_curve)
    
    return mesh

def create_torus_mesh_data(shape_type, props):
    seg = max(3, props.segments)
    if shape_type == 'SQUARE_TORUS':
        pts = [mathutils.Vector((-1,-1,0)), mathutils.Vector((1,-1,0)), mathutils.Vector((1,1,0)), mathutils.Vector((-1,1,0))]
    elif shape_type == 'CIRCLE_TORUS':
        pts = [mathutils.Vector((math.cos(a), math.sin(a), 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    elif shape_type == 'ELLIPSE_TORUS':
        pts = [mathutils.Vector((math.cos(a)*1.5, math.sin(a)*0.75, 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    else: return None
        
    curve_data = bpy.data.curves.new(name="TempTorusPath", type='CURVE')
    curve_data.dimensions = '3D'
    spline = curve_data.splines.new(type='BEZIER')
    spline.use_cyclic_u = True
    spline.bezier_points.add(len(pts) - 1)
    for i, pt in enumerate(pts):
        bp = spline.bezier_points[i]
        bp.co = pt
        bp.handle_left_type = 'VECTOR'
        bp.handle_right_type = 'VECTOR'
        
    curve_data.bevel_depth = props.torus_pipe_radius
    curve_data.bevel_mode = 'ROUND'
    curve_data.bevel_resolution = max(1, props.segments // 8)
    
    temp_obj = bpy.data.objects.new("TempTorusObj", curve_data)
    bpy.context.scene.collection.objects.link(temp_obj)
    
    depsgraph = bpy.context.evaluated_depsgraph_get()
    object_eval = temp_obj.evaluated_get(depsgraph)
    mesh = bpy.data.meshes.new_from_object(object_eval)
    
    bpy.data.objects.remove(temp_obj)
    bpy.data.curves.remove(curve_data) 
    
    return mesh

def rebuild_mesh_geometry(obj, shape_type, props):
    is_torus_or_text = shape_type in ('SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS', 'TEXT')
    
    if is_torus_or_text:
        if shape_type == 'TEXT':
            new_mesh = create_text_mesh_data(props)
        else:
            new_mesh = create_torus_mesh_data(shape_type, props)
            
        if new_mesh:
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh and old_mesh.name == MESH_NAME:
                bpy.data.meshes.remove(old_mesh)
            return

    if obj.data.name != MESH_NAME:
        old_mesh = obj.data
        obj.data = bpy.data.meshes.new(MESH_NAME)
        if old_mesh and old_mesh.users == 0:
            bpy.data.meshes.remove(old_mesh)

    mesh = obj.data
    bm = bmesh.new()
    is_line_shape = shape_type in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
    
    if is_line_shape:
        p1, p2 = mathutils.Vector(props.p1), mathutils.Vector(props.p2)
        r1, r2 = props.r1, props.r2
        segments = max(3, props.segments)
        
        u = mathutils.Vector((1, 0, 0))
        v = mathutils.Vector((0, 1, 0))
        
        verts_p1, verts_p2 = [], []
        for i in range(segments):
            angle = 2.0 * math.pi * i / segments
            cos_a, sin_a = math.cos(angle), math.sin(angle)
            verts_p2.append(bm.verts.new(p2 + (u * cos_a + v * sin_a) * r2))
            verts_p1.append(bm.verts.new(p1 + (u * cos_a + v * sin_a) * r1))
            
        for i in range(segments):
            next_i = (i + 1) % segments
            bm.faces.new((verts_p2[i], verts_p2[next_i], verts_p1[next_i], verts_p1[i]))
            
        if props.cap_bottom and r2 > 0.001: bm.faces.new(reversed(verts_p2))
        if props.cap_top and r1 > 0.001: bm.faces.new(verts_p1)
        bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
        
    else:
        seg = max(3, props.segments)
        if shape_type == 'SQUARE_FACE':
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
        elif shape_type == 'CIRCLE_FACE':
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=False, radius=0.5, segments=seg)
        elif shape_type == 'ELLIPSE_FACE':
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=False, radius=0.5, segments=seg)
            for v in bm.verts: v.co.y *= 0.5
        elif shape_type == 'MONKEY':
            bmesh.ops.create_monkey(bm)
        elif shape_type == 'UV_SPHERE':
            bmesh.ops.create_uvsphere(bm, u_segments=seg, v_segments=max(3, seg//2), radius=0.5)
        elif shape_type == 'CUBE':
            bmesh.ops.create_cube(bm, size=1.0)
        elif shape_type == 'ICO_SPHERE':
            subdiv = max(1, min(6, seg // 10)) 
            bmesh.ops.create_icosphere(bm, subdivisions=subdiv, radius=0.5)
        elif shape_type == 'GRID':
            grid_seg = max(2, seg)
            bmesh.ops.create_grid(bm, x_segments=grid_seg, y_segments=grid_seg, size=1.0)
            
    bm.to_mesh(mesh)
    bm.free()
    mesh.update()

# =========================================================
# プロパティグループ定義
# =========================================================
class AddonProperties(bpy.types.PropertyGroup):
    center_x: bpy.props.FloatProperty(name="Center X", default=0.0)
    center_y: bpy.props.FloatProperty(name="Center Y", default=0.0)
    center_z: bpy.props.FloatProperty(name="Center Z", default=0.0)
    view_width: bpy.props.FloatProperty(name="Width", default=0.0)
    view_height: bpy.props.FloatProperty(name="Height", default=0.0)
    view_dist: bpy.props.FloatProperty(name="Distance", default=0.0)
    is_persp: bpy.props.BoolProperty(default=True)
    
    col_name: bpy.props.StringProperty(name="Collection", default=DEFAULT_COLLECTION_NAME)
    subcol_name: bpy.props.StringProperty(name="Sub Collection", default=DEFAULT_SUBCOLLECTION_NAME)
    obj_name: bpy.props.StringProperty(name="Object Name", default=DEFAULT_OBJECT_NAME)
    
    shape_enum: bpy.props.EnumProperty(
        name="Shape",
        items=[
            ('CYLINDER', "Cylinder (円柱)", ""),
            ('CONE', "Cone (円錐)", ""),
            ('TRUNCATED_CONE', "Truncated Cone (円錐台)", ""),
            ('SKEW_CONE', "Skew Cone (斜め円錐台)", ""),
            ('SQUARE_FACE', "Square Face (正方形の面)", ""),
            ('CIRCLE_FACE', "Circle Face (円の面)", ""),
            ('ELLIPSE_FACE', "Ellipse Face (楕円の面)", ""),
            ('SQUARE_TORUS', "Square Torus (正方形トーラス)", ""),
            ('CIRCLE_TORUS', "Circle Torus (円形トーラス)", ""),
            ('ELLIPSE_TORUS', "Ellipse Torus (楕円形トーラス)", ""),
            ('TEXT', "Text (テキスト)", ""),
            ('MONKEY', "Monkey (モンキー)", ""),
            ('UV_SPHERE', "UV Sphere (UV球)", ""),
            ('CUBE', "Cube (立方体)", ""),
            ('ICO_SPHERE', "Ico Sphere (ICO球)", ""),
            ('GRID', "Grid (グリッド)", ""),
        ],
        default='CYLINDER',
        update=update_shape_enum
    )
    
    color: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', size=4, default=DEFAULT_COLOR, min=0.0, max=1.0, update=update_controlled_object)
    
    location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), subtype='TRANSLATION', update=update_controlled_object)
    rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), subtype='EULER', update=update_controlled_object)
    scale_vec: bpy.props.FloatVectorProperty(name="サイズ", default=(1.0, 1.0, 1.0), subtype='XYZ', update=update_controlled_object)
    
    segments: bpy.props.IntProperty(name="分割数 / 頂点数", default=DEFAULT_SEGMENTS, min=3, max=512, update=update_controlled_object)
    
    p1: bpy.props.FloatVectorProperty(name="上面中心", default=(DEFAULT_P1_X, DEFAULT_P1_Y, DEFAULT_P1_Z), update=update_controlled_object)
    p2: bpy.props.FloatVectorProperty(name="下面中心", default=(DEFAULT_P2_X, DEFAULT_P2_Y, DEFAULT_P2_Z), update=update_controlled_object)
    r1: bpy.props.FloatProperty(name="上面半径", default=DEFAULT_R1, min=0.0, update=update_controlled_object)
    r2: bpy.props.FloatProperty(name="下面半径", default=DEFAULT_R2, min=0.0, update=update_controlled_object)
    cap_top: bpy.props.BoolProperty(name="上面を塞ぐ", default=True, update=update_controlled_object)
    cap_bottom: bpy.props.BoolProperty(name="下面を塞ぐ", default=True, update=update_controlled_object)

    torus_pipe_radius: bpy.props.FloatProperty(name="Pipe Thickness", default=0.1, min=0.01, update=update_controlled_object)
    
    # 【追加】テキスト用のプロパティ(リアルタイム連動)
    input_text: bpy.props.StringProperty(name="文字", default="Sample Text", update=update_controlled_object)
    text_extrude: bpy.props.FloatProperty(name="厚み", default=0.05, min=0.0, update=update_controlled_object)
    
    image_filepath: bpy.props.StringProperty(name="Image Path", subtype='FILE_PATH', default="")

# =========================================================
# オペレーター (機能)
# =========================================================
def get_or_create_collection(context, main_name, sub_name):
    main_col = bpy.data.collections.get(main_name)
    if not main_col:
        main_col = bpy.data.collections.new(main_name)
        context.scene.collection.children.link(main_col)
    sub_col = bpy.data.collections.get(sub_name)
    if not sub_col:
        sub_col = bpy.data.collections.new(sub_name)
        main_col.children.link(sub_col)
    return sub_col

class OT_MoveToCenter(bpy.types.Operator):
    bl_idname = f"{PREFIX}.move_to_center"
    bl_label = "選択オブジェクトを画面中央へ"
    def execute(self, context):
        obj = context.active_object
        if obj and context.region_data:
            obj.location = context.region_data.view_location
            self.report({'INFO'}, "画面中央に移動しました")
        return {'FINISHED'}

class OT_UpdateViewInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.update_view_info"
    bl_label = "画面情報を更新"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        region = context.region
        region_data = context.region_data
        space = context.space_data
        if not region_data: return {'CANCELLED'}
            
        loc = region_data.view_location
        props.center_x, props.center_y, props.center_z = loc.x, loc.y, loc.z
        props.view_dist = region_data.view_distance
        props.is_persp = (region_data.view_perspective == 'PERSP')
        
        if props.is_persp:
            lens = space.camera.data.lens if (space.camera and space.region_3d.view_perspective == 'CAMERA') else space.lens
            sensor = space.camera.data.sensor_width if (space.camera and space.region_3d.view_perspective == 'CAMERA') else 32.0 
            fov = 2 * math.atan(sensor / (2 * lens))
            props.view_width = 2 * props.view_dist * math.tan(fov / 2)
            props.view_height = props.view_width * (region.height / region.width)
        else:
            props.view_width = props.view_dist * 2 
            props.view_height = props.view_width * (region.height / region.width)
        return {'FINISHED'}

class OT_CopyInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_info"
    bl_label = "情報をコピー"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        text = (
            f"中央位置: {props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f}\n"
            f"水平サイズ: {props.view_width:.1f}\n"
            f"上下サイズ: {props.view_height:.1f}\n"
        )
        text += f"視座距離: {props.view_dist:.1f}" if props.is_persp else "視座距離: (平行投影)"
        context.window_manager.clipboard = text
        self.report({'INFO'}, "情報をクリップボードにコピーしました")
        return {'FINISHED'}

class OT_Create(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_object"
    bl_label = "作成・同期"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for obj in context.scene.objects:
            if obj.get(CTRL_FLAG) == True: obj[CTRL_FLAG] = False
                
        mesh = bpy.data.meshes.new(MESH_NAME)
        obj = bpy.data.objects.new(props.obj_name, mesh)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        col.objects.link(obj)
        
        obj[CTRL_FLAG] = True
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_controlled_object(props, context)
        self.report({'INFO'}, "オブジェクトを生成しました")
        return {'FINISHED'}

class OT_Detach(bpy.types.Operator):
    bl_idname = f"{PREFIX}.detach_object"
    bl_label = "切り離す"
    def execute(self, context):
        obj = context.active_object
        if not obj or obj.get(CTRL_FLAG) != True:
            return {'CANCELLED'}
            
        obj[CTRL_FLAG] = False
        props = getattr(context.scene, PROP_NAME)
        
        if obj.data.materials:
            live_mat = obj.data.materials[0]
            if live_mat and live_mat.name == MAT_NAME:
                c = props.color
                rgb_str = f"{round(c[0],2)}_{round(c[1],2)}_{round(c[2],2)}"
                new_mat_name = f"{PREFIX}_Mat_{rgb_str}"
                exist_mat = bpy.data.materials.get(new_mat_name)
                if exist_mat: obj.data.materials[0] = exist_mat
                else:
                    new_mat = live_mat.copy()
                    new_mat.name = new_mat_name
                    obj.data.materials[0] = new_mat
                    
        if obj.data.name == MESH_NAME:
            new_mesh = obj.data.copy()
            new_mesh.name = f"Mesh_{obj.name}"
            obj.data = new_mesh
            
        self.report({'INFO'}, "完全に切り離しました")
        return {'FINISHED'}

def load_image_helper(self, context, mode):
    props = getattr(context.scene, PROP_NAME)
    path = bpy.path.abspath(props.image_filepath)
    if not path: return {'CANCELLED'}
    try:
        img = bpy.data.images.load(path)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        
        if mode in ('REF', 'BG'):
            empty = bpy.data.objects.new(f"{props.obj_name}_{mode}", img)
            col.objects.link(empty)
            empty.empty_display_type = 'IMAGE'
            if mode == 'BG': empty.empty_image_depth = 'BACK'
            empty.location, empty.rotation_euler = props.location, props.rotation
            
        elif mode == 'PLANE':
            mesh = bpy.data.meshes.new("ImgPlane")
            obj = bpy.data.objects.new(f"{props.obj_name}_Plane", mesh)
            col.objects.link(obj)
            aspect = img.size[0] / img.size[1] if img.size[1] > 0 else 1.0
            bm = bmesh.new()
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
            for v in bm.verts: v.co.x *= aspect
            bm.to_mesh(mesh)
            bm.free()
            
            mat = bpy.data.materials.new("ImgMat")
            mat.use_nodes = True
            tex = mat.node_tree.nodes.new("ShaderNodeTexImage")
            tex.image = img
            
            bsdf = next((node for node in mat.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'), None)
            if bsdf:
                mat.node_tree.links.new(tex.outputs['Color'], bsdf.inputs['Base Color'])
                
            obj.data.materials.append(mat)
            obj.location, obj.rotation_euler = props.location, props.rotation
            
        elif mode == 'CAM':
            cam = context.scene.camera
            if not cam:
                cam = bpy.data.objects.new("Camera", bpy.data.cameras.new("Camera"))
                context.scene.collection.objects.link(cam)
                context.scene.camera = cam
            cam.data.show_background_images = True
            cam.data.background_images.new().image = img
    except Exception as e:
        self.report({'ERROR'}, str(e))
    return {'FINISHED'}

class OT_AddImageRef(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_ref", "参照画像", lambda self, context: load_image_helper(self, context, 'REF')
class OT_AddImageBg(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_bg", "背景画像", lambda self, context: load_image_helper(self, context, 'BG')
class OT_AddImagePlane(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_plane", "画像プレーン", lambda self, context: load_image_helper(self, context, 'PLANE')
class OT_AddImageCam(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_cam", "カメラ背景", lambda self, context: load_image_helper(self, context, 'CAM')

class OT_ResetDefaults(bpy.types.Operator):
    bl_idname = f"{PREFIX}.reset_defaults"
    bl_label = "初期値に戻す"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for k in props.bl_rna.properties.keys():
            if k not in {"rna_type", "image_filepath"}: props.property_unset(k)
        return {'FINISHED'}

class OT_DisableAddon(bpy.types.Operator):
    bl_idname = f"{PREFIX}.disable_addon"
    bl_label = "Unregister Addon"
    bl_description = "このアドオンの登録を解除し、UIから消去します"
    def execute(self, context):
        bpy.app.timers.register(unregister, first_interval=0.01)
        self.report({'INFO'}, f"[{ADDON_TITLE}] を無効化・解除しました。")
        return {'FINISHED'}

# =========================================================
# UI パネル定義
# =========================================================
class PT_Base(bpy.types.Panel):
    bl_space_type, bl_region_type, bl_category = 'VIEW_3D', 'UI', ADDON_TITLE

class PT_1_View(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_1", "1. 視点・座標制御", 0
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.operator(f"{PREFIX}.move_to_center", icon='VIEWZOOM')
        box = layout.box()
        row = box.row()
        row.label(text=f"中央: ({props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f})")
        row.operator(f"{PREFIX}.update_view_info", text="", icon='FILE_REFRESH')
        box.label(text=f"水平: {props.view_width:.1f} / 上下: {props.view_height:.1f}")
        box.label(text=f"距離: {props.view_dist:.1f} (透視投影)" if props.is_persp else "距離: --- (平行投影)")
        box.operator(f"{PREFIX}.copy_info", icon='COPYDOWN')

class PT_2_Generate(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_2", "2. 生成・切り離し", 1
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "shape_enum", text="形状選択")
        
        col = layout.column(align=True)
        col.scale_y = 1.1
        col.operator(f"{PREFIX}.create_object", text="作成 / 同期", icon='ADD')
        col.operator(f"{PREFIX}.detach_object", text="アドオンから切り離す", icon='UNLINKED')

class PT_3_Realtime(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_3", "3. リアルタイム調整", 2
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        
        box = layout.box()
        box.prop(props, "color")
        
        is_line = props.shape_enum in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
        is_torus = props.shape_enum in {'SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS'}
        
        box.label(text="[ トランスフォーム ]", icon='OUTLINER_OB_EMPTY')
        box.prop(props, "location")
        box.prop(props, "rotation")
        box.prop(props, "scale_vec")
        
        if is_line:
            box.label(text="[ 2点軸パラメータ ]", icon='TRACKING')
            box.prop(props, "p1")
            box.prop(props, "p2")
            row = box.row()
            row.prop(props, "r1")
            row.prop(props, "r2")
            box.prop(props, "segments")
            row2 = box.row()
            row2.prop(props, "cap_top")
            row2.prop(props, "cap_bottom")
        elif props.shape_enum == 'TEXT':
            box.label(text="[ テキストパラメータ ]", icon='SORTALPHA')
            box.prop(props, "input_text")
            box.prop(props, "text_extrude")
        else:
            if is_torus: box.prop(props, "torus_pipe_radius")
            if props.shape_enum not in {'CUBE', 'MONKEY', 'SQUARE_FACE', 'SQUARE_TORUS'}:
                box.prop(props, "segments")

class PT_4_IO(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_4", "4. 画像・背景追加", 3
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        box = layout.box()
        box.prop(props, "image_filepath", text="画像パス")
        grid = box.grid_flow(columns=2, align=True)
        grid.operator(f"{PREFIX}.add_img_ref", icon='EMPTY_DATA')
        grid.operator(f"{PREFIX}.add_img_bg", icon='IMAGE_BACKGROUND')
        grid.operator(f"{PREFIX}.add_img_plane", icon='MESH_PLANE')
        grid.operator(f"{PREFIX}.add_img_cam", icon='OUTLINER_OB_CAMERA')

class PT_5_Settings(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_5", "5. 初期設定", 4
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "col_name")
        layout.prop(props, "subcol_name")
        layout.prop(props, "obj_name")
        layout.operator(f"{PREFIX}.reset_defaults", icon='LOOP_BACK')

class PT_6_Link(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_6", "6. リンク", 5
    def draw(self, context):
        self.layout.operator("wm.url_open", text="公式ドキュメント", icon='URL').url = "<https://www.blender.org/>"

class PT_7_Delete(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_7", "7. アドオン削除パネル", 6
    bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context):
        self.layout.label(text="このアドオンを直ちに消去します", icon='ERROR')
        self.layout.operator(f"{PREFIX}.disable_addon", icon='CANCEL')

# =========================================================
# 登録処理
# =========================================================
classes = (
    AddonProperties,
    OT_MoveToCenter, OT_UpdateViewInfo, OT_CopyInfo,
    OT_Create, OT_Detach,
    OT_AddImageRef, OT_AddImageBg, OT_AddImagePlane, OT_AddImageCam,
    OT_ResetDefaults, OT_DisableAddon,
    PT_1_View, PT_2_Generate, PT_3_Realtime, PT_4_IO, PT_5_Settings, PT_6_Link, PT_7_Delete,
)

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

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

if __name__ == "__main__":
    register()

bl_info = {
    "name": "zionad, オブジェクト統合ジェネレーター",
    "author": "zionad & AI Assistant",
    "version": (2, 0, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar",
    "description": "全図形対応、バグ完全修正(色反映・メモリリーク対策済み)、安全解除機能",
    "category": "3D View",
}

# 【作成日】 2026年7月12日 

# 【初期設定・定数定義】
ADDON_TITLE = "図形 All-in-One"
PREFIX = "zukkei2026"
DEFAULT_COLLECTION_NAME = "Zion_Collection"
DEFAULT_SUBCOLLECTION_NAME = "Zion_SubCollection"
DEFAULT_OBJECT_NAME = "Zion_Object"
DEFAULT_COLOR = (0.1, 0.6, 0.8, 1.0)
DEFAULT_P1_X = 0.0
DEFAULT_P1_Y = 0.0
DEFAULT_P1_Z = 2.0
DEFAULT_P2_X = 0.0
DEFAULT_P2_Y = 0.0
DEFAULT_P2_Z = 0.0
DEFAULT_R1 = 0.5
DEFAULT_R2 = 1.0
DEFAULT_SEGMENTS = 32

import bpy
import bmesh
import math
import mathutils

PROP_NAME = f"{PREFIX}_props"
CTRL_FLAG = f"is_{PREFIX}_controlled"
MAT_NAME = f"{PREFIX}_Live_Material"
MESH_NAME = f"{PREFIX}_Live_Mesh"

# =========================================================
# コールバック関数 (リアルタイムメッシュ更新)
# =========================================================
def update_controlled_object(self, context):
    obj = context.view_layer.objects.active
    if not obj or obj.get(CTRL_FLAG) != True:
        return
        
    shape = self.shape_enum
    
    # どの図形でもXYZの位置・回転・サイズ(スケール)を適用
    obj.location = self.location
    obj.rotation_euler = self.rotation
    obj.scale = self.scale_vec

    # 【修正1】 先にメッシュ形状を再構築する(トーラスなどでメッシュがすり替わるため)
    rebuild_mesh_geometry(obj, shape, self)

    # 【修正1】 メッシュ再構築後にマテリアルと色を確実にアサインする
    mat = bpy.data.materials.get(MAT_NAME)
    if not mat:
        mat = bpy.data.materials.new(name=MAT_NAME)
        
    if len(obj.data.materials) == 0:
        obj.data.materials.append(mat)
    elif obj.data.materials[0] != mat:
        obj.data.materials[0] = mat
        
    mat.use_nodes = True
    mat.blend_method = 'BLEND' if self.color[3] < 1.0 else 'OPAQUE'
    
    # ソリッド(ビューポート)モード用カラー
    mat.diffuse_color = self.color
    
    # ノード名ではなくノードタイプで確実にBSDFを取得
    bsdf = next((node for node in mat.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'), None)
    if bsdf:
        if "Base Color" in bsdf.inputs:
            bsdf.inputs["Base Color"].default_value = self.color
        if "Alpha" in bsdf.inputs:
            bsdf.inputs["Alpha"].default_value = self.color[3]

def update_shape_enum(self, context):
    shape = self.shape_enum
    if shape == 'CYLINDER':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 1.0, 1.0
        self.cap_top, self.cap_bottom = True, True
    elif shape == 'CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.0, 1.0
        self.cap_top, self.cap_bottom = False, True
    elif shape == 'TRUNCATED_CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
        self.cap_top, self.cap_bottom = False, False
    elif shape == 'SKEW_CONE':
        self.p1, self.p2 = (1, 1, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
        self.cap_top, self.cap_bottom = False, False
        
    update_controlled_object(self, context)

# =========================================================
# 形状生成ロジック統合 (分割数対応)
# =========================================================
def create_torus_mesh_data(shape_type, props):
    seg = max(3, props.segments)
    if shape_type == 'SQUARE_TORUS':
        pts = [mathutils.Vector((-1,-1,0)), mathutils.Vector((1,-1,0)), mathutils.Vector((1,1,0)), mathutils.Vector((-1,1,0))]
    elif shape_type == 'CIRCLE_TORUS':
        pts = [mathutils.Vector((math.cos(a), math.sin(a), 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    elif shape_type == 'ELLIPSE_TORUS':
        pts = [mathutils.Vector((math.cos(a)*1.5, math.sin(a)*0.75, 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    else: return None
        
    curve_data = bpy.data.curves.new(name="TempTorusPath", type='CURVE')
    curve_data.dimensions = '3D'
    spline = curve_data.splines.new(type='BEZIER')
    spline.use_cyclic_u = True
    spline.bezier_points.add(len(pts) - 1)
    for i, pt in enumerate(pts):
        bp = spline.bezier_points[i]
        bp.co = pt
        bp.handle_left_type = 'VECTOR'
        bp.handle_right_type = 'VECTOR'
        
    curve_data.bevel_depth = props.torus_pipe_radius
    curve_data.bevel_mode = 'ROUND'
    curve_data.bevel_resolution = max(1, props.segments // 8)
    
    temp_obj = bpy.data.objects.new("TempTorusObj", curve_data)
    bpy.context.scene.collection.objects.link(temp_obj)
    
    depsgraph = bpy.context.evaluated_depsgraph_get()
    object_eval = temp_obj.evaluated_get(depsgraph)
    mesh = bpy.data.meshes.new_from_object(object_eval)
    
    # 【修正2】 メモリリーク防止。不要になったオブジェクトとカーブのデータ両方を確実に消去する
    bpy.data.objects.remove(temp_obj)
    bpy.data.curves.remove(curve_data) 
    
    return mesh

def rebuild_mesh_geometry(obj, shape_type, props):
    is_torus = shape_type in ('SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS')
    
    if is_torus:
        new_mesh = create_torus_mesh_data(shape_type, props)
        if new_mesh:
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh and old_mesh.name == MESH_NAME:
                bpy.data.meshes.remove(old_mesh)
            return

    if obj.data.name != MESH_NAME:
        old_mesh = obj.data
        obj.data = bpy.data.meshes.new(MESH_NAME)
        if old_mesh and old_mesh.users == 0:
            bpy.data.meshes.remove(old_mesh)

    mesh = obj.data
    bm = bmesh.new()
    is_line_shape = shape_type in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
    
    if is_line_shape:
        p1, p2 = mathutils.Vector(props.p1), mathutils.Vector(props.p2)
        r1, r2 = props.r1, props.r2
        segments = max(3, props.segments)
        
        u = mathutils.Vector((1, 0, 0))
        v = mathutils.Vector((0, 1, 0))
        
        verts_p1, verts_p2 = [], []
        for i in range(segments):
            angle = 2.0 * math.pi * i / segments
            cos_a, sin_a = math.cos(angle), math.sin(angle)
            verts_p2.append(bm.verts.new(p2 + (u * cos_a + v * sin_a) * r2))
            verts_p1.append(bm.verts.new(p1 + (u * cos_a + v * sin_a) * r1))
            
        for i in range(segments):
            next_i = (i + 1) % segments
            bm.faces.new((verts_p2[i], verts_p2[next_i], verts_p1[next_i], verts_p1[i]))
            
        if props.cap_bottom and r2 > 0.001: bm.faces.new(reversed(verts_p2))
        if props.cap_top and r1 > 0.001: bm.faces.new(verts_p1)
        bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
        
    else:
        seg = max(3, props.segments)
        if shape_type == 'SQUARE_FACE':
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
        elif shape_type == 'CIRCLE_FACE':
            # 【修正4】 中心に不要な頂点を作らず、綺麗な1枚の面(N-gon)にする
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=False, radius=0.5, segments=seg)
        elif shape_type == 'ELLIPSE_FACE':
            # 【修正4】 同じく綺麗な1枚の面にする
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=False, radius=0.5, segments=seg)
            for v in bm.verts: v.co.y *= 0.5
        elif shape_type == 'MONKEY':
            bmesh.ops.create_monkey(bm)
        elif shape_type == 'UV_SPHERE':
            bmesh.ops.create_uvsphere(bm, u_segments=seg, v_segments=max(3, seg//2), radius=0.5)
        elif shape_type == 'CUBE':
            bmesh.ops.create_cube(bm, size=1.0)
        elif shape_type == 'ICO_SPHERE':
            subdiv = max(1, min(6, seg // 10)) 
            bmesh.ops.create_icosphere(bm, subdivisions=subdiv, radius=0.5)
        elif shape_type == 'GRID':
            grid_seg = max(2, seg)
            bmesh.ops.create_grid(bm, x_segments=grid_seg, y_segments=grid_seg, size=1.0)
            
    bm.to_mesh(mesh)
    bm.free()
    mesh.update()

# =========================================================
# プロパティグループ定義
# =========================================================
class AddonProperties(bpy.types.PropertyGroup):
    center_x: bpy.props.FloatProperty(name="Center X", default=0.0)
    center_y: bpy.props.FloatProperty(name="Center Y", default=0.0)
    center_z: bpy.props.FloatProperty(name="Center Z", default=0.0)
    view_width: bpy.props.FloatProperty(name="Width", default=0.0)
    view_height: bpy.props.FloatProperty(name="Height", default=0.0)
    view_dist: bpy.props.FloatProperty(name="Distance", default=0.0)
    is_persp: bpy.props.BoolProperty(default=True)
    
    col_name: bpy.props.StringProperty(name="Collection", default=DEFAULT_COLLECTION_NAME)
    subcol_name: bpy.props.StringProperty(name="Sub Collection", default=DEFAULT_SUBCOLLECTION_NAME)
    obj_name: bpy.props.StringProperty(name="Object Name", default=DEFAULT_OBJECT_NAME)
    
    shape_enum: bpy.props.EnumProperty(
        name="Shape",
        items=[
            ('CYLINDER', "Cylinder (円柱)", ""),
            ('CONE', "Cone (円錐)", ""),
            ('TRUNCATED_CONE', "Truncated Cone (円錐台)", ""),
            ('SKEW_CONE', "Skew Cone (斜め円錐台)", ""),
            ('SQUARE_FACE', "Square Face (正方形の面)", ""),
            ('CIRCLE_FACE', "Circle Face (円の面)", ""),
            ('ELLIPSE_FACE', "Ellipse Face (楕円の面)", ""),
            ('SQUARE_TORUS', "Square Torus (正方形トーラス)", ""),
            ('CIRCLE_TORUS', "Circle Torus (円形トーラス)", ""),
            ('ELLIPSE_TORUS', "Ellipse Torus (楕円形トーラス)", ""),
            ('MONKEY', "Monkey (モンキー)", ""),
            ('UV_SPHERE', "UV Sphere (UV球)", ""),
            ('CUBE', "Cube (立方体)", ""),
            ('ICO_SPHERE', "Ico Sphere (ICO球)", ""),
            ('GRID', "Grid (グリッド)", ""),
        ],
        default='CYLINDER',
        update=update_shape_enum
    )
    
    color: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', size=4, default=DEFAULT_COLOR, min=0.0, max=1.0, update=update_controlled_object)
    
    location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), subtype='TRANSLATION', update=update_controlled_object)
    rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), subtype='EULER', update=update_controlled_object)
    scale_vec: bpy.props.FloatVectorProperty(name="サイズ", default=(1.0, 1.0, 1.0), subtype='XYZ', update=update_controlled_object)
    
    segments: bpy.props.IntProperty(name="分割数 / 頂点数", default=DEFAULT_SEGMENTS, min=3, max=512, update=update_controlled_object)
    
    p1: bpy.props.FloatVectorProperty(name="上面中心", default=(DEFAULT_P1_X, DEFAULT_P1_Y, DEFAULT_P1_Z), update=update_controlled_object)
    p2: bpy.props.FloatVectorProperty(name="下面中心", default=(DEFAULT_P2_X, DEFAULT_P2_Y, DEFAULT_P2_Z), update=update_controlled_object)
    r1: bpy.props.FloatProperty(name="上面半径", default=DEFAULT_R1, min=0.0, update=update_controlled_object)
    r2: bpy.props.FloatProperty(name="下面半径", default=DEFAULT_R2, min=0.0, update=update_controlled_object)
    cap_top: bpy.props.BoolProperty(name="上面を塞ぐ", default=True, update=update_controlled_object)
    cap_bottom: bpy.props.BoolProperty(name="下面を塞ぐ", default=True, update=update_controlled_object)

    torus_pipe_radius: bpy.props.FloatProperty(name="Pipe Thickness", default=0.1, min=0.01, update=update_controlled_object)
    image_filepath: bpy.props.StringProperty(name="Image Path", subtype='FILE_PATH', default="")

# =========================================================
# オペレーター (機能)
# =========================================================
def get_or_create_collection(context, main_name, sub_name):
    main_col = bpy.data.collections.get(main_name)
    if not main_col:
        main_col = bpy.data.collections.new(main_name)
        context.scene.collection.children.link(main_col)
    sub_col = bpy.data.collections.get(sub_name)
    if not sub_col:
        sub_col = bpy.data.collections.new(sub_name)
        main_col.children.link(sub_col)
    return sub_col

class OT_MoveToCenter(bpy.types.Operator):
    bl_idname = f"{PREFIX}.move_to_center"
    bl_label = "選択オブジェクトを画面中央へ"
    def execute(self, context):
        obj = context.active_object
        if obj and context.region_data:
            obj.location = context.region_data.view_location
            self.report({'INFO'}, "画面中央に移動しました")
        return {'FINISHED'}

class OT_UpdateViewInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.update_view_info"
    bl_label = "画面情報を更新"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        region = context.region
        region_data = context.region_data
        space = context.space_data
        if not region_data: return {'CANCELLED'}
            
        loc = region_data.view_location
        props.center_x, props.center_y, props.center_z = loc.x, loc.y, loc.z
        props.view_dist = region_data.view_distance
        props.is_persp = (region_data.view_perspective == 'PERSP')
        
        if props.is_persp:
            lens = space.camera.data.lens if (space.camera and space.region_3d.view_perspective == 'CAMERA') else space.lens
            sensor = space.camera.data.sensor_width if (space.camera and space.region_3d.view_perspective == 'CAMERA') else 32.0 
            fov = 2 * math.atan(sensor / (2 * lens))
            props.view_width = 2 * props.view_dist * math.tan(fov / 2)
            props.view_height = props.view_width * (region.height / region.width)
        else:
            props.view_width = props.view_dist * 2 
            props.view_height = props.view_width * (region.height / region.width)
        return {'FINISHED'}

class OT_CopyInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_info"
    bl_label = "情報をコピー"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        text = (
            f"中央位置: {props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f}\n"
            f"水平サイズ: {props.view_width:.1f}\n"
            f"上下サイズ: {props.view_height:.1f}\n"
        )
        text += f"視座距離: {props.view_dist:.1f}" if props.is_persp else "視座距離: (平行投影)"
        context.window_manager.clipboard = text
        self.report({'INFO'}, "情報をクリップボードにコピーしました")
        return {'FINISHED'}

class OT_Create(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_object"
    bl_label = "作成・同期"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for obj in context.scene.objects:
            if obj.get(CTRL_FLAG) == True: obj[CTRL_FLAG] = False
                
        mesh = bpy.data.meshes.new(MESH_NAME)
        obj = bpy.data.objects.new(props.obj_name, mesh)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        col.objects.link(obj)
        
        obj[CTRL_FLAG] = True
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_controlled_object(props, context)
        self.report({'INFO'}, "オブジェクトを生成しました")
        return {'FINISHED'}

class OT_Detach(bpy.types.Operator):
    bl_idname = f"{PREFIX}.detach_object"
    bl_label = "切り離す"
    def execute(self, context):
        obj = context.active_object
        if not obj or obj.get(CTRL_FLAG) != True:
            return {'CANCELLED'}
            
        obj[CTRL_FLAG] = False
        props = getattr(context.scene, PROP_NAME)
        
        if obj.data.materials:
            live_mat = obj.data.materials[0]
            if live_mat and live_mat.name == MAT_NAME:
                c = props.color
                rgb_str = f"{round(c[0],2)}_{round(c[1],2)}_{round(c[2],2)}"
                new_mat_name = f"{PREFIX}_Mat_{rgb_str}"
                exist_mat = bpy.data.materials.get(new_mat_name)
                if exist_mat: obj.data.materials[0] = exist_mat
                else:
                    new_mat = live_mat.copy()
                    new_mat.name = new_mat_name
                    obj.data.materials[0] = new_mat
                    
        if obj.data.name == MESH_NAME:
            new_mesh = obj.data.copy()
            new_mesh.name = f"Mesh_{obj.name}"
            obj.data = new_mesh
            
        self.report({'INFO'}, "完全に切り離しました")
        return {'FINISHED'}

class OT_PasteText(bpy.types.Operator):
    bl_idname = f"{PREFIX}.paste_text"
    bl_label = "Paste Clipboard as Text"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        txt = context.window_manager.clipboard
        if not txt or txt.strip() == "": return {'CANCELLED'}
        font_curve = bpy.data.curves.new(name="Text_Curve", type='FONT')
        font_curve.body = txt
        font_curve.extrude = 0.05
        obj = bpy.data.objects.new(f"{props.obj_name}_Text", font_curve)
        get_or_create_collection(context, props.col_name, props.subcol_name).objects.link(obj)
        obj.location = props.location
        obj.rotation_euler = props.rotation
        obj.scale = props.scale_vec
        return {'FINISHED'}

def load_image_helper(self, context, mode):
    props = getattr(context.scene, PROP_NAME)
    path = bpy.path.abspath(props.image_filepath)
    if not path: return {'CANCELLED'}
    try:
        img = bpy.data.images.load(path)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        
        if mode in ('REF', 'BG'):
            # 【修正3】 Emptyに安全に画像をセットする
            empty = bpy.data.objects.new(f"{props.obj_name}_{mode}", img)
            col.objects.link(empty)
            empty.empty_display_type = 'IMAGE'
            if mode == 'BG': empty.empty_image_depth = 'BACK'
            empty.location, empty.rotation_euler = props.location, props.rotation
            
        elif mode == 'PLANE':
            mesh = bpy.data.meshes.new("ImgPlane")
            obj = bpy.data.objects.new(f"{props.obj_name}_Plane", mesh)
            col.objects.link(obj)
            aspect = img.size[0] / img.size[1] if img.size[1] > 0 else 1.0
            bm = bmesh.new()
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
            for v in bm.verts: v.co.x *= aspect
            bm.to_mesh(mesh)
            bm.free()
            
            mat = bpy.data.materials.new("ImgMat")
            mat.use_nodes = True
            tex = mat.node_tree.nodes.new("ShaderNodeTexImage")
            tex.image = img
            
            # 【修正3】 日本語UIなどでも確実にPrincipled BSDFノードを取得しエラーを防ぐ
            bsdf = next((node for node in mat.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'), None)
            if bsdf:
                mat.node_tree.links.new(tex.outputs['Color'], bsdf.inputs['Base Color'])
                
            obj.data.materials.append(mat)
            obj.location, obj.rotation_euler = props.location, props.rotation
            
        elif mode == 'CAM':
            cam = context.scene.camera
            if not cam:
                cam = bpy.data.objects.new("Camera", bpy.data.cameras.new("Camera"))
                context.scene.collection.objects.link(cam)
                context.scene.camera = cam
            cam.data.show_background_images = True
            cam.data.background_images.new().image = img
    except Exception as e:
        self.report({'ERROR'}, str(e))
    return {'FINISHED'}

class OT_AddImageRef(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_ref", "参照画像", lambda self, context: load_image_helper(self, context, 'REF')
class OT_AddImageBg(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_bg", "背景画像", lambda self, context: load_image_helper(self, context, 'BG')
class OT_AddImagePlane(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_plane", "画像プレーン", lambda self, context: load_image_helper(self, context, 'PLANE')
class OT_AddImageCam(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_cam", "カメラ背景", lambda self, context: load_image_helper(self, context, 'CAM')

class OT_ResetDefaults(bpy.types.Operator):
    bl_idname = f"{PREFIX}.reset_defaults"
    bl_label = "初期値に戻す"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for k in props.bl_rna.properties.keys():
            if k not in {"rna_type", "image_filepath"}: props.property_unset(k)
        return {'FINISHED'}

class OT_DisableAddon(bpy.types.Operator):
    bl_idname = f"{PREFIX}.disable_addon"
    bl_label = "Unregister Addon"
    bl_description = "このアドオンの登録を解除し、UIから消去します"
    def execute(self, context):
        # Timer経由で安全に解除
        bpy.app.timers.register(unregister, first_interval=0.01)
        self.report({'INFO'}, f"[{ADDON_TITLE}] を無効化・解除しました。")
        return {'FINISHED'}

# =========================================================
# UI パネル定義
# =========================================================
class PT_Base(bpy.types.Panel):
    bl_space_type, bl_region_type, bl_category = 'VIEW_3D', 'UI', ADDON_TITLE

class PT_1_View(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_1", "1. 視点・座標制御", 0
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.operator(f"{PREFIX}.move_to_center", icon='VIEWZOOM')
        box = layout.box()
        row = box.row()
        row.label(text=f"中央: ({props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f})")
        row.operator(f"{PREFIX}.update_view_info", text="", icon='FILE_REFRESH')
        box.label(text=f"水平: {props.view_width:.1f} / 上下: {props.view_height:.1f}")
        box.label(text=f"距離: {props.view_dist:.1f} (透視投影)" if props.is_persp else "距離: --- (平行投影)")
        box.operator(f"{PREFIX}.copy_info", icon='COPYDOWN')

class PT_2_Generate(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_2", "2. 生成・切り離し", 1
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "shape_enum", text="形状選択")
        
        col = layout.column(align=True)
        col.scale_y = 1.1
        col.operator(f"{PREFIX}.create_object", text="作成 / 同期", icon='ADD')
        col.operator(f"{PREFIX}.detach_object", text="アドオンから切り離す", icon='UNLINKED')

class PT_3_Realtime(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_3", "3. リアルタイム調整", 2
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        
        box = layout.box()
        box.prop(props, "color")
        
        is_line = props.shape_enum in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
        is_torus = props.shape_enum in {'SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS'}
        
        box.label(text="[ トランスフォーム ]", icon='OUTLINER_OB_EMPTY')
        box.prop(props, "location")
        box.prop(props, "rotation")
        box.prop(props, "scale_vec")
        
        if is_line:
            box.label(text="[ 2点軸パラメータ ]", icon='TRACKING')
            box.prop(props, "p1")
            box.prop(props, "p2")
            row = box.row()
            row.prop(props, "r1")
            row.prop(props, "r2")
            box.prop(props, "segments")
            row2 = box.row()
            row2.prop(props, "cap_top")
            row2.prop(props, "cap_bottom")
        else:
            if is_torus: box.prop(props, "torus_pipe_radius")
            if props.shape_enum not in {'CUBE', 'MONKEY', 'SQUARE_FACE', 'SQUARE_TORUS'}:
                box.prop(props, "segments")

class PT_4_IO(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_4", "4. テキスト & 画像追加", 3
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.operator(f"{PREFIX}.paste_text", text="テキストをペースト", icon='PASTEDOWN')
        box = layout.box()
        box.prop(props, "image_filepath", text="画像パス")
        grid = box.grid_flow(columns=2, align=True)
        grid.operator(f"{PREFIX}.add_img_ref", icon='EMPTY_DATA')
        grid.operator(f"{PREFIX}.add_img_bg", icon='IMAGE_BACKGROUND')
        grid.operator(f"{PREFIX}.add_img_plane", icon='MESH_PLANE')
        grid.operator(f"{PREFIX}.add_img_cam", icon='OUTLINER_OB_CAMERA')

class PT_5_Settings(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_5", "5. 初期設定", 4
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "col_name")
        layout.prop(props, "subcol_name")
        layout.prop(props, "obj_name")
        layout.operator(f"{PREFIX}.reset_defaults", icon='LOOP_BACK')

class PT_6_Link(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_6", "6. リンク", 5
    def draw(self, context):
        self.layout.operator("wm.url_open", text="公式ドキュメント", icon='URL').url = "<https://www.blender.org/>"

class PT_7_Delete(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_7", "7. アドオン削除パネル", 6
    bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context):
        self.layout.label(text="このアドオンを直ちに消去します", icon='ERROR')
        self.layout.operator(f"{PREFIX}.disable_addon", icon='CANCEL')

# =========================================================
# 登録処理
# =========================================================
classes = (
    AddonProperties,
    OT_MoveToCenter, OT_UpdateViewInfo, OT_CopyInfo,
    OT_Create, OT_Detach, OT_PasteText,
    OT_AddImageRef, OT_AddImageBg, OT_AddImagePlane, OT_AddImageCam,
    OT_ResetDefaults, OT_DisableAddon,
    PT_1_View, PT_2_Generate, PT_3_Realtime, PT_4_IO, PT_5_Settings, PT_6_Link, PT_7_Delete,
)

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

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

if __name__ == "__main__":
    register()

bl_info = {
    "name": "zionad, オブジェクト統合ジェネレーター",
    "author": "zionad & AI Assistant",
    "version": (1, 6, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar",
    "description": "全図形対応、画像・テキスト追加、色反映と分割数調整が完璧に連動する完全版",
    "category": "3D View",
}

# 【作成日】 2026年7月12日 06:45

# 【初期設定・定数定義】 (1行ずつ定義)
ADDON_TITLE = "図形 All-in-One"
PREFIX = "zukkei2026" # ※アンダースコアや英小文字のみ推奨
DEFAULT_COLLECTION_NAME = "Zion_Collection"
DEFAULT_SUBCOLLECTION_NAME = "Zion_SubCollection"
DEFAULT_OBJECT_NAME = "Zion_Object"
DEFAULT_COLOR = (0.1, 0.6, 0.8, 1.0)
DEFAULT_P1_X = 0.0
DEFAULT_P1_Y = 0.0
DEFAULT_P1_Z = 2.0
DEFAULT_P2_X = 0.0
DEFAULT_P2_Y = 0.0
DEFAULT_P2_Z = 0.0
DEFAULT_R1 = 0.5
DEFAULT_R2 = 1.0
DEFAULT_SEGMENTS = 32

import bpy
import bmesh
import math
import mathutils
import webbrowser
import addon_utils

PROP_NAME = f"{PREFIX}_props"
CTRL_FLAG = f"is_{PREFIX}_controlled"
MAT_NAME = f"{PREFIX}_Live_Material"
MESH_NAME = f"{PREFIX}_Live_Mesh"

# =========================================================
# コールバック関数 (リアルタイムメッシュ更新)
# =========================================================
def update_controlled_object(self, context):
    obj = context.view_layer.objects.active
    if not obj or obj.get(CTRL_FLAG) != True:
        return
        
    shape = self.shape_enum
    is_line_shape = shape in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
    
    if not is_line_shape:
        obj.location = self.location
        obj.rotation_euler = self.rotation
        obj.scale = (self.size, self.size, self.size)
    else:
        obj.location = (0.0, 0.0, 0.0)
        obj.rotation_euler = (0.0, 0.0, 0.0)
        obj.scale = (1.0, 1.0, 1.0)

    # ---------------------------------------------------------
    # 【修正】マテリアルと色の確実な反映(日本語UI対応)
    # ---------------------------------------------------------
    mat = bpy.data.materials.get(MAT_NAME)
    if not mat:
        mat = bpy.data.materials.new(name=MAT_NAME)
        
    if len(obj.data.materials) == 0:
        obj.data.materials.append(mat)
    elif obj.data.materials[0] != mat:
        obj.data.materials[0] = mat
        
    mat.use_nodes = True
    mat.blend_method = 'BLEND' if self.color[3] < 1.0 else 'OPAQUE'
    
    # ソリッド(ビューポート)モード用カラー
    mat.diffuse_color = self.color
    
    # ノード名ではなくノードタイプで確実にBSDFを取得
    bsdf = next((node for node in mat.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'), None)
    if bsdf:
        if "Base Color" in bsdf.inputs:
            bsdf.inputs["Base Color"].default_value = self.color
        if "Alpha" in bsdf.inputs:
            bsdf.inputs["Alpha"].default_value = self.color[3]
    
    # メッシュ形状再構築
    rebuild_mesh_geometry(obj, shape, self)

def update_shape_enum(self, context):
    shape = self.shape_enum
    if shape == 'CYLINDER':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 1.0, 1.0
    elif shape == 'CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.0, 1.0
    elif shape == 'TRUNCATED_CONE':
        self.p1, self.p2 = (0, 0, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
    elif shape == 'SKEW_CONE':
        self.p1, self.p2 = (1, 1, 2), (0, 0, 0)
        self.r1, self.r2 = 0.5, 1.0
        
    update_controlled_object(self, context)

# =========================================================
# 形状生成ロジック統合 (分割数対応)
# =========================================================
def create_torus_mesh_data(shape_type, props):
    seg = max(3, props.segments)  # 分割数を適用
    if shape_type == 'SQUARE_TORUS':
        pts = [mathutils.Vector((-1,-1,0)), mathutils.Vector((1,-1,0)), mathutils.Vector((1,1,0)), mathutils.Vector((-1,1,0))]
    elif shape_type == 'CIRCLE_TORUS':
        pts = [mathutils.Vector((math.cos(a), math.sin(a), 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    elif shape_type == 'ELLIPSE_TORUS':
        pts = [mathutils.Vector((math.cos(a)*1.5, math.sin(a)*0.75, 0)) for a in [2*math.pi*i/seg for i in range(seg)]]
    else: return None
        
    curve_data = bpy.data.curves.new(name="TempTorusPath", type='CURVE')
    curve_data.dimensions = '3D'
    spline = curve_data.splines.new(type='BEZIER')
    spline.use_cyclic_u = True
    spline.bezier_points.add(len(pts) - 1)
    for i, pt in enumerate(pts):
        bp = spline.bezier_points[i]
        bp.co = pt
        bp.handle_left_type = 'VECTOR'
        bp.handle_right_type = 'VECTOR'
        
    curve_data.bevel_depth = props.torus_pipe_radius
    curve_data.bevel_mode = 'ROUND'
    curve_data.bevel_resolution = max(1, props.segments // 8) # 管の解像度も分割数に連動
    
    temp_obj = bpy.data.objects.new("TempTorusObj", curve_data)
    bpy.context.scene.collection.objects.link(temp_obj)
    
    depsgraph = bpy.context.evaluated_depsgraph_get()
    object_eval = temp_obj.evaluated_get(depsgraph)
    mesh = bpy.data.meshes.new_from_object(object_eval)
    
    bpy.data.objects.remove(temp_obj)
    return mesh

def rebuild_mesh_geometry(obj, shape_type, props):
    is_torus = shape_type in ('SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS')
    
    if is_torus:
        new_mesh = create_torus_mesh_data(shape_type, props)
        if new_mesh:
            old_mesh = obj.data
            obj.data = new_mesh
            if old_mesh and old_mesh.name == MESH_NAME:
                bpy.data.meshes.remove(old_mesh)
            return

    if obj.data.name != MESH_NAME:
        old_mesh = obj.data
        obj.data = bpy.data.meshes.new(MESH_NAME)
        if old_mesh and old_mesh.users == 0:
            bpy.data.meshes.remove(old_mesh)

    mesh = obj.data
    bm = bmesh.new()
    is_line_shape = shape_type in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
    
    if is_line_shape:
        p1, p2 = mathutils.Vector(props.p1), mathutils.Vector(props.p2)
        r1, r2 = props.r1, props.r2
        segments = max(3, props.segments)
        
        axis = p1 - p2
        if axis.length < 0.0001: axis = mathutils.Vector((0, 0, 1))
        axis_norm = axis.normalized()
        
        up = mathutils.Vector((0, 0, 1)) if abs(axis_norm.z) < 0.99 else mathutils.Vector((1, 0, 0))
        u = up.cross(axis_norm).normalized()
        v = axis_norm.cross(u).normalized()
        
        verts_p1, verts_p2 = [], []
        for i in range(segments):
            angle = 2.0 * math.pi * i / segments
            cos_a, sin_a = math.cos(angle), math.sin(angle)
            verts_p2.append(bm.verts.new(p2 + (u * cos_a + v * sin_a) * r2))
            verts_p1.append(bm.verts.new(p1 + (u * cos_a + v * sin_a) * r1))
            
        for i in range(segments):
            next_i = (i + 1) % segments
            bm.faces.new((verts_p2[i], verts_p2[next_i], verts_p1[next_i], verts_p1[i]))
            
        if props.cap_bottom and r2 > 0.001: bm.faces.new(reversed(verts_p2))
        if props.cap_top and r1 > 0.001: bm.faces.new(verts_p1)
        bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
        
    else:
        # 他の図形に分割数 (segments) を反映
        seg = max(3, props.segments)
        if shape_type == 'SQUARE_FACE':
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
        elif shape_type == 'CIRCLE_FACE':
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=True, radius=0.5, segments=seg)
        elif shape_type == 'ELLIPSE_FACE':
            bmesh.ops.create_circle(bm, cap_ends=True, cap_tris=True, radius=0.5, segments=seg)
            for v in bm.verts: v.co.y *= 0.5
        elif shape_type == 'MONKEY':
            bmesh.ops.create_monkey(bm)
        elif shape_type == 'UV_SPHERE':
            bmesh.ops.create_uvsphere(bm, u_segments=seg, v_segments=max(3, seg//2), radius=0.5)
        elif shape_type == 'CUBE':
            bmesh.ops.create_cube(bm, size=1.0)
        elif shape_type == 'ICO_SPHERE':
            # Subdivisions は重くなりすぎるのを防ぐため、分割数をスケール変換
            subdiv = max(1, min(6, seg // 10)) 
            bmesh.ops.create_icosphere(bm, subdivisions=subdiv, radius=0.5)
        elif shape_type == 'GRID':
            grid_seg = max(2, seg)
            bmesh.ops.create_grid(bm, x_segments=grid_seg, y_segments=grid_seg, size=1.0)
            
    bm.to_mesh(mesh)
    bm.free()
    mesh.update()

# =========================================================
# プロパティグループ定義
# =========================================================
class AddonProperties(bpy.types.PropertyGroup):
    center_x: bpy.props.FloatProperty(name="Center X", default=0.0)
    center_y: bpy.props.FloatProperty(name="Center Y", default=0.0)
    center_z: bpy.props.FloatProperty(name="Center Z", default=0.0)
    view_width: bpy.props.FloatProperty(name="Width", default=0.0)
    view_height: bpy.props.FloatProperty(name="Height", default=0.0)
    view_dist: bpy.props.FloatProperty(name="Distance", default=0.0)
    is_persp: bpy.props.BoolProperty(default=True)
    
    col_name: bpy.props.StringProperty(name="Collection", default=DEFAULT_COLLECTION_NAME)
    subcol_name: bpy.props.StringProperty(name="Sub Collection", default=DEFAULT_SUBCOLLECTION_NAME)
    obj_name: bpy.props.StringProperty(name="Object Name", default=DEFAULT_OBJECT_NAME)
    
    shape_enum: bpy.props.EnumProperty(
        name="Shape",
        items=[
            ('CYLINDER', "Cylinder (円柱)", ""),
            ('CONE', "Cone (円錐)", ""),
            ('TRUNCATED_CONE', "Truncated Cone (円錐台)", ""),
            ('SKEW_CONE', "Skew Cone (斜め円錐台)", ""),
            ('SQUARE_FACE', "Square Face (正方形の面)", ""),
            ('CIRCLE_FACE', "Circle Face (円の面)", ""),
            ('ELLIPSE_FACE', "Ellipse Face (楕円の面)", ""),
            ('SQUARE_TORUS', "Square Torus (正方形トーラス)", ""),
            ('CIRCLE_TORUS', "Circle Torus (円形トーラス)", ""),
            ('ELLIPSE_TORUS', "Ellipse Torus (楕円形トーラス)", ""),
            ('MONKEY', "Monkey (モンキー)", ""),
            ('UV_SPHERE', "UV Sphere (UV球)", ""),
            ('CUBE', "Cube (立方体)", ""),
            ('ICO_SPHERE', "Ico Sphere (ICO球)", ""),
            ('GRID', "Grid (グリッド)", ""),
        ],
        default='CYLINDER',
        update=update_shape_enum
    )
    
    # 色とトランスフォーム
    color: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', size=4, default=DEFAULT_COLOR, min=0.0, max=1.0, update=update_controlled_object)
    size: bpy.props.FloatProperty(name="Size", default=1.0, min=0.1, update=update_controlled_object)
    location: bpy.props.FloatVectorProperty(name="Location", default=(0,0,0), subtype='TRANSLATION', update=update_controlled_object)
    rotation: bpy.props.FloatVectorProperty(name="Rotation", default=(0,0,0), subtype='EULER', update=update_controlled_object)
    
    # 汎用:分割数 (最大512まで設定可能)
    segments: bpy.props.IntProperty(name="分割数 / 頂点数", default=DEFAULT_SEGMENTS, min=3, max=512, update=update_controlled_object)
    
    # 2点指定用パラメータ
    p1: bpy.props.FloatVectorProperty(name="上面中心", default=(DEFAULT_P1_X, DEFAULT_P1_Y, DEFAULT_P1_Z), update=update_controlled_object)
    p2: bpy.props.FloatVectorProperty(name="下面中心", default=(DEFAULT_P2_X, DEFAULT_P2_Y, DEFAULT_P2_Z), update=update_controlled_object)
    r1: bpy.props.FloatProperty(name="上面半径", default=DEFAULT_R1, min=0.0, update=update_controlled_object)
    r2: bpy.props.FloatProperty(name="下面半径", default=DEFAULT_R2, min=0.0, update=update_controlled_object)
    cap_top: bpy.props.BoolProperty(name="上面を塞ぐ", default=True, update=update_controlled_object)
    cap_bottom: bpy.props.BoolProperty(name="下面を塞ぐ", default=True, update=update_controlled_object)

    torus_pipe_radius: bpy.props.FloatProperty(name="Pipe Thickness", default=0.1, min=0.01, update=update_controlled_object)
    image_filepath: bpy.props.StringProperty(name="Image Path", subtype='FILE_PATH', default="")

# =========================================================
# オペレーター (機能)
# =========================================================
def get_or_create_collection(context, main_name, sub_name):
    main_col = bpy.data.collections.get(main_name)
    if not main_col:
        main_col = bpy.data.collections.new(main_name)
        context.scene.collection.children.link(main_col)
    sub_col = bpy.data.collections.get(sub_name)
    if not sub_col:
        sub_col = bpy.data.collections.new(sub_name)
        main_col.children.link(sub_col)
    return sub_col

class OT_MoveToCenter(bpy.types.Operator):
    bl_idname = f"{PREFIX}.move_to_center"
    bl_label = "選択オブジェクトを画面中央へ"
    def execute(self, context):
        obj = context.active_object
        if obj and context.region_data:
            obj.location = context.region_data.view_location
            self.report({'INFO'}, "画面中央に移動しました")
        return {'FINISHED'}

class OT_UpdateViewInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.update_view_info"
    bl_label = "画面情報を更新"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        region = context.region
        region_data = context.region_data
        space = context.space_data
        if not region_data: return {'CANCELLED'}
            
        loc = region_data.view_location
        props.center_x, props.center_y, props.center_z = loc.x, loc.y, loc.z
        props.view_dist = region_data.view_distance
        props.is_persp = (region_data.view_perspective == 'PERSP')
        
        if props.is_persp:
            lens = space.camera.data.lens if (space.camera and space.region_3d.view_perspective == 'CAMERA') else space.lens
            sensor = space.camera.data.sensor_width if (space.camera and space.region_3d.view_perspective == 'CAMERA') else 32.0 
            fov = 2 * math.atan(sensor / (2 * lens))
            props.view_width = 2 * props.view_dist * math.tan(fov / 2)
            props.view_height = props.view_width * (region.height / region.width)
        else:
            props.view_width = props.view_dist * 2 
            props.view_height = props.view_width * (region.height / region.width)
        return {'FINISHED'}

class OT_CopyInfo(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_info"
    bl_label = "情報をコピー"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        text = (
            f"中央位置: {props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f}\n"
            f"水平サイズ: {props.view_width:.1f}\n"
            f"上下サイズ: {props.view_height:.1f}\n"
        )
        text += f"視座距離: {props.view_dist:.1f}" if props.is_persp else "視座距離: (平行投影)"
        context.window_manager.clipboard = text
        self.report({'INFO'}, "情報をクリップボードにコピーしました")
        return {'FINISHED'}

class OT_Create(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_object"
    bl_label = "作成・同期"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for obj in context.scene.objects:
            if obj.get(CTRL_FLAG) == True: obj[CTRL_FLAG] = False
                
        mesh = bpy.data.meshes.new(MESH_NAME)
        obj = bpy.data.objects.new(props.obj_name, mesh)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        col.objects.link(obj)
        
        obj[CTRL_FLAG] = True
        context.view_layer.objects.active = obj
        obj.select_set(True)
        update_controlled_object(props, context)
        self.report({'INFO'}, "オブジェクトを生成しました")
        return {'FINISHED'}

class OT_Detach(bpy.types.Operator):
    bl_idname = f"{PREFIX}.detach_object"
    bl_label = "切り離す"
    def execute(self, context):
        obj = context.active_object
        if not obj or obj.get(CTRL_FLAG) != True:
            return {'CANCELLED'}
            
        obj[CTRL_FLAG] = False
        props = getattr(context.scene, PROP_NAME)
        
        if obj.data.materials:
            live_mat = obj.data.materials[0]
            if live_mat and live_mat.name == MAT_NAME:
                c = props.color
                rgb_str = f"{round(c[0],2)}_{round(c[1],2)}_{round(c[2],2)}"
                new_mat_name = f"{PREFIX}_Mat_{rgb_str}"
                exist_mat = bpy.data.materials.get(new_mat_name)
                if exist_mat: obj.data.materials[0] = exist_mat
                else:
                    new_mat = live_mat.copy()
                    new_mat.name = new_mat_name
                    obj.data.materials[0] = new_mat
                    
        if obj.data.name == MESH_NAME:
            new_mesh = obj.data.copy()
            new_mesh.name = f"Mesh_{obj.name}"
            obj.data = new_mesh
            
        self.report({'INFO'}, "完全に切り離しました")
        return {'FINISHED'}

class OT_PasteText(bpy.types.Operator):
    bl_idname = f"{PREFIX}.paste_text"
    bl_label = "Paste Clipboard as Text"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        txt = context.window_manager.clipboard
        if not txt or txt.strip() == "": return {'CANCELLED'}
        font_curve = bpy.data.curves.new(name="Text_Curve", type='FONT')
        font_curve.body = txt
        font_curve.extrude = 0.05
        obj = bpy.data.objects.new(f"{props.obj_name}_Text", font_curve)
        get_or_create_collection(context, props.col_name, props.subcol_name).objects.link(obj)
        obj.location = props.location
        obj.rotation_euler = props.rotation
        obj.scale = (props.size, props.size, props.size)
        return {'FINISHED'}

def load_image_helper(self, context, mode):
    props = getattr(context.scene, PROP_NAME)
    path = bpy.path.abspath(props.image_filepath)
    if not path: return {'CANCELLED'}
    try:
        img = bpy.data.images.load(path)
        col = get_or_create_collection(context, props.col_name, props.subcol_name)
        
        if mode in ('REF', 'BG'):
            empty = bpy.data.objects.new(f"{props.obj_name}_{mode}", None)
            col.objects.link(empty)
            empty.empty_display_type = 'IMAGE'
            empty.data = img
            if mode == 'BG': empty.empty_image_depth = 'BACK'
            empty.location, empty.rotation_euler = props.location, props.rotation
            
        elif mode == 'PLANE':
            mesh = bpy.data.meshes.new("ImgPlane")
            obj = bpy.data.objects.new(f"{props.obj_name}_Plane", mesh)
            col.objects.link(obj)
            aspect = img.size[0] / img.size[1] if img.size[1] > 0 else 1.0
            bm = bmesh.new()
            bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=0.5)
            for v in bm.verts: v.co.x *= aspect
            bm.to_mesh(mesh)
            bm.free()
            mat = bpy.data.materials.new("ImgMat")
            mat.use_nodes = True
            tex = mat.node_tree.nodes.new("ShaderNodeTexImage")
            tex.image = img
            mat.node_tree.links.new(tex.outputs['Color'], mat.node_tree.nodes["Principled BSDF"].inputs['Base Color'])
            obj.data.materials.append(mat)
            obj.location, obj.rotation_euler = props.location, props.rotation
            
        elif mode == 'CAM':
            cam = context.scene.camera
            if not cam:
                cam = bpy.data.objects.new("Camera", bpy.data.cameras.new("Camera"))
                context.scene.collection.objects.link(cam)
                context.scene.camera = cam
            cam.data.show_background_images = True
            cam.data.background_images.new().image = img
    except Exception as e:
        self.report({'ERROR'}, str(e))
    return {'FINISHED'}

class OT_AddImageRef(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_ref", "参照画像", lambda self, context: load_image_helper(self, context, 'REF')
class OT_AddImageBg(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_bg", "背景画像", lambda self, context: load_image_helper(self, context, 'BG')
class OT_AddImagePlane(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_plane", "画像プレーン", lambda self, context: load_image_helper(self, context, 'PLANE')
class OT_AddImageCam(bpy.types.Operator): bl_idname, bl_label, execute = f"{PREFIX}.add_img_cam", "カメラ背景", lambda self, context: load_image_helper(self, context, 'CAM')

class OT_ResetDefaults(bpy.types.Operator):
    bl_idname = f"{PREFIX}.reset_defaults"
    bl_label = "初期値に戻す"
    def execute(self, context):
        props = getattr(context.scene, PROP_NAME)
        for k in props.bl_rna.properties.keys():
            if k not in {"rna_type", "image_filepath"}: props.property_unset(k)
        return {'FINISHED'}

class OT_DisableAddon(bpy.types.Operator):
    bl_idname = f"{PREFIX}.disable_addon"
    bl_label = "アドオンを完全に無効化"
    def execute(self, context):
        try:
            addon_utils.disable(__name__)
            self.report({'INFO'}, f"[{ADDON_TITLE}] を無効化しました。")
        except:
            self.report({'ERROR'}, "無効化に失敗しました。")
        return {'FINISHED'}

# =========================================================
# UI パネル定義
# =========================================================
class PT_Base(bpy.types.Panel):
    bl_space_type, bl_region_type, bl_category = 'VIEW_3D', 'UI', ADDON_TITLE

class PT_1_View(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_1", "1. 視点・座標制御", 0
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.operator(f"{PREFIX}.move_to_center", icon='VIEWZOOM')
        box = layout.box()
        row = box.row()
        row.label(text=f"中央: ({props.center_x:.1f}, {props.center_y:.1f}, {props.center_z:.1f})")
        row.operator(f"{PREFIX}.update_view_info", text="", icon='FILE_REFRESH')
        box.label(text=f"水平: {props.view_width:.1f} / 上下: {props.view_height:.1f}")
        box.label(text=f"距離: {props.view_dist:.1f} (透視投影)" if props.is_persp else "距離: --- (平行投影)")
        box.operator(f"{PREFIX}.copy_info", icon='COPYDOWN')

class PT_2_Generate(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_2", "2. 生成・切り離し", 1
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "shape_enum", text="形状選択")
        
        col = layout.column(align=True)
        col.scale_y = 1.1
        col.operator(f"{PREFIX}.create_object", text="作成 / 同期", icon='ADD')
        col.operator(f"{PREFIX}.detach_object", text="アドオンから切り離す", icon='UNLINKED')

class PT_3_Realtime(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_3", "3. リアルタイム調整", 2
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        
        box = layout.box()
        box.prop(props, "color")
        
        is_line = props.shape_enum in {'CYLINDER', 'CONE', 'TRUNCATED_CONE', 'SKEW_CONE'}
        is_torus = props.shape_enum in {'SQUARE_TORUS', 'CIRCLE_TORUS', 'ELLIPSE_TORUS'}
        
        if is_line:
            box.label(text="[ 2点軸パラメータ ]", icon='TRACKING')
            box.prop(props, "p1")
            box.prop(props, "p2")
            row = box.row()
            row.prop(props, "r1")
            row.prop(props, "r2")
            box.prop(props, "segments")
            row2 = box.row()
            row2.prop(props, "cap_top")
            row2.prop(props, "cap_bottom")
        else:
            box.label(text="[ トランスフォーム ]", icon='OUTLINER_OB_EMPTY')
            box.prop(props, "size")
            box.prop(props, "location")
            box.prop(props, "rotation")
            if is_torus: box.prop(props, "torus_pipe_radius")
            
            # 分割数を持たない一部の図形以外では「分割数」を表示
            if props.shape_enum not in {'CUBE', 'MONKEY', 'SQUARE_FACE', 'SQUARE_TORUS'}:
                box.prop(props, "segments")

class PT_4_IO(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_4", "4. テキスト & 画像追加", 3
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.operator(f"{PREFIX}.paste_text", text="テキストをペースト", icon='PASTEDOWN')
        box = layout.box()
        box.prop(props, "image_filepath", text="画像パス")
        grid = box.grid_flow(columns=2, align=True)
        grid.operator(f"{PREFIX}.add_img_ref", icon='EMPTY_DATA')
        grid.operator(f"{PREFIX}.add_img_bg", icon='IMAGE_BACKGROUND')
        grid.operator(f"{PREFIX}.add_img_plane", icon='MESH_PLANE')
        grid.operator(f"{PREFIX}.add_img_cam", icon='OUTLINER_OB_CAMERA')

class PT_5_Settings(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_5", "5. 初期設定", 4
    def draw(self, context):
        props = getattr(context.scene, PROP_NAME)
        layout = self.layout
        layout.prop(props, "col_name")
        layout.prop(props, "subcol_name")
        layout.prop(props, "obj_name")
        layout.operator(f"{PREFIX}.reset_defaults", icon='LOOP_BACK')

class PT_6_Link(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_6", "6. リンク", 5
    def draw(self, context):
        self.layout.operator("wm.url_open", text="公式ドキュメント", icon='URL').url = "<https://www.blender.org/>"

class PT_7_Delete(PT_Base):
    bl_idname, bl_label, bl_order = f"VIEW3D_PT_{PREFIX}_7", "7. アドオン削除パネル", 6
    bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context):
        self.layout.label(text="このアドオンを直ちに無効化します", icon='INFO')
        self.layout.operator(f"{PREFIX}.disable_addon", text="アドオンを無効化する", icon='CANCEL')

# =========================================================
# 登録処理
# =========================================================
classes = (
    AddonProperties,
    OT_MoveToCenter, OT_UpdateViewInfo, OT_CopyInfo,
    OT_Create, OT_Detach, OT_PasteText,
    OT_AddImageRef, OT_AddImageBg, OT_AddImagePlane, OT_AddImageCam,
    OT_ResetDefaults, OT_DisableAddon,
    PT_1_View, PT_2_Generate, PT_3_Realtime, PT_4_IO, PT_5_Settings, PT_6_Link, PT_7_Delete,
)

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

def unregister():
    for cls in reversed(classes): bpy.utils.unregister_class(cls)
    if hasattr(bpy.types.Scene, PROP_NAME): delattr(bpy.types.Scene, PROP_NAME)

if __name__ == "__main__":
    register()