rapture_20260711220422.png

bl_info = {
    "name": "Viewport & Automation Master",
    "author": "Your Name",
    "version": (2, 8),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "画面中央化、ビュー詳細情報取得、グリッド・背景色設定、枠額縁・3軸矢印作成、各種リンク",
    "category": "3D View",
}

# 【作成日】 2026年7月11日 12:44 (統合版)

# ===================================
# 【初期設定・定数定義】 (1行ずつ定義)
# ===================================
# 元アドオン用
PREFIX = "front_tools"
TAB_NAME = " back color "
GRID_DEFAULT_1 = 10.0
GRID_DEFAULT_2 = 100.0
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>"

# 追加アドオン(額縁&矢印作成)用
PREFIX_WA = "windows_axes"
INIT_COLL_MAIN = "枠額縁"
INIT_OBJ_FLOOR = "床面枠"
INIT_OBJ_WINDOW = "窓平面枠"
INIT_OBJ_X_PERS = "遠近法 x"

# 額縁のパラメータ初期値 (4角を±30にするためサイズは60)
INIT_FRAME_SIZE = 60.0
INIT_FRAME_THICKNESS = 2.0                 # 太さ初期値 2.0
INIT_COLOR_FLOOR = (0.0, 0.0, 1.0, 1.0)    # 床面枠: Blue (透明度1.0)
INIT_COLOR_WINDOW = (0.0, 0.4, 0.0, 1.0)   # 窓平面枠: Green (透明度1.0)
INIT_COLOR_X_PERS = (1.0, 0.0, 0.3, 1.0)   # x軸遠近法 額縁: Red (透明度1.0)

# 矢印パラメータ初期値
INIT_ARROW_LENGTH = 40.0
INIT_ARROW_THICKNESS = 1.0
INIT_CONE_RADIUS = 2.0
INIT_CONE_HEIGHT = 10.0
INIT_ARROW_CENTER = True                   # 矢印中心を000にする設定 (デフォルト: True)

# 矢印カラー初期値 (CGのデファクトスタンダード基準)
INIT_ARROW_COLOR_X = (1.0, 0.0, 0.0, 0.5)  # X軸: Red (透明度0.5)
INIT_ARROW_COLOR_Y = (0.1, 0.9, 0.1, 0.5)  # Y軸: Green (透明度0.5)
INIT_ARROW_COLOR_Z = (0.1, 0.4, 1.0, 0.5)  # Z軸: Blue (透明度0.5)

import bpy
import math
import mathutils
import webbrowser

# ===================================
# 【ビュー情報取得 関数】
# ===================================
def get_rv3d(context):
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            return a.spaces.active.region_3d
    return None

def get_view_info_data(context):
    rv3d = None
    area = None
    space = None
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            rv3d = a.spaces.active.region_3d
            area = a
            space = a.spaces.active
            break
            
    if not rv3d or not area or not space: return None
        
    mode = rv3d.view_perspective
    target_pos = rv3d.view_location
    view_dist = rv3d.view_distance
    cam_pos = rv3d.view_matrix.inverted().translation
    view_dir = rv3d.view_rotation @ mathutils.Vector((0.0, 0.0, -1.0))
    aspect = area.height / area.width if area.width > 0 else 1.0
    sensor = 32.0 
    
    if mode == 'PERSP':
        proj_text = "透視投影"
        is_persp = True
    elif mode == 'ORTHO':
        proj_text = "平行投影"
        is_persp = False
    elif mode == 'CAMERA':
        proj_text = "カメラビュー"
        is_persp = True
    else:
        proj_text = "不明"
        is_persp = False
    
    if is_persp:
        fov_h = 2.0 * math.atan(sensor / (2.0 * space.lens))
        w = 2.0 * view_dist * math.tan(fov_h / 2.0)
        h = w * aspect
    else: 
        w = view_dist * (sensor / space.lens)
        h = w * aspect
        
    return {
        'proj_text': proj_text,
        'cam_pos': cam_pos,
        'view_dir': view_dir,
        'target_pos': target_pos,
        'dist': view_dist,
        'w': w,
        'h': h,
        'is_persp': is_persp
    }

