blender Million 2026

時空図のマイケルソン干渉計 20260311版aa











bl_info = {
    "name": "Tube World Light Cones (v1.1)",
    "author": "zionadchat Gemini",
    "version": (1, 1),
    "blender": (4, 3, 0),
    "location": "View3D > Sidebar",
    "description": "Base and Boosted Circular Light Cones Generator",
    "category": "Physics",
}

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

# ==============================================================================
#  PREFIX & GLOBAL SETTINGS
# ==============================================================================
# ★ ここを好きな名前に変更するだけで、アドオン全体の内部IDが独立して動作します
ADDON_PREFIX = "tube_world_01"  

# ★ 球体の最大個数をここで設定できます
MAX_SPHERE_COUNT = 36

P_ID = ADDON_PREFIX.lower()
P_UC = ADDON_PREFIX.upper()

PROP_NAME = f"{P_ID}_light_cones"
COLLECTION_NAME = f"{P_UC}_LightCones_Output"
TAB_NAME = f"{P_UC}_Cones"
# ==============================================================================

# ==============================================================================
#  DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "velocity": 0.6000,
    "radius": 10.0000,
    "sphere_count": 12,
    "sphere_size": 1.0000,
    "cone_height": 15.0000,
    "show_base_spheres": True,
    "color_base_spheres": (0.000, 0.500, 1.000, 1.000),
    "show_base_cones": True,
    "color_base_cones": (0.000, 0.500, 1.000, 0.150),
    "boost_offset_xy": (0.0, 0.0),
    "boost_offset_z": 0.0,
    "show_boosted_spheres": True,
    "color_boosted_spheres": (1.000, 0.200, 0.000, 1.000),
    "show_boosted_cones": True,
    "color_boosted_cones": (1.000, 0.200, 0.000, 0.150),
}
# <END_DICT>

ADDON_LINKS = (
    {"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
    {"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)

# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
    mat_name = f"{P_UC}_Mat_{part_id}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    
    # Blender 4.2+ (EEVEE Next) 対応: プロパティが存在する場合のみ設定
    if hasattr(mat, "blend_method"):
        mat.blend_method = 'BLEND'
    if hasattr(mat, "shadow_method"):
        mat.shadow_method = 'NONE'
        
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
        if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
        if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
    mat.diffuse_color = color
    return mat

# ------------------------------------------------------------------------
# Properties
# ------------------------------------------------------------------------
def update_view(self, context):
    try:
        op_module = getattr(bpy.ops, P_ID)
        op_module.draw_light_cones('INVOKE_DEFAULT')
    except Exception as e:
        print(f"Refresh Error: {e}")

class PG_LightConesSettings(bpy.types.PropertyGroup):
    velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_view)
    radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, max=100.0, update=update_view)
    sphere_count: bpy.props.IntProperty(name="Sphere Count", default=CURRENT_DEFAULTS["sphere_count"], min=1, max=MAX_SPHERE_COUNT, update=update_view)
    
    sphere_size: bpy.props.FloatProperty(name="Sphere Size", default=CURRENT_DEFAULTS["sphere_size"], min=0.01, max=10.0, update=update_view)
    cone_height: bpy.props.FloatProperty(name="Cone Height", default=CURRENT_DEFAULTS["cone_height"], min=1.0, max=100.0, update=update_view)
    
    # Base System
    show_base_spheres: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_base_spheres"], update=update_view)
    color_base_spheres: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_base_spheres"], update=update_view)
    show_base_cones: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_base_cones"], update=update_view)
    color_base_cones: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_base_cones"], update=update_view)
    
    # Boosted System Offsets
    boost_offset_xy: bpy.props.FloatVectorProperty(name="Offset XY", size=2, default=CURRENT_DEFAULTS["boost_offset_xy"], update=update_view)
    boost_offset_z: bpy.props.FloatProperty(name="Offset Z(t)", default=CURRENT_DEFAULTS["boost_offset_z"], update=update_view)
    
    # Boosted System
    show_boosted_spheres: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_boosted_spheres"], update=update_view)
    color_boosted_spheres: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_boosted_spheres"], update=update_view)
    show_boosted_cones: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_boosted_cones"], update=update_view)
    color_boosted_cones: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_boosted_cones"], update=update_view)

# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
class OT_DrawLightCones(bpy.types.Operator):
    bl_idname = f"{P_ID}.draw_light_cones"
    bl_label = "Refresh View"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        p = getattr(context.scene, PROP_NAME)
        v = p.velocity
        R = p.radius
        gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
        H = p.cone_height
        sz = p.sphere_size
        n = p.sphere_count
        
        col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
        if COLLECTION_NAME not in context.scene.collection.children:
            context.scene.collection.children.link(col)
            
        for obj in col.objects:
            bpy.data.objects.remove(obj, do_unlink=True)

        base_pts = []
        boosted_pts =[]
        
        # 座標計算
        for i in range(n):
            theta = math.radians(i * (360.0 / n))
            cos_t = math.cos(theta)
            sin_t = math.sin(theta)
            
            # Base System: z=0 の平面上の円周 (中心 0,0,0)
            p_base = Vector((R * cos_t, R * sin_t, 0))
            
            # Boosted System: 速度vでローレンツ変換された同時刻面上の円周
            # さらにユーザー指定のオフセット(xyとz)を加算
            p_boosted = Vector((
                (gamma * R * cos_t) + p.boost_offset_xy[0], 
                (R * sin_t) + p.boost_offset_xy[1], 
                (gamma * v * R * cos_t) + p.boost_offset_z
            ))
            
            base_pts.append(p_base)
            boosted_pts.append(p_boosted)

        # ---------------------------
        # Base Spheres
        # ---------------------------
        if p.show_base_spheres:
            mesh = bpy.data.meshes.new("Base_Spheres")
            obj = bpy.data.objects.new("Base_Spheres", mesh)
            col.objects.link(obj)
            bm = bmesh.new()
            for pt in base_pts:
                mat = Matrix.Translation(pt)
                bmesh.ops.create_uvsphere(bm, u_segments=16, v_segments=8, radius=sz, matrix=mat)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("BaseSph", p.color_base_spheres))

        # ---------------------------
        # Base Future Cones (未来光円錐:頂点が下、底面が上)
        # ---------------------------
        if p.show_base_cones:
            mesh = bpy.data.meshes.new("Base_Cones")
            obj = bpy.data.objects.new("Base_Cones", mesh)
            col.objects.link(obj)
            bm = bmesh.new()
            for pt in base_pts:
                # 頂点が下部になるように高さをオフセット (+H/2)
                mat = Matrix.Translation(pt + Vector((0, 0, H/2)))
                bmesh.ops.create_cone(bm, cap_ends=False, segments=32, radius1=0.0, radius2=H, depth=H, matrix=mat)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("BaseCone", p.color_base_cones))

        # ---------------------------
        # Boosted Spheres
        # ---------------------------
        if p.show_boosted_spheres:
            mesh = bpy.data.meshes.new("Boosted_Spheres")
            obj = bpy.data.objects.new("Boosted_Spheres", mesh)
            col.objects.link(obj)
            bm = bmesh.new()
            for pt in boosted_pts:
                mat = Matrix.Translation(pt)
                bmesh.ops.create_uvsphere(bm, u_segments=16, v_segments=8, radius=sz, matrix=mat)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("BoostedSph", p.color_boosted_spheres))

        # ---------------------------
        # Boosted Cones (オレンジも未来光円錐:頂点が下、底面が上)
        # ---------------------------
        if p.show_boosted_cones:
            mesh = bpy.data.meshes.new("Boosted_Cones")
            obj = bpy.data.objects.new("Boosted_Cones", mesh)
            col.objects.link(obj)
            bm = bmesh.new()
            for pt in boosted_pts:
                # 頂点が下部になるように高さをオフセット (+H/2)
                mat = Matrix.Translation(pt + Vector((0, 0, H/2)))
                bmesh.ops.create_cone(bm, cap_ends=False, segments=32, radius1=0.0, radius2=H, depth=H, matrix=mat)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("BoostedCone", p.color_boosted_cones))

        return {'FINISHED'}

# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class PT_LightConesMain(bpy.types.Panel):
    bl_label = f"{P_UC} Light Cones Controller"
    bl_idname = f"{P_UC}_PT_main"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        p = getattr(context.scene, PROP_NAME)
        
        layout.operator(f"{P_ID}.copy_full_script", text="全コードをコピー", icon='COPY_ID')

        box = layout.box()
        box.label(text="Physics & Geometry", icon='PHYSICS')
        box.prop(p, "velocity", text="Boost Velocity (v/c)")
        box.prop(p, "radius", text="Circle Radius")
        box.prop(p, "sphere_count", text="Sphere Count")
        
        # 2行に分割
        row1 = box.row()
        row1.prop(p, "sphere_size", text="Sphere Size")
        row2 = box.row()
        row2.prop(p, "cone_height", text="Cone Height")

        # Base System
        box = layout.box()
        box.label(text="Base System (Rest Frame)", icon='OBJECT_ORIGIN')
        
        row = box.row(align=True)
        row.prop(p, "show_base_spheres", text="Base Spheres", icon='SPHERE')
        row.prop(p, "color_base_spheres", text="")
        
        row = box.row(align=True)
        row.prop(p, "show_base_cones", text="Future Cones", icon='CONE')
        row.prop(p, "color_base_cones", text="")

        # Boosted System
        box = layout.box()
        box.label(text="Boosted System (Moving Frame)", icon='FORWARD')
        
        # オフセット位置指定(XY平面とZ(t)で2行に分割)
        row = box.row()
        row.prop(p, "boost_offset_xy", text="Center XY")
        row = box.row()
        row.prop(p, "boost_offset_z", text="Center Z (t)")
        
        row = box.row(align=True)
        row.prop(p, "show_boosted_spheres", text="Boosted Spheres", icon='SPHERE')
        row.prop(p, "color_boosted_spheres", text="")
        
        row = box.row(align=True)
        row.prop(p, "show_boosted_cones", text="Future Cones", icon='CONE')
        row.prop(p, "color_boosted_cones", text="")

        layout.operator(f"{P_ID}.draw_light_cones", text="Refresh View", icon='FILE_REFRESH')

class PT_RelLinks(bpy.types.Panel):
    bl_label = "Theory Links"
    bl_idname = f"{P_UC}_PT_rel_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        for l in ADDON_LINKS:
            op = self.layout.operator(f"{P_ID}.open_rel_url", text=l["label"])
            op.url = l["url"]

class PT_RelSystem(bpy.types.Panel):
    bl_label = "System"
    bl_idname = f"{P_UC}_PT_rel_system"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        self.layout.operator(f"{P_ID}.remove_rel_addon", icon='CANCEL', text="アドオン削除")

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_CopyFullScript(bpy.types.Operator):
    bl_idname = f"{P_ID}.copy_full_script"
    bl_label = "Copy Full Script"

    def execute(self, context):
        p = getattr(context.scene, PROP_NAME)
        M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
        texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
        
        if not texts:
            self.report({'ERROR'}, "ソースコードが見つかりません。")
            return {'CANCELLED'}
            
        d_str = "CURRENT_DEFAULTS = {\n"
        for k in CURRENT_DEFAULTS.keys():
            val = getattr(p, k)
            if isinstance(val, str):
                d_str += f'    "{k}": "{val}",\n'
            elif hasattr(val, "__len__"):
                if len(val) == 4:
                    d_str += f'    "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
                elif len(val) == 2:
                    d_str += f'    "{k}": ({val[0]:.3f}, {val[1]:.3f}),\n'
            elif isinstance(val, float):
                d_str += f'    "{k}": {val:.4f},\n'
            else:
                d_str += f'    "{k}": {val},\n'
        d_str += "}\n"
        
        target_text = texts[-1]
        
        new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
        lines = new_code.split('\n')
        if lines and lines[0].startswith("bl_info"):
            lines.insert(0, f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)\n")
            
        context.window_manager.clipboard = '\n'.join(lines)
        self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
        return {'FINISHED'}

class OT_OpenRelUrl(bpy.types.Operator):
    bl_idname = f"{P_ID}.open_rel_url"
    bl_label = "Open URL"
    url: bpy.props.StringProperty()

    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class OT_RemoveRelAddon(bpy.types.Operator):
    bl_idname = f"{P_ID}.remove_rel_addon"
    bl_label = "Remove"

    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
    PG_LightConesSettings, 
    OT_DrawLightCones, 
    OT_CopyFullScript, 
    OT_OpenRelUrl, 
    OT_RemoveRelAddon, 
    PT_LightConesMain, 
    PT_RelLinks, 
    PT_RelSystem
)

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

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

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Dynamic Prefix Symmetric Spacetime (v6.1 Michelson)",
    "author": "zionadchat Gemini",
    "version": (6, 1),
    "blender": (4, 3, 0),
    "location": "View3D > Sidebar",
    "description": "Michelson Interferometer (1-360 Rays) with Dynamic Prefix",
    "category": "Physics",
}

import bpy
import webbrowser
import math
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  PREFIX SETTINGS (変名しても動くようにするための設定)
# ==============================================================================
# ★ ここを好きな名前に変更するだけで、アドオン全体の内部IDが独立して動作します
ADDON_PREFIX = "orefix"  

P_ID = ADDON_PREFIX.lower()
P_UC = ADDON_PREFIX.upper()

PROP_NAME = f"{P_ID}_rel_sym"
INFO_NAME = f"{P_ID}_rel_v5_info"
COLLECTION_NAME = f"{P_UC}_Relativity_Sym_Output"
TAB_NAME = f"{P_UC}_Sym"
# ==============================================================================

# ==============================================================================
#  DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "calc_mode": "VEL", "velocity": 0.6000, "radius": 10.0000, "target_x": 15.0000,
    "base_angle": 0.0000, "ray_count": 12,
    
    "show_ray_classic": True, "show_ray_converge": True, "show_ray_ell_conv": True,
    
    "show_classic_out": True, "show_classic_in": True,
    "color_classic_out": (1.000, 0.500, 0.000, 1.000), "thick_classic_out": 1.41,
    "color_classic_in": (0.005, 0.008, 0.271, 1.000), "thick_classic_in": 0.73,
    
    "show_conv_out": True, "show_conv_in": True,
    "color_conv_out": (0.400, 0.000, 0.000, 1.000), "thick_conv_out": 0.80,
    "color_conv_in": (0.000, 0.500, 0.000, 1.000), "thick_conv_in": 0.30,
    
    "show_ell_conv_out": True, "show_ell_conv_in": True,
    "color_ell_conv_out": (0.500, 0.000, 0.500, 1.000), "thick_ell_conv_out": 0.80,
    "color_ell_conv_in": (0.000, 0.500, 0.500, 1.000), "thick_ell_conv_in": 0.30,

    "show_spheroid": True, "color_spheroid": (0.200, 0.100, 0.500, 0.10),
    "show_st_ellipse": True, "color_st_ellipse": (1.000, 1.000, 1.000, 0.80), "thick_st_ellipse": 0.05,
    "show_wavefronts": True, "color_wavefronts": (0.160, 0.000, 0.050, 0.40), "thick_wavefronts": 0.40,
    "show_circle_rings": True, "color_circle_rings": (0.100, 0.200, 0.300, 0.30), "thick_circle_rings": 0.03,
    "show_refl_rings": False, "color_refl_rings": (0.800, 0.200, 0.200, 0.50), "thick_refl_rings": 0.03,
    "show_skeleton": True, "color_skeleton": (0.500, 0.500, 0.500, 0.20), "skel_thick": 0.01,
}
# <END_DICT>

