サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { "> サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { "> サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { ">














# Copied: 14:55:00
import bpy
import math
from bpy.props import FloatVectorProperty, FloatProperty, EnumProperty, BoolProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector, Euler
from math import radians
from datetime import datetime

# ==============================================================================
# 【 開発方針 】
# 本アドオンは Blender 5.x 専用です。Blender 4.x以前との互換性は維持しません。
# ==============================================================================

# アドオンのメタデータ
bl_info = {
    "name": "[ 500 背景色 ]",
    "author": "zionadchat",
    "version": (3, 6, 0),
    "blender": (5, 0, 0),
    "category": "[ 500 背景色 ]",
    "description": "【Blender 5.x 専用】3Dビューポートの色、太陽、透視投影視座位置をリアルタイム制御します",
    "location": "3Dビュー > サイドバー",
}

# ==============================================================================
#  デフォルト値設定 (すべて小数2桁に整理済み)
# ==============================================================================
CURRENT_DEFAULTS = {
    "sun_control_mode": 'ANGLE',
    "grid_preset": 'white',
    "wire_preset": 'orange',
    "camera_preset": 'Cam',
    "background_type": 'LINEAR',
    "header_preset": 'Dark Green',
    "preset": 'Dark Green',
    "render_preset": 'Blue',
    "outliner_preset": 'Outliner 5.0.0',
    "text_editor_preset": 'Text 5.0.0',
    "sun_target_location": (0.00, 0.00, 0.00),
    "sun_rotation": (0.79, 0.00, 0.79),
    "sun_location": (0.00, 0.00, 10.00),
    "sun_strength": 2.50,
    "custom_grid_scale": 1.00,
    "grid_color": (0.21, 0.21, 0.21, 1.00),
    "wire_color": (0.00, 0.00, 0.00),
    "camera_color": (0.47, 0.55, 1.00),
    "header_color": (0.00, 0.03, 0.00, 1.00),
    "custom_gradient_high": (0.16, 0.77, 1.00),
    "custom_gradient_low": (0.01, 0.01, 0.15),
    "reverse_gradient": False,
    "render_color": (0.19, 0.60, 1.00, 1.00),
    "render_environment_strength": 1.00,
    "outliner_header_color": (0.19, 0.19, 0.19, 0.70),
    "outliner_background_color": (0.14, 0.14, 0.14, 1.00),
    "text_editor_header_color": (0.19, 0.19, 0.19, 0.70),
    "text_editor_background_color": (0.14, 0.14, 0.14, 1.00),
    "view_pos": (2.40, -13.26, 3.84),
}

# ==============================================================================
# コアロジック
# ==============================================================================

# 定数
ADDON_CATEGORY_NAME = bl_info["category"]
PREFIX = "backcolor2026"

VIEW_RESET_BTN_TEXT = "Reset View (0, -10, 10)"  
VIEW_POS_INIT = (0.0, -10.0, 10.0)  

# パネル定義
LINK_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_link_panel"
MAIN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_main_panel"
PERSP_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_persp_control"
BG_PANEL_IDNAME_1 = f"{PREFIX}_VIEW3D_PT_solid_background_panel"
HEADER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_header_panel"
RENDER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_render_panel"
SUN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_sun_panel"
GRID_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_gridpanel"
WIRE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_wirepanel"
CAMERA_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_camerapanel"
OUTLINER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_outliner_panel"
TEXT_EDITOR_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_text_editor_panel"
REMOVE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_remove"

# パネルラベル
PANEL_LABELS = {
    "LINK": "リンク",
    "MAIN": "メイン操作",
    "PERSP": "透視投影 視座位置",
    "BACKGROUND": "3D Viewport Color",
    "HEADER": "Header Color",
    "RENDER": "Render Color",
    "SUN": "太陽 設定",
    "GRID": "Grid Color",
    "WIRE": "Wire Color",
    "CAMERA": "Camera Color",
    "OUTLINER": "Outliner Color",
    "TEXT_EDITOR": "Text Editor Color",
    "REMOVE": "アドオン削除",
}

# プリセット群
BASE_PRESETS =[
    ("Dark Green", "Dark Green", "Dark Green background", (0.00, 0.28, 0.02), (0.10, 0.15, 0.05)),
    ("purple", "purple", "purple background", (0.49, 0.45, 1.00), (0.74, 0.65, 0.88)),
    ("BlueGreen", "BlueGreen", "BlueGreen background", (0.14, 0.34, 0.83), (0.57, 0.88, 0.63)),
    ("DARK_BLUE", "Dark Blue", "Dark Blue background", (0.07, 0.13, 0.31), (0.05, 0.05, 0.15)),
    ("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 background", (0.24, 0.24, 0.24), (0.19, 0.19, 0.19)),
    ("FOREST_GREEN", "Forest Green", "Forest Green background", (0.50, 0.70, 0.50), (0.10, 0.15, 0.05)),
]
HEADER_PRESETS =[
    ("Dark Green", "Dark Green", "Dark Green header", (0.00, 0.03, 0.00, 1.00)),
    ("purple", "purple", "purple header", (0.00, 0.00, 0.00, 1.00)),
    ("BlueGreen", "BlueGreen", "BlueGreen header", (0.00, 0.00, 0.00, 1.00)),
    ("DARK_BLUE", "Dark Blue", "Dark Blue header", (0.10, 0.10, 0.30, 1.00)),
    ("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 header", (0.19, 0.19, 0.19, 0.70)),
    ("FOREST_GREEN", "Forest Green", "Forest Green header", (0.20, 0.30, 0.10, 1.00)),
]
RENDER_PRESETS =[
    ("Blue", "Blue", "Blue render color", (0.19, 0.60, 1.00, 1.00)),
    ("Render 5.0.0", "Render 5.0.0", "Render 5.0.0 color", (0.05, 0.05, 0.05, 1.00)),
    ("LIGHT_GRAY", "Light Gray", "Light gray render", (0.80, 0.80, 0.80, 1.00)),
]
GRID_PRESETS =[
    ("white", "white", "white grid color", (1.00, 1.00, 1.00, 1.00)),
    ("Grid 5.0.0", "Grid 5.0.0", "Grid 5.0.0 color", (0.33, 0.33, 0.33, 0.50)),
    ("DARK_GRAY", "Dark Gray", "Dark gray grid", (0.10, 0.10, 0.10, 1.00)),
]
WIRE_PRESETS =[
    ("orange", "orange", "orange wire", (0.71, 0.21, 0.05)),
    ("Wire 5.0.0", "Wire 5.0.0", "Wire 5.0.0 color", (0.00, 0.00, 0.00)),
    ("WHITE", "White", "White wire", (1.00, 1.00, 1.00)),
]
CAMERA_PRESETS =[
    ("Cam", "Cam", "Cam camera color", (0.47, 0.55, 1.00)),
    ("Cam 5.0.0", "Cam 5.0.0", "Cam 5.0.0 color", (0.00, 0.00, 0.00)),
    ("YELLOW", "Yellow", "Yellow camera", (1.00, 1.00, 0.00)),
]
OUTLINER_PRESETS =[
    ("Outliner 5.0.0", "Outliner 5.0.0", "Outliner 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
    ("DARK_TEAL", "Dark Teal", "Dark teal outliner", (0.00, 0.20, 0.20, 1.00), (0.00, 0.10, 0.10, 1.00)),
]
TEXT_EDITOR_PRESETS =[
    ("Text 5.0.0", "Text 5.0.0", "Text Editor 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
    ("DARK_GREEN", "Dark Green", "Dark green text editor", (0.00, 0.20, 0.00, 1.00), (0.00, 0.10, 0.00, 1.00)),
]
BACKGROUND_TYPES =[
    ('SINGLE_COLOR', "Single Color", "Uniform background color"),
    ('LINEAR', "Linear", "Linear gradient background"),
    ('RADIAL', "Vignette", "Radial gradient simulating a vignette effect"),
]

# ==============================================================================
#  リアルタイム更新用コールバック関数(ビューポート色・太陽)
# ==============================================================================
def update_custom_grid_scale(self, context):
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.custom_grid_scale

def update_grid_color(self, context): bpy.context.preferences.themes[0].view_3d.grid = self.grid_color
def update_wire_color(self, context): bpy.context.preferences.themes[0].view_3d.wire = self.wire_color
def update_camera_color(self, context): bpy.context.preferences.themes[0].view_3d.camera = self.camera_color

def update_background_color(self, context):
    gradients = bpy.context.preferences.themes[0].view_3d.space.gradients
    if self.background_type == 'SINGLE_COLOR':
        gradients.background_type = 'LINEAR'
        gradients.high_gradient = gradients.gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
    else:
        gradients.background_type = self.background_type
        gradients.high_gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
        gradients.gradient = self.custom_gradient_high if self.reverse_gradient else self.custom_gradient_low
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()

def update_header_color(self, context):
    bpy.context.preferences.themes[0].view_3d.space.header = self.header_color
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()

def update_render_color(self, context):
    bpy.context.preferences.themes[0].image_editor.space.back = self.render_color[:3]
    world = bpy.data.worlds.get('MyWorld') or bpy.data.worlds.new('MyWorld')
    context.scene.world = world
    world.use_nodes = True
    bg_node = world.node_tree.nodes.get('Background') or world.node_tree.nodes.new(type='ShaderNodeBackground')
    bg_node.name = 'Background'
    bg_node.inputs[0].default_value = self.render_color
    bg_node.inputs[1].default_value = self.render_environment_strength
    output_node = world.node_tree.nodes.get('World Output') or world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
    output_node.name = 'World Output'
    world.node_tree.links.new(bg_node.outputs[0], output_node.inputs['Surface'])

def update_outliner_color(self, context):
    space = bpy.context.preferences.themes[0].outliner.space
    space.header = self.outliner_header_color
    space.back = self.outliner_background_color[:3]
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'OUTLINER': area.tag_redraw()

def update_text_editor_color(self, context):
    space = bpy.context.preferences.themes[0].text_editor.space
    space.header = self.text_editor_header_color
    space.back = self.text_editor_background_color[:3]
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'TEXT_EDITOR': area.tag_redraw()

def get_or_create_sun():
    sun_obj = bpy.data.objects.get("Sun")
    if sun_obj is None or sun_obj.type != 'LIGHT' or sun_obj.data.type != 'SUN':
        if sun_obj:
            try: bpy.data.objects.remove(sun_obj, do_unlink=True)
            except: pass
        bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 0))
        sun_obj = bpy.context.active_object
        sun_obj.name = "Sun"; sun_obj.data.name = "Sun"
    return sun_obj

def update_sun(self, context):
    sun = get_or_create_sun()
    sun.location = self.sun_location
    if self.sun_control_mode == 'ANGLE':
        sun.rotation_euler = self.sun_rotation
    else:
        target_vec = Vector(self.sun_target_location)
        sun_vec = Vector(self.sun_location)
        if (target_vec - sun_vec).length_squared < 0.0001: return
        direction = target_vec - sun_vec
        sun.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
    sun.data.energy = self.sun_strength

# ==============================================================================
#  リアルタイム更新用コールバック関数(透視投影 視座位置)
# ==============================================================================
_is_updating_view = False

def update_view_position(self, context):
    global _is_updating_view
    if _is_updating_view: return
    props = getattr(context.scene, "persp_view_props", None)
    if not props: return
    limit = props.slider_limit
    v = list(props.view_pos)
    clamped = False
    for i in range(3):
        if v[i] > limit: v[i] = limit; clamped = True
        elif v[i] < -limit: v[i] = -limit; clamped = True
    if clamped:
        _is_updating_view = True
        props.view_pos = v
        _is_updating_view = False
    
    _is_updating_view = True
    try:
        cam_pos = Vector(props.view_pos)
        for window in context.window_manager.windows:
            for area in window.screen.areas:
                if area.type == 'VIEW_3D':
                    for space in area.spaces:
                        if space.type == 'VIEW_3D':
                            r3d = space.region_3d
                            r3d.view_perspective = 'PERSP'
                            target_pos = Vector(r3d.view_location)
                            rel_pos = cam_pos - target_pos
                            dist = rel_pos.length
                            if dist > 0.001:
                                r3d.view_distance = dist
                                r3d.view_rotation = rel_pos.to_track_quat('Z', 'Y')
    finally:
        _is_updating_view = False

def view_sync_timer():
    global _is_updating_view
    if _is_updating_view: return 0.05
    context = bpy.context
    if getattr(context, "scene", None) is None: return 0.05
    props = getattr(context.scene, "persp_view_props", None)
    if not props: return 0.05
    r3d, target_area = None, None
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        r3d, target_area = space.region_3d, area
                        break
                if r3d: break
        if r3d: break

    if r3d and target_area:
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        current_pos = Vector(props.view_pos)
        if (current_pos - actual_cam_pos).length > 0.001:
            _is_updating_view = True
            max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
            if max_val > props.slider_limit: props.slider_limit = max_val + 50.0 
            props.view_pos = actual_cam_pos
            _is_updating_view = False
            target_area.tag_redraw()
    return 0.05

# ------------------------------------------------------------------------
# Property Groups
# ------------------------------------------------------------------------
class SunSettingsProperties(PropertyGroup):
    sun_control_mode: EnumProperty(name="制御モード", items=[('ANGLE', "角度", "太陽の回転を直接指定"), ('TARGET', "ターゲット", "指定位置に太陽を向ける")], default=CURRENT_DEFAULTS["sun_control_mode"], update=update_sun)
    sun_target_location: FloatVectorProperty(name="ターゲット位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_target_location"], update=update_sun)
    sun_rotation: FloatVectorProperty(name="角度", subtype='EULER', unit='ROTATION', default=CURRENT_DEFAULTS["sun_rotation"], update=update_sun)
    sun_location: FloatVectorProperty(name="位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_location"], update=update_sun)
    sun_strength: FloatProperty(name="強さ", default=CURRENT_DEFAULTS["sun_strength"], min=0.0, update=update_sun)

