https://gemini.google.com/share/4b26fce1374c
# 2026-02-21 Session Template: YZ Plane 12 Rays (V4 - Prominent Execute Button)
# Blender 4.3+ Exclusive
UNIQUE_SCRIPT_ID = "YZ_RAYS_TEMPLATE_2026_02_21_V4"
SCRIPT_VERSION = 4
bl_info = {
"name": "YZ Plane 12 Rays Generator",
"author": "zionadchat Gemini",
"version": (1, 4),
"blender": (4, 3, 0),
"location": "View3D > Sidebar",
"description": "Visualize Light Cannot Double Spend in YZ plane (x=0).",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
from datetime import datetime
# ==============================================================================
# DYNAMIC DEFAULTS (Saved State)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_rays": True,
"ray_color": (0.8000, 0.8000, 0.1000, 1.0000),
"speed_v": 0.1000,
"time_t": 10.0000,
"ray_thickness": 0.0500,
}
# <END_DICT>
# ==============================================================================
# SYSTEM DEFAULTS (Factory Reset)
# ==============================================================================
SYSTEM_DEFAULTS = {
"show_rays": True,
"speed_v": 0.1,
"time_t": 10.0,
"ray_thickness": 0.05,
}
TAB_NAME = "YZ_Rays"
COLLECTION_NAME = "YZ_Rays_Output"
OBJECT_TAG = "yz_rays_tag"
ADDON_LINKS = (
{"label": "Theory Background: Light Cannot Double Spend", "url": "<https://www.notion.so/>"},
{"label": "Blender Simulation Guide", "url": "<https://www.notion.so/>"},
)
# ------------------------------------------------------------------------
# Material Setup
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
mat_name = f"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'
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf = nodes.get("Principled BSDF")
if not bsdf:
nodes.clear()
bsdf = nodes.new("ShaderNodeBsdfPrincipled")
output = nodes.new("ShaderNodeOutputMaterial")
output.location = (300, 0)
links.new(bsdf.outputs[0], output.inputs[0])
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
# ------------------------------------------------------------------------
# Core Drawing Logic
# ------------------------------------------------------------------------
def draw_rays_core(context):
if not context or not context.scene: return
p = context.scene.yz_rays_props
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)
objects_to_remove = [obj for obj in col.objects if obj.get(OBJECT_TAG)]
for obj in objects_to_remove:
mesh_data = obj.data
bpy.data.objects.remove(obj, do_unlink=True)
if mesh_data and mesh_data.users == 0:
bpy.data.meshes.remove(mesh_data)
if not p.show_rays:
return
mesh = bpy.data.meshes.new("YZ_Rays")
obj = bpy.data.objects.new("YZ_Rays", mesh)
obj[OBJECT_TAG] = True
col.objects.link(obj)
bm = bmesh.new()
try:
c = 1.0
c_dist = c * p.time_t
v_dist = p.speed_v * p.time_t
if c_dist >= v_dist:
R = math.sqrt(c_dist**2 - v_dist**2)
else:
R = 0.0
if R > 0.001:
num_rays = 12
for i in range(num_rays):
angle_deg = i * 30
angle_rad = math.radians(angle_deg)
shaft_len = R * 0.90
head_len = R * 0.10
thickness = p.ray_thickness
shaft = bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=12, radius1=thickness, radius2=thickness, depth=shaft_len)
bmesh.ops.translate(bm, verts=shaft['verts'], vec=Vector((0, 0, shaft_len/2)))
head = bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=12, radius1=thickness*2, radius2=0.0, depth=head_len)
bmesh.ops.translate(bm, verts=head['verts'], vec=Vector((0, 0, shaft_len + head_len/2)))
rot_mat = Matrix.Rotation(angle_rad, 4, 'X')
bmesh.ops.rotate(bm, verts=shaft['verts'] + head['verts'], cent=(0,0,0), matrix=rot_mat)
bm.to_mesh(mesh)
except Exception as e:
print(f"BMesh Error: {e}")
finally:
bm.free()
obj.data.materials.clear()
obj.data.materials.append(get_fixed_material("YZ_Rays", p.ray_color))
# ------------------------------------------------------------------------
# Update Throttling (Debounce)
# ------------------------------------------------------------------------
_update_timer = None
def delayed_update_func():
global _update_timer
_update_timer = None
try:
if bpy.context and bpy.context.scene:
draw_rays_core(bpy.context)
except Exception as e:
print(f"Update Error: {e}")
return None
def update_view(self, context):
global _update_timer
if _update_timer:
try:
bpy.app.timers.unregister(_update_timer)
except ValueError:
pass
_update_timer = bpy.app.timers.register(delayed_update_func, first_interval=0.05)
# ------------------------------------------------------------------------
# Properties
# ------------------------------------------------------------------------
class PG_YZRaysProps(bpy.types.PropertyGroup):
show_rays: bpy.props.BoolProperty(name="Show Rays", default=CURRENT_DEFAULTS["show_rays"], update=update_view)
ray_color: bpy.props.FloatVectorProperty(name="Ray Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["ray_color"], update=update_view)
speed_v: bpy.props.FloatProperty(name="Train Speed Ratio (v/c)", default=CURRENT_DEFAULTS["speed_v"], min=0.0, max=1.0, update=update_view)
time_t: bpy.props.FloatProperty(name="Time (t)", default=CURRENT_DEFAULTS["time_t"], min=0.01, update=update_view)
ray_thickness: bpy.props.FloatProperty(name="Ray Thickness", default=CURRENT_DEFAULTS["ray_thickness"], min=0.01, max=1.0, update=update_view)
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OBJECT_OT_DrawYZRays(bpy.types.Operator):
bl_idname = "object.draw_yz_rays"
bl_label = "描画実行"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
draw_rays_core(context)
self.report({'INFO'}, "YZ Plane Rays Drawing Executed.")
return {'FINISHED'}
class OBJECT_OT_DetachRays(bpy.types.Operator):
bl_idname = "object.detach_rays"; 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
count = 0
for obj in targets:
if OBJECT_TAG in obj: del obj[OBJECT_TAG]
timestamp = datetime.now().strftime('%H%M%S')
obj.name = f"YZ_Rays_Baked_{timestamp}"
if obj.data.materials:
new_mat = obj.data.materials[0].copy()
new_mat.name = f"Mat_Baked_{timestamp}"
obj.data.materials.clear()
obj.data.materials.append(new_mat)
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
count += 1
return {'FINISHED'}
class WM_OT_ResetRaysDefaults(bpy.types.Operator):
bl_idname = "wm.reset_rays_defaults"; bl_label = "Reset Defaults"
def execute(self, context):
p = context.scene.yz_rays_props
p.speed_v = SYSTEM_DEFAULTS["speed_v"]
p.time_t = SYSTEM_DEFAULTS["time_t"]
p.ray_thickness = SYSTEM_DEFAULTS["ray_thickness"]
return {'FINISHED'}
class WM_OT_RemoveYZAddon(bpy.types.Operator):
bl_idname = "wm.remove_yz_addon"; bl_label = "Remove Addon"
def execute(self, context):
def cleanup_logic():
try: unregister()
except: pass
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)
return {'FINISHED'}
class WM_OT_CopyRaysScript(bpy.types.Operator):
bl_idname = "wm.copy_rays_script"; bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.yz_rays_props
M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
target_text = next((t for t in bpy.data.texts if UNIQUE_SCRIPT_ID in t.as_string()), None)
if not target_text: return {'CANCELLED'}
code_str = target_text.as_string()
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__") and not isinstance(val, str): d_str += f' "{k}": ({", ".join([f"{v:.4f}" for v in val])}),\n'
elif isinstance(val, float): d_str += f' "{k}": {val:.4f},\n'
else: d_str += f' "{k}": {val},\n'
d_str += "}\n"
try:
new_code = code_str.split(M_START)[0] + M_START + "\n" + d_str + M_END + code_str.split(M_END)[1]
context.window_manager.clipboard = f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Session Template\n" + '\n'.join(new_code.split('\n')[1:])
self.report({'INFO'}, "Code copied with current values.")
except Exception as e: return {'CANCELLED'}
return {'FINISHED'}
class WM_OT_OpenRaysUrl(bpy.types.Operator):
bl_idname = "wm.open_rays_url"; bl_label = "Open URL"; url: bpy.props.StringProperty()
def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}
# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class VIEW3D_PT_YZRays(bpy.types.Panel):
bl_label = "YZ Plane Rays (x=0)"; bl_idname = "VIEW3D_PT_yz_rays"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME
def draw(self, context):
layout = self.layout; p = context.scene.yz_rays_props
# 最上段に「描画実行ボタン」を配置!
draw_row = layout.row()
draw_row.scale_y = 1.8
draw_row.operator("object.draw_yz_rays", text="描画実行 (Execute)", icon='PLAY')
layout.separator()
row = layout.row()
row.operator("wm.copy_rays_script", text="Copy Code with Values", icon='COPY_ID')
layout.separator()
layout.prop(p, "show_rays", text="Show Rays")
if p.show_rays:
box = layout.box()
box.prop(p, "ray_color")
box.prop(p, "ray_thickness")
layout.separator()
phys_box = layout.box()
phys_box.label(text="Physics Setup (c=1.0 Fixed)", icon='PHYSICS')
phys_box.prop(p, "speed_v")
phys_box.prop(p, "time_t")
c = 1.0
c_d = c * p.time_t
v_d = p.speed_v * p.time_t
current_R = math.sqrt(c_d**2 - v_d**2) if c_d >= v_d else 0.0
phys_box.label(text=f"→ YZ Plane Radius (R): {current_R:.4f}", icon='OUT_OF_NODE')
sub = box.row()
sub.scale_y = 1.2
sub.operator("wm.reset_rays_defaults", text="Reset Defaults", icon='LOOP_BACK')
layout.separator()
row = layout.row()
row.scale_y = 1.2
row.operator("object.detach_rays", text="Detach & Keep", icon='PINNED')
class VIEW3D_PT_YZLinks(bpy.types.Panel):
bl_label = "Theory Links"; bl_idname = "VIEW3D_PT_yz_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_rays_url", text=l["label"]); op.url = l["url"]
class VIEW3D_PT_YZSystem(bpy.types.Panel):
bl_label = "System"; bl_idname = "VIEW3D_PT_yz_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("wm.remove_yz_addon", icon='CANCEL', text="Remove Addon")
# ------------------------------------------------------------------------
# Registration (Safe Reload Logic)
# ------------------------------------------------------------------------
classes = (
PG_YZRaysProps, OBJECT_OT_DrawYZRays, OBJECT_OT_DetachRays,
WM_OT_ResetRaysDefaults, WM_OT_CopyRaysScript, WM_OT_OpenRaysUrl, WM_OT_RemoveYZAddon,
VIEW3D_PT_YZRays, VIEW3D_PT_YZLinks, VIEW3D_PT_YZSystem
)
def register():
for cls in classes: bpy.utils.register_class(cls)
if not hasattr(bpy.types.Scene, "yz_rays_props"):
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=PG_YZRaysProps)
def unregister():
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__):
bpy.utils.unregister_class(cls)
if hasattr(bpy.types.Scene, "yz_rays_props"):
del bpy.types.Scene.yz_rays_props
if __name__ == "__main__":
# 強制的に古いクラスを削除してから登録し直す(テキストエディタ用)
try: unregister()
except Exception: pass
register()