ADDON_LINKS = (
    {"label": "時空図のマイケルソン干渉計 20260305版", "url": "<https://www.notion.so/20260305-31af5dacaf4380729b34e3136c3649aa>"},
    {"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
    {"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)

# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
    mat_name = f"{P_UC}_Mat_{part_id}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
        if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
        if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
    mat.diffuse_color = color
    return mat

# ------------------------------------------------------------------------
# Properties & Physics
# ------------------------------------------------------------------------
_is_updating = False

def update_physics(self, context):
    global _is_updating
    if _is_updating:
        return
    _is_updating = True
    
    try:
        p = self
        if p.calc_mode == 'X':
            p.velocity = p.target_x / math.sqrt(p.target_x**2 + (2*p.radius)**2)
        else:
            gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - p.velocity**2))
            p.target_x = p.velocity * 2 * p.radius * gamma
    finally:
        _is_updating = False
        
    update_view(self, context)

def update_view(self, context):
    try:
        op_module = getattr(bpy.ops, P_ID)
        op_module.draw_spacetime_sym_v5('INVOKE_DEFAULT')
    except Exception as e:
        print(f"Refresh Error: {e}")

class PG_RelativitySymV5(bpy.types.PropertyGroup):
    calc_mode: bpy.props.EnumProperty(items=[('VEL', "Velocity Mode", ""), ('X', "Target X Mode", "")], default=CURRENT_DEFAULTS["calc_mode"], update=update_view)
    
    velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_physics)
    target_x: bpy.props.FloatProperty(name="Target X", default=CURRENT_DEFAULTS["target_x"], min=0.001, soft_max=1000.0, update=update_physics)
    radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, soft_max=100.0, update=update_physics)
    
    base_angle: bpy.props.FloatProperty(name="Base Angle", default=CURRENT_DEFAULTS["base_angle"], min=0, max=360, update=update_view)
    ray_count: bpy.props.IntProperty(name="Ray Count", default=CURRENT_DEFAULTS["ray_count"], min=1, max=360, update=update_view)

    show_ray_classic: bpy.props.BoolProperty(name="Classic Mode", default=CURRENT_DEFAULTS["show_ray_classic"], update=update_view)
    show_ray_converge: bpy.props.BoolProperty(name="Converge Mode", default=CURRENT_DEFAULTS["show_ray_converge"], update=update_view)
    show_ray_ell_conv: bpy.props.BoolProperty(name="Ellipse Conv Mode", default=CURRENT_DEFAULTS["show_ray_ell_conv"], update=update_view)

    # Classic
    show_classic_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_classic_out"], update=update_view)
    show_classic_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_classic_in"], update=update_view)
    color_classic_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_classic_out"], update=update_view)
    color_classic_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_classic_in"], update=update_view)
    thick_classic_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_classic_out"], min=0, max=2.0, update=update_view)
    thick_classic_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_classic_in"], min=0, max=2.0, update=update_view)

    # Converge
    show_conv_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_conv_out"], update=update_view)
    show_conv_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_conv_in"], update=update_view)
    color_conv_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_conv_out"], update=update_view)
    color_conv_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_conv_in"], update=update_view)
    thick_conv_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_conv_out"], min=0, max=2.0, update=update_view)
    thick_conv_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_conv_in"], min=0, max=2.0, update=update_view)

    # Ellipse Conv
    show_ell_conv_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ell_conv_out"], update=update_view)
    show_ell_conv_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ell_conv_in"], update=update_view)
    color_ell_conv_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ell_conv_out"], update=update_view)
    color_ell_conv_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ell_conv_in"], update=update_view)
    thick_ell_conv_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ell_conv_out"], min=0, max=2.0, update=update_view)
    thick_ell_conv_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ell_conv_in"], min=0, max=2.0, update=update_view)

    show_spheroid:   bpy.props.BoolProperty(name="Spheroid Surface", default=CURRENT_DEFAULTS["show_spheroid"], update=update_view)
    color_spheroid:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_spheroid"], update=update_view)
    show_st_ellipse: bpy.props.BoolProperty(name="Longit. Ellipse", default=CURRENT_DEFAULTS["show_st_ellipse"], update=update_view)
    color_st_ellipse: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_st_ellipse"], update=update_view)
    thick_st_ellipse: bpy.props.FloatProperty(name="Thick Ellipse", default=CURRENT_DEFAULTS["thick_st_ellipse"], min=0.0, max=1.0, update=update_view)
    
    show_wavefronts: bpy.props.BoolProperty(name="Wavefronts (Horiz.)", default=CURRENT_DEFAULTS["show_wavefronts"], update=update_view)
    color_wavefronts: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_wavefronts"], update=update_view)
    thick_wavefronts: bpy.props.FloatProperty(name="Thick WF", default=CURRENT_DEFAULTS["thick_wavefronts"], min=0.0, max=2.0, update=update_view)

    show_circle_rings: bpy.props.BoolProperty(name="Tube Rings", default=CURRENT_DEFAULTS["show_circle_rings"], update=update_view)
    color_circle_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_circle_rings"], update=update_view)
    thick_circle_rings: bpy.props.FloatProperty(name="Thick Rings", default=CURRENT_DEFAULTS["thick_circle_rings"], min=0.0, max=1.0, update=update_view)
    
    show_refl_rings: bpy.props.BoolProperty(name="Reflection Rings", default=CURRENT_DEFAULTS["show_refl_rings"], update=update_view)
    color_refl_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_refl_rings"], update=update_view)
    thick_refl_rings: bpy.props.FloatProperty(name="Thick Refl", default=CURRENT_DEFAULTS["thick_refl_rings"], min=0.0, max=1.0, update=update_view)

    show_skeleton: bpy.props.BoolProperty(name="Skeleton", default=CURRENT_DEFAULTS["show_skeleton"], update=update_view)
    color_skeleton: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_skeleton"], update=update_view)
    skel_thick: bpy.props.FloatProperty(name="Thick Skeleton", default=CURRENT_DEFAULTS["skel_thick"], min=0.0, max=1.0, update=update_view)

# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
def create_curve(col, name, points, thickness, part_id, color, circular=False):
    curve = bpy.data.curves.new(name, 'CURVE')
    curve.dimensions = '3D'
    obj = bpy.data.objects.new(name, curve)
    col.objects.link(obj)
    spline = curve.splines.new('POLY')
    spline.use_cyclic_u = circular
    spline.points.add(len(points) - 1)
    for i, p in enumerate(points):
        spline.points[i].co = (p.x, p.y, p.z, 1)
    curve.bevel_depth = thickness
    obj.data.materials.append(get_fixed_material(part_id, color))
    return obj