class ViewportColorProperties(PropertyGroup):
    custom_grid_scale: FloatProperty(name="Scale", default=CURRENT_DEFAULTS["custom_grid_scale"], min=0.001, update=update_custom_grid_scale)
    grid_color: FloatVectorProperty(name="Grid Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["grid_color"], update=update_grid_color)
    grid_preset: EnumProperty(name="Grid Preset", items=[(p[0], p[1], p[2]) for p in GRID_PRESETS], default=CURRENT_DEFAULTS["grid_preset"], update=lambda self, context: self.update_grid_preset(context))
    wire_color: FloatVectorProperty(name="Wire Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["wire_color"], update=update_wire_color)
    wire_preset: EnumProperty(name="Wire Preset", items=[(p[0], p[1], p[2]) for p in WIRE_PRESETS], default=CURRENT_DEFAULTS["wire_preset"], update=lambda self, context: self.update_wire_preset(context))
    camera_color: FloatVectorProperty(name="Camera Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["camera_color"], update=update_camera_color)
    camera_preset: EnumProperty(name="Camera Preset", items=[(p[0], p[1], p[2]) for p in CAMERA_PRESETS], default=CURRENT_DEFAULTS["camera_preset"], update=lambda self, context: self.update_camera_preset(context))
    background_type: EnumProperty(name="Background Type", items=BACKGROUND_TYPES, default=CURRENT_DEFAULTS["background_type"], update=update_background_color)
    header_color: FloatVectorProperty(name="Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["header_color"], update=update_header_color)
    header_preset: EnumProperty(name="Header Preset", items=[(p[0], p[1], p[2]) for p in HEADER_PRESETS], default=CURRENT_DEFAULTS["header_preset"], update=lambda self, context: self.update_header_preset(context))
    custom_gradient_high: FloatVectorProperty(name="Gradient High Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_high"], update=update_background_color)
    custom_gradient_low: FloatVectorProperty(name="Gradient Low Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_low"], update=update_background_color)
    reverse_gradient: BoolProperty(name="Reverse Gradient", default=CURRENT_DEFAULTS["reverse_gradient"], update=update_background_color)
    preset: EnumProperty(name="Color Preset", items=[(p[0], p[1], p[2]) for p in BASE_PRESETS], default=CURRENT_DEFAULTS["preset"], update=lambda self, context: self.update_preset(context))
    render_color: FloatVectorProperty(name="Render Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["render_color"], update=update_render_color)
    render_preset: EnumProperty(name="Render Preset", items=[(p[0], p[1], p[2]) for p in RENDER_PRESETS], default=CURRENT_DEFAULTS["render_preset"], update=lambda self, context: self.update_render_preset(context))
    render_environment_strength: FloatProperty(name="Render Environment Strength", default=CURRENT_DEFAULTS["render_environment_strength"], min=0.0, max=1900.0, update=update_render_color)
    outliner_header_color: FloatVectorProperty(name="Outliner Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_header_color"], update=update_outliner_color)
    outliner_background_color: FloatVectorProperty(name="Outliner Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_background_color"], update=update_outliner_color)
    outliner_preset: EnumProperty(name="Outliner Preset", items=[(p[0], p[1], p[2]) for p in OUTLINER_PRESETS], default=CURRENT_DEFAULTS["outliner_preset"], update=lambda self, context: self.update_outliner_preset(context))
    text_editor_header_color: FloatVectorProperty(name="Text Editor Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_header_color"], update=update_text_editor_color)
    text_editor_background_color: FloatVectorProperty(name="Text Editor Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_background_color"], update=update_text_editor_color)
    text_editor_preset: EnumProperty(name="Text Editor Preset", items=[(p[0], p[1], p[2]) for p in TEXT_EDITOR_PRESETS], default=CURRENT_DEFAULTS["text_editor_preset"], update=lambda self, context: self.update_text_editor_preset(context))

    def update_grid_preset(self, context):
        for p in GRID_PRESETS:
            if p[0] == self.grid_preset: self.grid_color = p[3]; break
    def update_wire_preset(self, context):
        for p in WIRE_PRESETS:
            if p[0] == self.wire_preset: self.wire_color = p[3]; break
    def update_camera_preset(self, context):
        for p in CAMERA_PRESETS:
            if p[0] == self.camera_preset: self.camera_color = p[3]; break
    def update_preset(self, context):
        for p in BASE_PRESETS:
            if p[0] == self.preset: self.custom_gradient_high = p[3]; self.custom_gradient_low = p[4]; break
    def update_header_preset(self, context):
        for p in HEADER_PRESETS:
            if p[0] == self.header_preset: self.header_color = p[3]; break
    def update_render_preset(self, context):
        for p in RENDER_PRESETS:
            if p[0] == self.render_preset: self.render_color = p[3]; break
    def update_outliner_preset(self, context):
        for p in OUTLINER_PRESETS:
            if p[0] == self.outliner_preset: self.outliner_header_color = p[3]; self.outliner_background_color = p[4]; break
    def update_text_editor_preset(self, context):
        for p in TEXT_EDITOR_PRESETS:
            if p[0] == self.text_editor_preset: self.text_editor_header_color = p[3]; self.text_editor_background_color = p[4]; break

class PerspViewProperties(PropertyGroup):
    slider_limit: FloatProperty(name="Range Limit", default=300.0, min=10.0, max=10000.0)
    view_pos: FloatVectorProperty(name="View Position", size=3, soft_min=-10000.0, soft_max=10000.0, default=CURRENT_DEFAULTS.get('view_pos', VIEW_POS_INIT), update=update_view_position)

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_ViewCenterFront(Operator):
    bl_idname = f"{PREFIX}_wm.view_center_front"
    bl_label = "Center 0,0,0 (Front View)"
    bl_description = "原点(0,0,0)を画面中央に配置し、Yマイナス方向からの視点(正面)にします"
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                rv3d = area.spaces.active.region_3d
                if rv3d:
                    rv3d.view_location = (0.0, 0.0, 0.0)
                    rv3d.view_rotation = Euler((radians(90.0), 0.0, 0.0), 'XYZ').to_quaternion()
                    if rv3d.view_distance < 10.0: rv3d.view_distance = 60.0
        return {'FINISHED'}