# ===================================
# 【枠額縁 作成用ヘルパー関数】
# ===================================
def create_frame(context, frame_type):
    props = getattr(context.scene, f"{PREFIX_WA}_props")
    size = props.frame_size
    thickness = props.frame_thickness

    # メインコレクションの取得または新規作成
    if INIT_COLL_MAIN in bpy.data.collections:
        main_col = bpy.data.collections[INIT_COLL_MAIN]
    else:
        main_col = bpy.data.collections.new(INIT_COLL_MAIN)
        context.scene.collection.children.link(main_col)

    # サイズ値を文字列に整形
    size_str = str(int(size)) if size.is_integer() else f"{size:.1f}"

    if frame_type == 'FLOOR':
        base_name = INIT_OBJ_FLOOR
        mat_name = f"{INIT_OBJ_FLOOR}_{size_str}_半透明マテリアル"
        color = props.floor_color
        # Z=0 (XY平面)
        rot_matrix = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
    elif frame_type == 'WINDOW':
        base_name = INIT_OBJ_WINDOW
        mat_name = f"{INIT_OBJ_WINDOW}_{size_str}_半透明マテリアル"
        color = props.window_color
        # Y=0 (ZX平面) : 床面枠をX軸に90度回転
        rot_z = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
        rot_x = mathutils.Matrix.Rotation(math.radians(90), 4, 'X')
        rot_matrix = rot_x @ rot_z
    elif frame_type == 'X_PERS':
        base_name = INIT_OBJ_X_PERS
        mat_name = f"{INIT_OBJ_X_PERS}_{size_str}_半透明マテリアル"
        color = props.x_pers_color
        # X=0 (YZ平面) : 床面枠をY軸に90度回転
        rot_z = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
        rot_y = mathutils.Matrix.Rotation(math.radians(90), 4, 'Y')
        rot_matrix = rot_y @ rot_z
    else:
        return

    name = f"{base_name}_{size_str}"

    # 1つ1つ完全に独立した新規マテリアルを生成
    mat = bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        principled = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
        if principled:
            principled.inputs['Base Color'].default_value = (color[0], color[1], color[2], 1.0)
            if 'Alpha' in principled.inputs:
                principled.inputs['Alpha'].default_value = color[3]
                
    # Blender 5.0 (EEVEE-Next) 対応
    if hasattr(mat, "surface_render_method"):
        mat.surface_render_method = 'BLENDED'
    else:
        mat.blend_method = 'BLEND'

    major_r = (size / 2.0) * math.sqrt(2)
    minor_r = thickness / 2.0
    
    # 事前に選択を解除
    bpy.ops.object.select_all(action='DESELECT')
    
    # トーラスを生成
    bpy.ops.mesh.primitive_torus_add(
        align='WORLD',
        location=(0.0, 0.0, 0.0),
        rotation=(0.0, 0.0, 0.0),
        major_radius=major_r,
        minor_radius=minor_r,
        major_segments=4,
        minor_segments=16
    )
    
    obj = context.active_object
    obj.name = name
    
    # 回転行列を直接適用
    obj.rotation_mode = 'XYZ'
    obj.matrix_world = rot_matrix
    
    # マテリアル割り当て
    obj.data.materials.append(mat)
        
    # コレクション整理 (メインコレクション「枠額縁」の直下に格納)
    if main_col.name not in [c.name for c in obj.users_collection]:
        main_col.objects.link(obj)
    for c in list(obj.users_collection):
        if c != main_col:
            c.objects.unlink(obj)
    
    context.view_layer.update()

