blender Million 2026

https://posfie.com/@timekagura?sort=0&page=1

https://x.com/zionadchat

作業場 初期完成

作業場 (1)

作業場 (2)

AI Studio million

https://aistudio.google.com/app/prompts?state={"ids":["1pHYZUZQbfSlNQmnsP8iVHwcJzGkxtBed"],"action":"open","userId":"107762062127755077833","resourceKeys":{}}&usp=sharing

rapture_20260306140932.png

# 2026-03-06 Session Template V5
# Blender 4.0+ / 4.2+ (EEVEE-Next compatible)

UNIQUE_SCRIPT_ID = "RELATIVITY_VISUALIZER_2026_03_06_V5"
SCRIPT_VERSION = 5

bl_info = {
    "name": "Relativity Visualizer: Dual Light Cones",
    "author": "zionadchat Gemini",
    "version": (6, 5),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar",
    "description": "Maxwell絶対静止系での未来光円錐(先進)と過去光円錐(遅延)の可視化",
    "category": "Physics",
}

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

# ==============================================================================
#  DYNAMIC DEFAULTS (Saved State)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "velocity": 0.6000,
    "radius": 10.0000,
    "obs_time": 0.0000,
    "resolution": 72,
    "ring_thick": 0.1000,
    "ray_thick": 0.0500,
    "obs_size": 0.3000,
    "show_future": True,
    "color_future_phys": (0.0000, 0.8000, 0.8000, 0.8000),
    "color_future_base": (0.0000, 0.0000, 1.0000, 0.8000),
    "color_future_ray": (1.0000, 0.0000, 1.0000, 0.4000),
    "show_past": True,
    "color_past_phys": (0.0000, 1.0000, 0.0000, 0.8000),
    "color_past_base": (1.0000, 0.0000, 0.0000, 0.8000),
    "color_past_ray": (1.0000, 1.0000, 0.0000, 0.4000),
}
# <END_DICT>

SYSTEM_DEFAULTS = CURRENT_DEFAULTS.copy()

TAB_NAME = "Relativity_Visual"
COLLECTION_NAME = "Relativity_Visualizer_Output"
OBJECT_TAG = "relativity_visualizer_tag"