class OT_CopyFullScript(Operator):
    bl_idname = f"{PREFIX}_wm.copy_script"
    bl_label = "Copy Defaults Only"
    
    def execute(self, context):
        props = context.scene.viewport_color_props
        sun_props = context.scene.sun_settings_props
        persp_props = context.scene.persp_view_props

        # すべての数値を小数2桁に丸めて出力
        def fmt_vec(v): return "(" + ", ".join(f"{x:.2f}" for x in v) + ")"
        def fmt_str(s): return f"'{s}'"

        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "sun_control_mode": {fmt_str(sun_props.sun_control_mode)},\n'
        new_dict += f'    "grid_preset": {fmt_str(props.grid_preset)},\n'
        new_dict += f'    "wire_preset": {fmt_str(props.wire_preset)},\n'
        new_dict += f'    "camera_preset": {fmt_str(props.camera_preset)},\n'
        new_dict += f'    "background_type": {fmt_str(props.background_type)},\n'
        new_dict += f'    "header_preset": {fmt_str(props.header_preset)},\n'
        new_dict += f'    "preset": {fmt_str(props.preset)},\n'
        new_dict += f'    "render_preset": {fmt_str(props.render_preset)},\n'
        new_dict += f'    "outliner_preset": {fmt_str(props.outliner_preset)},\n'
        new_dict += f'    "text_editor_preset": {fmt_str(props.text_editor_preset)},\n'
        new_dict += f'    "sun_target_location": {fmt_vec(sun_props.sun_target_location)},\n'
        new_dict += f'    "sun_rotation": {fmt_vec(sun_props.sun_rotation)},\n'
        new_dict += f'    "sun_location": {fmt_vec(sun_props.sun_location)},\n'
        new_dict += f'    "sun_strength": {sun_props.sun_strength:.2f},\n'
        new_dict += f'    "custom_grid_scale": {props.custom_grid_scale:.2f},\n'
        new_dict += f'    "grid_color": {fmt_vec(props.grid_color)},\n'
        new_dict += f'    "wire_color": {fmt_vec(props.wire_color)},\n'
        new_dict += f'    "camera_color": {fmt_vec(props.camera_color)},\n'
        new_dict += f'    "header_color": {fmt_vec(props.header_color)},\n'
        new_dict += f'    "custom_gradient_high": {fmt_vec(props.custom_gradient_high)},\n'
        new_dict += f'    "custom_gradient_low": {fmt_vec(props.custom_gradient_low)},\n'
        new_dict += f'    "reverse_gradient": {props.reverse_gradient},\n'
        new_dict += f'    "render_color": {fmt_vec(props.render_color)},\n'
        new_dict += f'    "render_environment_strength": {props.render_environment_strength:.2f},\n'
        new_dict += f'    "outliner_header_color": {fmt_vec(props.outliner_header_color)},\n'
        new_dict += f'    "outliner_background_color": {fmt_vec(props.outliner_background_color)},\n'
        new_dict += f'    "text_editor_header_color": {fmt_vec(props.text_editor_header_color)},\n'
        new_dict += f'    "text_editor_background_color": {fmt_vec(props.text_editor_background_color)},\n'
        new_dict += f'    "view_pos": {fmt_vec(persp_props.view_pos)},\n'
        new_dict += "}\n"
        
        context.window_manager.clipboard = new_dict
        self.report({'INFO'}, "最新数値を初期値コード(CURRENT_DEFAULTS)としてコピーしました!")
        return {'FINISHED'}

class OVERLAY_OT_set_grid_scale(Operator):
    bl_idname = f"{PREFIX}_overlay.set_grid_scale"; bl_label = "Set Grid Scale"
    scale_value: FloatProperty()
    def execute(self, context):
        for window in context.window_manager.windows:
            for area in window.screen.areas:
                if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.scale_value
        return {'FINISHED'}

class SUN_OT_Create(Operator):
    bl_idname = f"{PREFIX}.create_sun"; bl_label = "太陽を作成"
    def execute(self, context):
        get_or_create_sun(); self.report({'INFO'}, "太陽を作成しました。"); return {'FINISHED'}

class SUN_OT_Reset(Operator):
    bl_idname = f"{PREFIX}.reset_sun"; bl_label = "太陽の設定を初期値にリセット"
    def execute(self, context):
        props = context.scene.sun_settings_props
        props.sun_control_mode, props.sun_target_location = 'ANGLE', (0.0, 0.0, 0.0)
        props.sun_rotation = (radians(45.0), radians(0.0), radians(45.0))
        props.sun_location, props.sun_strength = (0.0, 0.0, 10.0), 2.5
        update_sun(props, context)
        self.report({'INFO'}, "太陽の設定をリセットしました。"); return {'FINISHED'}

class RemoveAllPanels(Operator):
    bl_idname = f"{PREFIX}_wm.remove_all_panels"; bl_label = PANEL_LABELS["REMOVE"]
    def execute(self, context): unregister(); return {'FINISHED'}

class PERSP_OT_GetCurrentView(Operator):
    bl_idname = f"{PREFIX}_persp.get_current_view"; bl_label = "Get Current View & Update"; bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        r3d = context.space_data.region_3d if context.space_data else None
        if not props or not r3d: return {'CANCELLED'}
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
        if max_val > props.slider_limit: props.slider_limit = max_val + 50.0 
        props.view_pos = actual_cam_pos; return {'FINISHED'}

class PERSP_OT_ResetView(Operator):
    bl_idname = f"{PREFIX}_persp.reset_view"; bl_label = VIEW_RESET_BTN_TEXT; bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        if props:
            for window in context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D': space.region_3d.view_location = (0.0, 0.0, 0.0)
            props.view_pos = VIEW_POS_INIT 
        return {'FINISHED'}

class PERSP_OT_CenterSelected(Operator):
    bl_idname = f"{PREFIX}_persp.center_selected"; bl_label = "Center Selected Object"; bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        bpy.ops.view3d.view_selected()
        r3d = context.space_data.region_3d if context.space_data else None
        props = getattr(context.scene, "persp_view_props", None)
        if r3d and props:
            target_pos = Vector(r3d.view_location)
            props.view_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        return {'FINISHED'}

class PERSP_OT_CopyActualViewPos(Operator):
    bl_idname = f"{PREFIX}_persp.copy_actual_pos"; bl_label = "Copy Position Only"
    def execute(self, context):
        r3d = context.space_data.region_3d if context.space_data else None
        if not r3d: return {'CANCELLED'}
        target_pos = Vector(r3d.view_location)
        p = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        context.window_manager.clipboard = f"Actual View Pos: ({p.x:.2f}, {p.y:.2f}, {p.z:.2f})"
        self.report({'INFO'}, "視座位置をコピーしました"); return {'FINISHED'}

class PERSP_OT_CopyAngles(Operator):
    bl_idname = f"{PREFIX}_persp.copy_angles"; bl_label = "Copy Full Info (Pos & Angles)"
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        r3d = context.space_data.region_3d if context.space_data else None
        if not r3d or not props: return {'CANCELLED'}
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        vec = target_pos - actual_cam_pos; length = vec.length
        if length < 0.0001: return {'CANCELLED'}
        ang_x, ang_y, ang_z = math.degrees(math.acos(vec.x / length)), math.degrees(math.acos(vec.y / length)), math.degrees(math.acos(vec.z / length))
        pl_x, pl_y, pl_z = math.degrees(math.asin(vec.x / length)), math.degrees(math.asin(vec.y / length)), math.degrees(math.asin(vec.z / length))
        info_text = (
            f"--- View Direction Info ---\n[ Actual 3D View Status ]\nActual View Pos : ({actual_cam_pos.x:.2f}, {actual_cam_pos.y:.2f}, {actual_cam_pos.z:.2f})\n"
            f"Target Pos      : ({target_pos.x:.2f}, {target_pos.y:.2f}, {target_pos.z:.2f})\nDistance        : {length:.2f}\n\n"
            f"[ Direction Angles (軸そのものとの角度 0〜180°) ]\nAngle from X Axis : {ang_x:.2f} deg\nAngle from Y Axis : {ang_y:.2f} deg\nAngle from Z Axis : {ang_z:.2f} deg\n\n"
            f"[ Planar Angles (直感的な傾き・ズレ角 -90〜90°) ]\nX (横のズレ角)    : {pl_x:.2f} deg\nY (前後の傾き)    : {pl_y:.2f} deg\nZ (仰角・俯角)    : {pl_z:.2f} deg\n"
        )
        context.window_manager.clipboard = info_text
        self.report({'INFO'}, "情報全体をクリップボードにコピーしました"); return {'FINISHED'}

# ------------------------------------------------------------------------
# Panels
# ------------------------------------------------------------------------
class BasePanel(Panel): bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = ADDON_CATEGORY_NAME

class VIEW3D_PT_LinkPanel(BasePanel):
    bl_label = PANEL_LABELS["LINK"]; bl_idname = LINK_PANEL_IDNAME; bl_order = 0
    def draw(self, context):
        layout = self.layout
        layout.operator("wm.url_open", text="背景色  20250720", icon='URL').url = "<https://app.notion.com/p/20250720-3a3347b3454a80f78e8dc0a5cc52e649>"
        layout.operator("wm.url_open", text="アドオン コンテナ 20260715", icon='URL').url = "<https://note.com/zionadmillion/n/n2158226ce616>"
        layout.operator("wm.url_open", text="posfie", icon='URL').url = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
        layout.operator("wm.url_open", text="zionadchat", icon='URL').url = "<https://x.com/zionadchat>"

class VIEW3D_PT_MainPanel(BasePanel):
    bl_label = PANEL_LABELS["MAIN"]; bl_idname = MAIN_PANEL_IDNAME; bl_order = 1
    def draw(self, context):
        layout = self.layout
        
        # メイン操作群
        layout.operator(f"{PREFIX}_persp.center_selected", icon='VIEWZOOM', text="選択Objectを画面中央に表示")
        layout.separator()
        row = layout.row(); row.scale_y = 1.2
        row.operator(f"{PREFIX}_wm.copy_script", icon='COPY_ID', text="初期値コード(辞書)のみをコピー")
        layout.row().operator(f"{PREFIX}_wm.view_center_front", icon='VIEWZOOM', text="0,0,0 を正面(Y-)から見る")
        
        layout.separator()
        
        # Overlays設定群(元のOverlayPanelから移動)
        layout.label(text="Overlays", icon='OVERLAY')
        props = context.scene.viewport_color_props
        if context.space_data.type == 'VIEW_3D':
            layout.prop(context.space_data.overlay, "show_floor", text="Floor")
            layout.prop(props, "custom_grid_scale", text="Scale 数値入力")
            row_grid = layout.row(align=True)
            row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="入力値").scale_value = props.custom_grid_scale
            row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="10.0").scale_value = 10.0
            row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="100.0").scale_value = 100.0
        else:
            layout.label(text="3D Viewport is required.")