# ===================================
# 【矢印 作成用ヘルパー関数】
# ===================================
def create_arrow(context, axis_type):
    props = getattr(context.scene, f"{PREFIX_WA}_props")
    length = props.arrow_length
    thickness = props.arrow_thickness
    cone_r = props.arrow_cone_radius
    cone_h = props.arrow_cone_height
    center_at_origin = props.arrow_center_at_origin

    # メインコレクションの取得または新規作成
    if INIT_COLL_MAIN in bpy.data.collections:
        main_col = bpy.data.collections[INIT_COLL_MAIN]
    else:
        main_col = bpy.data.collections.new(INIT_COLL_MAIN)
        context.scene.collection.children.link(main_col)

    if axis_type == 'X':
        name = "X軸矢印"
        color = props.arrow_color_x
        # X軸方向への回転行列 (Y軸周り90度)
        rot_matrix = mathutils.Matrix.Rotation(math.radians(90), 4, 'Y')
    elif axis_type == 'Y':
        name = "Y軸矢印"
        color = props.arrow_color_y
        # Y軸方向への回転行列 (X軸周り-90度)
        rot_matrix = mathutils.Matrix.Rotation(math.radians(-90), 4, 'X')
    elif axis_type == 'Z':
        name = "Z軸矢印"
        color = props.arrow_color_z
        # Z軸方向 (回転なし)
        rot_matrix = mathutils.Matrix.Identity(4)
    else:
        return

    # 完全に独立した新規マテリアルを作成
    mat_name = f"{name}_半透明マテリアル"
    mat = bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        principled = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
        if principled:
            principled.inputs['Base Color'].default_value = (color[0], color[1], color[2], 1.0)
            if 'Alpha' in principled.inputs:
                principled.inputs['Alpha'].default_value = color[3]
                
    if hasattr(mat, "surface_render_method"):
        mat.surface_render_method = 'BLENDED'
    else:
        mat.blend_method = 'BLEND'

    # 干渉防止のために全選択解除
    bpy.ops.object.select_all(action='DESELECT')

    # 1. 円柱作成 (Z軸方向に伸びるように配置)
    bpy.ops.mesh.primitive_cylinder_add(
        radius=thickness / 2.0,
        depth=length,
        location=(0.0, 0.0, length / 2.0)
    )
    cylinder = context.active_object
    cylinder.select_set(False)

    # 2. 円錐作成 (円柱の先端に配置)
    bpy.ops.mesh.primitive_cone_add(
        radius1=cone_r,
        depth=cone_h,
        location=(0.0, 0.0, length + cone_h / 2.0)
    )
    cone = context.active_object

    # 3. 結合 (円錐と円柱を一体化)
    cylinder.select_set(True)
    cone.select_set(True)
    context.view_layer.objects.active = cylinder
    bpy.ops.object.join()

    arrow_obj = cylinder
    arrow_obj.name = name

    # 中心点調整オフセット
    dz = -(length + cone_h) / 2.0 if center_at_origin else 0.0
    translation_matrix = mathutils.Matrix.Translation((0.0, 0.0, dz))

    # 各軸方向にマトリクスで回転移動
    arrow_obj.rotation_mode = 'XYZ'
    arrow_obj.matrix_world = rot_matrix @ translation_matrix

    # マテリアル適用
    arrow_obj.data.materials.append(mat)

    # コレクション整理 (メインコレクション「枠額縁」の直下に格納)
    if main_col.name not in [c.name for c in arrow_obj.users_collection]:
        main_col.objects.link(arrow_obj)
    for c in list(arrow_obj.users_collection):
        if c != main_col:
            c.objects.unlink(arrow_obj)

    context.view_layer.update()