ADDON_LINKS = (
    {"label": "干渉計 スカートの裾 20260306aa", "url": "<https://www.notion.so/20260306aa-31bf5dacaf4380fc86eedcda55812c81>"},
    {"label": "Code Copy Template 20260221", "url": "<https://www.notion.so/Code-copy-20260221-30ef5dacaf4380f2984bd865b38b55b3>"},
    {"label": "Theory Background: Notion Doc", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
    {"label": "Blender Simulation Guide", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)

# ------------------------------------------------------------------------
# Material & Object Utilities
# ------------------------------------------------------------------------
def get_fixed_material(name, color):
    mat = bpy.data.materials.get(name) or bpy.data.materials.new(name=name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    
    nodes = mat.node_tree.nodes
    bsdf = nodes.get("Principled BSDF")

    if not bsdf:
        nodes.clear()
        bsdf = nodes.new("ShaderNodeBsdfPrincipled")
        output = nodes.new("ShaderNodeOutputMaterial")
        output.location = (300, 0)
        mat.node_tree.links.new(bsdf.outputs[0], output.inputs[0])

    if bsdf:
        if "Base Color" in bsdf.inputs: 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 = 0.5
    
    mat.diffuse_color = color
    return mat

def create_sphere(col, name, location, radius, mat_name, color):
    mesh = bpy.data.meshes.new(name)
    obj = bpy.data.objects.new(name, mesh)
    obj[OBJECT_TAG] = True 
    col.objects.link(obj)

    bm = bmesh.new()
    try:
        bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=radius)
        bmesh.ops.translate(bm, vec=Vector(location), verts=bm.verts)
        bm.to_mesh(mesh)
    finally:
        bm.free()

    obj.data.materials.append(get_fixed_material(mat_name, color))
    return obj

def create_light_cone_object(col, name, phys_points, base_points, ray_paths, thickness, ray_thickness, mats):
    """基準円周、底面円周、射線をすべて1つのカーブオブジェクトとして生成する"""
    curve = bpy.data.curves.new(name, 'CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = thickness
    curve.bevel_resolution = 4
    
    obj = bpy.data.objects.new(name, curve)
    obj[OBJECT_TAG] = True
    col.objects.link(obj)
    
    for mat in mats:
        obj.data.materials.append(mat)
        
    ray_radius_factor = ray_thickness / thickness if thickness > 0 else 1.0
    
    # 0: 底面円周 (Base Ring)
    if base_points:
        spline_base = curve.splines.new('POLY')
        spline_base.use_cyclic_u = True
        spline_base.points.add(len(base_points) - 1)
        for i, p in enumerate(base_points):
            spline_base.points[i].co = (p.x, p.y, p.z, 1)
        spline_base.material_index = 0
        
    # 1: 射線 (Ray Paths)
    if ray_paths:
        for path in ray_paths:
            spline_ray = curve.splines.new('POLY')
            spline_ray.use_cyclic_u = False
            spline_ray.points.add(len(path) - 1)
            for i, p in enumerate(path):
                spline_ray.points[i].co = (p.x, p.y, p.z, 1)
                spline_ray.points[i].radius = ray_radius_factor
            spline_ray.material_index = 1
            
    # 2: 基準円周 (Phys Ring)
    if phys_points:
        spline_phys = curve.splines.new('POLY')
        spline_phys.use_cyclic_u = True
        spline_phys.points.add(len(phys_points) - 1)
        for i, p in enumerate(phys_points):
            spline_phys.points[i].co = (p.x, p.y, p.z, 1)
        spline_phys.material_index = 2
        
    return obj

# ------------------------------------------------------------------------
# Core Drawing Logic
# ------------------------------------------------------------------------
def draw_rel_visual_core(context):
    if not context or not context.scene: return

    p = context.scene.rel_visual
    v = p.velocity; R = p.radius; t_obs = p.obs_time; res = p.resolution

    col = bpy.data.collections.get(COLLECTION_NAME)
    if not col: col = bpy.data.collections.new(COLLECTION_NAME)
    if col.name not in context.scene.collection.children: context.scene.collection.children.link(col)

    # 古いオブジェクトの削除
    for obj in[o for o in col.objects if o.get(OBJECT_TAG)]:
        m = obj.data
        bpy.data.objects.remove(obj, do_unlink=True)
        if m and m.users == 0:
            if isinstance(m, bpy.types.Mesh): bpy.data.meshes.remove(m)
            elif isinstance(m, bpy.types.Curve): bpy.data.curves.remove(m)

    # 観測者(現在位置)
    center_pos_now = Vector((v * t_obs, 0, t_obs))
    create_sphere(col, "Observer_Now", center_pos_now, p.obs_size, "Mat_Obs_Center", (1.0, 1.0, 1.0, 1.0))

    # 現在時刻の実体円周(共通基準)
    phys_points =[]
    for i in range(res):
        phi = math.radians(i * 360.0 / res)
        phys_points.append(Vector((v * t_obs + R * math.cos(phi), R * math.sin(phi), t_obs)))

    denom = 1.0 - v**2
    if abs(denom) < 1e-9: denom = 1e-9

    # 1. Future Light Cone (未来光円錐:先進)
    if p.show_future:
        base_points =[]; ray_paths =[]
        for i in range(res):
            phi = math.radians(i * 360.0 / res)
            # 先進時間 (+ v*cos)
            dt_adv = (R * (math.sqrt(max(0.0, 1.0 - (v*math.sin(phi))**2)) + v*math.cos(phi))) / denom
            t_adv = t_obs + dt_adv
            p_adv = Vector((v * t_adv + R * math.cos(phi), R * math.sin(phi), t_adv))
            base_points.append(p_adv)
            # 未来の底面から現在の中心へ
            ray_paths.append([p_adv, center_pos_now])

        mats =[
            get_fixed_material("Mat_Future_Base", p.color_future_base),
            get_fixed_material("Mat_Future_Ray", p.color_future_ray),
            get_fixed_material("Mat_Future_Phys", p.color_future_phys)
        ]
        create_light_cone_object(col, "Future_Light_Cone", phys_points, base_points, ray_paths, p.ring_thick, p.ray_thick, mats)

    # 2. Past Light Cone (過去光円錐:遅延)
    if p.show_past:
        base_points =[]; ray_paths =[]
        for i in range(res):
            phi = math.radians(i * 360.0 / res)
            dt_ret = (R * (math.sqrt(max(0.0, 1.0 - (v*math.sin(phi))**2)) - v*math.cos(phi))) / denom
            t_emit = t_obs - dt_ret
            p_emit = Vector((v * t_emit + R * math.cos(phi), R * math.sin(phi), t_emit))
            base_points.append(p_emit)
            ray_paths.append([p_emit, center_pos_now])

        mats =[
            get_fixed_material("Mat_Past_Base", p.color_past_base),
            get_fixed_material("Mat_Past_Ray", p.color_past_ray),
            get_fixed_material("Mat_Past_Phys", p.color_past_phys)
        ]
        create_light_cone_object(col, "Past_Light_Cone", phys_points, base_points, ray_paths, p.ring_thick, p.ray_thick, mats)

# ------------------------------------------------------------------------
# Throttling & Properties
# ------------------------------------------------------------------------
_update_timer = None
def delayed_update_func():
    global _update_timer
    _update_timer = None 
    if bpy.context and bpy.context.scene: draw_rel_visual_core(bpy.context)
    return None 

def update_view(self, context):
    global _update_timer
    if _update_timer:
        try: bpy.app.timers.unregister(_update_timer)
        except: pass 
    _update_timer = bpy.app.timers.register(delayed_update_func, first_interval=0.05)

class PG_RelativityVisual(bpy.types.PropertyGroup):
    velocity: bpy.props.FloatProperty(name="Velocity (v/c)", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.99, update=update_view)
    radius: bpy.props.FloatProperty(name="Radius (R)", default=CURRENT_DEFAULTS["radius"], min=0.1, update=update_view)
    obs_time: bpy.props.FloatProperty(name="Observation Time (t)", default=CURRENT_DEFAULTS["obs_time"], min=0.0, update=update_view)
    resolution: bpy.props.IntProperty(name="Resolution", default=CURRENT_DEFAULTS["resolution"], min=12, max=360, update=update_view)
    ring_thick: bpy.props.FloatProperty(name="Ring Thick", default=CURRENT_DEFAULTS["ring_thick"], min=0.01, update=update_view)
    ray_thick: bpy.props.FloatProperty(name="Ray Thick", default=CURRENT_DEFAULTS["ray_thick"], min=0.01, update=update_view)
    obs_size: bpy.props.FloatProperty(name="Center Size", default=CURRENT_DEFAULTS["obs_size"], min=0.01, update=update_view)

    # Future
    show_future: bpy.props.BoolProperty(name="Show Future Cone", default=CURRENT_DEFAULTS["show_future"], update=update_view)
    color_future_phys: bpy.props.FloatVectorProperty(name="Phys Ring (Center)", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_future_phys"], update=update_view)
    color_future_base: bpy.props.FloatVectorProperty(name="Base Ring (Top)", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_future_base"], update=update_view)
    color_future_ray: bpy.props.FloatVectorProperty(name="Rays", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_future_ray"], update=update_view)

    # Past
    show_past: bpy.props.BoolProperty(name="Show Past Cone", default=CURRENT_DEFAULTS["show_past"], update=update_view)
    color_past_phys: bpy.props.FloatVectorProperty(name="Phys Ring (Center)", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_past_phys"], update=update_view)
    color_past_base: bpy.props.FloatVectorProperty(name="Base Ring (Bottom)", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_past_base"], update=update_view)
    color_past_ray: bpy.props.FloatVectorProperty(name="Rays", subtype='COLOR', size=4, default=CURRENT_DEFAULTS["color_past_ray"], update=update_view)

# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OBJECT_OT_DrawRelVisual(bpy.types.Operator):
    bl_idname = "object.draw_rel_visual"; bl_label = "Force Refresh"; bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context): draw_rel_visual_core(context); return {'FINISHED'}

class OBJECT_OT_DetachVisual(bpy.types.Operator):
    bl_idname = "object.detach_visual"; bl_label = "Detach & Keep"; bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        col = bpy.data.collections.get(COLLECTION_NAME)
        if not col: return {'CANCELLED'}
        targets =[obj for obj in col.objects if obj.get(OBJECT_TAG)]
        if not targets: return {'CANCELLED'}
        
        scene_col = context.scene.collection
        timestamp = datetime.now().strftime('%H%M%S')
        
        for obj in targets:
            del obj[OBJECT_TAG]
            obj.name = f"{obj.name}_Baked_{timestamp}"
            
            # マルチマテリアル対応の独立化
            if obj.data.materials:
                new_mats =[m.copy() for m in obj.data.materials if m]
                for m in new_mats:
                    m.name = f"{m.name}_Baked_{timestamp}"
                obj.data.materials.clear()
                for m in new_mats:
                    obj.data.materials.append(m)

            if obj.name not in scene_col.objects: scene_col.objects.link(obj)
            col.objects.unlink(obj)
            obj.select_set(True)
            context.view_layer.objects.active = obj
            
        self.report({'INFO'}, f"Detached {len(targets)} object(s).")
        return {'FINISHED'}

class WM_OT_CopyFullScript(bpy.types.Operator):
    bl_idname = "wm.copy_full_script"; bl_label = "Copy Script with Current Values"
    def execute(self, context):
        p = context.scene.rel_visual
        M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
        texts =[t for t in bpy.data.texts if UNIQUE_SCRIPT_ID in t.as_string()]
        if not texts: return {'CANCELLED'}
        
        d_str = "CURRENT_DEFAULTS = {\n"
        for k in CURRENT_DEFAULTS.keys():
            val = getattr(p, k)
            if hasattr(val, "__len__") and not isinstance(val, str): 
                v_str = ", ".join([f"{v:.4f}" for v in val])
                d_str += f'    "{k}": ({v_str}),\n'
            elif isinstance(val, float): d_str += f'    "{k}": {val:.4f},\n'
            else: d_str += f'    "{k}": {val},\n'
        d_str += "}\n"
        
        code = texts[0].as_string()
        new_code = code.split(M_START)[0] + M_START + "\n" + d_str + M_END + code.split(M_END)[1]
        context.window_manager.clipboard = new_code
        self.report({'INFO'}, "Copied to clipboard!")
        return {'FINISHED'}

class WM_OT_RemoveAddon(bpy.types.Operator):
    bl_idname = "wm.remove_addon"
    bl_label = "Remove Addon"
    
    def execute(self, context):
        def cleanup_logic():
            if __name__ == "__main__":
                unregister()
                print(f"[{bl_info['name']}] Unregistered from Script Mode.")
            else:
                import addon_utils
                module_name = __package__ if __package__ else __name__
                addon_utils.disable(module_name, default_set=True)
                print(f"[{bl_info['name']}] Disabled from Addon Mode.")
            
            for win in bpy.context.window_manager.windows:
                for area in win.screen.areas:
                    area.tag_redraw()

        bpy.app.timers.register(cleanup_logic, first_interval=0.1)
        self.report({'INFO'}, "Removing Addon UI...")
        return {'FINISHED'}

class WM_OT_OpenUrl(bpy.types.Operator):
    bl_idname = "wm.open_url"
    bl_label = "Open URL"
    url: bpy.props.StringProperty()
    
    def execute(self, context): 
        webbrowser.open(self.url)
        return {'FINISHED'}

# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class VIEW3D_PT_RelVisualMain(bpy.types.Panel):
    bl_label = "Relativity Time & Physics"
    bl_idname = "VIEW3D_PT_rel_visual_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout; p = context.scene.rel_visual
        
        # トップアクションボタン3種
        layout.operator("wm.copy_full_script", icon='COPY_ID', text="Copy Script with Values")
        
        row = layout.row()
        row.scale_y = 1.5
        row.operator("object.detach_visual", text="Detach & Keep Scene", icon='PINNED')
        
        layout.operator("object.draw_rel_visual", icon='FILE_REFRESH', text="Force Refresh")
        
        layout.separator()
        
        # パラメータ群
        box = layout.box()
        box.label(text="Time & Physics", icon='TIME')
        box.prop(p, "obs_time")
        box.prop(p, "velocity")
        box.prop(p, "radius")
        
        box = layout.box()
        box.label(text="Geometry details", icon='MESH_GRID')
        box.prop(p, "resolution")
        box.prop(p, "obs_size")
        box.prop(p, "ring_thick")
        box.prop(p, "ray_thick")

class VIEW3D_PT_RelVisualFuture(bpy.types.Panel):
    bl_label = "Future Light Cone (Advanced)"
    bl_idname = "VIEW3D_PT_rel_visual_future"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout; p = context.scene.rel_visual
        layout.prop(p, "show_future")
        if p.show_future:
            box = layout.box()
            box.prop(p, "color_future_phys")
            box.prop(p, "color_future_base")
            box.prop(p, "color_future_ray")

class VIEW3D_PT_RelVisualPast(bpy.types.Panel):
    bl_label = "Past Light Cone (Retarded)"
    bl_idname = "VIEW3D_PT_rel_visual_past"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout; p = context.scene.rel_visual
        layout.prop(p, "show_past")
        if p.show_past:
            box = layout.box()
            box.prop(p, "color_past_phys")
            box.prop(p, "color_past_base")
            box.prop(p, "color_past_ray")

class VIEW3D_PT_RelVisualLinks(bpy.types.Panel):
    bl_label = "Theory Links"
    bl_idname = "VIEW3D_PT_rel_visual_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("wm.open_url", text=l["label"])
            op.url = l["url"]

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

    def draw(self, context):
        layout = self.layout
        layout.operator("wm.remove_addon", icon='CANCEL', text="Remove Addon")

# ------------------------------------------------------------------------
# Register
# ------------------------------------------------------------------------
classes = (
    PG_RelativityVisual, 
    OBJECT_OT_DrawRelVisual, 
    OBJECT_OT_DetachVisual, 
    WM_OT_CopyFullScript,
    WM_OT_RemoveAddon,
    WM_OT_OpenUrl,
    VIEW3D_PT_RelVisualMain, 
    VIEW3D_PT_RelVisualFuture, 
    VIEW3D_PT_RelVisualPast, 
    VIEW3D_PT_RelVisualLinks,
    VIEW3D_PT_RelVisualSystem
)

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

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

if __name__ == "__main__": register()