class VIEW3D_PT_PerspControlPanel(BasePanel):
    bl_label = PANEL_LABELS["PERSP"]; bl_idname = PERSP_PANEL_IDNAME; bl_order = 2
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, "persp_view_props", None)
        if not props: return
        box = layout.box(); box.label(text="Perspective Viewpoint", icon='VIEW_CAMERA'); box.prop(props, "slider_limit", text="Range Limit (+/-)")
        col = box.column(align=True); col.prop(props, "view_pos", text="X", index=0); col.prop(props, "view_pos", text="Y", index=1); col.prop(props, "view_pos", text="Z", index=2)
        box.separator(); box.operator(f"{PREFIX}_persp.get_current_view", icon='RESTRICT_VIEW_OFF'); box.operator(f"{PREFIX}_persp.reset_view", icon='LOOP_BACK')
        layout.separator()
        box_info = layout.box(); box_info.label(text="Actual View Status", icon='INFO')
        r3d = context.space_data.region_3d if context.space_data else None
        if r3d:
            target_pos = Vector(r3d.view_location); actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
            vec = target_pos - actual_cam_pos; length = vec.length
            col_pos = box_info.column(align=True); col_pos.label(text="[ Actual Position ]", icon='VIEW_CAMERA')
            col_pos.label(text=f"  X: {actual_cam_pos.x:.2f}"); col_pos.label(text=f"  Y: {actual_cam_pos.y:.2f}"); col_pos.label(text=f"  Z: {actual_cam_pos.z:.2f}"); col_pos.label(text=f"  Distance: {length:.2f}") 
            box_info.operator(f"{PREFIX}_persp.copy_actual_pos", icon='COPYDOWN'); box_info.separator()
            col_ang = box_info.column(align=True)
            if length > 0.0001:
                a_x, a_y, a_z = math.degrees(math.acos(vec.x / length)), math.degrees(math.acos(vec.y / length)), math.degrees(math.acos(vec.z / length))
                p_x, p_y, p_z = math.degrees(math.asin(vec.x / length)), math.degrees(math.asin(vec.y / length)), math.degrees(math.asin(vec.z / length))
                col_ang.label(text="[ Direction Angles (軸との角度) ]", icon='ORIENTATION_GLOBAL')
                col_ang.label(text=f"  X: {a_x:.2f}°"); col_ang.label(text=f"  Y: {a_y:.2f}°"); col_ang.label(text=f"  Z: {a_z:.2f}°")
                col_ang.separator(); col_ang.label(text="[ Planar Angles (直感的な傾き) ]", icon='DRIVER_ROTATIONAL_DIFFERENCE')
                col_ang.label(text=f"  X (ズレ角): {p_x:.2f}°"); col_ang.label(text=f"  Y (ズレ角): {p_y:.2f}°"); col_ang.label(text=f"  Z (仰俯角): {p_z:.2f}°")
            else: col_ang.label(text="  Target is too close")
            box_info.separator(); box_info.operator(f"{PREFIX}_persp.copy_angles", icon='COPYDOWN')
        else: box_info.label(text="Please use in 3D View")

class VIEW3D_PT_solid_background_panel(BasePanel):
    bl_label = PANEL_LABELS["BACKGROUND"]; bl_idname = BG_PANEL_IDNAME_1; bl_order = 3
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "preset", text="Background Preset"); layout.prop(props, "background_type", expand=True)
        layout.prop(props, "custom_gradient_high", text="Color High" if props.background_type != 'SINGLE_COLOR' else "Color")
        if props.background_type != 'SINGLE_COLOR': layout.prop(props, "custom_gradient_low")
        layout.prop(props, "reverse_gradient", text="Reverse Gradient")

class VIEW3D_PT_HeaderPanel(BasePanel):
    bl_label = PANEL_LABELS["HEADER"]; bl_idname = HEADER_PANEL_IDNAME; bl_order = 4
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "header_preset", text="Header Preset"); layout.prop(props, "header_color")

class VIEW3D_PT_RenderPanel(BasePanel):
    bl_label = PANEL_LABELS["RENDER"]; bl_idname = RENDER_PANEL_IDNAME; bl_order = 5
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "render_preset"); layout.prop(props, "render_color"); layout.prop(props, "render_environment_strength")

class VIEW3D_PT_SunPanel(BasePanel):
    bl_label = PANEL_LABELS["SUN"]; bl_idname = SUN_PANEL_IDNAME; bl_order = 6
    def draw(self, context):
        layout, props = self.layout, context.scene.sun_settings_props
        row = layout.row(align=True); row.operator(f"{PREFIX}.create_sun", text="太陽作成ボタン"); row.operator(f"{PREFIX}.reset_sun", icon='FILE_REFRESH', text="")
        layout.separator(); layout.prop(props, "sun_control_mode", expand=True)
        if props.sun_control_mode == 'ANGLE': layout.prop(props, "sun_rotation")
        else: layout.prop(props, "sun_target_location")
        layout.prop(props, "sun_location"); layout.prop(props, "sun_strength")

class VIEW3D_PT_GridPanel(BasePanel):
    bl_label = PANEL_LABELS["GRID"]; bl_idname = GRID_PANEL_IDNAME; bl_order = 7
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "grid_preset"); layout.prop(props, "grid_color")

class VIEW3D_PT_WirePanel(BasePanel):
    bl_label = PANEL_LABELS["WIRE"]; bl_idname = WIRE_PANEL_IDNAME; bl_order = 8
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "wire_preset"); layout.prop(props, "wire_color")

class VIEW3D_PT_CameraPanel(BasePanel):
    bl_label = PANEL_LABELS["CAMERA"]; bl_idname = CAMERA_PANEL_IDNAME; bl_order = 9
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "camera_preset"); layout.prop(props, "camera_color")

class VIEW3D_PT_OutlinerPanel(BasePanel):
    bl_label = PANEL_LABELS["OUTLINER"]; bl_idname = OUTLINER_PANEL_IDNAME; bl_order = 10
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "outliner_preset"); layout.prop(props, "outliner_header_color"); layout.prop(props, "outliner_background_color")

class VIEW3D_PT_TextEditorPanel(BasePanel):
    bl_label = PANEL_LABELS["TEXT_EDITOR"]; bl_idname = TEXT_EDITOR_PANEL_IDNAME; bl_order = 11
    def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "text_editor_preset"); layout.prop(props, "text_editor_header_color"); layout.prop(props, "text_editor_background_color")

class VIEW3D_PT_RemovePanel(BasePanel):
    bl_label = PANEL_LABELS["REMOVE"]; bl_idname = REMOVE_PANEL_IDNAME; bl_order = 12
    def draw(self, context): self.layout.operator(f"{PREFIX}_wm.remove_all_panels", text=PANEL_LABELS["REMOVE"])

# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes =[
    SunSettingsProperties, ViewportColorProperties, PerspViewProperties,
    OT_CopyFullScript, OT_ViewCenterFront, OVERLAY_OT_set_grid_scale,
    SUN_OT_Create, SUN_OT_Reset,
    PERSP_OT_GetCurrentView, PERSP_OT_ResetView, PERSP_OT_CenterSelected, PERSP_OT_CopyActualViewPos, PERSP_OT_CopyAngles,
    RemoveAllPanels, VIEW3D_PT_LinkPanel, VIEW3D_PT_MainPanel, VIEW3D_PT_PerspControlPanel,
    VIEW3D_PT_solid_background_panel, VIEW3D_PT_HeaderPanel, VIEW3D_PT_RenderPanel, 
    VIEW3D_PT_SunPanel, VIEW3D_PT_GridPanel, VIEW3D_PT_WirePanel, VIEW3D_PT_CameraPanel, 
    VIEW3D_PT_OutlinerPanel, VIEW3D_PT_TextEditorPanel, VIEW3D_PT_RemovePanel
]

def register():
    for cls in classes:
        # 二重登録防止処理
        try: bpy.utils.register_class(cls)
        except ValueError:
            try: bpy.utils.unregister_class(getattr(bpy.types, cls.__name__)); bpy.utils.register_class(cls)
            except Exception: pass
            
    bpy.types.Scene.viewport_color_props = PointerProperty(type=ViewportColorProperties)
    bpy.types.Scene.sun_settings_props = PointerProperty(type=SunSettingsProperties)
    bpy.types.Scene.persp_view_props = PointerProperty(type=PerspViewProperties)
    
    def apply_initial_settings():
        if bpy.context.scene and hasattr(bpy.context.scene, 'viewport_color_props'):
            props = bpy.context.scene.viewport_color_props
            sun_props = bpy.context.scene.sun_settings_props
            persp_props = bpy.context.scene.persp_view_props
            
            for key, val in CURRENT_DEFAULTS.items():
                if hasattr(props, key): setattr(props, key, val)
                elif hasattr(sun_props, key): setattr(sun_props, key, val)
                elif hasattr(persp_props, key): setattr(persp_props, key, val)
            
            update_background_color(props, bpy.context); update_header_color(props, bpy.context); update_render_color(props, bpy.context)
            update_grid_color(props, bpy.context); update_wire_color(props, bpy.context); update_camera_color(props, bpy.context)
            update_outliner_color(props, bpy.context); update_text_editor_color(props, bpy.context); update_sun(sun_props, bpy.context)

            # アドオン有効化時の自動設定(サイドパネル展開&マテリアルプレビュー化)
            for window in bpy.context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D':
                                space.show_region_ui = True
                                space.shading.type = 'MATERIAL'
    bpy.app.timers.register(apply_initial_settings, first_interval=0.1)
    if not bpy.app.timers.is_registered(view_sync_timer): bpy.app.timers.register(view_sync_timer)

def unregister():
    if hasattr(bpy.types.Scene, 'viewport_color_props'): del bpy.types.Scene.viewport_color_props
    if hasattr(bpy.types.Scene, 'sun_settings_props'): del bpy.types.Scene.sun_settings_props
    if hasattr(bpy.types.Scene, 'persp_view_props'): del bpy.types.Scene.persp_view_props
    for cls in reversed(classes):
        try: bpy.utils.unregister_class(cls)
        except RuntimeError: pass
    if bpy.app.timers.is_registered(view_sync_timer): bpy.app.timers.unregister(view_sync_timer)

# ファイルとして直接実行された場合
if __name__ == "__main__":
    if "unregister" in globals():
        try: unregister()
        except Exception: pass
    if "register" in globals(): register()
# Copied: 12:40:00
import bpy
import os
import math
from bpy.props import FloatVectorProperty, FloatProperty, EnumProperty, StringProperty, BoolProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector, Euler
from math import radians
from datetime import datetime

# ==============================================================================
# 【 開発方針 】
# 本アドオンは Blender 5.x 専用です。
# Blender 4.x以前との互換性は維持しません。
# 最新APIを利用し、保守性・安定性・開発速度を優先します。
# ==============================================================================

# ★ このスクリプト自身のID (コピー機能で使用)
# ### ZIONAD_SOURCE_ID: VIEWPORT_COLOR_2026_03_16 ###
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: VIEWPORT_COLOR_2026_03_16 ###"

