I have rebuilt the script
to be strictly ASCII-only (no Japanese characters anywhere)
and made the Copy logic more robust to ensure it
captures the latest values without breaking.
I have corrected the script to include the missing Train Disk
and Railway Disk panels.
I have also improved the Export Script logic to ensure that
every single property—including the disk colors
and thicknesses—is perfectly updated in the output text block.
This version is 100% English and uses standard ASCII characters
to prevent any syntax errors.
平面矢印 (Planar Rays): 放射状の太さ、色、透明度
円錐矢印 (Conical Rays): 放射状の太さ、色、透明度
列車円盤 (Train Disk): 板の厚み(奥行き)、色、透明度
線路円盤 (Railway Disk): 板の厚み(奥行き)、色、透明度
共通: アドオン削除、コピー、理論リンクの全機能
context.window_manager.clipboard は
BlenderのクリップボードAPIで、
長いテキストが途中で切れる既知の制限があります。
代わりに、Blender内のTextブロックに書き出す方式に変更します。
コピーボタン押下時に YZ_RAYS_EXPORT という名前の
Textブロックを作成・上書きし、
ユーザーはそこから全文をコピーできるようにします。
# 2026-02-22 Session Template
# Blender 5.0+ | V46 - Unified Colors & Fixed Value Export
UNIQUE_ID = "YZ_RAYS_V46_STABLE"
bl_info = {
"name": "YZ Plane 12 Rays Generator (V46)",
"author": "zionadchat Gemini + Claude",
"version": (1, 46),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "Shaft & arrowhead use same color. 100% accurate export.",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
# ==============================================================================
# FIXED RATIO
# ==============================================================================
HEAD_RATIO = 2.5
# ==============================================================================
# DICTIONARY DATA - Source of Truth
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"speed_v": 0.6000,
"time_t": 10.0000,
"p_show": True,
"p_color": (1.0000, 0.9000, 0.0000),
"p_thick": 0.0500,
"p_alpha": 1.0000,
"p_head_alpha": 1.0000,
"s_show": True,
"s_color": (1.0000, 0.3000, 0.0000),
"s_thick": 0.0400,
"s_alpha": 0.8000,
"s_head_alpha": 0.8000,
"show_tr_disk": True,
"tr_disk_col": (0.1000, 0.4000, 0.8000),
"tr_disk_alpha": 0.2000,
"tr_disk_thick": 0.0100,
"show_rw_disk": True,
"rw_disk_col": (0.1000, 0.8000, 0.5000),
"rw_disk_alpha": 0.1500,
"rw_disk_thick": 0.0100,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COL_NAME = "YZ_Rays_Output"
# ==============================================================================
# CORE FUNCTIONS
# ==============================================================================
def get_physics_mat(name, color, alpha):
m_name = f"Mat_{name}_V46"
mat = bpy.data.materials.get(m_name) or bpy.data.materials.new(name=m_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"): mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial')
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Alpha"].default_value = alpha
bsdf.inputs["Emission Color"].default_value = (*color, 1.0)
bsdf.inputs["Emission Strength"].default_value = 1.5
mat.node_tree.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
return mat
def build_arrow_dual(bm_s, bm_h, start, end, s_thick, h_thick):
direction = end - start
dist = direction.length
if dist < 0.01: return
s_h, h_h = dist * 0.82, dist * 0.18
rot = Vector((0, 0, 1)).rotation_difference(direction.normalized()).to_matrix().to_4x4()
# Shaft
res_s = bmesh.ops.create_cone(bm_s, cap_ends=True, segments=12, radius1=s_thick, radius2=s_thick, depth=s_h)
bmesh.ops.translate(bm_s, verts=res_s['verts'], vec=Vector((0, 0, s_h/2)))
bmesh.ops.rotate(bm_s, verts=res_s['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_s, verts=res_s['verts'], vec=start)
# Head
res_h = bmesh.ops.create_cone(bm_h, cap_ends=True, segments=12, radius1=h_thick, radius2=0.0, depth=h_h)
bmesh.ops.translate(bm_h, verts=res_h['verts'], vec=Vector((0, 0, s_h + h_h/2)))
bmesh.ops.rotate(bm_h, verts=res_h['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_h, verts=res_h['verts'], vec=start)
def draw_physics_scene(context):
p = context.scene.yz_rays_props
col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(COL_NAME)
if col.name not in context.scene.collection.children: context.scene.collection.children.link(col)
def get_obj(name, tag):
o = next((obj for obj in col.objects if obj.get(tag)), None)
if o: o.data.clear_geometry(); return o, o.data
m = bpy.data.meshes.new(name); o = bpy.data.objects.new(name, m); o[tag] = True
col.objects.link(o); return o, m
t, v = p.time_t, p.speed_v
r_c = math.sqrt(max(0.0, t**2 - (v*t)**2))
e_pt, c_pt = Vector((-v*t, 0, 0)), Vector((0, 0, 0))
# 1. Planar (Unified Color)
o_ps, m_ps = get_obj("R_P_Shaft", "t_ps"); o_ph, m_ph = get_obj("R_P_Head", "t_ph")
o_ps.hide_viewport = o_ph.hide_viewport = not p.p_show
bm_s, bm_h = bmesh.new(), bmesh.new()
for i in range(12):
ang = math.radians(i*30)
tgt = Vector((0, r_c * math.cos(ang), r_c * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, c_pt, tgt, p.p_thick, p.p_thick * HEAD_RATIO)
bm_s.to_mesh(m_ps); bm_h.to_mesh(m_ph); bm_s.free(); bm_h.free()
o_ps.data.materials.clear(); o_ps.data.materials.append(get_physics_mat("P_S", p.p_color, p.p_alpha))
o_ph.data.materials.clear(); o_ph.data.materials.append(get_physics_mat("P_H", p.p_color, p.p_head_alpha))
# 2. Conical (Unified Color)
o_ss, m_ss = get_obj("R_S_Shaft", "t_ss"); o_sh, m_sh = get_obj("R_S_Head", "t_sh")
o_ss.hide_viewport = o_sh.hide_viewport = not p.s_show
bm_s, bm_h = bmesh.new(), bmesh.new()
for i in range(12):
ang = math.radians(i*30)
tgt = Vector((0, r_c * math.cos(ang), r_c * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, e_pt, tgt, p.s_thick, p.s_thick * HEAD_RATIO)
bm_s.to_mesh(m_ss); bm_h.to_mesh(m_sh); bm_s.free(); bm_h.free()
o_ss.data.materials.clear(); o_ss.data.materials.append(get_physics_mat("S_S", p.s_color, p.s_alpha))
o_sh.data.materials.clear(); o_sh.data.materials.append(get_physics_mat("S_H", p.s_color, p.s_head_alpha))
# 3. Disks
for d in [("Disk_Tr", "t_tr", r_c, c_pt, p.show_tr_disk, p.tr_disk_col, p.tr_disk_alpha, p.tr_disk_thick),
("Disk_Rw", "t_rw", t, e_pt, p.show_rw_disk, p.rw_disk_col, p.rw_disk_alpha, p.rw_disk_thick)]:
o, m = get_obj(d[0], d[1]); o.hide_viewport = not d[4]
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64, radius1=d[2], radius2=d[2], depth=max(0.002, d[7]))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0,0,0), matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bmesh.ops.translate(bm, verts=bm.verts, vec=d[3])
bm.to_mesh(m); bm.free()
o.data.materials.clear(); o.data.materials.append(get_physics_mat(d[0], d[5], d[6]))
# ==============================================================================
# PROPERTIES & OPERATORS
# ==============================================================================
_timer = None
def update_call(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(lambda: draw_physics_scene(bpy.context) and None, first_interval=0.05)
class YZ_Props(bpy.types.PropertyGroup):
speed_v: bpy.props.FloatProperty(name="Velocity (v/c)", default=0.6, min=0.0, max=0.99, update=update_call)
time_t: bpy.props.FloatProperty(name="Time (t)", default=10.0, min=0.1, update=update_call)
# Planar
p_show: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
p_color: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', default=(1, 0.9, 0), update=update_call)
p_thick: bpy.props.FloatProperty(name="Thickness", default=0.05, min=0.002, update=update_call)
p_alpha: bpy.props.FloatProperty(name="Shaft Alpha", default=1.0, min=0, max=1, update=update_call)
p_head_alpha: bpy.props.FloatProperty(name="Head Alpha", default=1.0, min=0, max=1, update=update_call)
# Conical
s_show: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
s_color: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', default=(1, 0.3, 0), update=update_call)
s_thick: bpy.props.FloatProperty(name="Thickness", default=0.04, min=0.002, update=update_call)
s_alpha: bpy.props.FloatProperty(name="Shaft Alpha", default=0.8, min=0, max=1, update=update_call)
s_head_alpha: bpy.props.FloatProperty(name="Head Alpha", default=0.8, min=0, max=1, update=update_call)
# Train Disk
show_tr_disk: bpy.props.BoolProperty(name="Show Train Disk", default=True, update=update_call)
tr_disk_col: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', default=(0.1, 0.4, 0.8), update=update_call)
tr_disk_alpha: bpy.props.FloatProperty(name="Alpha", default=0.2, min=0, max=1, update=update_call)
tr_disk_thick: bpy.props.FloatProperty(name="Thickness", default=0.01, min=0.001, update=update_call)
# Railway Disk
show_rw_disk: bpy.props.BoolProperty(name="Show Railway Disk", default=True, update=update_call)
rw_disk_col: bpy.props.FloatVectorProperty(name="Color", subtype='COLOR', default=(0.1, 0.8, 0.5), update=update_call)
rw_disk_alpha: bpy.props.FloatProperty(name="Alpha", default=0.15, min=0, max=1, update=update_call)
rw_disk_thick: bpy.props.FloatProperty(name="Thickness", default=0.01, min=0.001, update=update_call)
class YZ_OT_Generate(bpy.types.Operator):
bl_idname = "yz.generate"; bl_label = "Generate Scene"
def execute(self, context): draw_physics_scene(context); return {'FINISHED'}
class YZ_OT_Copy(bpy.types.Operator):
bl_idname = "yz.copy_script"; bl_label = "Export to Text Block"
def execute(self, context):
p = context.scene.yz_rays_props
M_S, M_E = "# <BEGIN_DICT>", "# <END_DICT>"
src = next((t for t in bpy.data.texts if UNIQUE_ID in t.as_string()), None)
if not src:
self.report({'WARNING'}, "Source script block not found.")
return {'CANCELLED'}
# Build fresh dictionary from current UI values
lines = ["CURRENT_DEFAULTS = {"]
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if hasattr(val, "__len__"):
val_str = f'({", ".join([f"{x:.4f}" for x in val])})'
elif isinstance(val, bool):
val_str = str(val)
else:
val_str = f"{val:.4f}"
lines.append(f' "{k}": {val_str},')
lines.append("}")
code = src.as_string()
if M_S not in code or M_E not in code:
self.report({'ERROR'}, "Markers missing in script.")
return {'CANCELLED'}
res = code.split(M_S)[0] + M_S + "\n" + "\n".join(lines) + "\n" + M_E + code.split(M_E)[1]
out = bpy.data.texts.get("YZ_RAYS_EXPORT") or bpy.data.texts.new("YZ_RAYS_EXPORT")
out.clear(); out.write(res)
self.report({'INFO'}, "Latest values exported to 'YZ_RAYS_EXPORT'")
return {'FINISHED'}
class YZ_OT_Reset(bpy.types.Operator):
bl_idname = "yz.reset"; bl_label = "Reset Defaults"
def execute(self, context):
p = context.scene.yz_rays_props
for k, v in CURRENT_DEFAULTS.items(): setattr(p, k, v)
return {'FINISHED'}
class YZ_OT_Url(bpy.types.Operator):
bl_idname = "wm.url_op"; bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}
class YZ_OT_Remove(bpy.types.Operator):
bl_idname = "wm.yz_remove"; bl_label = "Remove Addon"
def execute(self, context): unregister(); return {'FINISHED'}
# ==============================================================================
# UI PANELS
# ==============================================================================
class YZ_PT_Main(bpy.types.Panel):
bl_label = "YZ Rays - Global Control"; 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
col = layout.column(align=True)
col.scale_y = 1.3; col.operator("yz.generate", icon='PLAY')
row = layout.row(align=True); row.operator("yz.reset", icon='LOOP_BACK'); row.operator("yz.copy_script", icon='TEXT')
layout.separator(); layout.prop(p, "speed_v"); layout.prop(p, "time_t")
class YZ_PT_Planar(bpy.types.Panel):
bl_label = "Planar Rays (Yellow)"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context): self.layout.prop(context.scene.yz_rays_props, "p_show", text="")
def draw(self, context):
layout = self.layout; p = context.scene.yz_rays_props; layout.enabled = p.p_show
box = layout.box(); box.prop(p, "p_thick"); box.prop(p, "p_color");
row = box.row(); row.prop(p, "p_alpha", slider=True); row.prop(p, "p_head_alpha", slider=True)
class YZ_PT_Conical(bpy.types.Panel):
bl_label = "Conical Rays (Red)"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context): self.layout.prop(context.scene.yz_rays_props, "s_show", text="")
def draw(self, context):
layout = self.layout; p = context.scene.yz_rays_props; layout.enabled = p.s_show
box = layout.box(); box.prop(p, "s_thick"); box.prop(p, "s_color");
row = box.row(); row.prop(p, "s_alpha", slider=True); row.prop(p, "s_head_alpha", slider=True)
class YZ_PT_Disks(bpy.types.Panel):
bl_label = "Disks (Train & Railway)"; 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.yz_rays_props
b1 = layout.box(); b1.prop(p, "show_tr_disk", text="Train Disk (x=0)")
if p.show_tr_disk:
b1.prop(p, "tr_disk_col"); b1.prop(p, "tr_disk_thick"); b1.prop(p, "tr_disk_alpha", slider=True)
b2 = layout.box(); b2.prop(p, "show_rw_disk", text="Railway Disk (x=-vt)")
if p.show_rw_disk:
b2.prop(p, "rw_disk_col"); b2.prop(p, "rw_disk_thick"); b2.prop(p, "rw_disk_alpha", slider=True)
class YZ_PT_System(bpy.types.Panel):
bl_label = "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.url_op", text="Lorentz Theory Community", icon='COMMUNITY').url = "<https://reddit.com/r/LightRayStandard>"
layout.operator("wm.yz_remove", icon='CANCEL', text="Uninstall Addon")
# ==============================================================================
# REGISTRATION
# ==============================================================================
classes = (YZ_Props, YZ_OT_Generate, YZ_OT_Copy, YZ_OT_Reset, YZ_OT_Url, YZ_OT_Remove,
YZ_PT_Main, YZ_PT_Planar, YZ_PT_Conical, YZ_PT_Disks, YZ_PT_System)
def register():
for c in classes: bpy.utils.register_class(c)
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=YZ_Props)
def unregister():
for c in reversed(classes): bpy.utils.unregister_class(c)
if hasattr(bpy.types.Scene, "yz_rays_props"): del bpy.types.Scene.yz_rays_props
if __name__ == "__main__": register()
# 2026-02-22 Session Template
# Blender 5.0+ | V46 - Shaft & Head linked by fixed ratio (single Thickness slider)
UNIQUE_ID = "YZ_RAYS_V46_STABLE"
bl_info = {
"name": "YZ Plane 12 Rays Generator (V46)",
"author": "zionadchat Gemini + Claude",
"version": (1, 46),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "Shaft & arrowhead scale together. 1 property per line UI.",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
# ==============================================================================
# FIXED RATIO - head_radius = shaft_radius * HEAD_RATIO
# ==============================================================================
HEAD_RATIO = 2.5
# ==============================================================================
# DICTIONARY DATA
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"speed_v": 0.6000,
"time_t": 10.0000,
"p_show": True,
"p_color": (1.0000, 0.9000, 0.0000),
"p_thick": 0.0500,
"p_alpha": 1.0000,
"p_head_color": (1.0000, 0.9000, 0.0000),
"p_head_alpha": 1.0000,
"s_show": True,
"s_color": (1.0000, 0.3000, 0.0000),
"s_thick": 0.0400,
"s_alpha": 0.8000,
"s_head_color": (1.0000, 0.3000, 0.0000),
"s_head_alpha": 0.8000,
"show_tr_disk": True,
"tr_disk_col": (0.1000, 0.4000, 0.8000),
"tr_disk_alpha": 0.2000,
"tr_disk_thick": 0.0100,
"show_rw_disk": True,
"rw_disk_col": (0.1000, 0.8000, 0.5000),
"rw_disk_alpha": 0.1500,
"rw_disk_thick": 0.0100,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COL_NAME = "YZ_Rays_Output"
# ==============================================================================
# MATERIAL ENGINE
# ==============================================================================
def get_physics_mat(name, color, alpha):
m_name = f"Mat_{name}_V46"
mat = bpy.data.materials.get(m_name) or bpy.data.materials.new(name=m_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial')
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Alpha"].default_value = alpha
bsdf.inputs["Emission Color"].default_value = (*color, 1.0)
bsdf.inputs["Emission Strength"].default_value = 1.5
mat.node_tree.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
return mat
# ==============================================================================
# GEOMETRY
# ==============================================================================
def build_arrow_dual(bm_shaft, bm_head, start, end, shaft_thick, head_thick):
direction = end - start
dist = direction.length
if dist < 0.01: return
s_h = dist * 0.82
h_h = dist * 0.18
rot = Vector((0, 0, 1)).rotation_difference(direction.normalized()).to_matrix().to_4x4()
res_s = bmesh.ops.create_cone(bm_shaft, cap_ends=True, segments=10, radius1=shaft_thick, radius2=shaft_thick, depth=s_h)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=Vector((0, 0, s_h / 2)))
bmesh.ops.rotate(bm_shaft, verts=res_s['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=start)
res_h = bmesh.ops.create_cone(bm_head, cap_ends=True, segments=10, radius1=head_thick, radius2=0.0, depth=h_h)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=Vector((0, 0, s_h + h_h / 2)))
bmesh.ops.rotate(bm_head, verts=res_h['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=start)
def draw_physics_scene(context):
p = context.scene.yz_rays_props
col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(COL_NAME)
if col.name not in context.scene.collection.children: context.scene.collection.children.link(col)
def get_obj(name, tag):
o = next((obj for obj in col.objects if obj.get(tag)), None)
if o: o.data.clear_geometry(); return o, o.data
m = bpy.data.meshes.new(name)
o = bpy.data.objects.new(name, m); o[tag] = True
col.objects.link(o); return o, m
t, v = p.time_t, p.speed_v
r_c = math.sqrt(max(0.0, t ** 2 - (v * t) ** 2))
e_pt, c_pt = Vector((-v * t, 0, 0)), Vector((0, 0, 0))
# 1. Planar
o_ps, m_ps = get_obj("R_Planar_Shaft", "t_ps"); o_ph, m_ph = get_obj("R_Planar_Head", "t_ph")
o_ps.hide_viewport = o_ph.hide_viewport = not p.p_show
bm_s, bm_h = bmesh.new(), bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_c * math.cos(ang), r_c * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, c_pt, tgt, p.p_thick, p.p_thick * HEAD_RATIO)
bm_s.to_mesh(m_ps); bm_h.to_mesh(m_ph); bm_s.free(); bm_h.free()
o_ps.data.materials.clear(); o_ps.data.materials.append(get_physics_mat("P_S", p.p_color, p.p_alpha))
o_ph.data.materials.clear(); o_ph.data.materials.append(get_physics_mat("P_H", p.p_head_color, p.p_head_alpha))
# 2. Conical
o_ss, m_ss = get_obj("R_Conical_Shaft", "t_ss"); o_sh, m_sh = get_obj("R_Conical_Head", "t_sh")
o_ss.hide_viewport = o_sh.hide_viewport = not p.s_show
bm_s, bm_h = bmesh.new(), bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_c * math.cos(ang), r_c * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, e_pt, tgt, p.s_thick, p.s_thick * HEAD_RATIO)
bm_s.to_mesh(m_ss); bm_h.to_mesh(m_sh); bm_s.free(); bm_h.free()
o_ss.data.materials.clear(); o_ss.data.materials.append(get_physics_mat("S_S", p.s_color, p.s_alpha))
o_sh.data.materials.clear(); o_sh.data.materials.append(get_physics_mat("S_H", p.s_head_color, p.s_head_alpha))
# 3. Disks
for d in [("Disk_Tr", "t_tr", r_c, Vector((0,0,0)), p.show_tr_disk, p.tr_disk_col, p.tr_disk_alpha, p.tr_disk_thick),
("Disk_Rw", "t_rw", t, e_pt, p.show_rw_disk, p.rw_disk_col, p.rw_disk_alpha, p.rw_disk_thick)]:
o, m = get_obj(d[0], d[1]); o.hide_viewport = not d[4]
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64, radius1=d[2], radius2=d[2], depth=max(0.002, d[7]))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0,0,0), matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bmesh.ops.translate(bm, verts=bm.verts, vec=d[3])
bm.to_mesh(m); bm.free()
o.data.materials.clear(); o.data.materials.append(get_physics_mat(d[0], d[5], d[6]))
# ==============================================================================
# PROPERTIES & OPS
# ==============================================================================
_timer = None
def update_call(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(lambda: draw_physics_scene(bpy.context) and None, first_interval=0.05)
class YZ_Props(bpy.types.PropertyGroup):
speed_v: bpy.props.FloatProperty(name="Velocity (v/c)", default=0.6, min=0.0, max=0.99, update=update_call)
time_t: bpy.props.FloatProperty(name="Time (t)", default=10.0, min=0.1, update=update_call)
p_show: bpy.props.BoolProperty(name="Show Planar", default=True, update=update_call)
p_color: bpy.props.FloatVectorProperty(name="P Color", subtype='COLOR', default=(1, 0.9, 0), update=update_call)
p_thick: bpy.props.FloatProperty(name="P Thick", default=0.05, min=0.002, update=update_call)
p_alpha: bpy.props.FloatProperty(name="P Alpha", default=1.0, min=0, max=1, update=update_call)
p_head_color: bpy.props.FloatVectorProperty(name="P Head Color", subtype='COLOR', default=(1, 0.9, 0), update=update_call)
p_head_alpha: bpy.props.FloatProperty(name="P Head Alpha", default=1.0, min=0, max=1, update=update_call)
s_show: bpy.props.BoolProperty(name="Show Conical", default=True, update=update_call)
s_color: bpy.props.FloatVectorProperty(name="S Color", subtype='COLOR', default=(1, 0.3, 0), update=update_call)
s_thick: bpy.props.FloatProperty(name="S Thick", default=0.04, min=0.002, update=update_call)
s_alpha: bpy.props.FloatProperty(name="S Alpha", default=0.8, min=0, max=1, update=update_call)
s_head_color: bpy.props.FloatVectorProperty(name="S Head Color", subtype='COLOR', default=(1, 0.3, 0), update=update_call)
s_head_alpha: bpy.props.FloatProperty(name="S Head Alpha", default=0.8, min=0, max=1, update=update_call)
show_tr_disk: bpy.props.BoolProperty(name="Show Train Disk", default=True, update=update_call)
tr_disk_col: bpy.props.FloatVectorProperty(name="Tr Disk Color", subtype='COLOR', default=(0.1, 0.4, 0.8), update=update_call)
tr_disk_alpha: bpy.props.FloatProperty(name="Tr Alpha", default=0.2, update=update_call)
tr_disk_thick: bpy.props.FloatProperty(name="Tr Thick", default=0.01, update=update_call)
show_rw_disk: bpy.props.BoolProperty(name="Show Railway Disk", default=True, update=update_call)
rw_disk_col: bpy.props.FloatVectorProperty(name="Rw Disk Color", subtype='COLOR', default=(0.1, 0.8, 0.5), update=update_call)
rw_disk_alpha: bpy.props.FloatProperty(name="Rw Alpha", default=0.15, update=update_call)
rw_disk_thick: bpy.props.FloatProperty(name="Rw Thick", default=0.01, update=update_call)
class YZ_OT_Copy(bpy.types.Operator):
bl_idname = "yz.copy_script"; bl_label = "Export Script"
def execute(self, context):
p = context.scene.yz_rays_props
M_S, M_E = "# <BEGIN_DICT>", "# <END_DICT>"
src = next((t for t in bpy.data.texts if UNIQUE_ID in t.as_string()), None)
if not src: return {'CANCELLED'}
lines = ["CURRENT_DEFAULTS = {"]
for k in CURRENT_DEFAULTS.keys():
v = getattr(p, k)
if hasattr(v, "__len__"): lines.append(f' "{k}": ({", ".join([f"{x:.4f}" for x in v])}),')
elif isinstance(v, bool): lines.append(f' "{k}": {v},')
else: lines.append(f' "{k}": {v:.4f},')
lines.append("}")
code = src.as_string()
result = code.split(M_S)[0] + M_S + "\n" + "\n".join(lines) + "\n" + M_E + code.split(M_E)[1]
out = bpy.data.texts.get("YZ_RAYS_EXPORT") or bpy.data.texts.new("YZ_RAYS_EXPORT")
out.clear(); out.write(result)
self.report({'INFO'}, "Exported to 'YZ_RAYS_EXPORT' text block.")
return {'FINISHED'}
class YZ_OT_Reset(bpy.types.Operator):
bl_idname = "yz.reset"; bl_label = "Reset"
def execute(self, context):
p = context.scene.yz_rays_props
for k, v in CURRENT_DEFAULTS.items(): setattr(p, k, v)
return {'FINISHED'}
# ==============================================================================
# PANEL
# ==============================================================================
class YZ_PT_Main(bpy.types.Panel):
bl_label = "YZ Rays V46"; 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
row = layout.row(align=True)
row.operator("yz.reset", icon='LOOP_BACK'); row.operator("yz.copy_script", icon='TEXT')
layout.prop(p, "speed_v"); layout.prop(p, "time_t")
for label, show_prop, thick, col, alpha, h_col, h_alpha in [
("Planar (Yellow)", "p_show", "p_thick", "p_color", "p_alpha", "p_head_color", "p_head_alpha"),
("Conical (Red)", "s_show", "s_thick", "s_color", "s_alpha", "s_head_color", "s_head_alpha")]:
box = layout.box(); box.prop(p, show_prop, text=label)
if getattr(p, show_prop):
box.prop(p, thick); box.prop(p, col); box.prop(p, alpha, slider=True)
box.prop(p, h_col); box.prop(p, h_alpha, slider=True)
classes = (YZ_Props, YZ_OT_Copy, YZ_OT_Reset, YZ_PT_Main)
def register():
for c in classes: bpy.utils.register_class(c)
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=YZ_Props)
def unregister():
for c in reversed(classes): bpy.utils.unregister_class(c)
del bpy.types.Scene.yz_rays_props
if __name__ == "__main__": register()
blender Million 2026
# 2026-02-22 Session Template copy error
# Blender 5.0+ | V46 - Shaft & Head linked by fixed ratio (single Thickness slider)
UNIQUE_ID = "YZ_RAYS_V46_STABLE"
bl_info = {
"name": "YZ Plane 12 Rays Generator (V46)",
"author": "zionadchat Gemini + Claude",
"version": (1, 46),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "Shaft & arrowhead scale together. 1 property per line UI.",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
# ==============================================================================
# FIXED RATIO — head_radius = shaft_radius * HEAD_RATIO
# ==============================================================================
HEAD_RATIO = 2.5 # 矢先底面半径 ÷ シャフト半径
# ==============================================================================
# DICTIONARY DATA
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"speed_v": 0.6000,
"time_t": 10.0000,
# Planar Rays
"p_show": True,
"p_color": (1.0000, 0.9000, 0.0000),
"p_thick": 0.0500,
"p_alpha": 1.0000,
"p_head_color": (1.0000, 0.9000, 0.0000),
"p_head_alpha": 1.0000,
# Conical Rays
"s_show": True,
"s_color": (1.0000, 0.3000, 0.0000),
"s_thick": 0.0400,
"s_alpha": 0.8000,
"s_head_color": (1.0000, 0.3000, 0.0000),
"s_head_alpha": 0.8000,
# Train Disk
"show_tr_disk": True,
"tr_disk_col": (0.1000, 0.4000, 0.8000),
"tr_disk_alpha": 0.2000,
"tr_disk_thick": 0.0100,
# Railway Disk
"show_rw_disk": True,
"rw_disk_col": (0.1000, 0.8000, 0.5000),
"rw_disk_alpha": 0.1500,
"rw_disk_thick": 0.0100,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COL_NAME = "YZ_Rays_Output"
# ==============================================================================
# MATERIAL ENGINE
# ==============================================================================
def get_physics_mat(name, color, alpha):
m_name = f"Mat_{name}_V46"
mat = bpy.data.materials.get(m_name) or bpy.data.materials.new(name=m_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial')
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Alpha"].default_value = alpha
bsdf.inputs["Emission Color"].default_value = (*color, 1.0)
bsdf.inputs["Emission Strength"].default_value = 1.5
mat.node_tree.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
return mat
# ==============================================================================
# GEOMETRY
# head_thick は呼び出し側で shaft_thick * HEAD_RATIO として渡す
# ==============================================================================
def build_arrow_dual(bm_shaft, bm_head, start, end, shaft_thick, head_thick):
"""
shaft_thick : シャフト(棒)の半径
head_thick : 矢先コーンの底面半径 = shaft_thick * HEAD_RATIO
"""
direction = end - start
dist = direction.length
if dist < 0.01:
return
s_h = dist * 0.82 # シャフト長
h_h = dist * 0.18 # 矢先長
rot = Vector((0, 0, 1)).rotation_difference(direction.normalized()).to_matrix().to_4x4()
# ── Shaft ──────────────────────────────────
res_s = bmesh.ops.create_cone(
bm_shaft, cap_ends=True, segments=10,
radius1=shaft_thick, radius2=shaft_thick, depth=s_h)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=Vector((0, 0, s_h / 2)))
bmesh.ops.rotate(bm_shaft, verts=res_s['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=start)
# ── Arrow Head ─────────────────────────────
res_h = bmesh.ops.create_cone(
bm_head, cap_ends=True, segments=10,
radius1=head_thick, radius2=0.0, depth=h_h)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=Vector((0, 0, s_h + h_h / 2)))
bmesh.ops.rotate(bm_head, verts=res_h['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=start)
def draw_physics_scene(context):
p = context.scene.yz_rays_props
col = bpy.data.collections.get(COL_NAME)
if not col:
col = bpy.data.collections.new(COL_NAME)
if col.name not in context.scene.collection.children:
context.scene.collection.children.link(col)
def get_obj(name, tag):
o = next((obj for obj in col.objects if obj.get(tag)), None)
if o:
o.data.clear_geometry()
return o, o.data
m = bpy.data.meshes.new(name)
o = bpy.data.objects.new(name, m)
o[tag] = True
col.objects.link(o)
return o, m
t = p.time_t
v = p.speed_v
r_contracted = math.sqrt(max(0.0, t ** 2 - (v * t) ** 2))
r_static = t
emission_pt = Vector((-v * t, 0, 0))
current_pt = Vector((0, 0, 0))
# ── 1. Planar Rays ────────────────────────────────────────────────
p_head_r = p.p_thick * HEAD_RATIO # 矢先半径を自動計算
o_ps, m_ps = get_obj("Rays_Planar_Shaft", "tag_p_shaft")
o_ph, m_ph = get_obj("Rays_Planar_Head", "tag_p_head")
o_ps.hide_viewport = not p.p_show
o_ph.hide_viewport = not p.p_show
bm_s = bmesh.new(); bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, current_pt, tgt, p.p_thick, p_head_r)
bm_s.to_mesh(m_ps); bm_s.free()
bm_h.to_mesh(m_ph); bm_h.free()
o_ps.data.materials.clear()
o_ps.data.materials.append(get_physics_mat("P_Shaft", p.p_color, p.p_alpha))
o_ph.data.materials.clear()
o_ph.data.materials.append(get_physics_mat("P_Head", p.p_head_color, p.p_head_alpha))
# ── 2. Conical Rays ───────────────────────────────────────────────
s_head_r = p.s_thick * HEAD_RATIO # 矢先半径を自動計算
o_ss, m_ss = get_obj("Rays_Conical_Shaft", "tag_s_shaft")
o_sh, m_sh = get_obj("Rays_Conical_Head", "tag_s_head")
o_ss.hide_viewport = not p.s_show
o_sh.hide_viewport = not p.s_show
bm_s = bmesh.new(); bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, emission_pt, tgt, p.s_thick, s_head_r)
bm_s.to_mesh(m_ss); bm_s.free()
bm_h.to_mesh(m_sh); bm_h.free()
o_ss.data.materials.clear()
o_ss.data.materials.append(get_physics_mat("S_Shaft", p.s_color, p.s_alpha))
o_sh.data.materials.clear()
o_sh.data.materials.append(get_physics_mat("S_Head", p.s_head_color, p.s_head_alpha))
# ── 3. Train Disk ─────────────────────────────────────────────────
o_tr, m_tr = get_obj("Disk_Train_x0", "tag_tr")
o_tr.hide_viewport = not p.show_tr_disk
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64,
radius1=r_contracted, radius2=r_contracted,
depth=max(0.002, p.tr_disk_thick))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bm.to_mesh(m_tr); bm.free()
o_tr.data.materials.clear()
o_tr.data.materials.append(get_physics_mat("DiskTr", p.tr_disk_col, p.tr_disk_alpha))
# ── 4. Railway Disk ───────────────────────────────────────────────
o_rw, m_rw = get_obj("Disk_Railway_xVT", "tag_rw")
o_rw.hide_viewport = not p.show_rw_disk
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64,
radius1=r_static, radius2=r_static,
depth=max(0.002, p.rw_disk_thick))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bmesh.ops.translate(bm, verts=bm.verts, vec=emission_pt)
bm.to_mesh(m_rw); bm.free()
o_rw.data.materials.clear()
o_rw.data.materials.append(get_physics_mat("DiskRw", p.rw_disk_col, p.rw_disk_alpha))
# ==============================================================================
# DEBOUNCE
# ==============================================================================
_timer = None
def update_call(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except Exception: pass
_timer = bpy.app.timers.register(
lambda: draw_physics_scene(bpy.context) and None,
first_interval=0.05)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class YZ_Props(bpy.types.PropertyGroup):
# Global
speed_v: bpy.props.FloatProperty(
name="Velocity (v/c)", default=0.6, min=0.0, max=0.99, update=update_call)
time_t: bpy.props.FloatProperty(
name="Time (t)", default=10.0, min=0.1, update=update_call)
# Planar Rays
p_show: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
p_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_thick: bpy.props.FloatProperty(
name="Thickness",
description=f"Shaft radius. Head radius = Thickness × {HEAD_RATIO}",
default=0.05, min=0.002, max=1.0, update=update_call)
p_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
p_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
# Conical Rays
s_show: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
s_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_thick: bpy.props.FloatProperty(
name="Thickness",
description=f"Shaft radius. Head radius = Thickness × {HEAD_RATIO}",
default=0.04, min=0.002, max=1.0, update=update_call)
s_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
s_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
# Train Disk
show_tr_disk: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
tr_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.4, 0.8), min=0.0, max=1.0, update=update_call)
tr_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.2, update=update_call)
tr_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=2.0, update=update_call)
# Railway Disk
show_rw_disk: bpy.props.BoolProperty(name="Show", default=True, update=update_call)
rw_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.8, 0.5), min=0.0, max=1.0, update=update_call)
rw_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.15, update=update_call)
rw_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=2.0, update=update_call)
# ==============================================================================
# OPERATORS
# ==============================================================================
class YZ_OT_Generate(bpy.types.Operator):
bl_idname = "yz.generate"; bl_label = "Generate Scene"
def execute(self, context):
draw_physics_scene(context); return {'FINISHED'}
class YZ_OT_Reset(bpy.types.Operator):
bl_idname = "yz.reset_defaults"; bl_label = "Reset Defaults"
def execute(self, context):
p = context.scene.yz_rays_props
for k, val in CURRENT_DEFAULTS.items():
if hasattr(p, k): setattr(p, k, val)
draw_physics_scene(context); return {'FINISHED'}
class YZ_OT_Copy(bpy.types.Operator):
bl_idname = "yz.copy_script"; bl_label = "Copy Script"
def execute(self, context):
p = context.scene.yz_rays_props
M_S, M_E = "# <BEGIN_DICT>", "# <END_DICT>"
txt = next((t for t in bpy.data.texts if UNIQUE_ID in t.as_string()), None)
if not txt:
self.report({'WARNING'}, "Script text block not found."); return {'CANCELLED'}
d_str = "CURRENT_DEFAULTS = {\n"
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if hasattr(val, "__len__"):
d_str += f' "{k}": ({", ".join([f"{x:.4f}" for x in val])}),\n'
elif isinstance(val, bool):
d_str += f' "{k}": {val},\n'
else:
d_str += f' "{k}": {val:.4f},\n'
d_str += "}\n"
code = txt.as_string()
result = code.split(M_S)[0] + M_S + "\n" + d_str + M_E + code.split(M_E)[1]
context.window_manager.clipboard = result
self.report({'INFO'}, "Script copied!"); return {'FINISHED'}
class YZ_OT_Url(bpy.types.Operator):
bl_idname = "wm.url_op"; bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}
class YZ_OT_Remove(bpy.types.Operator):
bl_idname = "wm.yz_remove"; bl_label = "Remove Addon"
def execute(self, context): unregister(); return {'FINISHED'}
# ==============================================================================
# PANELS — 1 property per line
# ==============================================================================
class YZ_PT_Global(bpy.types.Panel):
bl_label = "Global Physics"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 0
def draw(self, context):
layout = self.layout
p = context.scene.yz_rays_props
row = layout.row(align=True)
row.scale_y = 1.25
row.operator("yz.generate", icon='PLAY', text="Generate")
row.operator("yz.reset_defaults", icon='LOOP_BACK', text="Reset")
row.operator("yz.copy_script", icon='COPY_ID', text="Copy")
layout.separator()
layout.prop(p, "speed_v")
layout.prop(p, "time_t")
# ── Planar Rays ──────────────────────────────────────────────────────────────
class YZ_PT_Planar(bpy.types.Panel):
bl_label = "Planar Rays (Yellow)"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 1
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
self.layout.prop(context.scene.yz_rays_props, "p_show", text="")
def draw(self, context):
layout = self.layout
p = context.scene.yz_rays_props
layout.enabled = p.p_show
# ── Shaft ──────────────────────────────
box = layout.box()
box.label(text="Shaft / 棒", icon='CURVE_PATH')
box.prop(p, "p_color")
box.prop(p, "p_thick") # ← 1つのスライダーで棒+矢先が連動
box.prop(p, "p_alpha", slider=True)
# ── Arrow Head ─────────────────────────
box = layout.box()
box.label(text="Arrow Head / 矢先 ( × 2.5 自動 )", icon='CONE_DATA')
box.prop(p, "p_head_color")
box.prop(p, "p_head_alpha", slider=True)
# 現在の矢先サイズを読み取り専用で表示
info = box.row()
info.enabled = False
info.label(text=f"Head radius = {p.p_thick * HEAD_RATIO:.4f}")
# ── Conical Rays ─────────────────────────────────────────────────────────────
class YZ_PT_Conical(bpy.types.Panel):
bl_label = "Conical Rays (Red)"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
self.layout.prop(context.scene.yz_rays_props, "s_show", text="")
def draw(self, context):
layout = self.layout
p = context.scene.yz_rays_props
layout.enabled = p.s_show
box = layout.box()
box.label(text="Shaft / 棒", icon='CURVE_PATH')
box.prop(p, "s_color")
box.prop(p, "s_thick") # ← 1つのスライダーで棒+矢先が連動
box.prop(p, "s_alpha", slider=True)
box = layout.box()
box.label(text="Arrow Head / 矢先 ( × 2.5 自動 )", icon='CONE_DATA')
box.prop(p, "s_head_color")
box.prop(p, "s_head_alpha", slider=True)
info = box.row()
info.enabled = False
info.label(text=f"Head radius = {p.s_thick * HEAD_RATIO:.4f}")
# ── Train Disk ───────────────────────────────────────────────────────────────
class YZ_PT_TrainDisk(bpy.types.Panel):
bl_label = "Train Disk (x = 0)"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 3
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
self.layout.prop(context.scene.yz_rays_props, "show_tr_disk", text="")
def draw(self, context):
layout = self.layout
p = context.scene.yz_rays_props
layout.enabled = p.show_tr_disk
layout.prop(p, "tr_disk_col")
layout.prop(p, "tr_disk_thick")
layout.prop(p, "tr_disk_alpha", slider=True)
# ── Railway Disk ─────────────────────────────────────────────────────────────
class YZ_PT_RailwayDisk(bpy.types.Panel):
bl_label = "Railway Disk (x = -vt)"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 4
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
self.layout.prop(context.scene.yz_rays_props, "show_rw_disk", text="")
def draw(self, context):
layout = self.layout
p = context.scene.yz_rays_props
layout.enabled = p.show_rw_disk
layout.prop(p, "rw_disk_col")
layout.prop(p, "rw_disk_thick")
layout.prop(p, "rw_disk_alpha", slider=True)
# ── System ───────────────────────────────────────────────────────────────────
class YZ_PT_System(bpy.types.Panel):
bl_label = "System"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'
bl_category = TAB_NAME; bl_order = 5
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.operator("wm.yz_remove", icon='CANCEL', text="Remove Addon")
layout.operator("wm.url_op", text="Lorentz Theory", icon='WORLD').url = "<https://www.notion.so/>"
# ==============================================================================
# REGISTRATION
# ==============================================================================
classes = (
YZ_Props,
YZ_OT_Generate,
YZ_OT_Reset,
YZ_OT_Copy,
YZ_OT_Url,
YZ_OT_Remove,
YZ_PT_Global,
YZ_PT_Planar,
YZ_PT_Conical,
YZ_PT_TrainDisk,
YZ_PT_RailwayDisk,
YZ_PT_System,
)
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=YZ_Props)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
if hasattr(bpy.types.Scene, "yz_rays_props"):
del bpy.types.Scene.yz_rays_props
if __name__ == "__main__":
register()
# 2026-02-22 Session Template
# Blender 5.0+ | V45 - One Property Per Line UI
UNIQUE_ID = "YZ_RAYS_V45_STABLE"
bl_info = {
"name": "YZ Plane 12 Rays Generator (V45)",
"author": "zionadchat Gemini + Claude",
"version": (1, 45),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "1 property per line. Full color/thickness/alpha for all elements.",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
# ==============================================================================
# DICTIONARY DATA
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"speed_v": 0.0000,
"time_t": 10.0000,
"p_show": True,
"p_color": (1.0000, 0.9000, 0.0000),
"p_thick": 0.0500,
"p_alpha": 1.0000,
"p_head_color": (1.0000, 0.9000, 0.0000),
"p_head_thick": 0.1250,
"p_head_alpha": 1.0000,
"s_show": True,
"s_color": (1.0000, 0.3000, 0.0000),
"s_thick": 0.0320,
"s_alpha": 0.8000,
"s_head_color": (1.0000, 0.3000, 0.0000),
"s_head_thick": 0.1000,
"s_head_alpha": 0.8000,
"show_tr_disk": True,
"tr_disk_col": (0.1000, 0.4000, 0.8000),
"tr_disk_alpha": 0.2000,
"tr_disk_thick": 0.0100,
"show_rw_disk": True,
"rw_disk_col": (0.1000, 0.8000, 0.5000),
"rw_disk_alpha": 0.0423,
"rw_disk_thick": 0.0010,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COL_NAME = "YZ_Rays_Output"
# ==============================================================================
# MATERIAL ENGINE
# ==============================================================================
def get_physics_mat(name, color, alpha):
m_name = f"Mat_{name}_V45"
mat = bpy.data.materials.get(m_name) or bpy.data.materials.new(name=m_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial')
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Alpha"].default_value = alpha
bsdf.inputs["Emission Color"].default_value = (*color, 1.0)
bsdf.inputs["Emission Strength"].default_value = 1.5
mat.node_tree.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
return mat
# ==============================================================================
# GEOMETRY
# ==============================================================================
def build_arrow_dual(bm_shaft, bm_head, start, end, shaft_thick, head_thick):
direction = end - start
dist = direction.length
if dist < 0.01:
return
s_h = dist * 0.82
h_h = dist * 0.18
rot = Vector((0, 0, 1)).rotation_difference(direction.normalized()).to_matrix().to_4x4()
res_s = bmesh.ops.create_cone(
bm_shaft, cap_ends=True, segments=10,
radius1=shaft_thick, radius2=shaft_thick, depth=s_h)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=Vector((0, 0, s_h / 2)))
bmesh.ops.rotate(bm_shaft, verts=res_s['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=start)
res_h = bmesh.ops.create_cone(
bm_head, cap_ends=True, segments=10,
radius1=head_thick, radius2=0.0, depth=h_h)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=Vector((0, 0, s_h + h_h / 2)))
bmesh.ops.rotate(bm_head, verts=res_h['verts'], cent=(0, 0, 0), matrix=rot)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=start)
def draw_physics_scene(context):
p = context.scene.yz_rays_props
col = bpy.data.collections.get(COL_NAME)
if not col:
col = bpy.data.collections.new(COL_NAME)
if col.name not in context.scene.collection.children:
context.scene.collection.children.link(col)
def get_obj(name, tag):
o = next((obj for obj in col.objects if obj.get(tag)), None)
if o:
o.data.clear_geometry()
return o, o.data
m = bpy.data.meshes.new(name)
o = bpy.data.objects.new(name, m)
o[tag] = True
col.objects.link(o)
return o, m
t = p.time_t
v = p.speed_v
r_contracted = math.sqrt(max(0.0, t ** 2 - (v * t) ** 2))
r_static = t
emission_pt = Vector((-v * t, 0, 0))
current_pt = Vector((0, 0, 0))
# 1. Planar Rays
o_ps, m_ps = get_obj("Rays_Planar_Shaft", "tag_p_shaft")
o_ph, m_ph = get_obj("Rays_Planar_Head", "tag_p_head")
o_ps.hide_viewport = not p.p_show
o_ph.hide_viewport = not p.p_show
bm_s = bmesh.new(); bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, current_pt, tgt, p.p_thick, p.p_head_thick)
bm_s.to_mesh(m_ps); bm_s.free()
bm_h.to_mesh(m_ph); bm_h.free()
o_ps.data.materials.clear()
o_ps.data.materials.append(get_physics_mat("P_Shaft", p.p_color, p.p_alpha))
o_ph.data.materials.clear()
o_ph.data.materials.append(get_physics_mat("P_Head", p.p_head_color, p.p_head_alpha))
# 2. Conical Rays
o_ss, m_ss = get_obj("Rays_Conical_Shaft", "tag_s_shaft")
o_sh, m_sh = get_obj("Rays_Conical_Head", "tag_s_head")
o_ss.hide_viewport = not p.s_show
o_sh.hide_viewport = not p.s_show
bm_s = bmesh.new(); bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
tgt = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, emission_pt, tgt, p.s_thick, p.s_head_thick)
bm_s.to_mesh(m_ss); bm_s.free()
bm_h.to_mesh(m_sh); bm_h.free()
o_ss.data.materials.clear()
o_ss.data.materials.append(get_physics_mat("S_Shaft", p.s_color, p.s_alpha))
o_sh.data.materials.clear()
o_sh.data.materials.append(get_physics_mat("S_Head", p.s_head_color, p.s_head_alpha))
# 3. Train Disk
o_tr, m_tr = get_obj("Disk_Train_x0", "tag_tr")
o_tr.hide_viewport = not p.show_tr_disk
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64,
radius1=r_contracted, radius2=r_contracted,
depth=max(0.002, p.tr_disk_thick))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bm.to_mesh(m_tr); bm.free()
o_tr.data.materials.clear()
o_tr.data.materials.append(get_physics_mat("DiskTr", p.tr_disk_col, p.tr_disk_alpha))
# 4. Railway Disk
o_rw, m_rw = get_obj("Disk_Railway_xVT", "tag_rw")
o_rw.hide_viewport = not p.show_rw_disk
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=64,
radius1=r_static, radius2=r_static,
depth=max(0.002, p.rw_disk_thick))
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bmesh.ops.translate(bm, verts=bm.verts, vec=emission_pt)
bm.to_mesh(m_rw); bm.free()
o_rw.data.materials.clear()
o_rw.data.materials.append(get_physics_mat("DiskRw", p.rw_disk_col, p.rw_disk_alpha))
# ==============================================================================
# DEBOUNCE
# ==============================================================================
_timer = None
def update_call(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except Exception: pass
_timer = bpy.app.timers.register(
lambda: draw_physics_scene(bpy.context) and None,
first_interval=0.05)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class YZ_Props(bpy.types.PropertyGroup):
speed_v: bpy.props.FloatProperty(
name="Velocity (v/c)", default=0.6, min=0.0, max=0.99, update=update_call)
time_t: bpy.props.FloatProperty(
name="Time (t)", default=10.0, min=0.1, update=update_call)
# Planar
p_show: bpy.props.BoolProperty(
name="Show", default=True, update=update_call)
p_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_thick: bpy.props.FloatProperty(
name="Shaft Thickness", default=0.05, min=0.002, max=1.0, update=update_call)
p_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
p_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_head_thick: bpy.props.FloatProperty(
name="Head Size", default=0.125, min=0.005, max=2.0, update=update_call)
p_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
# Conical
s_show: bpy.props.BoolProperty(
name="Show", default=True, update=update_call)
s_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_thick: bpy.props.FloatProperty(
name="Shaft Thickness", default=0.04, min=0.002, max=1.0, update=update_call)
s_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
s_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_head_thick: bpy.props.FloatProperty(
name="Head Size", default=0.1, min=0.005, max=2.0, update=update_call)
s_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
# Train Disk
show_tr_disk: bpy.props.BoolProperty(
name="Show", default=True, update=update_call)
tr_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.4, 0.8), min=0.0, max=1.0, update=update_call)
tr_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.2, update=update_call)
tr_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=2.0, update=update_call)
# Railway Disk
show_rw_disk: bpy.props.BoolProperty(
name="Show", default=True, update=update_call)
rw_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.8, 0.5), min=0.0, max=1.0, update=update_call)
rw_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.15, update=update_call)
rw_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=2.0, update=update_call)
# ==============================================================================
# OPERATORS
# ==============================================================================
class YZ_OT_Generate(bpy.types.Operator):
bl_idname = "yz.generate"; bl_label = "Generate Scene"
def execute(self, context):
draw_physics_scene(context); return {'FINISHED'}
class YZ_OT_Reset(bpy.types.Operator):
bl_idname = "yz.reset_defaults"; bl_label = "Reset Defaults"
def execute(self, context):
p = context.scene.yz_rays_props
for k, val in CURRENT_DEFAULTS.items():
if hasattr(p, k): setattr(p, k, val)
draw_physics_scene(context); return {'FINISHED'}
class YZ_OT_Copy(bpy.types.Operator):
bl_idname = "yz.copy_script"; bl_label = "Copy Script"
def execute(self, context):
p = context.scene.yz_rays_props
M_S, M_E = "# <BEGIN_DICT>", "
平面矢印 (Planar Rays): 放射状の太さ、色、透明度
円錐矢印 (Conical Rays): 放射状の太さ、色、透明度
列車円盤 (Train Disk): 板の厚み(奥行き)、色、透明度
線路円盤 (Railway Disk): 板の厚み(奥行き)、色、透明度
共通: アドオン削除、コピー、理論リンクの全機能
# 2026-02-22 Session Template
# Blender 5.0+ | V44 - Full Individual Control for ALL Elements
UNIQUE_ID = "YZ_RAYS_V44_STABLE"
bl_info = {
"name": "YZ Plane 12 Rays Generator (V44)",
"author": "zionadchat Gemini + Claude",
"version": (1, 44),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "Full color/thickness/alpha controls for ALL elements: 2 Disks, 2 Ray types, Arrow heads.",
"category": "Object",
}
import bpy
import bmesh
import math
import webbrowser
from mathutils import Vector, Matrix
from datetime import datetime
# ==============================================================================
# DICTIONARY DATA
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
# Global
"speed_v": 0.6000,
"time_t": 10.0000,
# Planar Rays (Yellow)
"p_show": True,
"p_color": (1.0000, 0.9000, 0.0000),
"p_thick": 0.0500,
"p_alpha": 1.0000,
"p_head_thick": 0.1250,
"p_head_alpha": 1.0000,
"p_head_color": (1.0000, 0.9000, 0.0000),
# Conical Rays (Red)
"s_show": True,
"s_color": (1.0000, 0.3000, 0.0000),
"s_thick": 0.0400,
"s_alpha": 0.8000,
"s_head_thick": 0.1000,
"s_head_alpha": 0.8000,
"s_head_color": (1.0000, 0.3000, 0.0000),
# Train Disk
"show_tr_disk": True,
"tr_disk_col": (0.1000, 0.4000, 0.8000),
"tr_disk_alpha": 0.2000,
"tr_disk_thick": 0.0100,
# Railway Disk
"show_rw_disk": True,
"rw_disk_col": (0.1000, 0.8000, 0.5000),
"rw_disk_alpha": 0.1500,
"rw_disk_thick": 0.0100,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COL_NAME = "YZ_Rays_Output"
# ------------------------------------------------------------------------
# Material Engine
# ------------------------------------------------------------------------
def get_physics_mat(name, color, alpha):
m_name = f"Mat_{name}_V44"
mat = bpy.data.materials.get(m_name) or bpy.data.materials.new(name=m_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial')
p_bsdf = nodes.new('ShaderNodeBsdfPrincipled')
p_bsdf.inputs["Base Color"].default_value = (*color, 1.0)
p_bsdf.inputs["Alpha"].default_value = alpha
p_bsdf.inputs["Emission Color"].default_value = (*color, 1.0)
p_bsdf.inputs["Emission Strength"].default_value = 1.5
mat.node_tree.links.new(p_bsdf.outputs["BSDF"], out.inputs["Surface"])
return mat
# ------------------------------------------------------------------------
# Geometry Helper - shaft and head with separate material slots
# ------------------------------------------------------------------------
def build_arrow_dual(
bm_shaft, bm_head,
start, end,
shaft_thick, head_thick
):
"""Build shaft and arrowhead into separate bmeshes for separate material control."""
direction = end - start
dist = direction.length
if dist < 0.01:
return
s_h = dist * 0.82
h_h = dist * 0.18
# Shaft
res_s = bmesh.ops.create_cone(
bm_shaft, cap_ends=True, segments=10,
radius1=shaft_thick, radius2=shaft_thick, depth=s_h
)
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=Vector((0, 0, s_h / 2)))
rot = Vector((0, 0, 1)).rotation_difference(direction.normalized())
bmesh.ops.rotate(bm_shaft, verts=res_s['verts'], cent=(0, 0, 0), matrix=rot.to_matrix().to_4x4())
bmesh.ops.translate(bm_shaft, verts=res_s['verts'], vec=start)
# Head (cone)
res_h = bmesh.ops.create_cone(
bm_head, cap_ends=True, segments=10,
radius1=head_thick, radius2=0.0, depth=h_h
)
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=Vector((0, 0, s_h + h_h / 2)))
bmesh.ops.rotate(bm_head, verts=res_h['verts'], cent=(0, 0, 0), matrix=rot.to_matrix().to_4x4())
bmesh.ops.translate(bm_head, verts=res_h['verts'], vec=start)
def draw_physics_scene(context):
p = context.scene.yz_rays_props
# Ensure collection exists
col = bpy.data.collections.get(COL_NAME)
if not col:
col = bpy.data.collections.new(COL_NAME)
if col.name not in context.scene.collection.children:
context.scene.collection.children.link(col)
def get_obj(name, tag):
o = next((obj for obj in col.objects if obj.get(tag)), None)
if o:
o.data.clear_geometry()
return o, o.data
m = bpy.data.meshes.new(name)
o = bpy.data.objects.new(name, m)
o[tag] = True
col.objects.link(o)
return o, m
c = 1.0
t = p.time_t
v = p.speed_v
r_contracted = math.sqrt(max(0.0, (c * t) ** 2 - (v * t) ** 2))
r_static = c * t
emission_pt = Vector((-v * t, 0, 0))
current_pt = Vector((0, 0, 0))
# ──────────────────────────────────────────────────
# 1. PLANAR RAYS SHAFT + HEAD (separate objects)
# ──────────────────────────────────────────────────
o_ps, m_ps = get_obj("Rays_Planar_Shaft", "tag_p_shaft")
o_ph, m_ph = get_obj("Rays_Planar_Head", "tag_p_head")
o_ps.hide_viewport = not p.p_show
o_ph.hide_viewport = not p.p_show
bm_s = bmesh.new()
bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
target = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, current_pt, target, p.p_thick, p.p_head_thick)
bm_s.to_mesh(m_ps); bm_s.free()
bm_h.to_mesh(m_ph); bm_h.free()
o_ps.data.materials.clear()
o_ps.data.materials.append(get_physics_mat("P_Shaft", p.p_color, p.p_alpha))
o_ph.data.materials.clear()
o_ph.data.materials.append(get_physics_mat("P_Head", p.p_head_color, p.p_head_alpha))
# ──────────────────────────────────────────────────
# 2. CONICAL RAYS SHAFT + HEAD (separate objects)
# ──────────────────────────────────────────────────
o_ss, m_ss = get_obj("Rays_Conical_Shaft", "tag_s_shaft")
o_sh, m_sh = get_obj("Rays_Conical_Head", "tag_s_head")
o_ss.hide_viewport = not p.s_show
o_sh.hide_viewport = not p.s_show
bm_s = bmesh.new()
bm_h = bmesh.new()
for i in range(12):
ang = math.radians(i * 30)
target = Vector((0, r_contracted * math.cos(ang), r_contracted * math.sin(ang)))
build_arrow_dual(bm_s, bm_h, emission_pt, target, p.s_thick, p.s_head_thick)
bm_s.to_mesh(m_ss); bm_s.free()
bm_h.to_mesh(m_sh); bm_h.free()
o_ss.data.materials.clear()
o_ss.data.materials.append(get_physics_mat("S_Shaft", p.s_color, p.s_alpha))
o_sh.data.materials.clear()
o_sh.data.materials.append(get_physics_mat("S_Head", p.s_head_color, p.s_head_alpha))
# ──────────────────────────────────────────────────
# 3. TRAIN DISK (x=0)
# ──────────────────────────────────────────────────
o_tr, m_tr = get_obj("Disk_Train_x0", "tag_tr")
o_tr.hide_viewport = not p.show_tr_disk
bm = bmesh.new()
bmesh.ops.create_cone(
bm, cap_ends=True, segments=64,
radius1=r_contracted, radius2=r_contracted,
depth=max(0.002, p.tr_disk_thick)
)
bmesh.ops.rotate(
bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y')
)
bm.to_mesh(m_tr); bm.free()
o_tr.data.materials.clear()
o_tr.data.materials.append(get_physics_mat("DiskTr", p.tr_disk_col, p.tr_disk_alpha))
# ──────────────────────────────────────────────────
# 4. RAILWAY DISK (x=-vt)
# ──────────────────────────────────────────────────
o_rw, m_rw = get_obj("Disk_Railway_xVT", "tag_rw")
o_rw.hide_viewport = not p.show_rw_disk
bm = bmesh.new()
bmesh.ops.create_cone(
bm, cap_ends=True, segments=64,
radius1=r_static, radius2=r_static,
depth=max(0.002, p.rw_disk_thick)
)
bmesh.ops.rotate(
bm, verts=bm.verts, cent=(0, 0, 0),
matrix=Matrix.Rotation(math.radians(90), 4, 'Y')
)
bmesh.ops.translate(bm, verts=bm.verts, vec=emission_pt)
bm.to_mesh(m_rw); bm.free()
o_rw.data.materials.clear()
o_rw.data.materials.append(get_physics_mat("DiskRw", p.rw_disk_col, p.rw_disk_alpha))
# ------------------------------------------------------------------------
# Debounce Timer
# ------------------------------------------------------------------------
_timer = None
def update_call(self, context):
global _timer
if _timer:
try:
bpy.app.timers.unregister(_timer)
except Exception:
pass
_timer = bpy.app.timers.register(
lambda: draw_physics_scene(bpy.context) and None,
first_interval=0.05
)
# ------------------------------------------------------------------------
# UI Properties
# ------------------------------------------------------------------------
class YZ_Props(bpy.types.PropertyGroup):
# ── Global ──
speed_v: bpy.props.FloatProperty(
name="Velocity (v/c)", default=0.6, min=0.0, max=0.99, update=update_call)
time_t: bpy.props.FloatProperty(
name="Time (t)", default=10.0, min=0.1, update=update_call)
# ── Planar Rays (Yellow) ──
p_show: bpy.props.BoolProperty(name="Visible", default=True, update=update_call)
p_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_thick: bpy.props.FloatProperty(
name="Shaft Thickness", default=0.05, min=0.002, max=1.0, update=update_call)
p_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
p_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.9, 0.0), min=0.0, max=1.0, update=update_call)
p_head_thick: bpy.props.FloatProperty(
name="Head Size", default=0.125, min=0.005, max=2.0, update=update_call)
p_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=1.0, update=update_call)
# ── Conical Rays (Red) ──
s_show: bpy.props.BoolProperty(name="Visible", default=True, update=update_call)
s_color: bpy.props.FloatVectorProperty(
name="Shaft Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_thick: bpy.props.FloatProperty(
name="Shaft Thickness", default=0.04, min=0.002, max=1.0, update=update_call)
s_alpha: bpy.props.FloatProperty(
name="Shaft Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
s_head_color: bpy.props.FloatVectorProperty(
name="Head Color", subtype='COLOR',
default=(1.0, 0.3, 0.0), min=0.0, max=1.0, update=update_call)
s_head_thick: bpy.props.FloatProperty(
name="Head Size", default=0.1, min=0.005, max=2.0, update=update_call)
s_head_alpha: bpy.props.FloatProperty(
name="Head Alpha", min=0.0, max=1.0, default=0.8, update=update_call)
# ── Train Disk ──
show_tr_disk: bpy.props.BoolProperty(name="Visible", default=True, update=update_call)
tr_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.4, 0.8), min=0.0, max=1.0, update=update_call)
tr_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.2, update=update_call)
tr_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=1.0, update=update_call)
# ── Railway Disk ──
show_rw_disk: bpy.props.BoolProperty(name="Visible", default=True, update=update_call)
rw_disk_col: bpy.props.FloatVectorProperty(
name="Color", subtype='COLOR',
default=(0.1, 0.8, 0.5), min=0.0, max=1.0, update=update_call)
rw_disk_alpha: bpy.props.FloatProperty(
name="Alpha", min=0.0, max=1.0, default=0.15, update=update_call)
rw_disk_thick: bpy.props.FloatProperty(
name="Thickness", default=0.01, min=0.001, max=1.0, update=update_call)
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class YZ_OT_Generate(bpy.types.Operator):
bl_idname = "yz.generate"; bl_label = "Generate Scene"
def execute(self, context):
draw_physics_scene(context)
return {'FINISHED'}
class YZ_OT_Copy(bpy.types.Operator):
bl_idname = "yz.copy_script"; bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.yz_rays_props
M_S, M_E = "# <BEGIN_DICT>", "# <END_DICT>"
txt = next((t for t in bpy.data.texts if UNIQUE_ID in t.as_string()), None)
if not txt:
self.report({'WARNING'}, "Script text block not found in Blender.")
return {'CANCELLED'}
d_str = "CURRENT_DEFAULTS = {\n"
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if hasattr(val, "__len__"):
d_str += f' "{k}": ({", ".join([f"{x:.4f}" for x in val])}),\n'
elif isinstance(val, bool):
d_str += f' "{k}": {val},\n'
else:
d_str += f' "{k}": {val:.4f},\n'
d_str += "}\n"
code = txt.as_string()
result = code.split(M_S)[0] + M_S + "\n" + d_str + M_E + code.split(M_E)[1]
context.window_manager.clipboard = result
self.report({'INFO'}, "Script copied to clipboard!")
return {'FINISHED'}
class YZ_OT_Reset(bpy.types.Operator):
bl_idname = "yz.reset_defaults"; bl_label = "Reset All Defaults"
def execute(self, context):
p = context.scene.yz_rays_props
for k, v in CURRENT_DEFAULTS.items():
if hasattr(p, k):
setattr(p, k, v)
draw_physics_scene(context)
return {'FINISHED'}
class YZ_OT_Url(bpy.types.Operator):
bl_idname = "wm.url_op"; bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class YZ_OT_Remove(bpy.types.Operator):
bl_idname = "wm.yz_remove"; bl_label = "Remove Addon"
def execute(self, context):
unregister()
return {'FINISHED'}
# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class YZ_PT_Main(bpy.types.Panel):
bl_label = "YZ Rays (V44)"; 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
# Top buttons
row = layout.row(align=True)
row.operator("yz.generate", icon='PLAY', text="Generate")
row.operator("yz.reset_defaults", icon='LOOP_BACK', text="Reset")
row.operator("yz.copy_script", icon='COPY_ID', text="Copy")
# ── Global Physics ──
box = layout.box()
box.label(text="GLOBAL PHYSICS", icon='PHYSICS')
box.prop(p, "speed_v")
box.prop(p, "time_t")
# ── Planar Rays ──
box = layout.box()
row = box.row()
row.label(text="PLANAR RAYS (Yellow)", icon='PROP_OFF')
row.prop(p, "p_show", text="")
col = box.column()
col.enabled = p.p_show
# Shaft
col.label(text="── Shaft ──")
row = col.row(align=True)
row.prop(p, "p_color", text="")
row.prop(p, "p_thick", text="Thickness")
col.prop(p, "p_alpha", text="Shaft Alpha", slider=True)
# Head
col.label(text="── Arrow Head ──")
row = col.row(align=True)
row.prop(p, "p_head_color", text="")
row.prop(p, "p_head_thick", text="Head Size")
col.prop(p, "p_head_alpha", text="Head Alpha", slider=True)
# ── Conical Rays ──
box = layout.box()
row = box.row()
row.label(text="CONICAL RAYS (Red)", icon='CONE_DATA')
row.prop(p, "s_show", text="")
col = box.column()
col.enabled = p.s_show
col.label(text="── Shaft ──")
row = col.row(align=True)
row.prop(p, "s_color", text="")
row.prop(p, "s_thick", text="Thickness")
col.prop(p, "s_alpha", text="Shaft Alpha", slider=True)
col.label(text="── Arrow Head ──")
row = col.row(align=True)
row.prop(p, "s_head_color", text="")
row.prop(p, "s_head_thick", text="Head Size")
col.prop(p, "s_head_alpha", text="Head Alpha", slider=True)
# ── Train Disk ──
box = layout.box()
row = box.row()
row.label(text="TRAIN DISK x=0", icon='MESH_CIRCLE')
row.prop(p, "show_tr_disk", text="")
col = box.column()
col.enabled = p.show_tr_disk
row = col.row(align=True)
row.prop(p, "tr_disk_col", text="")
row.prop(p, "tr_disk_thick", text="Thickness")
col.prop(p, "tr_disk_alpha", text="Alpha", slider=True)
# ── Railway Disk ──
box = layout.box()
row = box.row()
row.label(text="RAILWAY DISK x=-vt", icon='MESH_CIRCLE')
row.prop(p, "show_rw_disk", text="")
col = box.column()
col.enabled = p.show_rw_disk
row = col.row(align=True)
row.prop(p, "rw_disk_col", text="")
row.prop(p, "rw_disk_thick", text="Thickness")
col.prop(p, "rw_disk_alpha", text="Alpha", slider=True)
class YZ_PT_System(bpy.types.Panel):
bl_label = "System Panel"; 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.yz_remove", icon='CANCEL', text="Remove Addon")
layout.operator("wm.url_op", text="Lorentz Theory", icon='WORLD').url = "<https://www.notion.so/>"
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
YZ_Props,
YZ_OT_Generate,
YZ_OT_Copy,
YZ_OT_Reset,
YZ_OT_Url,
YZ_OT_Remove,
YZ_PT_Main,
YZ_PT_System,
)
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=YZ_Props)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
if hasattr(bpy.types.Scene, "yz_rays_props"):
del bpy.types.Scene.yz_rays_props
if __name__ == "__main__":
register()