class OT_DrawSpacetimeSymV5(bpy.types.Operator):
    bl_idname = f"{P_ID}.draw_spacetime_sym_v5"
    bl_label = "Refresh View"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        p = getattr(context.scene, PROP_NAME)
        v, R_val = p.velocity, p.radius
        gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
        A = 2.0 * R_val * gamma
        offset = Vector((v * A / 2.0, 0, A / 2.0))
        F1 = Vector((0,0,0)) - offset
        Mid = Vector((v*A/2,0,A/2)) - offset
        F2 = Vector((v*A,0,A)) - offset

        def get_P_locus(deg):
            rad = math.radians(deg)
            return Vector((gamma*(R_val*math.cos(rad)+v*R_val), R_val*math.sin(rad), gamma*(R_val+v*(R_val*math.cos(rad))))) - offset

        def get_P_rigid(deg, z_v):
            t_p = z_v + offset.z
            rad = math.radians(deg)
            return Vector((v*t_p - offset.x + R_val*math.cos(rad), R_val*math.sin(rad), z_v))

        col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
        if COLLECTION_NAME not in context.scene.collection.children:
            context.scene.collection.children.link(col)
            
        for obj in col.objects:
            bpy.data.objects.remove(obj, do_unlink=True)

        # Longitudinal Ellipse & Spheroid
        major_2a = math.sqrt(A**2 * (v**2 + 1) + 4 * R_val**2)
        dir_vec = F2 - F1
        rot_quat = Vector((1,0,0)).rotation_difference(dir_vec)

        if p.show_st_ellipse:
            v_pts =[Mid + rot_quat @ Vector((major_2a/2 * math.cos(math.radians(d)), 0, R_val * math.sin(math.radians(d)))) for d in range(0, 365, 5)]
            create_curve(col, "ST_Ellipse", v_pts, p.thick_st_ellipse, "ST_Ellipse", p.color_st_ellipse, True)
        
        if p.show_spheroid:
            mesh = bpy.data.meshes.new("Spheroid")
            obj = bpy.data.objects.new("Spheroid", mesh)
            col.objects.link(obj)
            import bmesh
            bm = bmesh.new()
            bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
            for vb in bm.verts:
                vb.co.x *= (major_2a/2.0)
                vb.co.y *= R_val
                vb.co.z *= R_val
            bmesh.ops.rotate(bm, cent=(0,0,0), matrix=rot_quat.to_matrix(), verts=bm.verts)
            bmesh.ops.translate(bm, vec=Mid, verts=bm.verts)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("Spheroid", p.color_spheroid))

        # Wavefronts
        if p.show_wavefronts:
            for i, name in[(0, "Mid"), (-1, "Low"), (1, "Up")]:
                trans = (F2 - Mid) * i
                pts =[get_P_locus(d*5) + trans for d in range(73)]
                create_curve(col, f"WF_{name}", pts, p.thick_wavefronts, "Wavefront", p.color_wavefronts, True)

        # Rays Integration (1 to 360 rays)
        z_set = set()
        for i in range(p.ray_count):
            deg = p.base_angle + i * (360.0 / p.ray_count) if p.ray_count > 1 else p.base_angle
            Pt = get_P_locus(deg)
            
            # Classic
            if p.show_ray_classic:
                if p.show_classic_out:
                    create_curve(col, f"Cl_{i}_O", [F1, Pt], p.thick_classic_out, "Classic_Out", p.color_classic_out)
                if p.show_classic_in:
                    create_curve(col, f"Cl_{i}_I", [Pt, F2], p.thick_classic_in, "Classic_In", p.color_classic_in)
                    
            # Converge
            if p.show_ray_converge:
                Ps, Pe = get_P_rigid(deg, F1.z), get_P_rigid(deg, F2.z)
                if p.show_conv_out:
                    create_curve(col, f"Cv_{i}_O",[Ps, Mid], p.thick_conv_out, "Conv_Out", p.color_conv_out)
                if p.show_conv_in:
                    create_curve(col, f"Cv_{i}_I", [Mid, Pe], p.thick_conv_in, "Conv_In", p.color_conv_in)
                    
            # Ellipse Conv
            if p.show_ray_ell_conv:
                PtL, PtU = Pt+(F1-Mid), Pt+(F2-Mid)
                if p.show_ell_conv_out:
                    create_curve(col, f"ElCv_{i}_O", [PtL, Mid], p.thick_ell_conv_out, "EllConv_Out", p.color_ell_conv_out)
                if p.show_ell_conv_in:
                    create_curve(col, f"ElCv_{i}_I",[Mid, PtU], p.thick_ell_conv_in, "EllConv_In", p.color_ell_conv_in)
                    
            # Reflection Rings for each Ray
            if p.show_refl_rings:
                z_rounded = round(Pt.z, 3)
                if z_rounded not in z_set:
                    z_set.add(z_rounded)
                    pts =[get_P_rigid(d*5, Pt.z) for d in range(73)]
                    create_curve(col, f"ReflRing_{i}", pts, p.thick_refl_rings, "ReflRing", p.color_refl_rings, True)

        # Helpers
        if p.show_skeleton:
            for i in range(p.ray_count):
                deg = p.base_angle + i * (360.0 / p.ray_count) if p.ray_count > 1 else p.base_angle
                Pl = get_P_locus(deg) + offset
                Ms = Vector((Pl.x+v*(0-Pl.z), Pl.y, 0)) - offset
                Me = Vector((Pl.x+v*(A-Pl.z), Pl.y, A)) - offset
                create_curve(col, f"Sk_{i}", [Ms, Me], p.skel_thick, "Skeleton", p.color_skeleton)
                
        if p.show_circle_rings:
            for z_v in[F1.z, 0.0, F2.z]:
                pts =[get_P_rigid(i*5, z_v) for i in range(73)]
                create_curve(col, f"T_{z_v}", pts, p.thick_circle_rings, "Tube", p.color_circle_rings, True)

        # --- Time Accounting ---
        info = f"Velocity: {v:.6f} c\nTarget X: {F2.x - F1.x:.4f}\nA (Invariant): {A:.4f}"
        
        Pt0 = get_P_locus(p.base_angle)
        info += f"\n "
        info += f"\n[Ray 0 Reflection (Base Angle)]"
        info += f"\n Time(Δt): {Pt0.z - F1.z:.4f} / z: {Pt0.z:.4f}"
        info += f"\n Pos: ({Pt0.x:.4f}, {Pt0.y:.4f}, {Pt0.z:.4f})"
            
        context.scene[INFO_NAME] = info
        
        return {'FINISHED'}

# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class PT_RelSymV5(bpy.types.Panel):
    bl_label = f"{P_UC} 4D Controller"
    bl_idname = f"{P_UC}_PT_rel_sym_v5"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        p = getattr(context.scene, PROP_NAME)
        
        layout.operator(f"{P_ID}.copy_full_script", text="全コードをコピー", icon='COPY_ID')

        # Physics Setup
        box = layout.box()
        row = box.row(align=True)
        row.prop(p, "calc_mode", expand=True)
        
        setup = box.box()
        setup.prop(p, "radius")
        if p.calc_mode == 'X':
            setup.prop(p, "target_x", text="Target X")
        else:
            setup.prop(p, "velocity", text="Velocity (v/c)")
            
        # Time Accounting
        acc = layout.box()
        acc.label(text="Time Accounting", icon='TIME')
        
        info_text = context.scene.get(INFO_NAME, "")
        for line in info_text.split("\n"):
            if line == " ":
                acc.separator(factor=0.5)
            elif line:
                acc.label(text=line)
                
        acc.operator(f"{P_ID}.copy_rel_info", text="数値を一括コピー", icon='COPYDOWN')
            
        # Rays Configuration
        cfg = layout.box()
        cfg.label(text="Rays Configuration", icon='LIGHT_SUN')
        row = cfg.row(align=True)
        row.prop(p, "ray_count", text="Count")
        row.prop(p, "base_angle", text="Base Angle")
        
        # Ray Visual Details
        det = layout.box()
        det.label(text="Ray Details", icon='NODE_MATERIAL')
        
        def draw_ray_box(parent, title, show_mode, show_out, show_in, thick_out, color_out, thick_in, color_in):
            b = parent.box()
            row = b.row(align=True)
            row.prop(p, show_mode, text="")
            row.label(text=title)
            
            if getattr(p, show_mode):
                c = b.column(align=True)
                
                # Out
                r = c.row(align=True)
                r.prop(p, show_out, text="", icon='HIDE_OFF')
                r.prop(p, thick_out, text="Thick Out")
                r.prop(p, color_out, text="")
                
                # In
                r = c.row(align=True)
                r.prop(p, show_in, text="", icon='HIDE_OFF')
                r.prop(p, thick_in, text="Thick In")
                r.prop(p, color_in, text="")

        draw_ray_box(det, "Classic Ray", "show_ray_classic", "show_classic_out", "show_classic_in", "thick_classic_out", "color_classic_out", "thick_classic_in", "color_classic_in")
        draw_ray_box(det, "Converge Ray", "show_ray_converge", "show_conv_out", "show_conv_in", "thick_conv_out", "color_conv_out", "thick_conv_in", "color_conv_in")
        draw_ray_box(det, "Ellipse Conv Ray", "show_ray_ell_conv", "show_ell_conv_out", "show_ell_conv_in", "thick_ell_conv_out", "color_ell_conv_out", "thick_ell_conv_in", "color_ell_conv_in")

        # Wavefronts
        wf = layout.box()
        wf.label(text="Wavefronts", icon='SPHERE')
        row = wf.row()
        row.prop(p, "show_wavefronts", text="Show Rings")
        row.prop(p, "color_wavefronts", text="")
        wf.prop(p, "thick_wavefronts", text="Thickness")

        # Spacetime Structure
        st = layout.box()
        st.label(text="Spacetime Structure", icon='WORLD')
        
        row = st.row(align=True)
        row.prop(p, "show_st_ellipse", text="Longit. Ellipse")
        row.prop(p, "color_st_ellipse", text="")
        if p.show_st_ellipse:
            st.prop(p, "thick_st_ellipse", text="Thickness")
        
        row = st.row(align=True)
        row.prop(p, "show_spheroid", text="3D Spheroid")
        row.prop(p, "color_spheroid", text="")

        # Visual Aids
        vbox = layout.box()
        vbox.label(text="Visual Aids", icon='MESH_GRID')
        
        row = vbox.row(align=True)
        row.prop(p, "show_circle_rings", text="Tube Rings")
        row.prop(p, "color_circle_rings", text="")
        if p.show_circle_rings:
            vbox.prop(p, "thick_circle_rings", text="Thickness")
            
        row = vbox.row(align=True)
        row.prop(p, "show_refl_rings", text="Reflection Rings")
        row.prop(p, "color_refl_rings", text="")
        if p.show_refl_rings:
            vbox.prop(p, "thick_refl_rings", text="Thickness")
        
        row = vbox.row(align=True)
        row.prop(p, "show_skeleton", text="Skeleton")
        row.prop(p, "color_skeleton", text="")
        if p.show_skeleton:
            vbox.prop(p, "skel_thick", text="Thickness")

        layout.operator(f"{P_ID}.draw_spacetime_sym_v5", text="Refresh View", icon='FILE_REFRESH')

class PT_RelLinks(bpy.types.Panel):
    bl_label = "Theory Links"
    bl_idname = f"{P_UC}_PT_rel_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        for l in ADDON_LINKS:
            op = self.layout.operator(f"{P_ID}.open_rel_url", text=l["label"])
            op.url = l["url"]

class PT_RelSystem(bpy.types.Panel):
    bl_label = "System"
    bl_idname = f"{P_UC}_PT_rel_system"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        self.layout.operator(f"{P_ID}.remove_rel_addon", icon='CANCEL', text="アドオン削除")

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_CopyFullScript(bpy.types.Operator):
    bl_idname = f"{P_ID}.copy_full_script"
    bl_label = "Copy Full Script"

    def execute(self, context):
        p = getattr(context.scene, PROP_NAME)
        M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
        texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
        
        if not texts:
            self.report({'ERROR'}, "ソースコードが見つかりません。")
            return {'CANCELLED'}
            
        d_str = "CURRENT_DEFAULTS = {\n"
        for k in CURRENT_DEFAULTS.keys():
            val = getattr(p, k)
            if isinstance(val, str):
                d_str += f'    "{k}": "{val}",\n'
            elif hasattr(val, "__len__"):
                d_str += f'    "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
            elif isinstance(val, float):
                d_str += f'    "{k}": {val:.4f},\n'
            else:
                d_str += f'    "{k}": {val},\n'
        d_str += "}\n"
        
        target_text = texts[-1]
        
        new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
        lines = new_code.split('\n')
        if lines and lines[0].startswith("bl_info"):
            lines.insert(0, f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)\n")
            
        context.window_manager.clipboard = '\n'.join(lines)
        self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
        return {'FINISHED'}

class OT_OpenRelUrl(bpy.types.Operator):
    bl_idname = f"{P_ID}.open_rel_url"
    bl_label = "Open URL"
    url: bpy.props.StringProperty()

    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class OT_CopyRelInfo(bpy.types.Operator):
    bl_idname = f"{P_ID}.copy_rel_info"
    bl_label = "Copy Info"

    def execute(self, context):
        context.window_manager.clipboard = context.scene.get(INFO_NAME, "")
        self.report({'INFO'}, "数値をクリップボードにコピーしました!")
        return {'FINISHED'}

class OT_RemoveRelAddon(bpy.types.Operator):
    bl_idname = f"{P_ID}.remove_rel_addon"
    bl_label = "Remove"

    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
    PG_RelativitySymV5, 
    OT_DrawSpacetimeSymV5, 
    OT_CopyFullScript, 
    OT_OpenRelUrl, 
    OT_CopyRelInfo, 
    OT_RemoveRelAddon, 
    PT_RelSymV5, 
    PT_RelLinks, 
    PT_RelSystem
)

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

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

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Orefix Symmetric Spacetime (v6.0 Michelson)",
    "author": "zionadchat Gemini",
    "version": (6, 0),
    "blender": (4, 3, 0),
    "location": "View3D > Sidebar",
    "description": "Michelson Interferometer Orefix version (1-360 Rays)",
    "category": "Physics",
}