# アドオンのメタデータ
bl_info = {
    "name": "zionad 5520[ 3D Viewport Color & Sun & Perspective ] 20260319",
    "author": "zionadchat",
    "version": (3, 0, 0),
    "blender": (5, 0, 0),
    "category": "   5520[  3D Viewport  ]   ",
    "description": "【Blender 5.x 専用】3Dビューポートの色、太陽、透視投影視座位置をリアルタイム制御します",
    "location": "3Dビュー > サイドバー",
}

# 定数
ADDON_CATEGORY_NAME = bl_info["category"]
PREFIX = "view2026316"

VIEW_RESET_BTN_TEXT = "Reset View (0, -10, 10)"  
VIEW_POS_INIT = (0.0, -10.0, 10.0)  

# ==============================================================================
#  デフォルト値設定 (コピー機能でここが書き換わります)
# ==============================================================================
# プリセットによる上書きを防ぐため、プリセット項目を先に読み込む順序にしています
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "sun_control_mode": 'ANGLE',
    "grid_preset": 'white',
    "wire_preset": 'orange',
    "camera_preset": 'Cam',
    "background_type": 'LINEAR',
    "header_preset": 'Dark Green',
    "preset": 'Dark Green',
    "render_preset": 'Blue',
    "outliner_preset": 'Outliner 5.0.0',
    "text_editor_preset": 'Text 5.0.0',
    "sun_target_location": (0.0000, 0.0000, 0.0000),
    "sun_rotation": (0.7854, 0.0000, 0.7854),
    "sun_location": (0.0000, 0.0000, 10.0000),
    "sun_strength": 2.5000,
    "custom_grid_scale": 1.0000,
    "grid_color": (1.0000, 1.0000, 1.0000, 1.0000),
    "wire_color": (0.0000, 0.0000, 0.0000),
    "camera_color": (0.4700, 0.5500, 1.0000),
    "header_color": (0.0000, 0.0300, 0.0000, 1.0000),
    "custom_gradient_high": (0.1641, 0.7668, 1.0000),
    "custom_gradient_low": (0.0088, 0.0098, 0.1500),
    "reverse_gradient": False,
    "render_color": (0.1900, 0.6000, 1.0000, 1.0000),
    "render_environment_strength": 1.0000,
    "outliner_header_color": (0.1900, 0.1900, 0.1900, 0.7000),
    "outliner_background_color": (0.1400, 0.1400, 0.1400, 1.0000),
    "text_editor_header_color": (0.1900, 0.1900, 0.1900, 0.7000),
    "text_editor_background_color": (0.1400, 0.1400, 0.1400, 1.0000),
    "view_pos": (0.0000, -10.0000, 10.0000),
}
# <END_DICT>

# パネル定義
COPY_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_copy_panel"
PERSP_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_persp_control"
OVERLAY_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_overlay_panel"
BG_PANEL_IDNAME_1 = f"{PREFIX}_VIEW3D_PT_solid_background_panel"
HEADER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_header_panel"
RENDER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_render_panel"
SUN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_sun_panel"
GRID_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_gridpanel"
WIRE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_wirepanel"
CAMERA_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_camerapanel"
OUTLINER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_outliner_panel"
TEXT_EDITOR_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_text_editor_panel"
LINK_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_link_panel"
REMOVE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_remove"

# パネルラベル
PANEL_LABELS = {
    "COPY": "コードコピー",
    "PERSP": "透視投影 視座位置",
    "OVERLAY": "Overlays",
    "BACKGROUND": "3D Viewport Color",
    "HEADER": "Header Color",
    "RENDER": "Render Color",
    "SUN": "太陽 設定",
    "GRID": "Grid Color",
    "WIRE": "Wire Color",
    "CAMERA": "Camera Color",
    "OUTLINER": "Outliner Color",
    "TEXT_EDITOR": "Text Editor Color",
    "LINK": "リンク",
    "REMOVE": "アドオン削除",
}

# プリセット群
BASE_PRESETS =[
    ("Dark Green", "Dark Green", "Dark Green background", (0.00, 0.28, 0.02), (0.10, 0.15, 0.05)),
    ("purple", "purple", "purple background", (0.49, 0.45, 1.00), (0.74, 0.65, 0.88)),
    ("BlueGreen", "BlueGreen", "BlueGreen background", (0.14, 0.34, 0.83), (0.57, 0.88, 0.63)),
    ("DARK_BLUE", "Dark Blue", "Dark Blue background", (0.07, 0.13, 0.31), (0.05, 0.05, 0.15)),
    ("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 background", (0.24, 0.24, 0.24), (0.19, 0.19, 0.19)),
    ("FOREST_GREEN", "Forest Green", "Forest Green background", (0.50, 0.70, 0.50), (0.10, 0.15, 0.05)),
]

HEADER_PRESETS =[
    ("Dark Green", "Dark Green", "Dark Green header", (0.00, 0.03, 0.00, 1.00)),
    ("purple", "purple", "purple header", (0.00, 0.00, 0.00, 1.00)),
    ("BlueGreen", "BlueGreen", "BlueGreen header", (0.00, 0.00, 0.00, 1.00)),
    ("DARK_BLUE", "Dark Blue", "Dark Blue header", (0.10, 0.10, 0.30, 1.00)),
    ("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 header", (0.19, 0.19, 0.19, 0.70)),
    ("FOREST_GREEN", "Forest Green", "Forest Green header", (0.20, 0.30, 0.10, 1.00)),
]

RENDER_PRESETS =[
    ("Blue", "Blue", "Blue render color", (0.19, 0.60, 1.00, 1.00)),
    ("Render 5.0.0", "Render 5.0.0", "Render 5.0.0 color", (0.05, 0.05, 0.05, 1.00)),
    ("LIGHT_GRAY", "Light Gray", "Light gray render", (0.80, 0.80, 0.80, 1.00)),
]

GRID_PRESETS =[
    ("white", "white", "white grid color", (1.00, 1.00, 1.00, 1.00)),
    ("Grid 5.0.0", "Grid 5.0.0", "Grid 5.0.0 color", (0.33, 0.33, 0.33, 0.50)),
    ("DARK_GRAY", "Dark Gray", "Dark gray grid", (0.10, 0.10, 0.10, 1.00)),
]

WIRE_PRESETS =[
    ("orange", "orange", "orange wire", (0.71, 0.21, 0.05)),
    ("Wire 5.0.0", "Wire 5.0.0", "Wire 5.0.0 color", (0.00, 0.00, 0.00)),
    ("WHITE", "White", "White wire", (1.00, 1.00, 1.00)),
]

CAMERA_PRESETS =[
    ("Cam", "Cam", "Cam camera color", (0.47, 0.55, 1.00)),
    ("Cam 5.0.0", "Cam 5.0.0", "Cam 5.0.0 color", (0.00, 0.00, 0.00)),
    ("YELLOW", "Yellow", "Yellow camera", (1.00, 1.00, 0.00)),
]

OUTLINER_PRESETS =[
    ("Outliner 5.0.0", "Outliner 5.0.0", "Outliner 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
    ("DARK_TEAL", "Dark Teal", "Dark teal outliner", (0.00, 0.20, 0.20, 1.00), (0.00, 0.10, 0.10, 1.00)),
]

TEXT_EDITOR_PRESETS =[
    ("Text 5.0.0", "Text 5.0.0", "Text Editor 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
    ("DARK_GREEN", "Dark Green", "Dark green text editor", (0.00, 0.20, 0.00, 1.00), (0.00, 0.10, 0.00, 1.00)),
]

BACKGROUND_TYPES =[
    ('SINGLE_COLOR', "Single Color", "Uniform background color"),
    ('LINEAR', "Linear", "Linear gradient background"),
    ('RADIAL', "Vignette", "Radial gradient simulating a vignette effect"),
]

# ==============================================================================
#  リアルタイム更新用コールバック関数(ビューポート色・太陽)
# ==============================================================================
def format_tuple(t):
    return '(' + ', '.join(f"{x:.3f}" for x in t) + ')'

def update_custom_grid_scale(self, context):
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                area.spaces.active.overlay.grid_scale = self.custom_grid_scale

def update_grid_color(self, context):
    bpy.context.preferences.themes[0].view_3d.grid = self.grid_color

def update_wire_color(self, context):
    bpy.context.preferences.themes[0].view_3d.wire = self.wire_color

def update_camera_color(self, context):
    bpy.context.preferences.themes[0].view_3d.camera = self.camera_color

def update_background_color(self, context):
    gradients = bpy.context.preferences.themes[0].view_3d.space.gradients
    if self.background_type == 'SINGLE_COLOR':
        gradients.background_type = 'LINEAR'
        gradients.high_gradient = gradients.gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
    else:
        gradients.background_type = self.background_type
        gradients.high_gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
        gradients.gradient = self.custom_gradient_high if self.reverse_gradient else self.custom_gradient_low
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()

def update_header_color(self, context):
    bpy.context.preferences.themes[0].view_3d.space.header = self.header_color
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()

def update_render_color(self, context):
    bpy.context.preferences.themes[0].image_editor.space.back = self.render_color[:3]
    world = bpy.data.worlds.get('MyWorld') or bpy.data.worlds.new('MyWorld')
    context.scene.world = world
    world.use_nodes = True
    bg_node = world.node_tree.nodes.get('Background') or world.node_tree.nodes.new(type='ShaderNodeBackground')
    bg_node.name = 'Background'
    bg_node.inputs[0].default_value = self.render_color
    bg_node.inputs[1].default_value = self.render_environment_strength
    output_node = world.node_tree.nodes.get('World Output') or world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
    output_node.name = 'World Output'
    world.node_tree.links.new(bg_node.outputs[0], output_node.inputs['Surface'])

def update_outliner_color(self, context):
    space = bpy.context.preferences.themes[0].outliner.space
    space.header = self.outliner_header_color
    space.back = self.outliner_background_color[:3]
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'OUTLINER': area.tag_redraw()

def update_text_editor_color(self, context):
    space = bpy.context.preferences.themes[0].text_editor.space
    space.header = self.text_editor_header_color
    space.back = self.text_editor_background_color[:3]
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'TEXT_EDITOR': area.tag_redraw()

def get_or_create_sun():
    sun_obj = bpy.data.objects.get("Sun")
    if sun_obj is None or sun_obj.type != 'LIGHT' or sun_obj.data.type != 'SUN':
        if sun_obj:
            try: bpy.data.objects.remove(sun_obj, do_unlink=True)
            except: pass
        bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 0))
        sun_obj = bpy.context.active_object
        sun_obj.name = "Sun"; sun_obj.data.name = "Sun"
    return sun_obj

