blender Million 2026

時空図 20260320版

addonjack

https://aistudio.google.com/app/prompts/1yvvrY8dGxh5u0ssaBw57LwKyKktKckc4







# Copied at: 20260320 15:33:26
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  設定エリア & ID管理
# ==============================================================================

PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = "   [ Minkowski ]   "

# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V8 ###"

bl_info = {
    "name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
    "author": "zionadchat",
    "version": (1, 6, 2),
    "blender": (5, 0, 0),
    "location": "3D View > Sidebar",
    "description": "Stable Minkowski Diagram with Intersections",
    "category": "3D View",
}

OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"

ADDON_LINKS = (
    {"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
    {"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
    {"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)

# ==============================================================================
#  デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "show_preview": True,
    "beta": 0.6000, "thickness": 0.0300,
    "precision": 4,
    "vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
    "vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
    "vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
    "vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
    "vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
    "vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>

# ==============================================================================
#  描画 & マテリアル制御
# ==============================================================================

def cleanup_minkowski_data():
    """PREFIXが付いた全データブロックを完全に削除"""
    for obj in bpy.data.objects:
        if obj.name.startswith(PREFIX):
            bpy.data.objects.remove(obj, do_unlink=True)
    
    for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
        for block in data:
            if block.name.startswith(PREFIX) and block.users == 0:
                data.remove(block)

def get_or_create_material(name_suffix, color):
    mat_name = f"{PREFIX}_Mat_{name_suffix}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.inputs['Base Color'].default_value = color
        if 'Alpha' in bsdf.inputs:
            bsdf.inputs['Alpha'].default_value = color[3]
    return mat

def draw_minkowski_diagram(context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return

    cleanup_minkowski_data()
    if not props.show_preview: return

    target_col = context.scene.collection
    beta = props.beta
    thickness = props.thickness
    gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0

    def add_line(name, points, color, is_visible):
        if not is_visible: return
        curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
        curve.dimensions = '3D'
        curve.fill_mode = 'FULL'
        curve.bevel_depth = thickness
        
        spline = curve.splines.new('POLY')
        spline.points.add(len(points) - 1)
        for i, p in enumerate(points):
            spline.points[i].co = (p[0], p[1], 0, 1)
            
        obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
        target_col.objects.link(obj)
        obj.data.materials.append(get_or_create_material(name, color))

    # 1. 静止系軸
    add_line("Axis_S",[(0, -6), (0, 6)], props.col_axis, props.vis_axis)
    add_line("Axis_X",[(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
    
    # 2. 光円錐
    add_line("Light_P",[(-6, -6), (6, 6)], props.col_light, props.vis_light)
    add_line("Light_M",[(-6, 6), (6, -6)], props.col_light, props.vis_light)

    # 3. 不変双曲線 (sinh/cosh)
    q_range =[i * 0.1 - 2.5 for i in range(51)]
    add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
    add_line("Hyp_S",[(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)

    # 4. 運動系軸
    add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
    add_line("Axis_XP",[(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)

    # 5. メモリ校正用マーカー (交点)
    if props.vis_axis or props.vis_ctp:
        m_mat = get_or_create_material("Marker", props.col_axis)
        for loc in[(beta*gamma, gamma), (gamma, beta*gamma)]:
            bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
            marker = context.active_object
            marker.name = f"{PREFIX}_Marker"
            marker.data.materials.append(m_mat)

# ==============================================================================
#  タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
    global _timer
    _timer = None
    if bpy.context and bpy.context.scene:
        draw_minkowski_diagram(bpy.context)
    return None

def on_property_update(self, context):
    global _timer
    if _timer:
        try: bpy.app.timers.unregister(_timer)
        except: pass
    _timer = bpy.app.timers.register(trigger_update, first_interval=0.03)

# ==============================================================================
#  PROPERTIES
# ==============================================================================

class PG_MinkowskiProps(PropertyGroup):
    show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
    beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
    thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
    precision: IntProperty(name="Decimals", default=CURRENT_DEFAULTS.get('precision', 4), min=0, max=9, description="Decimal places for intersections")
    
    vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
    col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
    
    vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
    col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
    
    vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
    col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
    
    vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
    col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
    
    vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
    col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
    
    vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
    col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)

# ==============================================================================
#  OPERATORS
# ==============================================================================

class OT_CopyMinkowskiFullCode(Operator):
    bl_idname = f"{OP_PREFIX}.copy_full_script"
    bl_label = "Copy Full Script"
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        target_text = None
        for t in bpy.data.texts:
            if SOURCE_ID_TAG in t.as_string():
                target_text = t
                break
        
        if not target_text:
            self.report({'ERROR'}, "Script source ID not found in Text Editor.")
            return {'CANCELLED'}

        code = target_text.as_string()
        def f4(v): return tuple(round(x, 4) for x in v)
        p = props.precision
        
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "show_preview": {props.show_preview},\n'
        new_dict += f'    "beta": {props.beta:.{p}f}, "thickness": {props.thickness:.4f},\n'
        new_dict += f'    "precision": {props.precision},\n'
        new_dict += f'    "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
        new_dict += f'    "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
        new_dict += f'    "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
        new_dict += f'    "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
        new_dict += f'    "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
        new_dict += f'    "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
        new_dict += "}\n"

        try:
            start_tag = "".join(["#", " <BEGIN_DICT>"])
            end_tag = "".join(["#", " <END_DICT>"])
            
            if start_tag not in code or end_tag not in code:
                self.report({'ERROR'}, "Dictionary tags missing.")
                return {'CANCELLED'}
                
            pre_part = code.split(start_tag, 1)[0]
            post_part = code.split(end_tag, 1)[1]
            
            if pre_part.startswith("# Copied at:"):
                parts = pre_part.split("\n", 1)
                if len(parts) > 1:
                    pre_part = parts[1]
            
            # 年月日を YYYYMMDD HH:MM:SS の形式で付与
            final_code = (
                f"# Copied at: {datetime.now().strftime('%Y%m%d %H:%M:%S')}\n" + 
                pre_part + start_tag + "\n" + 
                new_dict + 
                end_tag + post_part
            )
            
            context.window_manager.clipboard = final_code
            self.report({'INFO'}, "Full script copied to clipboard!")
        except Exception as e:
            self.report({'ERROR'}, f"Copy failed: {str(e)}")
            return {'CANCELLED'}
            
        return {'FINISHED'}

class OT_CopyMinkowskiIntersections(Operator):
    bl_idname = f"{OP_PREFIX}.copy_intersections"
    bl_label = "Copy Intersections"
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        beta = props.beta
        gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
        p = props.precision
        
        t_x, t_ct = beta * gamma, gamma
        s_x, s_ct = gamma, beta * gamma
        
        text = (
            f"Beta (v/c): {beta:.{p}f}\n"
            f"Time Intersection (x, ct): ({t_x:.{p}f}, {t_ct:.{p}f})\n"
            f"Space Intersection (x, ct): ({s_x:.{p}f}, {s_ct:.{p}f})"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "Intersections copied to clipboard!")
        return {'FINISHED'}

class OT_OpenMinkowskiUrl(Operator):
    bl_idname = f"{OP_PREFIX}.open_url"; bl_label = "Open URL"; url: StringProperty()
    def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}

class OT_RemoveMinkowskiAddon(Operator):
    bl_idname = f"{OP_PREFIX}.remove_addon"; bl_label = "Remove Addon"
    def execute(self, context):
        bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
        return {'FINISHED'}

# ==============================================================================
#  PANELS
# ==============================================================================

class PT_MinkowskiPanel(Panel):
    bl_label = "Minkowski Stable V7"
    bl_idname = f"{PREFIX}_PT_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROPS_NAME)
        p = props.precision

        row = layout.row()
        row.scale_y = 1.2
        row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
        
        layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
        
        # 物理パラメータ (Beta表示を指定桁数に連動)
        box = layout.box()
        box.label(text="Physical Parameters", icon='PHYSICS')
        box.prop(props, "beta", slider=True, text=f"Beta (v/c) :  {props.beta:.{p}f}")
        box.prop(props, "thickness", slider=True)

        # 交点情報エリア
        box = layout.box()
        box.label(text="Intersections (x, ct)", icon='MESH_GRID')
        box.prop(props, "precision", slider=True)
        
        beta = props.beta
        gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
        
        t_x, t_ct = beta * gamma, gamma
        s_x, s_ct = gamma, beta * gamma
        
        col = box.column(align=True)
        col.label(text=f"Time (ct' axis):  ({t_x:.{p}f}, {t_ct:.{p}f})")
        col.label(text=f"Space (x' axis): ({s_x:.{p}f}, {s_ct:.{p}f})")
        
        row = box.row()
        row.operator(OT_CopyMinkowskiIntersections.bl_idname, icon='COPYDOWN', text="Copy Intersections")

        # 色・表示設定
        box = layout.box()
        box.label(text="Layers & Colors", icon='COLOR')
        
        layers =[
            ("vis_axis", "col_axis", "Main Axes"),
            ("vis_light", "col_light", "Light Cone"),
            ("vis_thyp", "col_thyp", "Timelike Hyp"),
            ("vis_shyp", "col_shyp", "Spacelike Hyp"),
            ("vis_ctp", "col_ctp", "ct' Axis"),
            ("vis_xp", "col_xp", "x' Axis"),
        ]

        for vis, col, label in layers:
            row = box.row(align=True)
            row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
            row.prop(props, col, text=label)

class PT_MinkowskiLinks(Panel):
    bl_label = "Links"; bl_idname = f"{PREFIX}_PT_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: self.layout.operator(OT_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]

class PT_MinkowskiSys(Panel):
    bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context): self.layout.operator(OT_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')

# ==============================================================================
#  REGISTER
# ==============================================================================

classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_CopyMinkowskiIntersections, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)

def register():
    for c in classes: bpy.utils.register_class(c)
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))

def unregister():
    cleanup_minkowski_data()
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): bpy.utils.unregister_class(c)

if __name__ == "__main__": register()

Blenderの仕様上、スライダーの右側にある入力ボックスの表示桁数はシステムで固定されていて動的に変更することができません。
そのため、「スライダーの左側に表示されるラベル名(Beta (v/c))」に、指定した小数点桁数の値を連動させて表示する ように修正しました。
また、「Copy Intersections」ボタンでコピーされるテキストの中にも、指定桁数に合わせたベータ(Beta)の値を一緒に含める ように改良しています。
# Copied at: 15:19:09
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  設定エリア & ID管理
# ==============================================================================

PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = "   [ Minkowski ]   "

# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V6 ###"

bl_info = {
    "name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
    "author": "zionadchat",
    "version": (1, 6, 0),
    "blender": (5, 0, 0),
    "location": "3D View > Sidebar",
    "description": "Stable Minkowski Diagram with Intersections",
    "category": "3D View",
}

OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"

ADDON_LINKS = (
    {"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
    {"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
    {"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)

# ==============================================================================
#  デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "show_preview": True,
    "beta": 0.6009, "thickness": 0.0300,
    "precision": 4,
    "vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
    "vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
    "vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
    "vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
    "vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
    "vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>

# ==============================================================================
#  描画 & マテリアル制御
# ==============================================================================

def cleanup_minkowski_data():
    """PREFIXが付いた全データブロックを完全に削除"""
    for obj in bpy.data.objects:
        if obj.name.startswith(PREFIX):
            bpy.data.objects.remove(obj, do_unlink=True)
    
    for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
        for block in data:
            if block.name.startswith(PREFIX) and block.users == 0:
                data.remove(block)

def get_or_create_material(name_suffix, color):
    mat_name = f"{PREFIX}_Mat_{name_suffix}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.inputs['Base Color'].default_value = color
        if 'Alpha' in bsdf.inputs:
            bsdf.inputs['Alpha'].default_value = color[3]
    return mat

def draw_minkowski_diagram(context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return

    cleanup_minkowski_data()
    if not props.show_preview: return

    target_col = context.scene.collection
    beta = props.beta
    thickness = props.thickness
    gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0

    def add_line(name, points, color, is_visible):
        if not is_visible: return
        curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
        curve.dimensions = '3D'
        curve.fill_mode = 'FULL'
        curve.bevel_depth = thickness
        
        spline = curve.splines.new('POLY')
        spline.points.add(len(points) - 1)
        for i, p in enumerate(points):
            spline.points[i].co = (p[0], p[1], 0, 1)
            
        obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
        target_col.objects.link(obj)
        obj.data.materials.append(get_or_create_material(name, color))

    # 1. 静止系軸
    add_line("Axis_S",[(0, -6), (0, 6)], props.col_axis, props.vis_axis)
    add_line("Axis_X", [(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
    
    # 2. 光円錐
    add_line("Light_P", [(-6, -6), (6, 6)], props.col_light, props.vis_light)
    add_line("Light_M",[(-6, 6), (6, -6)], props.col_light, props.vis_light)

    # 3. 不変双曲線 (sinh/cosh)
    q_range =[i * 0.1 - 2.5 for i in range(51)]
    add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
    add_line("Hyp_S",[(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)

    # 4. 運動系軸
    add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
    add_line("Axis_XP",[(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)

    # 5. メモリ校正用マーカー (交点)
    if props.vis_axis or props.vis_ctp:
        m_mat = get_or_create_material("Marker", props.col_axis)
        for loc in [(beta*gamma, gamma), (gamma, beta*gamma)]:
            bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
            marker = context.active_object
            marker.name = f"{PREFIX}_Marker"
            marker.data.materials.append(m_mat)

# ==============================================================================
#  タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
    global _timer
    _timer = None
    if bpy.context and bpy.context.scene:
        draw_minkowski_diagram(bpy.context)
    return None

def on_property_update(self, context):
    global _timer
    if _timer:
        try: bpy.app.timers.unregister(_timer)
        except: pass
    _timer = bpy.app.timers.register(trigger_update, first_interval=0.03)

# ==============================================================================
#  PROPERTIES
# ==============================================================================

class PG_MinkowskiProps(PropertyGroup):
    show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
    beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
    thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
    precision: IntProperty(name="Decimals", default=CURRENT_DEFAULTS.get('precision', 4), min=0, max=9, description="Decimal places for intersections")
    
    vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
    col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
    
    vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
    col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
    
    vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
    col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
    
    vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
    col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
    
    vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
    col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
    
    vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
    col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)

# ==============================================================================
#  OPERATORS
# ==============================================================================

class OT_CopyMinkowskiFullCode(Operator):
    bl_idname = f"{OP_PREFIX}.copy_full_script"
    bl_label = "Copy Full Script"
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        target_text = None
        for t in bpy.data.texts:
            if SOURCE_ID_TAG in t.as_string():
                target_text = t
                break
        
        if not target_text:
            self.report({'ERROR'}, "Script source ID not found in Text Editor.")
            return {'CANCELLED'}

        code = target_text.as_string()
        def f4(v): return tuple(round(x, 4) for x in v)
        
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "show_preview": {props.show_preview},\n'
        new_dict += f'    "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
        new_dict += f'    "precision": {props.precision},\n'
        new_dict += f'    "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
        new_dict += f'    "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
        new_dict += f'    "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
        new_dict += f'    "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
        new_dict += f'    "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
        new_dict += f'    "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
        new_dict += "}\n"

        try:
            start_tag = "".join(["#", " <BEGIN_DICT>"])
            end_tag = "".join(["#", " <END_DICT>"])
            
            if start_tag not in code or end_tag not in code:
                self.report({'ERROR'}, "Dictionary tags missing.")
                return {'CANCELLED'}
                
            pre_part = code.split(start_tag, 1)[0]
            post_part = code.split(end_tag, 1)[1]
            
            if pre_part.startswith("# Copied at:"):
                parts = pre_part.split("\n", 1)
                if len(parts) > 1:
                    pre_part = parts[1]
            
            final_code = (
                f"# Copied at: {datetime.now().strftime('%H:%M:%S')}\n" + 
                pre_part + start_tag + "\n" + 
                new_dict + 
                end_tag + post_part
            )
            
            context.window_manager.clipboard = final_code
            self.report({'INFO'}, "Full script copied to clipboard!")
        except Exception as e:
            self.report({'ERROR'}, f"Copy failed: {str(e)}")
            return {'CANCELLED'}
            
        return {'FINISHED'}

class OT_CopyMinkowskiIntersections(Operator):
    bl_idname = f"{OP_PREFIX}.copy_intersections"
    bl_label = "Copy Intersections"
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        beta = props.beta
        gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
        p = props.precision
        
        t_x, t_ct = beta * gamma, gamma
        s_x, s_ct = gamma, beta * gamma
        
        text = (
            f"Time Intersection (x, ct): ({t_x:.{p}f}, {t_ct:.{p}f})\n"
            f"Space Intersection (x, ct): ({s_x:.{p}f}, {s_ct:.{p}f})"
        )
        context.window_manager.clipboard = text
        self.report({'INFO'}, "Intersections copied to clipboard!")
        return {'FINISHED'}

class OT_OpenMinkowskiUrl(Operator):
    bl_idname = f"{OP_PREFIX}.open_url"; bl_label = "Open URL"; url: StringProperty()
    def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}

class OT_RemoveMinkowskiAddon(Operator):
    bl_idname = f"{OP_PREFIX}.remove_addon"; bl_label = "Remove Addon"
    def execute(self, context):
        bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
        return {'FINISHED'}

# ==============================================================================
#  PANELS
# ==============================================================================

class PT_MinkowskiPanel(Panel):
    bl_label = "Minkowski Stable V6"
    bl_idname = f"{PREFIX}_PT_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME

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

        row = layout.row()
        row.scale_y = 1.2
        row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
        
        layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
        
        # 物理パラメータ
        box = layout.box()
        box.label(text="Physical Parameters", icon='PHYSICS')
        box.prop(props, "beta", slider=True)
        box.prop(props, "thickness", slider=True)

        # 交点情報エリア
        box = layout.box()
        box.label(text="Intersections (x, ct)", icon='MESH_GRID')
        box.prop(props, "precision", slider=True)
        
        beta = props.beta
        gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
        p = props.precision
        
        t_x, t_ct = beta * gamma, gamma
        s_x, s_ct = gamma, beta * gamma
        
        col = box.column(align=True)
        col.label(text=f"Time (ct' axis):  ({t_x:.{p}f}, {t_ct:.{p}f})")
        col.label(text=f"Space (x' axis): ({s_x:.{p}f}, {s_ct:.{p}f})")
        
        row = box.row()
        row.operator(OT_CopyMinkowskiIntersections.bl_idname, icon='COPYDOWN', text="Copy Intersections")

        # 色・表示設定
        box = layout.box()
        box.label(text="Layers & Colors", icon='COLOR')
        
        layers =[
            ("vis_axis", "col_axis", "Main Axes"),
            ("vis_light", "col_light", "Light Cone"),
            ("vis_thyp", "col_thyp", "Timelike Hyp"),
            ("vis_shyp", "col_shyp", "Spacelike Hyp"),
            ("vis_ctp", "col_ctp", "ct' Axis"),
            ("vis_xp", "col_xp", "x' Axis"),
        ]

        for vis, col, label in layers:
            row = box.row(align=True)
            row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
            row.prop(props, col, text=label)

class PT_MinkowskiLinks(Panel):
    bl_label = "Links"; bl_idname = f"{PREFIX}_PT_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: self.layout.operator(OT_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]

class PT_MinkowskiSys(Panel):
    bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context): self.layout.operator(OT_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')

# ==============================================================================
#  REGISTER
# ==============================================================================

classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_CopyMinkowskiIntersections, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)

def register():
    for c in classes: bpy.utils.register_class(c)
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))

def unregister():
    cleanup_minkowski_data()
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): bpy.utils.unregister_class(c)

if __name__ == "__main__": register()
# Copied at: 15:12:46
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  設定エリア & ID管理
# ==============================================================================

PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = "   [ Minkowski ]   "

# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V5 ###"

bl_info = {
    "name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
    "author": "zionadchat",
    "version": (1, 5, 2),
    "blender": (5, 0, 0),
    "location": "3D View > Sidebar",
    "description": "Stable Minkowski Diagram with Full Script Copy Support",
    "category": "3D View",
}

OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"

ADDON_LINKS = (
    {"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
    {"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
    {"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)

# ==============================================================================
#  デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "show_preview": True,
    "beta": 0.6000, "thickness": 0.0200,
    "vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
    "vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
    "vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
    "vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
    "vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
    "vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>

# ==============================================================================
#  描画 & マテリアル制御
# ==============================================================================

def cleanup_minkowski_data():
    """PREFIXが付いた全データブロックを完全に削除"""
    for obj in bpy.data.objects:
        if obj.name.startswith(PREFIX):
            bpy.data.objects.remove(obj, do_unlink=True)
    
    # メッシュ、カーブ、マテリアルの掃除
    for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
        for block in data:
            if block.name.startswith(PREFIX) and block.users == 0:
                data.remove(block)

def get_or_create_material(name_suffix, color):
    mat_name = f"{PREFIX}_Mat_{name_suffix}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.inputs['Base Color'].default_value = color
        if 'Alpha' in bsdf.inputs:
            bsdf.inputs['Alpha'].default_value = color[3]
    return mat

def draw_minkowski_diagram(context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return

    cleanup_minkowski_data()
    if not props.show_preview: return

    target_col = context.scene.collection
    beta = props.beta
    thickness = props.thickness
    gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0

    def add_line(name, points, color, is_visible):
        if not is_visible: return
        curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
        curve.dimensions = '3D'
        curve.fill_mode = 'FULL'
        curve.bevel_depth = thickness
        
        spline = curve.splines.new('POLY')
        spline.points.add(len(points) - 1)
        for i, p in enumerate(points):
            spline.points[i].co = (p[0], p[1], 0, 1)
            
        obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
        target_col.objects.link(obj)
        obj.data.materials.append(get_or_create_material(name, color))

    # 1. 静止系軸
    add_line("Axis_S", [(0, -6), (0, 6)], props.col_axis, props.vis_axis)
    add_line("Axis_X",[(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
    
    # 2. 光円錐
    add_line("Light_P",[(-6, -6), (6, 6)], props.col_light, props.vis_light)
    add_line("Light_M", [(-6, 6), (6, -6)], props.col_light, props.vis_light)

    # 3. 不変双曲線 (sinh/cosh)
    q_range =[i * 0.1 - 2.5 for i in range(51)]
    add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
    add_line("Hyp_S", [(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)

    # 4. 運動系軸
    add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
    add_line("Axis_XP", [(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)

    # 5. メモリ校正用マーカー
    if props.vis_axis or props.vis_ctp:
        m_mat = get_or_create_material("Marker", props.col_axis)
        for loc in[(beta*gamma, gamma), (gamma, beta*gamma)]:
            bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
            marker = context.active_object
            marker.name = f"{PREFIX}_Marker"
            marker.data.materials.append(m_mat)

# ==============================================================================
#  タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
    global _timer
    _timer = None
    if bpy.context and bpy.context.scene:
        draw_minkowski_diagram(bpy.context)
    return None

def on_property_update(self, context):
    global _timer
    if _timer:
        try: bpy.app.timers.unregister(_timer)
        except: pass
    _timer = bpy.app.timers.register(trigger_update, first_interval=0.03)

# ==============================================================================
#  PROPERTIES
# ==============================================================================

class PG_MinkowskiProps(PropertyGroup):
    show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
    beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
    thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
    
    vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
    col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
    
    vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
    col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
    
    vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
    col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
    
    vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
    col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
    
    vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
    col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
    
    vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
    col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)

# ==============================================================================
#  OPERATORS
# ==============================================================================

class OT_CopyMinkowskiFullCode(Operator):
    bl_idname = f"{OP_PREFIX}.copy_full_script"
    bl_label = "Copy Full Script"
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        # テキストエディタ内を全探索して自分自身を探す
        target_text = None
        for t in bpy.data.texts:
            if SOURCE_ID_TAG in t.as_string():
                target_text = t
                break
        
        if not target_text:
            self.report({'ERROR'}, "Script source ID not found in Text Editor.")
            return {'CANCELLED'}

        code = target_text.as_string()
        def f4(v): return tuple(round(x, 4) for x in v)
        
        # 新しい辞書文字列の作成
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "show_preview": {props.show_preview},\n'
        new_dict += f'    "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
        new_dict += f'    "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
        new_dict += f'    "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
        new_dict += f'    "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
        new_dict += f'    "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
        new_dict += f'    "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
        new_dict += f'    "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
        new_dict += "}\n"

        try:
            # ここがエラー箇所だったので、文字の囲みが欠落しても問題ない結合方式に修正
            start_tag = "".join(["#", " <BEGIN_DICT>"])
            end_tag = "".join(["#", " <END_DICT>"])
            
            if start_tag not in code or end_tag not in code:
                self.report({'ERROR'}, "Dictionary tags missing.")
                return {'CANCELLED'}
                
            # 第2引数「1」を指定し、最初のタグのみで分割
            pre_part = code.split(start_tag, 1)[0]
            post_part = code.split(end_tag, 1)[1]
            
            # 既にCopied atが先頭にあれば削除して増殖を防ぐ
            if pre_part.startswith("# Copied at:"):
                parts = pre_part.split("\n", 1)
                if len(parts) > 1:
                    pre_part = parts[1]
            
            final_code = (
                f"# Copied at: {datetime.now().strftime('%H:%M:%S')}\n" + 
                pre_part + start_tag + "\n" + 
                new_dict + 
                end_tag + post_part
            )
            
            context.window_manager.clipboard = final_code
            self.report({'INFO'}, "Full script copied to clipboard!")
        except Exception as e:
            self.report({'ERROR'}, f"Copy failed: {str(e)}")
            return {'CANCELLED'}
            
        return {'FINISHED'}

class OT_OpenMinkowskiUrl(Operator):
    bl_idname = f"{OP_PREFIX}.open_url"; bl_label = "Open URL"; url: StringProperty()
    def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}

class OT_RemoveMinkowskiAddon(Operator):
    bl_idname = f"{OP_PREFIX}.remove_addon"; bl_label = "Remove Addon"
    def execute(self, context):
        bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
        return {'FINISHED'}

# ==============================================================================
#  PANELS
# ==============================================================================

class PT_MinkowskiPanel(Panel):
    bl_label = "Minkowski Stable V5"
    bl_idname = f"{PREFIX}_PT_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME

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

        row = layout.row()
        row.scale_y = 1.2
        row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
        
        layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
        
        box = layout.box()
        box.label(text="Physical Parameters", icon='PHYSICS')
        box.prop(props, "beta", slider=True)
        box.prop(props, "thickness", slider=True)

        box = layout.box()
        box.label(text="Layers & Colors (Alpha supported)", icon='COLOR')
        
        layers =[
            ("vis_axis", "col_axis", "Main Axes"),
            ("vis_light", "col_light", "Light Cone"),
            ("vis_thyp", "col_thyp", "Timelike Hyp"),
            ("vis_shyp", "col_shyp", "Spacelike Hyp"),
            ("vis_ctp", "col_ctp", "ct' Axis"),
            ("vis_xp", "col_xp", "x' Axis"),
        ]

        for vis, col, label in layers:
            row = box.row(align=True)
            row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
            row.prop(props, col, text=label)

class PT_MinkowskiLinks(Panel):
    bl_label = "Links"; bl_idname = f"{PREFIX}_PT_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: self.layout.operator(OT_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]

class PT_MinkowskiSys(Panel):
    bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context): self.layout.operator(OT_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')

# ==============================================================================
#  REGISTER
# ==============================================================================

classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)

def register():
    for c in classes: bpy.utils.register_class(c)
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))

def unregister():
    cleanup_minkowski_data()
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): bpy.utils.unregister_class(c)

if __name__ == "__main__": register()

import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  設定エリア & ID管理
# ==============================================================================

PREFIX = "Minkowski20260320_v4"
TAB_NAME = "   [ Minkowski ]   "

# ### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_V4_STABLE ###

bl_info = {
    "name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
    "author": "zionadchat",
    "version": (1, 3, 0),
    "blender": (5, 0, 0),
    "location": "3D View > Sidebar",
    "description": "Stable Minkowski Diagram Generator with Alpha",
    "category": "3D View",
}

OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_V4_STABLE ###"

ADDON_LINKS = (
    {"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
    {"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
)

# ==============================================================================
#  デフォルト値
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "show_preview": True,
    "beta": 0.6000,
    "thickness": 0.0200,
    "vis_axis": True, "col_axis": (1.00, 1.00, 1.00, 1.00),
    "vis_light": True, "col_light": (1.00, 0.80, 0.00, 0.50),
    "vis_thyp": True, "col_thyp": (1.00, 0.20, 0.20, 1.00),
    "vis_shyp": True, "col_shyp": (0.20, 0.20, 1.00, 1.00),
    "vis_ctp": True, "col_ctp": (0.00, 1.00, 0.20, 1.00),
    "vis_xp": True, "col_xp": (1.00, 0.00, 1.00, 1.00),
}
# <END_DICT>

# ==============================================================================
#  描画 & クリーンアップロジック
# ==============================================================================

def cleanup_minkowski_data():
    """PREFIXが付いたオブジェクト、メッシュ、カーブ、マテリアルをすべて削除"""
    for obj in bpy.data.objects:
        if obj.name.startswith(PREFIX):
            bpy.data.objects.remove(obj, do_unlink=True)
    
    # 未使用のデータブロックも掃除(名前がPREFIXで始まるもの)
    for data in [bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
        for block in data:
            if block.name.startswith(PREFIX):
                if block.users == 0:
                    data.remove(block)

def create_minkowski_material(name_suffix, color):
    mat_name = f"{PREFIX}_Mat_{name_suffix}"
    mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    
    if mat.use_nodes:
        nodes = mat.node_tree.nodes
        bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.inputs['Base Color'].default_value = color
        if 'Alpha' in bsdf.inputs:
            bsdf.inputs['Alpha'].default_value = color[3]
    return mat

def update_geometry(context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return

    cleanup_minkowski_data()
    if not props.show_preview: return

    scene_collection = context.scene.collection
    beta = props.beta
    thickness = props.thickness
    gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0

    def draw_line(name, points, color, visible):
        if not visible: return
        curve_name = f"{PREFIX}_Curve_{name}"
        curve_data = bpy.data.curves.new(curve_name, type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.fill_mode = 'FULL'
        curve_data.bevel_depth = thickness
        
        polyline = curve_data.splines.new('POLY')
        polyline.points.add(len(points) - 1)
        for i, p in enumerate(points):
            polyline.points[i].co = (p[0], p[1], 0, 1)
            
        obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve_data)
        scene_collection.objects.link(obj)
        obj.data.materials.append(create_minkowski_material(name, color))

    # 1. メイン軸 (x, ct)
    draw_line("Axis_S", [(0, -5), (0, 5)], props.col_axis, props.vis_axis)
    draw_line("Axis_X", [(-5, 0), (5, 0)], props.col_axis, props.vis_axis)
    
    # 2. 光円錐
    draw_line("Light_P", [(-5, -5), (5, 5)], props.col_light, props.vis_light)
    draw_line("Light_M", [(-5, 5), (5, -5)], props.col_light, props.vis_light)

    # 3. 不変双曲線
    q_range = [i * 0.1 - 2.0 for i in range(41)]
    draw_line("Hyp_T", [(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
    draw_line("Hyp_S", [(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)

    # 4. 移動系軸 (ct', x')
    draw_line("Axis_CTP", [(-5*beta, -5), (5*beta, 5)], props.col_ctp, props.vis_ctp)
    draw_line("Axis_XP", [(-5, -5*beta), (5, 5*beta)], props.col_xp, props.vis_xp)

    # 5. メモリマーカー(球体)
    if props.vis_axis or props.vis_ctp:
        marker_mat = create_minkowski_material("Marker", props.col_axis)
        for loc in [(beta*gamma, gamma), (gamma, beta*gamma)]:
            bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
            marker = context.active_object
            marker.name = f"{PREFIX}_Marker"
            marker.data.materials.append(marker_mat)

# タイマーによる更新制御
_timer = None
def delayed_update():
    global _timer
    _timer = None
    if bpy.context and bpy.context.scene:
        update_geometry(bpy.context)
    return None

def on_update(self, context):
    global _timer
    if _timer: 
        try: bpy.app.timers.unregister(_timer)
        except: pass
    _timer = bpy.app.timers.register(delayed_update, first_interval=0.03)

# ==============================================================================
#  PROPERTIES / UI
# ==============================================================================

class PG_MinkowskiProps(PropertyGroup):
    show_preview: BoolProperty(name="Live Preview", default=CURRENT_DEFAULTS['show_preview'], update=on_update)
    beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.99, update=on_update)
    thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_update)
    
    vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_update)
    col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_update)
    
    vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_update)
    col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_update)
    
    vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_update)
    col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_update)
    
    vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_update)
    col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_update)
    
    vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_update)
    col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_update)
    
    vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_update)
    col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_update)

class OT_CopyMinkowskiScript(Operator):
    bl_idname = f"{OP_PREFIX}.copy_script"
    bl_label = "Copy Script"
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME)
        target_text = next((t for t in bpy.data.texts if SOURCE_ID_TAG in t.as_string()), None)
        if not target_text: return {'CANCELLED'}
        code = target_text.as_string()
        def f2(v): return tuple(round(x, 2) for x in v)
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "show_preview": {props.show_preview},\n'
        new_dict += f'    "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
        new_dict += f'    "vis_axis": {props.vis_axis}, "col_axis": {f2(props.col_axis)},\n'
        new_dict += f'    "vis_light": {props.vis_light}, "col_light": {f2(props.col_light)},\n'
        new_dict += f'    "vis_thyp": {props.vis_thyp}, "col_thyp": {f2(props.col_thyp)},\n'
        new_dict += f'    "vis_shyp": {props.vis_shyp}, "col_shyp": {f2(props.col_shyp)},\n'
        new_dict += f'    "vis_ctp": {props.vis_ctp}, "col_ctp": {f2(props.col_ctp)},\n'
        new_dict += f'    "vis_xp": {props.vis_xp}, "col_xp": {f2(props.col_xp)},\n'
        new_dict += "}\n"
        try:
            start, end = "# <BEGIN_DICT>", "# <END_DICT>"
            pre, post = code.split(start)[0], code.split(end)[1]
            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'}, "Setup Copied!")
        except: return {'CANCELLED'}
        return {'FINISHED'}

class PT_MinkowskiPanel(Panel):
    bl_label = "Minkowski Stable Control"
    bl_idname = f"{PREFIX}_PT_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROPS_NAME)
        layout.operator(OT_CopyMinkowskiScript.bl_idname, icon='COPY_ID')
        layout.prop(props, "show_preview", toggle=True)
        box = layout.box()
        box.prop(props, "beta", slider=True)
        box.prop(props, "thickness", slider=True)
        box = layout.box()
        box.label(text="Layers & Colors (Alpha supported)")
        layers = [("vis_axis", "col_axis", "Main Axes"), ("vis_light", "col_light", "Light Cone"),
                  ("vis_thyp", "col_thyp", "Timelike Hyp"), ("vis_shyp", "col_shyp", "Spacelike Hyp"),
                  ("vis_ctp", "col_ctp", "ct' Axis"), ("vis_xp", "col_xp", "x' Axis")]
        for v, c, l in layers:
            row = box.row(align=True)
            row.prop(props, v, text="", icon='HIDE_OFF' if getattr(props, v) else 'HIDE_ON')
            row.prop(props, c, text=l)

# ==============================================================================
#  REGISTER
# ==============================================================================

classes = (PG_MinkowskiProps, OT_CopyMinkowskiScript, PT_MinkowskiPanel)

def register():
    for c in classes: bpy.utils.register_class(c)
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))

def unregister():
    cleanup_minkowski_data()
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): bpy.utils.unregister_class(c)

if __name__ == "__main__": register()
import bpy
import math

bl_info = {
    "name": "Minkowski Diagram Generator",
    "author": "zionadchat Assistant",
    "version": (1, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar > Physics",
    "description": "ミンコフスキー時空図を生成するアドオン",
    "category": "Add Mesh",
}

# ------------------------------------------------------------------
# 1. 描画ロジック本体
# ------------------------------------------------------------------
def create_minkowski(context):
    props = context.scene.minkowski_props
    beta = props.beta
    thickness = props.thickness
    
    # 既存の図形を削除(名前で管理)
    for obj in bpy.data.objects:
        if obj.name.startswith("Minkowski_"):
            bpy.data.objects.remove(obj, do_unlink=True)

    def draw_curve(name, points, color, is_cyclic=False):
        curve_data = bpy.data.curves.new(name, type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.fill_mode = 'FULL'
        curve_data.bevel_depth = thickness  # 線の太さ
        
        polyline = curve_data.splines.new('POLY')
        polyline.points.add(len(points) - 1)
        for i, p in enumerate(points):
            polyline.points[i].co = (p[0], p[1], 0, 1)
        
        obj = bpy.data.objects.new(f"Minkowski_{name}", curve_data)
        context.collection.objects.link(obj)
        
        # マテリアル設定
        mat = bpy.data.materials.new(name=f"Mat_{name}")
        mat.use_nodes = True
        nodes = mat.node_tree.nodes
        nodes["Principled BSDF"].inputs[0].default_value = color
        obj.data.materials.append(mat)

    # 静止系軸
    draw_curve("Axis_CT", [(0, -5), (0, 5)], (1, 1, 1, 1))
    draw_curve("Axis_X", [(-5, 0), (5, 0)], (1, 1, 1, 1))
    
    # 光円錐
    draw_curve("Light_1", [(-5, -5), (5, 5)], (1, 1, 0, 1))
    draw_curve("Light_2", [(-5, 5), (5, -5)], (1, 1, 0, 1))

    # 双曲線の生成
    steps = 40
    q_range = [i * 0.1 - 2.0 for i in range(41)] # -2.0 to 2.0
    
    # 時間的 (ct^2 - x^2 = 1)
    t_points = [(math.sinh(q), math.cosh(q)) for q in q_range]
    draw_curve("Hyp_Time", t_points, (1, 0.2, 0.2, 1))
    
    # 空間的 (x^2 - ct^2 = 1)
    s_points = [(math.cosh(q), math.sinh(q)) for q in q_range]
    draw_curve("Hyp_Space", s_points, (0.2, 0.2, 1, 1))

    # 動いている系の軸 (ct', x')
    gamma = 1 / math.sqrt(1 - beta**2)
    draw_curve("Axis_CT_P", [(-5*beta, -5), (5*beta, 5)], (0, 1, 0, 1))
    draw_curve("Axis_X_P", [(-5, -5*beta), (5, 5*beta)], (1, 0, 1, 1))

    # メモリ点
    bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*3, location=(beta*gamma, gamma, 0))
    bpy.context.active_object.name = "Minkowski_Marker_T"
    bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*3, location=(gamma, beta*gamma, 0))
    bpy.context.active_object.name = "Minkowski_Marker_S"

# ------------------------------------------------------------------
# 2. UI定義とプロパティ
# ------------------------------------------------------------------
class MinkowskiProperties(bpy.types.PropertyGroup):
    beta: bpy.props.FloatProperty(
        name="Beta (v/c)",
        description="観測者の速度",
        default=0.6,
        min=0.0,
        max=0.99,
        update=lambda self, context: create_minkowski(context)
    )
    thickness: bpy.props.FloatProperty(
        name="Line Thickness",
        description="線の太さ",
        default=0.02,
        min=0.001,
        max=0.2,
        update=lambda self, context: create_minkowski(context)
    )

class MINKOWSKI_PT_panel(bpy.types.Panel):
    bl_label = "Minkowski Diagram"
    bl_idname = "MINKOWSKI_PT_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Physics'

    def draw(self, context):
        layout = self.layout
        props = context.scene.minkowski_props
        
        col = layout.column(align=True)
        col.prop(props, "beta", slider=True)
        col.prop(props, "thickness", slider=True)
        
        layout.operator("minkowski.generate", text="Refresh Diagram")

class MINKOWSKI_OT_generate(bpy.types.Operator):
    bl_label = "Generate Minkowski"
    bl_idname = "minkowski.generate"
    
    def execute(self, context):
        create_minkowski(context)
        return {'FINISHED'}

# ------------------------------------------------------------------
# 3. 登録
# ------------------------------------------------------------------
classes = [MinkowskiProperties, MINKOWSKI_PT_panel, MINKOWSKI_OT_generate]

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

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

if __name__ == "__main__":
    register()