import bpy
import webbrowser
import math
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "calc_mode": "VEL", "velocity": 0.6000, "radius": 10.0000, "target_x": 15.0000,
    "base_angle": 0.0000, "ray_count": 12,
    
    "show_ray_classic": True, "show_ray_converge": True, "show_ray_ell_conv": True,
    
    "show_classic_out": True, "show_classic_in": True,
    "color_classic_out": (1.000, 0.500, 0.000, 1.000), "thick_classic_out": 1.41,
    "color_classic_in": (0.005, 0.008, 0.271, 1.000), "thick_classic_in": 0.73,
    
    "show_conv_out": True, "show_conv_in": True,
    "color_conv_out": (0.400, 0.000, 0.000, 1.000), "thick_conv_out": 0.80,
    "color_conv_in": (0.000, 0.500, 0.000, 1.000), "thick_conv_in": 0.30,
    
    "show_ell_conv_out": True, "show_ell_conv_in": True,
    "color_ell_conv_out": (0.500, 0.000, 0.500, 1.000), "thick_ell_conv_out": 0.80,
    "color_ell_conv_in": (0.000, 0.500, 0.500, 1.000), "thick_ell_conv_in": 0.30,

    "show_spheroid": True, "color_spheroid": (0.200, 0.100, 0.500, 0.10),
    "show_st_ellipse": True, "color_st_ellipse": (1.000, 1.000, 1.000, 0.80), "thick_st_ellipse": 0.05,
    "show_wavefronts": True, "color_wavefronts": (0.160, 0.000, 0.050, 0.40), "thick_wavefronts": 0.40,
    "show_circle_rings": True, "color_circle_rings": (0.100, 0.200, 0.300, 0.30), "thick_circle_rings": 0.03,
    "show_refl_rings": False, "color_refl_rings": (0.800, 0.200, 0.200, 0.50), "thick_refl_rings": 0.03,
    "show_skeleton": True, "color_skeleton": (0.500, 0.500, 0.500, 0.20), "skel_thick": 0.01,
}
# <END_DICT>

TAB_NAME = "Orefix_Sym"
COLLECTION_NAME = "Orefix_Relativity_Sym_Output"

ADDON_LINKS = (
    {"label": "時空図のマイケルソン干渉計 20260305版", "url": "<https://www.notion.so/20260305-31af5dacaf4380729b34e3136c3649aa>"},
    {"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
    {"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)

# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
    mat_name = f"Orefix_Mat_{part_id}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
        if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
        if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
    mat.diffuse_color = color
    return mat

# ------------------------------------------------------------------------
# Properties & Physics
# ------------------------------------------------------------------------
_is_updating = False

def update_physics(self, context):
    global _is_updating
    if _is_updating:
        return
    _is_updating = True
    
    try:
        p = self
        if p.calc_mode == 'X':
            p.velocity = p.target_x / math.sqrt(p.target_x**2 + (2*p.radius)**2)
        else:
            gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - p.velocity**2))
            p.target_x = p.velocity * 2 * p.radius * gamma
    finally:
        _is_updating = False
        
    update_view(self, context)

def update_view(self, context):
    try: bpy.ops.orefix.draw_spacetime_sym_v5('INVOKE_DEFAULT')
    except: pass

class OREFIX_PG_RelativitySymV5(bpy.types.PropertyGroup):
    calc_mode: bpy.props.EnumProperty(items=[('VEL', "Velocity Mode", ""), ('X', "Target X Mode", "")], default=CURRENT_DEFAULTS["calc_mode"], update=update_view)
    
    velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_physics)
    target_x: bpy.props.FloatProperty(name="Target X", default=CURRENT_DEFAULTS["target_x"], min=0.001, soft_max=1000.0, update=update_physics)
    radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, soft_max=100.0, update=update_physics)
    
    base_angle: bpy.props.FloatProperty(name="Base Angle", default=CURRENT_DEFAULTS["base_angle"], min=0, max=360, update=update_view)
    ray_count: bpy.props.IntProperty(name="Ray Count", default=CURRENT_DEFAULTS["ray_count"], min=1, max=360, update=update_view)

    show_ray_classic: bpy.props.BoolProperty(name="Classic Mode", default=CURRENT_DEFAULTS["show_ray_classic"], update=update_view)
    show_ray_converge: bpy.props.BoolProperty(name="Converge Mode", default=CURRENT_DEFAULTS["show_ray_converge"], update=update_view)
    show_ray_ell_conv: bpy.props.BoolProperty(name="Ellipse Conv Mode", default=CURRENT_DEFAULTS["show_ray_ell_conv"], update=update_view)

    # Classic
    show_classic_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_classic_out"], update=update_view)
    show_classic_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_classic_in"], update=update_view)
    color_classic_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_classic_out"], update=update_view)
    color_classic_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_classic_in"], update=update_view)
    thick_classic_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_classic_out"], min=0, max=2.0, update=update_view)
    thick_classic_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_classic_in"], min=0, max=2.0, update=update_view)

    # Converge
    show_conv_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_conv_out"], update=update_view)
    show_conv_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_conv_in"], update=update_view)
    color_conv_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_conv_out"], update=update_view)
    color_conv_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_conv_in"], update=update_view)
    thick_conv_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_conv_out"], min=0, max=2.0, update=update_view)
    thick_conv_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_conv_in"], min=0, max=2.0, update=update_view)

    # Ellipse Conv
    show_ell_conv_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ell_conv_out"], update=update_view)
    show_ell_conv_in:  bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ell_conv_in"], update=update_view)
    color_ell_conv_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ell_conv_out"], update=update_view)
    color_ell_conv_in:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ell_conv_in"], update=update_view)
    thick_ell_conv_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ell_conv_out"], min=0, max=2.0, update=update_view)
    thick_ell_conv_in:  bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ell_conv_in"], min=0, max=2.0, update=update_view)

    show_spheroid:   bpy.props.BoolProperty(name="Spheroid Surface", default=CURRENT_DEFAULTS["show_spheroid"], update=update_view)
    color_spheroid:  bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_spheroid"], update=update_view)
    show_st_ellipse: bpy.props.BoolProperty(name="Longit. Ellipse", default=CURRENT_DEFAULTS["show_st_ellipse"], update=update_view)
    color_st_ellipse: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_st_ellipse"], update=update_view)
    thick_st_ellipse: bpy.props.FloatProperty(name="Thick Ellipse", default=CURRENT_DEFAULTS["thick_st_ellipse"], min=0.0, max=1.0, update=update_view)
    
    show_wavefronts: bpy.props.BoolProperty(name="Wavefronts (Horiz.)", default=CURRENT_DEFAULTS["show_wavefronts"], update=update_view)
    color_wavefronts: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_wavefronts"], update=update_view)
    thick_wavefronts: bpy.props.FloatProperty(name="Thick WF", default=CURRENT_DEFAULTS["thick_wavefronts"], min=0.0, max=2.0, update=update_view)

    show_circle_rings: bpy.props.BoolProperty(name="Tube Rings", default=CURRENT_DEFAULTS["show_circle_rings"], update=update_view)
    color_circle_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_circle_rings"], update=update_view)
    thick_circle_rings: bpy.props.FloatProperty(name="Thick Rings", default=CURRENT_DEFAULTS["thick_circle_rings"], min=0.0, max=1.0, update=update_view)
    
    show_refl_rings: bpy.props.BoolProperty(name="Reflection Rings", default=CURRENT_DEFAULTS["show_refl_rings"], update=update_view)
    color_refl_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_refl_rings"], update=update_view)
    thick_refl_rings: bpy.props.FloatProperty(name="Thick Refl", default=CURRENT_DEFAULTS["thick_refl_rings"], min=0.0, max=1.0, update=update_view)

    show_skeleton: bpy.props.BoolProperty(name="Skeleton", default=CURRENT_DEFAULTS["show_skeleton"], update=update_view)
    color_skeleton: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_skeleton"], update=update_view)
    skel_thick: bpy.props.FloatProperty(name="Thick Skeleton", default=CURRENT_DEFAULTS["skel_thick"], min=0.0, max=1.0, update=update_view)

# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
def create_curve(col, name, points, thickness, part_id, color, circular=False):
    curve = bpy.data.curves.new(name, 'CURVE')
    curve.dimensions = '3D'
    obj = bpy.data.objects.new(name, curve)
    col.objects.link(obj)
    spline = curve.splines.new('POLY')
    spline.use_cyclic_u = circular
    spline.points.add(len(points) - 1)
    for i, p in enumerate(points):
        spline.points[i].co = (p.x, p.y, p.z, 1)
    curve.bevel_depth = thickness
    obj.data.materials.append(get_fixed_material(part_id, color))
    return obj

class OREFIX_OT_DrawSpacetimeSymV5(bpy.types.Operator):
    bl_idname = "orefix.draw_spacetime_sym_v5"
    bl_label = "Refresh View"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        p = context.scene.orefix_rel_sym
        v, R_val = p.velocity, p.radius
        gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
        A = 2.0 * R_val * gamma
        offset = Vector((v * A / 2.0, 0, A / 2.0))
        F1 = Vector((0,0,0)) - offset
        Mid = Vector((v*A/2,0,A/2)) - offset
        F2 = Vector((v*A,0,A)) - offset

        def get_P_locus(deg):
            rad = math.radians(deg)
            return Vector((gamma*(R_val*math.cos(rad)+v*R_val), R_val*math.sin(rad), gamma*(R_val+v*(R_val*math.cos(rad))))) - offset

        def get_P_rigid(deg, z_v):
            t_p = z_v + offset.z
            rad = math.radians(deg)
            return Vector((v*t_p - offset.x + R_val*math.cos(rad), R_val*math.sin(rad), z_v))

        col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
        if COLLECTION_NAME not in context.scene.collection.children:
            context.scene.collection.children.link(col)
            
        for obj in col.objects:
            bpy.data.objects.remove(obj, do_unlink=True)

        # Longitudinal Ellipse & Spheroid
        major_2a = math.sqrt(A**2 * (v**2 + 1) + 4 * R_val**2)
        dir_vec = F2 - F1
        rot_quat = Vector((1,0,0)).rotation_difference(dir_vec)

        if p.show_st_ellipse:
            v_pts =[Mid + rot_quat @ Vector((major_2a/2 * math.cos(math.radians(d)), 0, R_val * math.sin(math.radians(d)))) for d in range(0, 365, 5)]
            create_curve(col, "ST_Ellipse", v_pts, p.thick_st_ellipse, "ST_Ellipse", p.color_st_ellipse, True)
        
        if p.show_spheroid:
            mesh = bpy.data.meshes.new("Spheroid")
            obj = bpy.data.objects.new("Spheroid", mesh)
            col.objects.link(obj)
            import bmesh
            bm = bmesh.new()
            bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
            for vb in bm.verts:
                vb.co.x *= (major_2a/2.0)
                vb.co.y *= R_val
                vb.co.z *= R_val
            bmesh.ops.rotate(bm, cent=(0,0,0), matrix=rot_quat.to_matrix(), verts=bm.verts)
            bmesh.ops.translate(bm, vec=Mid, verts=bm.verts)
            bm.to_mesh(mesh)
            bm.free()
            obj.data.materials.append(get_fixed_material("Spheroid", p.color_spheroid))

        # Wavefronts
        if p.show_wavefronts:
            for i, name in[(0, "Mid"), (-1, "Low"), (1, "Up")]:
                trans = (F2 - Mid) * i
                pts =[get_P_locus(d*5) + trans for d in range(73)]
                create_curve(col, f"WF_{name}", pts, p.thick_wavefronts, "Wavefront", p.color_wavefronts, True)

        # Rays Integration (1 to 360 rays)
        z_set = set()
        for i in range(p.ray_count):
            deg = p.base_angle + i * (360.0 / p.ray_count) if p.ray_count > 1 else p.base_angle
            Pt = get_P_locus(deg)
            
            # Classic
            if p.show_ray_classic:
                if p.show_classic_out:
                    create_curve(col, f"Cl_{i}_O", [F1, Pt], p.thick_classic_out, "Classic_Out", p.color_classic_out)
                if p.show_classic_in:
                    create_curve(col, f"Cl_{i}_I", [Pt, F2], p.thick_classic_in, "Classic_In", p.color_classic_in)
                    
            # Converge
            if p.show_ray_converge:
                Ps, Pe = get_P_rigid(deg, F1.z), get_P_rigid(deg, F2.z)
                if p.show_conv_out:
                    create_curve(col, f"Cv_{i}_O",[Ps, Mid], p.thick_conv_out, "Conv_Out", p.color_conv_out)
                if p.show_conv_in:
                    create_curve(col, f"Cv_{i}_I", [Mid, Pe], p.thick_conv_in, "Conv_In", p.color_conv_in)
                    
            # Ellipse Conv
            if p.show_ray_ell_conv:
                PtL, PtU = Pt+(F1-Mid), Pt+(F2-Mid)
                if p.show_ell_conv_out:
                    create_curve(col, f"ElCv_{i}_O", [PtL, Mid], p.thick_ell_conv_out, "EllConv_Out", p.color_ell_conv_out)
                if p.show_ell_conv_in:
                    create_curve(col, f"ElCv_{i}_I",[Mid, PtU], p.thick_ell_conv_in, "EllConv_In", p.color_ell_conv_in)
                    
            # Reflection Rings for each Ray (Optimized to avoid duplicates)
            if p.show_refl_rings:
                z_rounded = round(Pt.z, 3)
                if z_rounded not in z_set:
                    z_set.add(z_rounded)
                    pts =[get_P_rigid(d*5, Pt.z) for d in range(73)]
                    create_curve(col, f"ReflRing_{i}", pts, p.thick_refl_rings, "ReflRing", p.color_refl_rings, True)

        # Helpers
        if p.show_skeleton:
            for i in range(p.ray_count):
                deg = p.base_angle + i * (360.0 / p.ray_count) if p.ray_count > 1 else p.base_angle
                Pl = get_P_locus(deg) + offset
                Ms = Vector((Pl.x+v*(0-Pl.z), Pl.y, 0)) - offset
                Me = Vector((Pl.x+v*(A-Pl.z), Pl.y, A)) - offset
                create_curve(col, f"Sk_{i}", [Ms, Me], p.skel_thick, "Skeleton", p.color_skeleton)
                
        if p.show_circle_rings:
            for z_v in[F1.z, 0.0, F2.z]:
                pts =[get_P_rigid(i*5, z_v) for i in range(73)]
                create_curve(col, f"T_{z_v}", pts, p.thick_circle_rings, "Tube", p.color_circle_rings, True)

        # --- Time Accounting ---
        info = f"Velocity: {v:.6f} c\nTarget X: {F2.x - F1.x:.4f}\nA (Invariant): {A:.4f}"
        
        Pt0 = get_P_locus(p.base_angle)
        info += f"\n "
        info += f"\n[Ray 0 Reflection (Base Angle)]"
        info += f"\n Time(Δt): {Pt0.z - F1.z:.4f} / z: {Pt0.z:.4f}"
        info += f"\n Pos: ({Pt0.x:.4f}, {Pt0.y:.4f}, {Pt0.z:.4f})"
            
        context.scene["orefix_rel_v5_info"] = info
        
        return {'FINISHED'}

# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class OREFIX_PT_RelSymV5(bpy.types.Panel):
    bl_label = "Orefix 4D Controller"
    bl_idname = "OREFIX_PT_rel_sym_v5"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        p = context.scene.orefix_rel_sym
        
        layout.operator("orefix.copy_full_script", text="全コードをコピー", icon='COPY_ID')

        # Physics Setup
        box = layout.box()
        row = box.row(align=True)
        row.prop(p, "calc_mode", expand=True)
        
        setup = box.box()
        setup.prop(p, "radius")
        if p.calc_mode == 'X':
            setup.prop(p, "target_x", text="Target X")
        else:
            setup.prop(p, "velocity", text="Velocity (v/c)")
            
        # Time Accounting
        acc = layout.box()
        acc.label(text="Time Accounting", icon='TIME')
        
        info_text = context.scene.get("orefix_rel_v5_info", "")
        for line in info_text.split("\n"):
            if line == " ":
                acc.separator(factor=0.5)
            elif line:
                acc.label(text=line)
                
        acc.operator("orefix.copy_rel_info", text="数値を一括コピー", icon='COPYDOWN')
            
        # Rays Configuration
        cfg = layout.box()
        cfg.label(text="Rays Configuration", icon='LIGHT_SUN')
        row = cfg.row(align=True)
        row.prop(p, "ray_count", text="Count")
        row.prop(p, "base_angle", text="Base Angle")
        
        # Ray Visual Details
        det = layout.box()
        det.label(text="Ray Details", icon='NODE_MATERIAL')
        
        def draw_ray_box(parent, title, show_mode, show_out, show_in, thick_out, color_out, thick_in, color_in):
            b = parent.box()
            row = b.row(align=True)
            row.prop(p, show_mode, text="")
            row.label(text=title)
            
            if getattr(p, show_mode):
                c = b.column(align=True)
                
                # Out
                r = c.row(align=True)
                r.prop(p, show_out, text="", icon='HIDE_OFF')
                r.prop(p, thick_out, text="Thick Out")
                r.prop(p, color_out, text="")
                
                # In
                r = c.row(align=True)
                r.prop(p, show_in, text="", icon='HIDE_OFF')
                r.prop(p, thick_in, text="Thick In")
                r.prop(p, color_in, text="")

        draw_ray_box(det, "Classic Ray", "show_ray_classic", "show_classic_out", "show_classic_in", "thick_classic_out", "color_classic_out", "thick_classic_in", "color_classic_in")
        draw_ray_box(det, "Converge Ray", "show_ray_converge", "show_conv_out", "show_conv_in", "thick_conv_out", "color_conv_out", "thick_conv_in", "color_conv_in")
        draw_ray_box(det, "Ellipse Conv Ray", "show_ray_ell_conv", "show_ell_conv_out", "show_ell_conv_in", "thick_ell_conv_out", "color_ell_conv_out", "thick_ell_conv_in", "color_ell_conv_in")

        # Wavefronts
        wf = layout.box()
        wf.label(text="Wavefronts", icon='SPHERE')
        row = wf.row()
        row.prop(p, "show_wavefronts", text="Show Rings")
        row.prop(p, "color_wavefronts", text="")
        wf.prop(p, "thick_wavefronts", text="Thickness")

        # Spacetime Structure
        st = layout.box()
        st.label(text="Spacetime Structure", icon='WORLD')
        
        row = st.row(align=True)
        row.prop(p, "show_st_ellipse", text="Longit. Ellipse")
        row.prop(p, "color_st_ellipse", text="")
        if p.show_st_ellipse:
            st.prop(p, "thick_st_ellipse", text="Thickness")
        
        row = st.row(align=True)
        row.prop(p, "show_spheroid", text="3D Spheroid")
        row.prop(p, "color_spheroid", text="")

        # Visual Aids
        vbox = layout.box()
        vbox.label(text="Visual Aids", icon='MESH_GRID')
        
        row = vbox.row(align=True)
        row.prop(p, "show_circle_rings", text="Tube Rings")
        row.prop(p, "color_circle_rings", text="")
        if p.show_circle_rings:
            vbox.prop(p, "thick_circle_rings", text="Thickness")
            
        row = vbox.row(align=True)
        row.prop(p, "show_refl_rings", text="Reflection Rings")
        row.prop(p, "color_refl_rings", text="")
        if p.show_refl_rings:
            vbox.prop(p, "thick_refl_rings", text="Thickness")
        
        row = vbox.row(align=True)
        row.prop(p, "show_skeleton", text="Skeleton")
        row.prop(p, "color_skeleton", text="")
        if p.show_skeleton:
            vbox.prop(p, "skel_thick", text="Thickness")

        layout.operator("orefix.draw_spacetime_sym_v5", text="Refresh View", icon='FILE_REFRESH')

class OREFIX_PT_RelLinks(bpy.types.Panel):
    bl_label = "Theory Links"
    bl_idname = "OREFIX_PT_rel_links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        for l in ADDON_LINKS:
            op = self.layout.operator("orefix.open_rel_url", text=l["label"])
            op.url = l["url"]

class OREFIX_PT_RelSystem(bpy.types.Panel):
    bl_label = "System"
    bl_idname = "OREFIX_PT_rel_system"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        self.layout.operator("orefix.remove_rel_addon", icon='CANCEL', text="アドオン削除")

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OREFIX_OT_CopyFullScript(bpy.types.Operator):
    bl_idname = "orefix.copy_full_script"
    bl_label = "Copy Full Script"

    def execute(self, context):
        p = context.scene.orefix_rel_sym
        M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
        texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
        
        if not texts:
            self.report({'ERROR'}, "ソースコードが見つかりません。")
            return {'CANCELLED'}
            
        d_str = "CURRENT_DEFAULTS = {\n"
        for k in CURRENT_DEFAULTS.keys():
            val = getattr(p, k)
            if isinstance(val, str):
                d_str += f'    "{k}": "{val}",\n'
            elif hasattr(val, "__len__"):
                d_str += f'    "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
            elif isinstance(val, float):
                d_str += f'    "{k}": {val:.4f},\n'
            else:
                d_str += f'    "{k}": {val},\n'
        d_str += "}\n"
        
        target_text = texts[-1]
        
        new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
        lines = new_code.split('\n')
        if lines and lines[0].startswith("bl_info"):
            lines.insert(0, f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)\n")
            
        context.window_manager.clipboard = '\n'.join(lines)
        self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
        return {'FINISHED'}

class OREFIX_OT_OpenRelUrl(bpy.types.Operator):
    bl_idname = "orefix.open_rel_url"
    bl_label = "Open URL"
    url: bpy.props.StringProperty()

    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class OREFIX_OT_CopyRelInfo(bpy.types.Operator):
    bl_idname = "orefix.copy_rel_info"
    bl_label = "Copy Info"

    def execute(self, context):
        context.window_manager.clipboard = context.scene.get("orefix_rel_v5_info", "")
        self.report({'INFO'}, "数値をクリップボードにコピーしました!")
        return {'FINISHED'}

class OREFIX_OT_RemoveRelAddon(bpy.types.Operator):
    bl_idname = "orefix.remove_rel_addon"
    bl_label = "Remove"

    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
    OREFIX_PG_RelativitySymV5, 
    OREFIX_OT_DrawSpacetimeSymV5, 
    OREFIX_OT_CopyFullScript, 
    OREFIX_OT_OpenRelUrl, 
    OREFIX_OT_CopyRelInfo, 
    OREFIX_OT_RemoveRelAddon, 
    OREFIX_PT_RelSymV5, 
    OREFIX_PT_RelLinks, 
    OREFIX_PT_RelSystem
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.orefix_rel_sym = bpy.props.PointerProperty(type=OREFIX_PG_RelativitySymV5)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    if hasattr(bpy.types.Scene, "orefix_rel_sym"):
        del bpy.types.Scene.orefix_rel_sym

if __name__ == "__main__":
    register()