def update_sun(self, context):
    sun = get_or_create_sun()
    sun.location = self.sun_location
    if self.sun_control_mode == 'ANGLE':
        sun.rotation_euler = self.sun_rotation
    else:
        target_vec = Vector(self.sun_target_location)
        sun_vec = Vector(self.sun_location)
        if (target_vec - sun_vec).length_squared < 0.0001: return
        direction = target_vec - sun_vec
        sun.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
    sun.data.energy = self.sun_strength

# ==============================================================================
#  リアルタイム更新用コールバック関数(透視投影 視座位置)
# ==============================================================================
_is_updating_view = False

def update_view_position(self, context):
    """スライダーが操作されたときに視点を更新する"""
    global _is_updating_view
    if _is_updating_view: return
    
    props = getattr(context.scene, "persp_view_props", None)
    if not props: return

    limit = props.slider_limit
    v = list(props.view_pos)
    clamped = False
    for i in range(3):
        if v[i] > limit: v[i] = limit; clamped = True
        elif v[i] < -limit: v[i] = -limit; clamped = True
            
    if clamped:
        _is_updating_view = True
        props.view_pos = v
        _is_updating_view = False
    
    _is_updating_view = True
    try:
        cam_pos = Vector(props.view_pos)
        for window in context.window_manager.windows:
            for area in window.screen.areas:
                if area.type == 'VIEW_3D':
                    for space in area.spaces:
                        if space.type == 'VIEW_3D':
                            r3d = space.region_3d
                            r3d.view_perspective = 'PERSP'
                            target_pos = Vector(r3d.view_location)
                            rel_pos = cam_pos - target_pos
                            dist = rel_pos.length
                            if dist > 0.001:
                                r3d.view_distance = dist
                                r3d.view_rotation = rel_pos.to_track_quat('Z', 'Y')
    finally:
        _is_updating_view = False

def view_sync_timer():
    """マウス操作での視点移動を検知し、スライダー(UI)を同期するタイマー"""
    global _is_updating_view
    if _is_updating_view: return 0.05
    
    context = bpy.context
    if getattr(context, "scene", None) is None: return 0.05
    props = getattr(context.scene, "persp_view_props", None)
    if not props: return 0.05

    r3d = None
    target_area = None
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        r3d = space.region_3d
                        target_area = area
                        break
                if r3d: break
        if r3d: break

    if r3d and target_area:
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        
        current_pos = Vector(props.view_pos)
        if (current_pos - actual_cam_pos).length > 0.001:
            _is_updating_view = True
            max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
            if max_val > props.slider_limit:
                props.slider_limit = max_val + 50.0 
                
            props.view_pos = actual_cam_pos
            _is_updating_view = False
            target_area.tag_redraw()
            
    return 0.05

# ------------------------------------------------------------------------
# Property Groups
# ------------------------------------------------------------------------
class SunSettingsProperties(PropertyGroup):
    sun_control_mode: EnumProperty(name="制御モード", items=[('ANGLE', "角度", "太陽の回転を直接指定"), ('TARGET', "ターゲット", "指定位置に太陽を向ける")], default=CURRENT_DEFAULTS["sun_control_mode"], update=update_sun)
    sun_target_location: FloatVectorProperty(name="ターゲット位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_target_location"], update=update_sun)
    sun_rotation: FloatVectorProperty(name="角度", subtype='EULER', unit='ROTATION', default=CURRENT_DEFAULTS["sun_rotation"], update=update_sun)
    sun_location: FloatVectorProperty(name="位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_location"], update=update_sun)
    sun_strength: FloatProperty(name="強さ", default=CURRENT_DEFAULTS["sun_strength"], min=0.0, update=update_sun)

class ViewportColorProperties(PropertyGroup):
    custom_grid_scale: FloatProperty(name="Scale", default=CURRENT_DEFAULTS["custom_grid_scale"], min=0.001, update=update_custom_grid_scale)
    grid_color: FloatVectorProperty(name="Grid Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["grid_color"], update=update_grid_color)
    grid_preset: EnumProperty(name="Grid Preset", items=[(p[0], p[1], p[2]) for p in GRID_PRESETS], default=CURRENT_DEFAULTS["grid_preset"], update=lambda self, context: self.update_grid_preset(context))
    wire_color: FloatVectorProperty(name="Wire Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["wire_color"], update=update_wire_color)
    wire_preset: EnumProperty(name="Wire Preset", items=[(p[0], p[1], p[2]) for p in WIRE_PRESETS], default=CURRENT_DEFAULTS["wire_preset"], update=lambda self, context: self.update_wire_preset(context))
    camera_color: FloatVectorProperty(name="Camera Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["camera_color"], update=update_camera_color)
    camera_preset: EnumProperty(name="Camera Preset", items=[(p[0], p[1], p[2]) for p in CAMERA_PRESETS], default=CURRENT_DEFAULTS["camera_preset"], update=lambda self, context: self.update_camera_preset(context))
    background_type: EnumProperty(name="Background Type", items=BACKGROUND_TYPES, default=CURRENT_DEFAULTS["background_type"], update=update_background_color)
    header_color: FloatVectorProperty(name="Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["header_color"], update=update_header_color)
    header_preset: EnumProperty(name="Header Preset", items=[(p[0], p[1], p[2]) for p in HEADER_PRESETS], default=CURRENT_DEFAULTS["header_preset"], update=lambda self, context: self.update_header_preset(context))
    custom_gradient_high: FloatVectorProperty(name="Gradient High Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_high"], update=update_background_color)
    custom_gradient_low: FloatVectorProperty(name="Gradient Low Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_low"], update=update_background_color)
    reverse_gradient: BoolProperty(name="Reverse Gradient", default=CURRENT_DEFAULTS["reverse_gradient"], update=update_background_color)
    preset: EnumProperty(name="Color Preset", items=[(p[0], p[1], p[2]) for p in BASE_PRESETS], default=CURRENT_DEFAULTS["preset"], update=lambda self, context: self.update_preset(context))
    render_color: FloatVectorProperty(name="Render Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["render_color"], update=update_render_color)
    render_preset: EnumProperty(name="Render Preset", items=[(p[0], p[1], p[2]) for p in RENDER_PRESETS], default=CURRENT_DEFAULTS["render_preset"], update=lambda self, context: self.update_render_preset(context))
    render_environment_strength: FloatProperty(name="Render Environment Strength", default=CURRENT_DEFAULTS["render_environment_strength"], min=0.0, max=1900.0, update=update_render_color)
    outliner_header_color: FloatVectorProperty(name="Outliner Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_header_color"], update=update_outliner_color)
    outliner_background_color: FloatVectorProperty(name="Outliner Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_background_color"], update=update_outliner_color)
    outliner_preset: EnumProperty(name="Outliner Preset", items=[(p[0], p[1], p[2]) for p in OUTLINER_PRESETS], default=CURRENT_DEFAULTS["outliner_preset"], update=lambda self, context: self.update_outliner_preset(context))
    text_editor_header_color: FloatVectorProperty(name="Text Editor Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_header_color"], update=update_text_editor_color)
    text_editor_background_color: FloatVectorProperty(name="Text Editor Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_background_color"], update=update_text_editor_color)
    text_editor_preset: EnumProperty(name="Text Editor Preset", items=[(p[0], p[1], p[2]) for p in TEXT_EDITOR_PRESETS], default=CURRENT_DEFAULTS["text_editor_preset"], update=lambda self, context: self.update_text_editor_preset(context))

    def update_grid_preset(self, context):
        for p in GRID_PRESETS:
            if p[0] == self.grid_preset: self.grid_color = p[3]; break
    def update_wire_preset(self, context):
        for p in WIRE_PRESETS:
            if p[0] == self.wire_preset: self.wire_color = p[3]; break
    def update_camera_preset(self, context):
        for p in CAMERA_PRESETS:
            if p[0] == self.camera_preset: self.camera_color = p[3]; break
    def update_preset(self, context):
        for p in BASE_PRESETS:
            if p[0] == self.preset: self.custom_gradient_high = p[3]; self.custom_gradient_low = p[4]; break
    def update_header_preset(self, context):
        for p in HEADER_PRESETS:
            if p[0] == self.header_preset: self.header_color = p[3]; break
    def update_render_preset(self, context):
        for p in RENDER_PRESETS:
            if p[0] == self.render_preset: self.render_color = p[3]; break
    def update_outliner_preset(self, context):
        for p in OUTLINER_PRESETS:
            if p[0] == self.outliner_preset: self.outliner_header_color = p[3]; self.outliner_background_color = p[4]; break
    def update_text_editor_preset(self, context):
        for p in TEXT_EDITOR_PRESETS:
            if p[0] == self.text_editor_preset: self.text_editor_header_color = p[3]; self.text_editor_background_color = p[4]; break

class PerspViewProperties(PropertyGroup):
    slider_limit: FloatProperty(name="Range Limit", default=300.0, min=10.0, max=10000.0)
    view_pos: FloatVectorProperty(name="View Position", size=3, soft_min=-10000.0, soft_max=10000.0, 
                                  default=CURRENT_DEFAULTS.get('view_pos', VIEW_POS_INIT), 
                                  update=update_view_position)

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_ViewCenterFront(Operator):
    bl_idname = f"{PREFIX}_wm.view_center_front"
    bl_label = "Center 0,0,0 (Front View)"
    bl_description = "原点(0,0,0)を画面中央に配置し、Yマイナス方向からの視点(正面)にします"
    
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                rv3d = area.spaces.active.region_3d
                if rv3d:
                    rv3d.view_location = (0.0, 0.0, 0.0)
                    rv3d.view_rotation = Euler((radians(90.0), 0.0, 0.0), 'XYZ').to_quaternion()
                    if rv3d.view_distance < 10.0:
                        rv3d.view_distance = 60.0
        return {'FINISHED'}

