基本オブジェクト操作
ローレンツ氏の 光速 予算配分 進化版 20260319
列車慣性系 干渉計20260222

# Copied: 18:41:56
# Copied: 20260319 18:37:36
# Copied: 15:00:01
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector, Matrix
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
# ★ PREFIXに大文字が含まれていても、内部で自動的に小文字に変換して処理します
PREFIX = "YZRays20260318"
TAB_NAME = " [ YZ Rays ] "
# ★ このスクリプト自身のID (コピー機能で使用)
# ### ZIONAD_SOURCE_ID: YZ_RAYS_2026_03_18_FIXED ###
bl_info = {
"name": f"zionad 520 [ YZ Rays ] {PREFIX}",
"author": "zionadchat",
"version": (1, 30, 0),
"blender": (3, 0, 0),
"location": "3D View > Sidebar",
"description": "YZ Plane 12 Rays Generator with Face Solidify",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: YZ_RAYS_2026_03_18_FIXED ###"
ADDON_LINKS = (
{"label": "Theory Background", "url": "<https://www.notion.so/>"},
{"label": "Blender Guide", "url": "<https://www.notion.so/>"},
)
# ==============================================================================
# デフォルト値設定 (RGBA対応)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_circle": True,
"show_train_rays": True,
"show_cone_rays": True,
"ray_color": (0.8000, 0.8000, 0.1000, 1.0000),
"cone_color": (0.8000, 0.2000, 0.1000, 0.6000),
"circle_color": (0.2141, 0.0047, 0.5063, 0.2790),
"speed_v": 0.6000,
"time_t": 100.0000,
"circle_depth": 10.0000,
"circle_solid": 2.0000,
"ray_thickness": 1.0000,
"cone_thickness": 1.0000,
}
# <END_DICT>
# ==============================================================================
# マテリアル作成ロジック (Sphereと全く同じノード構築構造)
# ==============================================================================
def create_material(color, name_prefix="Mat"):
# 毎フレーム無限増殖しないように、PREFIXを使ってマテリアルを使い回します
mat_name = f"{name_prefix}_{PREFIX}"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
tree = mat.node_tree
tree.nodes.clear()
bsdf = tree.nodes.new("ShaderNodeBsdfPrincipled")
bsdf.location = (0, 0)
out = tree.nodes.new("ShaderNodeOutputMaterial")
out.location = (300, 0)
tree.links.new(bsdf.outputs[0], out.inputs[0])
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]
mat.diffuse_color = color
return mat
# ==============================================================================
# ジオメトリエンジン & 描画ロジック
# ==============================================================================
def create_arrow_bm(bm, start, end, thick):
vec = end - start
length = vec.length
if length < 0.001: return
s_len, h_len = length * 0.9, length * 0.1
s = bmesh.ops.create_cone(bm, cap_ends=True, segments=12, radius1=thick, radius2=thick, depth=s_len)
bmesh.ops.translate(bm, verts=s['verts'], vec=Vector((0, 0, s_len/2)))
h = bmesh.ops.create_cone(bm, cap_ends=True, segments=12, radius1=thick*2, radius2=0, depth=h_len)
bmesh.ops.translate(bm, verts=h['verts'], vec=Vector((0, 0, s_len + h_len/2)))
rot = Vector((0, 0, 1)).rotation_difference(vec.normalized())
bmesh.ops.rotate(bm, verts=list(s['verts']) + list(h['verts']), cent=(0,0,0), matrix=rot.to_matrix().to_4x4())
bmesh.ops.translate(bm, verts=list(s['verts']) + list(h['verts']), vec=start)
OUTPUT_COL_NAME = f"{PREFIX}_Output"
TAG_C, TAG_T, TAG_O = f"{PREFIX}_c", f"{PREFIX}_t", f"{PREFIX}_o"
def update_geometry(context):
p = getattr(context.scene, PROPS_NAME, None)
if not p: return
col = bpy.data.collections.get(OUTPUT_COL_NAME)
if not col:
col = bpy.data.collections.new(OUTPUT_COL_NAME)
context.scene.collection.children.link(col)
def sync_obj(name, tag):
obj = next((o for o in col.objects if o.get(tag)), None)
if obj:
obj.data.clear_geometry()
return obj, obj.data
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj[tag] = True
col.objects.link(obj)
return obj, mesh
c, t, v = 1.0, p.time_t, p.speed_v
R_circle = c * t
val = (c*t)**2 - (v*t)**2
R_rays = math.sqrt(max(0, val))
emission_origin = Vector((-v * t, 0, 0))
# 1. Circle
o_c, m_c = sync_obj(f"YZ_Circle_{PREFIX}", TAG_C)
o_c.hide_viewport = not p.show_circle
bm = bmesh.new()
res_c = bmesh.ops.create_cone(bm, cap_ends=False, segments=96, radius1=R_circle, radius2=R_circle, depth=p.circle_depth)
circle_faces = [f for f in res_c.get('geom', res_c.get('faces', [])) if isinstance(f, bmesh.types.BMFace)]
if not circle_faces: circle_faces = bm.faces[:]
if p.circle_solid > 0:
bmesh.ops.solidify(bm, geom=circle_faces, thickness=p.circle_solid)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0,0,0), matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bm.to_mesh(m_c)
bm.free()
o_c.data.materials.clear()
o_c.data.materials.append(create_material(p.circle_color, "Mat_Circle"))
# 2. Train Rays
o_t, m_t = sync_obj(f"YZ_Train_Rays_{PREFIX}", TAG_T)
o_t.hide_viewport = not p.show_train_rays
bm = bmesh.new()
if R_rays > 0.001:
for i in range(12):
ang = math.radians(i * 30)
tip = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, Vector((0,0,0)), tip, p.ray_thickness)
bm.to_mesh(m_t)
bm.free()
o_t.data.materials.clear()
o_t.data.materials.append(create_material(p.ray_color, "Mat_Train"))
# 3. Cone Rays
o_o, m_o = sync_obj(f"YZ_Cone_Rays_{PREFIX}", TAG_O)
o_o.hide_viewport = not p.show_cone_rays
bm = bmesh.new()
if R_rays > 0.001:
for i in range(12):
ang = math.radians(i * 30)
tip = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, emission_origin, tip, p.cone_thickness)
bm.to_mesh(m_o)
bm.free()
o_o.data.materials.clear()
o_o.data.materials.append(create_material(p.cone_color, "Mat_Cone"))
# ==============================================================================
# 自動更新用タイマー (Sphereの構造)
# ==============================================================================
_timer = None
def delayed_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
update_geometry(bpy.context)
return None
def on_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(delayed_update, first_interval=0.05)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class PG_YZProps(PropertyGroup):
show_circle: BoolProperty(name="Show Circle", default=CURRENT_DEFAULTS["show_circle"], update=on_update)
show_train_rays: BoolProperty(name="Show Train Rays", default=CURRENT_DEFAULTS["show_train_rays"], update=on_update)
show_cone_rays: BoolProperty(name="Show Cone Rays", default=CURRENT_DEFAULTS["show_cone_rays"], update=on_update)
# ★ Sphereと同じ RGBA (size=4) 仕様に変更しました
ray_color: FloatVectorProperty(name="Train Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS["ray_color"], update=on_update)
cone_color: FloatVectorProperty(name="Cone Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS["cone_color"], update=on_update)
circle_color: FloatVectorProperty(name="Circle Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS["circle_color"], update=on_update)
speed_v: FloatProperty(name="Velocity (v/c)", default=CURRENT_DEFAULTS["speed_v"], min=0.0, max=0.99, update=on_update)
time_t: FloatProperty(name="Time (t)", default=CURRENT_DEFAULTS["time_t"], min=0.1, update=on_update)
circle_depth: FloatProperty(name="Axial Width", default=CURRENT_DEFAULTS["circle_depth"], min=0.0, max=10.0, update=on_update)
circle_solid: FloatProperty(name="Face Solidify", default=CURRENT_DEFAULTS["circle_solid"], min=0.0, max=2.0, update=on_update)
ray_thickness: FloatProperty(name="Train Ray Thick", default=CURRENT_DEFAULTS["ray_thickness"], min=0.01, max=10.0, update=on_update)
cone_thickness: FloatProperty(name="Cone Ray Thick", default=CURRENT_DEFAULTS["cone_thickness"], min=0.01, max=10.0, update=on_update)
# ==============================================================================
# OPERATORS
# ==============================================================================
class OT_ExecuteDraw(Operator):
bl_idname = f"{OP_PREFIX}.execute_draw"
bl_label = "Force Execute Draw"
def execute(self, context):
update_geometry(context)
return {'FINISHED'}
class OT_CopyFullScript(Operator):
bl_idname = f"{OP_PREFIX}.copy_script"
bl_label = "Copy Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return {'CANCELLED'}
target_text = None
for t in bpy.data.texts:
if SOURCE_ID_TAG in t.as_string():
target_text = t; break
if not target_text:
self.report({'ERROR'}, "Script source not found.")
return {'CANCELLED'}
code = target_text.as_string()
# ★ RGBAの4要素を正しくコピーコードに書き込むよう修正
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_circle": {props.show_circle},\n'
new_dict += f' "show_train_rays": {props.show_train_rays},\n'
new_dict += f' "show_cone_rays": {props.show_cone_rays},\n'
rc, cc, circ = props.ray_color, props.cone_color, props.circle_color
new_dict += f' "ray_color": ({rc[0]:.4f}, {rc[1]:.4f}, {rc[2]:.4f}, {rc[3]:.4f}),\n'
new_dict += f' "cone_color": ({cc[0]:.4f}, {cc[1]:.4f}, {cc[2]:.4f}, {cc[3]:.4f}),\n'
new_dict += f' "circle_color": ({circ[0]:.4f}, {circ[1]:.4f}, {circ[2]:.4f}, {circ[3]:.4f}),\n'
new_dict += f' "speed_v": {props.speed_v:.4f},\n'
new_dict += f' "time_t": {props.time_t:.4f},\n'
new_dict += f' "circle_depth": {props.circle_depth:.4f},\n'
new_dict += f' "circle_solid": {props.circle_solid:.4f},\n'
new_dict += f' "ray_thickness": {props.ray_thickness:.4f},\n'
new_dict += f' "cone_thickness": {props.cone_thickness:.4f},\n'
new_dict += "}\n"
try:
start, end = "# <BEGIN" + "_DICT>", "# <END" + "_DICT>"
pre, post = code.split(start)[0], code.split(end)[1]
final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n" + pre + start + "\n" + new_dict + end + post
context.window_manager.clipboard = final
self.report({'INFO'}, "Code copied!")
except Exception as e:
self.report({'ERROR'}, f"Copy Failed: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class OT_Reset(Operator):
bl_idname = f"{OP_PREFIX}.reset"
bl_label = "Reset View"
def execute(self, context):
p = getattr(context.scene, PROPS_NAME)
p.speed_v = 0.6; p.time_t = 100.0
return {'FINISHED'}
class OT_OpenUrl(Operator):
bl_idname = f"{OP_PREFIX}.open_url"
bl_label = "Open URL"
url: StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class OT_RemoveAddon(Operator):
bl_idname = f"{OP_PREFIX}.remove_addon"
bl_label = "Remove Addon"
def execute(self, context):
bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
return {'FINISHED'}
# ==============================================================================
# PANELS
# ==============================================================================
class PT_MainPanel(Panel):
bl_label = "YZ Plane Rays (V30)"
bl_idname = f"{PREFIX}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
props = getattr(context.scene, PROPS_NAME, None)
if not props:
layout.label(text="Reload Script")
return
row = layout.row()
row.scale_y = 1.2
row.operator(OT_CopyFullScript.bl_idname, icon='COPY_ID', text="Copy Code with Values")
layout.separator()
col = layout.column()
col.scale_y = 1.2
col.operator(OT_ExecuteDraw.bl_idname, icon='PLAY', text="Force Execute Draw")
layout.separator()
box = layout.box()
box.prop(props, "show_circle")
if props.show_circle:
box.prop(props, "circle_color")
box.prop(props, "circle_depth")
box.prop(props, "circle_solid")
box = layout.box()
box.prop(props, "show_train_rays")
if props.show_train_rays:
box.prop(props, "ray_color")
box.prop(props, "ray_thickness")
box = layout.box()
box.prop(props, "show_cone_rays")
if props.show_cone_rays:
box.prop(props, "cone_color")
box.prop(props, "cone_thickness")
phys = layout.box()
phys.label(text="Physics Parameters", icon='PHYSICS')
phys.prop(props, "speed_v")
phys.prop(props, "time_t")
phys.operator(OT_Reset.bl_idname, icon='LOOP_BACK', text="Reset Params")
class PT_LinksPanel(Panel):
bl_label = "Theory Links"
bl_idname = f"{PREFIX}_PT_links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
for l in ADDON_LINKS:
self.layout.operator(OT_OpenUrl.bl_idname, text=l["label"], icon='WORLD').url = l["url"]
class PT_RemovePanel(Panel):
bl_label = "System"
bl_idname = f"{PREFIX}_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
self.layout.operator(OT_RemoveAddon.bl_idname, icon='CANCEL', text="Remove Addon")
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (
PG_YZProps,
OT_ExecuteDraw,
OT_CopyFullScript,
OT_Reset,
OT_OpenUrl,
OT_RemoveAddon,
PT_MainPanel,
PT_LinksPanel,
PT_RemovePanel
)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_YZProps))
def unregister():
if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
for c in reversed(classes): bpy.utils.unregister_class(c)
if __name__ == "__main__":
register()
# 2026-02-22 02:30:00 Session Template
# Blender 5.0+ Exclusive | International English | V30 - Circle Solidify
UNIQUE_SCRIPT_ID = "YZ_RAYS_STABLE_TEMPLATE_2026_02_22_V30"
SCRIPT_VERSION = 30
bl_info = {
"name": "YZ Plane 12 Rays Generator (V30)",
"author": "zionadchat Gemini",
"version": (1, 30),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > YZ_Rays",
"description": "Added Face Solidify (Thickness) to the circular wave front.",
"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_circle": True,
"show_train_rays": True,
"show_cone_rays": True,
"ray_color": (0.8000, 0.8000, 0.1000),
"cone_color": (0.8000, 0.2000, 0.1000),
"circle_color": (0.1000, 0.5000, 0.8000),
"speed_v": 0.5000,
"time_t": 10.0000,
"circle_depth": 0.1000,
"circle_solid": 0.0500,
"ray_thickness": 0.0500,
"cone_thickness": 0.0350,
}
# <END_DICT>
TAB_NAME = "YZ_Rays"
COLLECTION_NAME = "YZ_Rays_Output"
TAG_C, TAG_T, TAG_O = "tag_yz_c", "tag_yz_t", "tag_yz_o"
ADDON_LINKS = (
{"label": "Theory Background", "url": "<https://www.notion.so/>"},
{"label": "Blender Guide", "url": "<https://www.notion.so/>"},
)
# ------------------------------------------------------------------------
# Material Helper
# ------------------------------------------------------------------------
def get_mat(name, color_rgb, alpha=1.0):
mat_name = f"Mat_{name}_V30"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
if hasattr(mat, "blend_method"): mat.blend_method = 'BLEND'
if hasattr(mat, "eevee"): mat.eevee.shadow_method = 'NONE'
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF") or nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs["Base Color"].default_value = (*color_rgb, 1.0)
bsdf.inputs["Alpha"].default_value = alpha
bsdf.inputs["Emission Color"].default_value = (*color_rgb, 1.0)
bsdf.inputs["Emission Strength"].default_value = 1.0
return mat
# ------------------------------------------------------------------------
# Geometry Engine
# ------------------------------------------------------------------------
def create_arrow_bm(bm, start, end, thick):
vec = end - start
length = vec.length
if length < 0.001: return
s_len, h_len = length * 0.9, length * 0.1
s = bmesh.ops.create_cone(bm, cap_ends=True, segments=12, radius1=thick, radius2=thick, depth=s_len)
bmesh.ops.translate(bm, verts=s['verts'], vec=Vector((0, 0, s_len/2)))
h = bmesh.ops.create_cone(bm, cap_ends=True, segments=12, radius1=thick*2, radius2=0, depth=h_len)
bmesh.ops.translate(bm, verts=h['verts'], vec=Vector((0, 0, s_len + h_len/2)))
rot = Vector((0, 0, 1)).rotation_difference(vec.normalized())
bmesh.ops.rotate(bm, verts=list(s['verts']) + list(h['verts']), cent=(0,0,0), matrix=rot.to_matrix().to_4x4())
bmesh.ops.translate(bm, verts=list(s['verts']) + list(h['verts']), vec=start)
def draw_rays_core(context):
p = context.scene.yz_rays_props
col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
if col.name not in context.scene.collection.children: context.scene.collection.children.link(col)
def sync_obj(name, tag):
obj = next((o for o in col.objects if o.get(tag)), None)
if obj: obj.data.clear_geometry(); return obj, obj.data
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh); obj[tag] = True; col.objects.link(obj)
return obj, mesh
c, t, v = 1.0, p.time_t, p.speed_v
R_circle = c * t
R_rays = math.sqrt(max(0, (c*t)**2 - (v*t)**2))
emission_origin = Vector((-v * t, 0, 0))
# 1. Circle with Solidify (Tube effect)
o_c, m_c = sync_obj("YZ_Circle", TAG_C)
o_c.hide_viewport = not p.show_circle
bm = bmesh.new()
res_c = bmesh.ops.create_cone(bm, cap_ends=False, segments=96, radius1=R_circle, radius2=R_circle, depth=p.circle_depth)
# Apply Face Solidify
circle_faces = [f for f in res_c.get('geom', res_c.get('faces', [])) if isinstance(f, bmesh.types.BMFace)]
if not circle_faces: circle_faces = bm.faces[:]
if p.circle_solid > 0:
bmesh.ops.solidify(bm, geom=circle_faces, thickness=p.circle_solid)
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0,0,0), matrix=Matrix.Rotation(math.radians(90), 4, 'Y'))
bm.to_mesh(m_c); bm.free()
o_c.data.materials.clear(); o_c.data.materials.append(get_mat("Circle", p.circle_color, 0.5))
# 2. Train Rays
o_t, m_t = sync_obj("YZ_Train_Rays", TAG_T)
o_t.hide_viewport = not p.show_train_rays
bm = bmesh.new()
if R_rays > 0.001:
for i in range(12):
ang = math.radians(i * 30); tip = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, Vector((0,0,0)), tip, p.ray_thickness)
bm.to_mesh(m_t); bm.free()
o_t.data.materials.clear(); o_t.data.materials.append(get_mat("Train", p.ray_color, 1.0))
# 3. Cone Rays
o_o, m_o = sync_obj("YZ_Cone_Rays", TAG_O)
o_o.hide_viewport = not p.show_cone_rays
bm = bmesh.new()
if R_rays > 0.001:
for i in range(12):
ang = math.radians(i * 30); tip = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, emission_origin, tip, p.cone_thickness)
bm.to_mesh(m_o); bm.free()
o_o.data.materials.clear(); o_o.data.materials.append(get_mat("Cone", p.cone_color, 0.6))
# ------------------------------------------------------------------------
# UI & Logic
# ------------------------------------------------------------------------
_update_timer = 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(lambda: draw_rays_core(bpy.context), first_interval=0.05)
class PG_YZRaysProps(bpy.types.PropertyGroup):
show_circle: bpy.props.BoolProperty(name="Show Circle", default=True, update=update_view)
show_train_rays: bpy.props.BoolProperty(name="Show Train Rays", default=True, update=update_view)
show_cone_rays: bpy.props.BoolProperty(name="Show Cone Rays", default=True, update=update_view)
ray_color: bpy.props.FloatVectorProperty(name="Train Color", subtype='COLOR', size=3, default=CURRENT_DEFAULTS["ray_color"], update=update_view)
cone_color: bpy.props.FloatVectorProperty(name="Cone Color", subtype='COLOR', size=3, default=CURRENT_DEFAULTS["cone_color"], update=update_view)
circle_color: bpy.props.FloatVectorProperty(name="Circle Color", subtype='COLOR', size=3, default=CURRENT_DEFAULTS["circle_color"], update=update_view)
speed_v: bpy.props.FloatProperty(name="Velocity (v/c)", default=0.5, min=0.0, max=0.99, update=update_view)
time_t: bpy.props.FloatProperty(name="Time (t)", default=10.0, min=0.1, update=update_view)
circle_depth: bpy.props.FloatProperty(name="Axial Width (Depth)", default=0.1, min=0.0, max=10.0, update=update_view)
circle_solid: bpy.props.FloatProperty(name="Face Solidify (Thickness)", default=0.05, min=0.0, max=2.0, update=update_view)
ray_thickness: bpy.props.FloatProperty(name="Train Ray Thick", default=0.05, min=0.01, max=1.0, update=update_view)
cone_thickness: bpy.props.FloatProperty(name="Cone Ray Thick", default=0.035, min=0.01, max=1.0, update=update_view)
class OBJECT_OT_DrawYZRays(bpy.types.Operator):
bl_idname = "object.draw_yz_rays"; bl_label = "EXECUTE DRAW"
def execute(self, context): draw_rays_core(context); return {'FINISHED'}
class WM_OT_CopyYZScript(bpy.types.Operator):
bl_idname = "wm.copy_yz_script"; bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.yz_rays_props; M_START, M_END = "# <BEGIN_DICT>", "# <END_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'}
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"{v:.4f}" for v 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 = target_text.as_string()
try:
res = code.split(M_START)[0] + M_START + "\n" + d_str + M_END + code.split(M_END)[1]
context.window_manager.clipboard = f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Session Template\n" + '\n'.join(res.split('\n')[1:])
self.report({'INFO'}, "Copied.")
except: pass
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'}
class WM_OT_RemoveYZAddon(bpy.types.Operator):
bl_idname = "wm.remove_yz_addon"; bl_label = "Remove Addon"
def execute(self, context): unregister(); return {'FINISHED'}
class VIEW3D_PT_YZRays(bpy.types.Panel):
bl_label = "YZ Plane Rays (V30)"; 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
layout.operator("object.draw_yz_rays", icon='PLAY')
layout.operator("wm.copy_yz_script", icon='COPY_ID')
box = layout.box(); box.prop(p, "show_circle"); box.prop(p, "circle_color", text=""); box.prop(p, "circle_depth"); box.prop(p, "circle_solid")
box = layout.box(); box.prop(p, "show_train_rays"); box.prop(p, "ray_color", text=""); box.prop(p, "ray_thickness")
box = layout.box(); box.prop(p, "show_cone_rays"); box.prop(p, "cone_color", text=""); box.prop(p, "cone_thickness")
phys = layout.box(); phys.prop(p, "speed_v"); phys.prop(p, "time_t")
class VIEW3D_PT_YZLinks(bpy.types.Panel):
bl_label = "Theory 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"], icon='WORLD'); op.url = l["url"]
class VIEW3D_PT_YZSystem(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): self.layout.operator("wm.remove_yz_addon", icon='CANCEL')
classes = (PG_YZRaysProps, OBJECT_OT_DrawYZRays, WM_OT_CopyYZScript, WM_OT_OpenRaysUrl, WM_OT_RemoveYZAddon, VIEW3D_PT_YZRays, VIEW3D_PT_YZLinks, VIEW3D_PT_YZSystem)
def register():
for c in classes: bpy.utils.register_class(c)
bpy.types.Scene.yz_rays_props = bpy.props.PointerProperty(type=PG_YZRaysProps)
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()