# ===================================
# 【オペレーター】
# ===================================
class VGM_OT_reset_view(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_view"
    bl_label = "選択オブジェクトを画面中央にする"
    def execute(self, context):
        rv3d = get_rv3d(context)
        if not rv3d: return {'CANCELLED'}
        target = context.active_object
        rv3d.view_location = target.matrix_world.to_translation() if target else rv3d.view_location.copy()
        rv3d.view_distance = max(target.dimensions.length * 1.5, 10.0) if target else 30.0
        rv3d.view_perspective = 'PERSP'
        context.view_layer.update()
        return {'FINISHED'}

class VGM_OT_update_info(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_update_info"
    bl_label = "情報を更新"
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()
        return {'FINISHED'}

class VGM_OT_copy_info(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_copy_info"
    bl_label = "情報をコピー"
    def execute(self, context):
        info = get_view_info_data(context)
        if not info: return {'CANCELLED'}
        
        lines = []
        lines.append(f"投影モード: {info['proj_text']}")
        if info['is_persp']:
            lines.append(f"架空の視座位置 X: {info['cam_pos'].x:.1f}, Y: {info['cam_pos'].y:.1f}, Z: {info['cam_pos'].z:.1f}")
        
        lines.append(f"画面中央への視線方向 X: {info['view_dir'].x:.1f}, Y: {info['view_dir'].y:.1f}, Z: {info['view_dir'].z:.1f}")
        lines.append(f"画面中央位置 X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}")
        lines.append(f"画面水平の大きさ: {info['w']:.1f}")
        lines.append(f"画面上下の大きさ: {info['h']:.1f}")
        
        if info['is_persp']:
            lines.append(f"視座からの距離: {info['dist']:.1f}")

        context.window_manager.clipboard = "\n".join(lines)
        self.report({'INFO'}, "ビュー情報をクリップボードにコピーしました")
        return {'FINISHED'}

class VGM_OT_set_grid_scale(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_set_grid_scale"
    bl_label = "Grid間隔を設定"
    scale_val: bpy.props.FloatProperty(name="Scale")
    use_custom: bpy.props.BoolProperty(name="カスタム値", default=False)
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        val = props.custom_grid_scale if self.use_custom else self.scale_val
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D': space.overlay.grid_scale = val
        return {'FINISHED'}

class VGM_OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    
    def execute(self, context):
        if self.url:
            webbrowser.open(self.url)
        return {'FINISHED'}

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

# --- 追加されたオペレーター ---

class WA_OT_create_frame_floor(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_frame_floor"
    bl_label = "床面枠を作成"
    def execute(self, context):
        create_frame(context, 'FLOOR')
        return {'FINISHED'}

class WA_OT_create_frame_window(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_frame_window"
    bl_label = "窓平面枠を作成"
    def execute(self, context):
        create_frame(context, 'WINDOW')
        return {'FINISHED'}

class WA_OT_create_frame_x_pers(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_frame_x_pers"
    bl_label = "x軸遠近法 額縁を作成"
    def execute(self, context):
        create_frame(context, 'X_PERS')
        return {'FINISHED'}

class WA_OT_create_arrow_x(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_arrow_x"
    bl_label = "X軸矢印を作成"
    def execute(self, context):
        create_arrow(context, 'X')
        return {'FINISHED'}

class WA_OT_create_arrow_y(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_arrow_y"
    bl_label = "Y軸矢印を作成"
    def execute(self, context):
        create_arrow(context, 'Y')
        return {'FINISHED'}

class WA_OT_create_arrow_z(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_arrow_z"
    bl_label = "Z軸矢印を作成"
    def execute(self, context):
        create_arrow(context, 'Z')
        return {'FINISHED'}

class WA_OT_create_arrow_all(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_create_arrow_all"
    bl_label = "3軸の矢印を一括作成"
    def execute(self, context):
        create_arrow(context, 'X')
        create_arrow(context, 'Y')
        create_arrow(context, 'Z')
        self.report({'INFO'}, "X, Y, Z軸の矢印を作成しました")
        return {'FINISHED'}

class WA_OT_copy_params(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_copy_params"
    bl_label = "現在の設定を初期設定コードとしてコピー"
    bl_description = "現在の設定値を、アドオンコード上部の初期設定(定数定義)に直接ペーストできるテキスト形式でコピーします"

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX_WA}_props")
        
        # 色のフォーマット整形関数 (RGBA)
        def fmt_color(col):
            return f"({col[0]:.1f}, {col[1]:.1f}, {col[2]:.1f}, {col[3]:.1f})"
            
        lines = []
        lines.append("# 額縁のパラメータ初期値 (4角を±30にするためサイズは60)")
        lines.append(f"INIT_FRAME_SIZE = {props.frame_size:.1f}")
        lines.append(f"INIT_FRAME_THICKNESS = {props.frame_thickness:.1f}                 # 太さ初期値 {props.frame_thickness:.1f}")
        lines.append(f"INIT_COLOR_FLOOR = {fmt_color(props.floor_color)}    # 床面枠: Blue (透明度{props.floor_color[3]:.1f})")
        lines.append(f"INIT_COLOR_WINDOW = {fmt_color(props.window_color)}   # 窓平面枠: Green (透明度{props.window_color[3]:.1f})")
        lines.append(f"INIT_COLOR_X_PERS = {fmt_color(props.x_pers_color)}   # x軸遠近法 額縁: Red (透明度{props.x_pers_color[3]:.1f})")
        lines.append("")
        lines.append("# 矢印パラメータ初期値")
        lines.append(f"INIT_ARROW_LENGTH = {props.arrow_length:.1f}")
        lines.append(f"INIT_ARROW_THICKNESS = {props.arrow_thickness:.1f}")
        lines.append(f"INIT_CONE_RADIUS = {props.arrow_cone_radius:.1f}")
        lines.append(f"INIT_CONE_HEIGHT = {props.arrow_cone_height:.1f}")
        lines.append(f"INIT_ARROW_CENTER = {props.arrow_center_at_origin}                   # 矢印中心を000にする設定 (デフォルト: {props.arrow_center_at_origin})")
        lines.append("")
        lines.append("# 矢印カラー初期値 (CGのデファクトスタンダード基準)")
        lines.append(f"INIT_ARROW_COLOR_X = {fmt_color(props.arrow_color_x)}  # X軸: Red (透明度{props.arrow_color_x[3]:.1f})")
        lines.append(f"INIT_ARROW_COLOR_Y = {fmt_color(props.arrow_color_y)}  # Y軸: Green (透明度{props.arrow_color_y[3]:.1f})")
        lines.append(f"INIT_ARROW_COLOR_Z = {fmt_color(props.arrow_color_z)}  # Z軸: Blue (透明度{props.arrow_color_z[3]:.1f})")
        
        # クリップボードへ格納
        context.window_manager.clipboard = "\n".join(lines)
        self.report({'INFO'}, "現在のパラメータ設定コードをクリップボードにコピーしました")
        return {'FINISHED'}

class WA_OT_reset_frame_props(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX_WA}_reset_frame_props"
    bl_label = "作成設定を初期値に戻す"
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX_WA}_props")
        # 額縁リセット
        props.frame_size = INIT_FRAME_SIZE
        props.frame_thickness = INIT_FRAME_THICKNESS
        props.floor_color = INIT_COLOR_FLOOR
        props.window_color = INIT_COLOR_WINDOW
        props.x_pers_color = INIT_COLOR_X_PERS
        # 矢印リセット
        props.arrow_length = INIT_ARROW_LENGTH
        props.arrow_thickness = INIT_ARROW_THICKNESS
        props.arrow_cone_radius = INIT_CONE_RADIUS
        props.arrow_cone_height = INIT_CONE_HEIGHT
        props.arrow_center_at_origin = INIT_ARROW_CENTER
        props.arrow_color_x = INIT_ARROW_COLOR_X
        props.arrow_color_y = INIT_ARROW_COLOR_Y
        props.arrow_color_z = INIT_ARROW_COLOR_Z
        self.report({'INFO'}, "設定をコードの初期値に戻しました")
        return {'FINISHED'}

# ===================================
# 【プロパティ定義】
# ===================================
class VGM_Properties(bpy.types.PropertyGroup):
    custom_grid_scale: bpy.props.FloatProperty(name="指定値", default=50.0, min=0.001)

# 追加された枠額縁・矢印用のプロパティクラス
class WA_Properties(bpy.types.PropertyGroup):
    # 額縁プロパティ
    frame_size: bpy.props.FloatProperty(name="1辺の長さ", default=INIT_FRAME_SIZE, min=0.01)
    frame_thickness: bpy.props.FloatProperty(name="太さ", default=INIT_FRAME_THICKNESS, min=0.01)
    floor_color: bpy.props.FloatVectorProperty(name="床面枠の色", subtype='COLOR', default=INIT_COLOR_FLOOR, size=4, min=0.0, max=1.0)
    window_color: bpy.props.FloatVectorProperty(name="窓平面枠の色", subtype='COLOR', default=INIT_COLOR_WINDOW, size=4, min=0.0, max=1.0)
    x_pers_color: bpy.props.FloatVectorProperty(name="x軸遠近法の色", subtype='COLOR', default=INIT_COLOR_X_PERS, size=4, min=0.0, max=1.0)
    
    # 矢印プロパティ
    arrow_length: bpy.props.FloatProperty(name="矢印 長さ", default=INIT_ARROW_LENGTH, min=0.01)
    arrow_thickness: bpy.props.FloatProperty(name="矢印 太さ", default=INIT_ARROW_THICKNESS, min=0.01)
    arrow_cone_radius: bpy.props.FloatProperty(name="円錐 太さ(半径)", default=INIT_CONE_RADIUS, min=0.01)
    arrow_cone_height: bpy.props.FloatProperty(name="円錐 高さ", default=INIT_CONE_HEIGHT, min=0.01)
    arrow_center_at_origin: bpy.props.BoolProperty(name="矢印中心を000にする", default=INIT_ARROW_CENTER)
    
    arrow_color_x: bpy.props.FloatVectorProperty(name="X軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_X, size=4, min=0.0, max=1.0)
    arrow_color_y: bpy.props.FloatVectorProperty(name="Y軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_Y, size=4, min=0.0, max=1.0)
    arrow_color_z: bpy.props.FloatVectorProperty(name="Z軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_Z, size=4, min=0.0, max=1.0)

# ===================================
# 【UIパネル】
# ===================================
# 1. 元メインパネル
class VGM_PT_main_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX.upper()}_PT_main_panel"
    bl_label = "Viewport & Grid Master"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX}_props")
        info = get_view_info_data(context)
        
        # --- 1. ビュー操作 (先頭) ---
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択オブジェクトを画面中央にする", icon='ZOOM_ALL')
        layout.separator(factor=1.5)
        
        # --- 2. ビュー情報 ---
        box_info = layout.box()
        row_info = box_info.row(align=True)
        row_info.label(text=f"情報: [{info['proj_text'] if info else '不明'}]", icon='INFO')
        row_info.operator(f"view3d.{PREFIX}_update_info", text="更新", icon='FILE_REFRESH')
        
        if info:
            col_info = box_info.column(align=True)
            col_info.label(text="画面中央位置:")
            col_info.label(text=f"  X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}")
            col_info.separator()
            
            col_info.label(text="画面中央への視線方向:")
            col_info.label(text=f"  X: {info['view_dir'].x:.1f}, Y: {info['view_dir'].y:.1f}, Z: {info['view_dir'].z:.1f}")
            col_info.separator()
            
            col_info.label(text=f"画面水平の大きさ: {info['w']:.1f}")
            col_info.label(text=f"画面上下の大きさ: {info['h']:.1f}")
            
            if info['is_persp']:
                col_info.separator()
                col_info.label(text=f"視座からの距離: {info['dist']:.1f}")
                
            box_info.separator()
            box_info.operator(f"view3d.{PREFIX}_copy_info", text="情報をコピー (1行ずつペースト)", icon='COPYDOWN')
        else:
            box_info.label(text="3Dビューが見つかりません")
            
        layout.separator(factor=1.5)
        
        # --- 3. Grid / 背景 設定 ---
        box_grid = layout.box()
        box_grid.label(text="Grid / 背景 設定", icon='GRID')
        
        row_preset = box_grid.row(align=True)
        row_preset.operator(f"view3d.{PREFIX}_set_grid_scale", text=f"間隔 {GRID_DEFAULT_1}").scale_val = GRID_DEFAULT_1
        row_preset.operator(f"view3d.{PREFIX}_set_grid_scale", text=f"間隔 {GRID_DEFAULT_2}").scale_val = GRID_DEFAULT_2
        
        row_custom = box_grid.row(align=True)
        row_custom.prop(props, "custom_grid_scale")
        row_custom.operator(f"view3d.{PREFIX}_set_grid_scale", text="指定値にする").use_custom = True
        
        box_grid.separator()
        prefs = context.preferences
        if len(prefs.themes) > 0:
            theme_view3d = prefs.themes[0].view_3d
            col_theme = box_grid.column(align=True)
            col_theme.prop(theme_view3d, "grid", text="Grid 色変更")
            col_theme.separator()
            col_theme.prop(theme_view3d.space.gradients, "high_gradient", text="背景色 (上)")
            col_theme.prop(theme_view3d.space.gradients, "low_gradient", text="背景色 (下)")

        layout.separator(factor=1.5)
        
        # --- 4. リンク / アドオン削除 ---
        box_sys = layout.box()
        box_sys.label(text="システム / リンク", icon='PREFERENCES')
        
        op1 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_1, icon='URL')
        op1.url = LINK_URL_1
        
        op2 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_2, icon='URL')
        op2.url = LINK_URL_2
        
        op3 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_3, icon='URL')
        op3.url = LINK_URL_3
        
        box_sys.separator()
        box_sys.operator(f"wm.{PREFIX}_remove_addon", text="アドオンの登録を解除", icon='CANCEL')

# 2. 追加された 枠額縁作成設定パネル (bl_categoryはTAB_NAMEに統一)
class WA_PT_frame_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_WA.upper()}_PT_frame_panel"
    bl_label = "枠額縁 作成設定"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX_WA}_props")
        
        box = layout.box()
        box.label(text="額縁の寸法・色", icon='MODIFIER')
        box.prop(props, "frame_size")
        box.prop(props, "frame_thickness")
        box.separator()
        box.prop(props, "floor_color", text="床面枠の色 (Z=0)")
        box.prop(props, "window_color", text="窓平面枠の色 (Y=0)")
        box.prop(props, "x_pers_color", text="x軸遠近法の色 (X=0)")
        
        layout.separator()
        
        col_buttons = layout.column(align=True)
        col_buttons.scale_y = 1.2
        col_buttons.operator(f"view3d.{PREFIX_WA}_create_frame_floor", text="床面枠を作成 (Z=0平面)", icon='MESH_PLANE')
        col_buttons.operator(f"view3d.{PREFIX_WA}_create_frame_window", text="窓平面枠を作成 (Y=0平面)", icon='GRID')
        col_buttons.operator(f"view3d.{PREFIX_WA}_create_frame_x_pers", text="x軸遠近法 額縁を作成 (X=0平面)", icon='OUTLINER_OB_FORCE_FIELD')

# 3. 追加された 軸矢印作成設定パネル (bl_categoryはTAB_NAMEに統一)
class WA_PT_arrow_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_WA.upper()}_PT_arrow_panel"
    bl_label = "軸矢印 作成設定"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX_WA}_props")
        
        box = layout.box()
        box.label(text="矢印・円錐の寸法・色", icon='CONE')
        box.prop(props, "arrow_length")
        box.prop(props, "arrow_thickness")
        box.prop(props, "arrow_cone_radius")
        box.prop(props, "arrow_cone_height")
        box.prop(props, "arrow_center_at_origin")
        box.separator()
        box.prop(props, "arrow_color_x", text="X軸 矢印の色")
        box.prop(props, "arrow_color_y", text="Y軸 矢印の色")
        box.prop(props, "arrow_color_z", text="Z軸 矢印の色")
        
        layout.separator()
        
        row = layout.row(align=True)
        row.scale_y = 1.1
        row.operator(f"view3d.{PREFIX_WA}_create_arrow_x", text="X軸矢印")
        row.operator(f"view3d.{PREFIX_WA}_create_arrow_y", text="Y軸矢印")
        row.operator(f"view3d.{PREFIX_WA}_create_arrow_z", text="Z軸矢印")
        
        layout.operator(f"view3d.{PREFIX_WA}_create_arrow_all", text="3軸 of 矢印を一括作成", icon='FORCE_VORTEX')
        
        layout.separator(factor=1.5)
        
        col_sys = layout.column(align=True)
        col_sys.operator(f"view3d.{PREFIX_WA}_copy_params", text="現在の設定を初期コードとしてコピー", icon='COPYDOWN')
        col_sys.separator()
        col_sys.operator(f"view3d.{PREFIX_WA}_reset_frame_props", text="すべてのパラメータを初期値に戻す", icon='LOOP_BACK')

# ===================================
# 【登録・削除処理】
# ===================================
classes = [
    VGM_Properties,
    WA_Properties,
    VGM_OT_reset_view,
    VGM_OT_update_info,
    VGM_OT_copy_info,
    VGM_OT_set_grid_scale,
    VGM_OT_open_url,
    VGM_OT_remove_addon,
    WA_OT_create_frame_floor,
    WA_OT_create_frame_window,
    WA_OT_create_frame_x_pers,
    WA_OT_create_arrow_x,
    WA_OT_create_arrow_y,
    WA_OT_create_arrow_z,
    WA_OT_create_arrow_all,
    WA_OT_copy_params,
    WA_OT_reset_frame_props,
    VGM_PT_main_panel,
    WA_PT_frame_panel,
    WA_PT_arrow_panel
]

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

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

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