class OT_CopyFullScript(Operator):
    bl_idname = f"{PREFIX}_wm.copy_script"
    bl_label = "Copy Script"
    
    def execute(self, context):
        props = context.scene.viewport_color_props
        sun_props = context.scene.sun_settings_props
        persp_props = context.scene.persp_view_props
        
        code = ""
        file_path = globals().get('__file__')
        if file_path:
            if file_path.endswith('.pyc') or file_path.endswith('.pyo'):
                file_path = file_path[:-1]
            try:
                if os.path.exists(file_path):
                    with open(file_path, 'r', encoding='utf-8') as f:
                        code = f.read()
            except Exception:
                pass
        
        if not code or SOURCE_ID_TAG not in code:
            for t in bpy.data.texts:
                if SOURCE_ID_TAG in t.as_string():
                    code = t.as_string()
                    break

        if not code:
            self.report({'ERROR'}, "スクリプトのソースが見つかりません。")
            return {'CANCELLED'}

        def fmt_vec(v): return "(" + ", ".join(f"{x:.4f}" for x in v) + ")"
        def fmt_str(s): return f"'{s}'"

        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "sun_control_mode": {fmt_str(sun_props.sun_control_mode)},\n'
        new_dict += f'    "grid_preset": {fmt_str(props.grid_preset)},\n'
        new_dict += f'    "wire_preset": {fmt_str(props.wire_preset)},\n'
        new_dict += f'    "camera_preset": {fmt_str(props.camera_preset)},\n'
        new_dict += f'    "background_type": {fmt_str(props.background_type)},\n'
        new_dict += f'    "header_preset": {fmt_str(props.header_preset)},\n'
        new_dict += f'    "preset": {fmt_str(props.preset)},\n'
        new_dict += f'    "render_preset": {fmt_str(props.render_preset)},\n'
        new_dict += f'    "outliner_preset": {fmt_str(props.outliner_preset)},\n'
        new_dict += f'    "text_editor_preset": {fmt_str(props.text_editor_preset)},\n'
        
        new_dict += f'    "sun_target_location": {fmt_vec(sun_props.sun_target_location)},\n'
        new_dict += f'    "sun_rotation": {fmt_vec(sun_props.sun_rotation)},\n'
        new_dict += f'    "sun_location": {fmt_vec(sun_props.sun_location)},\n'
        new_dict += f'    "sun_strength": {sun_props.sun_strength:.4f},\n'
        new_dict += f'    "custom_grid_scale": {props.custom_grid_scale:.4f},\n'
        new_dict += f'    "grid_color": {fmt_vec(props.grid_color)},\n'
        new_dict += f'    "wire_color": {fmt_vec(props.wire_color)},\n'
        new_dict += f'    "camera_color": {fmt_vec(props.camera_color)},\n'
        new_dict += f'    "header_color": {fmt_vec(props.header_color)},\n'
        new_dict += f'    "custom_gradient_high": {fmt_vec(props.custom_gradient_high)},\n'
        new_dict += f'    "custom_gradient_low": {fmt_vec(props.custom_gradient_low)},\n'
        new_dict += f'    "reverse_gradient": {props.reverse_gradient},\n'
        new_dict += f'    "render_color": {fmt_vec(props.render_color)},\n'
        new_dict += f'    "render_environment_strength": {props.render_environment_strength:.4f},\n'
        new_dict += f'    "outliner_header_color": {fmt_vec(props.outliner_header_color)},\n'
        new_dict += f'    "outliner_background_color": {fmt_vec(props.outliner_background_color)},\n'
        new_dict += f'    "text_editor_header_color": {fmt_vec(props.text_editor_header_color)},\n'
        new_dict += f'    "text_editor_background_color": {fmt_vec(props.text_editor_background_color)},\n'
        new_dict += f'    "view_pos": {fmt_vec(persp_props.view_pos)},\n'
        new_dict += "}\n"

        try:
            start, end = "# <BEGIN" + "_DICT>", "# <END" + "_DICT>"
            parts = code.split(start)
            if len(parts) < 2: return {'CANCELLED'}
            pre = parts[0]
            post = code.split(end)[1]
            
            lines = pre.split('\n')
            if len(lines) > 0 and lines[0].startswith("# Copied:"): lines.pop(0)
            pre = '\n'.join(lines).lstrip('\n')
            
            final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n{pre}{start}\n{new_dict}{end}{post}"
            context.window_manager.clipboard = final
            self.report({'INFO'}, "現在の数値でコードをコピーしました!")
        except Exception as e:
            self.report({'ERROR'}, f"Failed to parse code: {str(e)}")
            return {'CANCELLED'}
            
        return {'FINISHED'}

class OVERLAY_OT_set_grid_scale(Operator):
    bl_idname = f"{PREFIX}_overlay.set_grid_scale"
    bl_label = "Set Grid Scale"
    scale_value: FloatProperty()

    def execute(self, context):
        for window in context.window_manager.windows:
            for area in window.screen.areas:
                if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.scale_value
        return {'FINISHED'}

class SUN_OT_Create(Operator):
    bl_idname = f"{PREFIX}.create_sun"; bl_label = "太陽を作成"
    def execute(self, context):
        get_or_create_sun()
        self.report({'INFO'}, "太陽を作成しました。"); return {'FINISHED'}

class SUN_OT_Reset(Operator):
    bl_idname = f"{PREFIX}.reset_sun"; bl_label = "太陽の設定を初期値にリセット"
    def execute(self, context):
        props = context.scene.sun_settings_props
        props.sun_control_mode, props.sun_target_location = 'ANGLE', (0.0, 0.0, 0.0)
        props.sun_rotation = (radians(45.0), radians(0.0), radians(45.0))
        props.sun_location, props.sun_strength = (0.0, 0.0, 10.0), 2.5
        update_sun(props, context)
        self.report({'INFO'}, "太陽の設定をリセットしました。"); return {'FINISHED'}

class RemoveAllPanels(Operator):
    bl_idname = f"{PREFIX}_wm.remove_all_panels"; bl_label = PANEL_LABELS["REMOVE"]
    def execute(self, context): unregister(); return {'FINISHED'}

# --- 透視投影用のオペレーター ---
class PERSP_OT_GetCurrentView(Operator):
    bl_idname = f"{PREFIX}_persp.get_current_view"
    bl_label = "Get Current View & Update"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        r3d = context.space_data.region_3d if context.space_data else None
        if not props or not r3d: return {'CANCELLED'}
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
        if max_val > props.slider_limit: props.slider_limit = max_val + 50.0 
        props.view_pos = actual_cam_pos
        return {'FINISHED'}

class PERSP_OT_ResetView(Operator):
    bl_idname = f"{PREFIX}_persp.reset_view"
    bl_label = VIEW_RESET_BTN_TEXT
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        if props:
            for window in context.window_manager.windows:
                for area in window.screen.areas:
                    if area.type == 'VIEW_3D':
                        for space in area.spaces:
                            if space.type == 'VIEW_3D': space.region_3d.view_location = (0.0, 0.0, 0.0)
            props.view_pos = VIEW_POS_INIT 
        return {'FINISHED'}

class PERSP_OT_CenterSelected(Operator):
    bl_idname = f"{PREFIX}_persp.center_selected"
    bl_label = "Center Selected Object"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        bpy.ops.view3d.view_selected()
        r3d = context.space_data.region_3d if context.space_data else None
        props = getattr(context.scene, "persp_view_props", None)
        if r3d and props:
            target_pos = Vector(r3d.view_location)
            props.view_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        return {'FINISHED'}

class PERSP_OT_CopyActualViewPos(Operator):
    bl_idname = f"{PREFIX}_persp.copy_actual_pos"
    bl_label = "Copy Position Only"
    def execute(self, context):
        r3d = context.space_data.region_3d if context.space_data else None
        if not r3d: return {'CANCELLED'}
        target_pos = Vector(r3d.view_location)
        p = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        context.window_manager.clipboard = f"Actual View Pos: ({p.x:.4f}, {p.y:.4f}, {p.z:.4f})"
        self.report({'INFO'}, "視座位置をコピーしました")
        return {'FINISHED'}

class PERSP_OT_CopyAngles(Operator):
    bl_idname = f"{PREFIX}_persp.copy_angles"
    bl_label = "Copy Full Info (Pos & Angles)"
    def execute(self, context):
        props = getattr(context.scene, "persp_view_props", None)
        r3d = context.space_data.region_3d if context.space_data else None
        if not r3d or not props: return {'CANCELLED'}
            
        target_pos = Vector(r3d.view_location)
        actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
        vec = target_pos - actual_cam_pos
        length = vec.length
        
        if length < 0.0001: return {'CANCELLED'}
        ang_x = math.degrees(math.acos(vec.x / length))
        ang_y = math.degrees(math.acos(vec.y / length))
        ang_z = math.degrees(math.acos(vec.z / length))
        pl_x = math.degrees(math.asin(vec.x / length))
        pl_y = math.degrees(math.asin(vec.y / length))
        pl_z = math.degrees(math.asin(vec.z / length))
        
        info_text = (
            f"--- View Direction Info ---\n"
            f"[ Actual 3D View Status ]\n"
            f"Actual View Pos : ({actual_cam_pos.x:.4f}, {actual_cam_pos.y:.4f}, {actual_cam_pos.z:.4f})\n"
            f"Target Pos      : ({target_pos.x:.4f}, {target_pos.y:.4f}, {target_pos.z:.4f})\n"
            f"Distance        : {length:.4f}\n\n"
            f"[ Direction Angles (軸そのものとの角度 0〜180°) ]\n"
            f"Angle from X Axis : {ang_x:.2f} deg\n"
            f"Angle from Y Axis : {ang_y:.2f} deg\n"
            f"Angle from Z Axis : {ang_z:.2f} deg\n\n"
            f"[ Planar Angles (直感的な傾き・ズレ角 -90〜90°) ]\n"
            f"X (横のズレ角)    : {pl_x:.2f} deg\n"
            f"Y (前後の傾き)    : {pl_y:.2f} deg\n"
            f"Z (仰角・俯角)    : {pl_z:.2f} deg\n"
        )
        context.window_manager.clipboard = info_text
        self.report({'INFO'}, "情報全体をクリップボードにコピーしました")
        return {'FINISHED'}

# ------------------------------------------------------------------------
# Panels
# ------------------------------------------------------------------------
class BasePanel(Panel):
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = ADDON_CATEGORY_NAME

class VIEW3D_PT_CopyPanel(BasePanel):
    bl_label = PANEL_LABELS["COPY"]; bl_idname = COPY_PANEL_IDNAME; bl_order = 0
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.scale_y = 1.2
        row.operator(f"{PREFIX}_wm.copy_script", icon='COPY_ID', text="最新数値付きコードコピー")

        row_view = layout.row()
        row_view.operator(f"{PREFIX}_wm.view_center_front", icon='VIEWZOOM', text="0,0,0 を正面(Y-)から見る")

class VIEW3D_PT_PerspControlPanel(BasePanel):
    bl_label = PANEL_LABELS["PERSP"]; bl_idname = PERSP_PANEL_IDNAME; bl_order = 1
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, "persp_view_props", None)
        if not props: return

        box = layout.box()
        box.label(text="Perspective Viewpoint", icon='VIEW_CAMERA')
        box.prop(props, "slider_limit", text="Range Limit (+/-)")
        
        col = box.column(align=True)
        col.prop(props, "view_pos", text="X", index=0)
        col.prop(props, "view_pos", text="Y", index=1)
        col.prop(props, "view_pos", text="Z", index=2)
        
        box.separator()
        box.operator(f"{PREFIX}_persp.get_current_view", icon='RESTRICT_VIEW_OFF')
        box.operator(f"{PREFIX}_persp.reset_view", icon='LOOP_BACK')
        layout.operator(f"{PREFIX}_persp.center_selected", icon='VIEWZOOM')
        
        layout.separator()
        box_info = layout.box()
        box_info.label(text="Actual View Status", icon='INFO')
        
        r3d = context.space_data.region_3d if context.space_data else None
        if r3d:
            target_pos = Vector(r3d.view_location)
            actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
            vec = target_pos - actual_cam_pos
            length = vec.length
            
            col_pos = box_info.column(align=True)
            col_pos.label(text="[ Actual Position ]", icon='VIEW_CAMERA')
            col_pos.label(text=f"  X: {actual_cam_pos.x:.4f}")
            col_pos.label(text=f"  Y: {actual_cam_pos.y:.4f}")
            col_pos.label(text=f"  Z: {actual_cam_pos.z:.4f}")
            col_pos.label(text=f"  Distance: {length:.4f}") 
            box_info.operator(f"{PREFIX}_persp.copy_actual_pos", icon='COPYDOWN')
            box_info.separator()
            
            col_ang = box_info.column(align=True)
            if length > 0.0001:
                a_x = math.degrees(math.acos(vec.x / length))
                a_y = math.degrees(math.acos(vec.y / length))
                a_z = math.degrees(math.acos(vec.z / length))
                p_x = math.degrees(math.asin(vec.x / length))
                p_y = math.degrees(math.asin(vec.y / length))
                p_z = math.degrees(math.asin(vec.z / length))
                
                col_ang.label(text="[ Direction Angles (軸との角度) ]", icon='ORIENTATION_GLOBAL')
                col_ang.label(text=f"  X: {a_x:.2f}°")
                col_ang.label(text=f"  Y: {a_y:.2f}°")
                col_ang.label(text=f"  Z: {a_z:.2f}°")
                col_ang.separator()
                col_ang.label(text="[ Planar Angles (直感的な傾き) ]", icon='DRIVER_ROTATIONAL_DIFFERENCE')
                col_ang.label(text=f"  X (ズレ角): {p_x:.2f}°")
                col_ang.label(text=f"  Y (ズレ角): {p_y:.2f}°")
                col_ang.label(text=f"  Z (仰俯角): {p_z:.2f}°")
            else:
                col_ang.label(text="  Target is too close")

            box_info.separator()
            box_info.operator(f"{PREFIX}_persp.copy_angles", icon='COPYDOWN')
        else:
            box_info.label(text="Please use in 3D View")

class VIEW3D_PT_OverlayPanel(BasePanel):
    bl_label = PANEL_LABELS["OVERLAY"]; bl_idname = OVERLAY_PANEL_IDNAME; bl_order = 2
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        if context.space_data.type == 'VIEW_3D':
            layout.prop(context.space_data.overlay, "show_floor", text="Floor")
            layout.prop(props, "custom_grid_scale", text="Scale 数値入力")
            row = layout.row(align=True)
            row.operator(f"{PREFIX}_overlay.set_grid_scale", text="入力値").scale_value = props.custom_grid_scale
            row.operator(f"{PREFIX}_overlay.set_grid_scale", text="10.0").scale_value = 10.0
            row.operator(f"{PREFIX}_overlay.set_grid_scale", text="100.0").scale_value = 100.0
        else:
            layout.label(text="3D Viewport is required.")

class VIEW3D_PT_solid_background_panel(BasePanel):
    bl_label = PANEL_LABELS["BACKGROUND"]; bl_idname = BG_PANEL_IDNAME_1; bl_order = 3
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "preset", text="Background Preset")
        layout.prop(props, "background_type", expand=True)
        layout.prop(props, "custom_gradient_high", text="Color High" if props.background_type != 'SINGLE_COLOR' else "Color")
        if props.background_type != 'SINGLE_COLOR': layout.prop(props, "custom_gradient_low")
        layout.prop(props, "reverse_gradient", text="Reverse Gradient")

class VIEW3D_PT_HeaderPanel(BasePanel):
    bl_label = PANEL_LABELS["HEADER"]; bl_idname = HEADER_PANEL_IDNAME; bl_order = 4
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "header_preset", text="Header Preset")
        layout.prop(props, "header_color")

class VIEW3D_PT_RenderPanel(BasePanel):
    bl_label = PANEL_LABELS["RENDER"]; bl_idname = RENDER_PANEL_IDNAME; bl_order = 5
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "render_preset")
        layout.prop(props, "render_color")
        layout.prop(props, "render_environment_strength")

class VIEW3D_PT_SunPanel(BasePanel):
    bl_label = PANEL_LABELS["SUN"]; bl_idname = SUN_PANEL_IDNAME; bl_order = 6
    def draw(self, context):
        layout, props = self.layout, context.scene.sun_settings_props
        row = layout.row(align=True)
        row.operator(f"{PREFIX}.create_sun", text="太陽作成ボタン")
        row.operator(f"{PREFIX}.reset_sun", icon='FILE_REFRESH', text="")
        layout.separator()
        layout.prop(props, "sun_control_mode", expand=True)
        if props.sun_control_mode == 'ANGLE': layout.prop(props, "sun_rotation")
        else: layout.prop(props, "sun_target_location")
        layout.prop(props, "sun_location"); layout.prop(props, "sun_strength")

class VIEW3D_PT_GridPanel(BasePanel):
    bl_label = PANEL_LABELS["GRID"]; bl_idname = GRID_PANEL_IDNAME; bl_order = 7
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "grid_preset")
        layout.prop(props, "grid_color")

class VIEW3D_PT_WirePanel(BasePanel):
    bl_label = PANEL_LABELS["WIRE"]; bl_idname = WIRE_PANEL_IDNAME; bl_order = 8
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "wire_preset")
        layout.prop(props, "wire_color")

class VIEW3D_PT_CameraPanel(BasePanel):
    bl_label = PANEL_LABELS["CAMERA"]; bl_idname = CAMERA_PANEL_IDNAME; bl_order = 9
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "camera_preset")
        layout.prop(props, "camera_color")

class VIEW3D_PT_OutlinerPanel(BasePanel):
    bl_label = PANEL_LABELS["OUTLINER"]; bl_idname = OUTLINER_PANEL_IDNAME; bl_order = 10
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "outliner_preset")
        layout.prop(props, "outliner_header_color")
        layout.prop(props, "outliner_background_color")

class VIEW3D_PT_TextEditorPanel(BasePanel):
    bl_label = PANEL_LABELS["TEXT_EDITOR"]; bl_idname = TEXT_EDITOR_PANEL_IDNAME; bl_order = 11
    def draw(self, context):
        layout, props = self.layout, context.scene.viewport_color_props
        layout.prop(props, "text_editor_preset")
        layout.prop(props, "text_editor_header_color")
        layout.prop(props, "text_editor_background_color")

class VIEW3D_PT_LinkPanel(BasePanel):
    bl_label = PANEL_LABELS["LINK"]; bl_idname = LINK_PANEL_IDNAME; bl_order = 12
    def draw(self, context):
        layout = self.layout
        layout.operator("wm.url_open", text="進化版 画面中央 透視投影視座位置 20260319bb", icon='URL').url = "<https://www.notion.so/20260319bb-327f5dacaf43801e8e37ce489dc1d593>"
        layout.operator("wm.url_open", text="5520 背景色 変更 20260316版", icon='URL').url = "<https://www.notion.so/5520-20260316-314f5dacaf4380da9be4c05551d40710>"

class VIEW3D_PT_RemovePanel(BasePanel):
    bl_label = PANEL_LABELS["REMOVE"]; bl_idname = REMOVE_PANEL_IDNAME; bl_order = 13
    def draw(self, context):
        self.layout.operator(f"{PREFIX}_wm.remove_all_panels", text=PANEL_LABELS["REMOVE"])

# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes =[
    SunSettingsProperties, ViewportColorProperties, PerspViewProperties,
    OT_CopyFullScript, OT_ViewCenterFront,
    OVERLAY_OT_set_grid_scale,
    SUN_OT_Create, SUN_OT_Reset,
    PERSP_OT_GetCurrentView, PERSP_OT_ResetView, PERSP_OT_CenterSelected, PERSP_OT_CopyActualViewPos, PERSP_OT_CopyAngles,
    RemoveAllPanels, 
    VIEW3D_PT_CopyPanel, VIEW3D_PT_PerspControlPanel,
    VIEW3D_PT_OverlayPanel, VIEW3D_PT_solid_background_panel,
    VIEW3D_PT_HeaderPanel, VIEW3D_PT_RenderPanel, VIEW3D_PT_SunPanel, VIEW3D_PT_GridPanel,
    VIEW3D_PT_WirePanel, VIEW3D_PT_CameraPanel, VIEW3D_PT_OutlinerPanel, VIEW3D_PT_TextEditorPanel,
    VIEW3D_PT_LinkPanel, VIEW3D_PT_RemovePanel
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.viewport_color_props = PointerProperty(type=ViewportColorProperties)
    bpy.types.Scene.sun_settings_props = PointerProperty(type=SunSettingsProperties)
    bpy.types.Scene.persp_view_props = PointerProperty(type=PerspViewProperties)
    
    def apply_initial_settings():
        if bpy.context.scene and hasattr(bpy.context.scene, 'viewport_color_props'):
            props = bpy.context.scene.viewport_color_props
            sun_props = bpy.context.scene.sun_settings_props
            persp_props = bpy.context.scene.persp_view_props
            
            for key, val in CURRENT_DEFAULTS.items():
                if hasattr(props, key): setattr(props, key, val)
                elif hasattr(sun_props, key): setattr(sun_props, key, val)
                elif hasattr(persp_props, key): setattr(persp_props, key, val)
            
            update_background_color(props, bpy.context)
            update_header_color(props, bpy.context)
            update_render_color(props, bpy.context)
            update_grid_color(props, bpy.context)
            update_wire_color(props, bpy.context)
            update_camera_color(props, bpy.context)
            update_outliner_color(props, bpy.context)
            update_text_editor_color(props, bpy.context)
            update_sun(sun_props, bpy.context)
    
    bpy.app.timers.register(apply_initial_settings, first_interval=0.1)

    if not bpy.app.timers.is_registered(view_sync_timer):
        bpy.app.timers.register(view_sync_timer)

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

    if bpy.app.timers.is_registered(view_sync_timer):
        bpy.app.timers.unregister(view_sync_timer)

if __name__ == "__main__":
    register()