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

# Copied: 20260319 19:52:40
# Copied: 19:50:42
# Copied: 19:46:40
# Copied: 19:42:18
# 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_V50"
TAB_NAME = " [ YZ Rays ] "
# ★ このスクリプト自身のID (コピー機能で使用)
# ### ZIONAD_SOURCE_ID: YZ_RAYS_V50_FIXED ###
bl_info = {
"name": f"zionad 520 [ YZ Rays ] {PREFIX}",
"author": "zionadchat",
"version": (1, 50, 0),
"blender": (3, 0, 0),
"location": "3D View > Sidebar",
"description": "YZ Plane Rays with If-100% Extension",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: YZ_RAYS_V50_FIXED ###"
ADDON_LINKS = (
{"label": "Theory Background", "url": "<https://www.notion.so/>"},
{"label": "Blender Guide", "url": "<https://www.notion.so/>"},
)
# ==============================================================================
# デフォルト値設定 (コピー機能でここが書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_circle": True,
"show_train_rays": True,
"show_cone_rays": True,
"show_if_train_rays": True,
"ray_color": (0.6396, 0.0172, 0.5576, 1.0000),
"cone_color": (0.8000, 0.5848, 0.0000, 0.6000),
"circle_color": (0.0082, 0.0098, 0.8000, 0.1000),
"if_train_color": (0.0112, 0.5279, 0.0418, 1.0000),
"emission_x": 0.0000,
"speed_v": 0.6900,
"time_t": 10.0000,
"circle_depth": 10.0000,
"circle_solid": 2.0000,
"ray_thickness": 0.3000,
"cone_thickness": 0.1000,
"if_train_thickness": 0.5000,
}
# <END_DICT>
# ==============================================================================
# 物理情報計算ロジック
# ==============================================================================
def get_physics_info(p):
c = 1.0
v = min(0.9999, max(0.0, p.speed_v))
t = p.time_t
# 実際の座標と半径
x_pos = p.emission_x + (v * t)
val = (c * t)**2 - (v * t)**2
r_rays = math.sqrt(max(0, val))
# 角度 (X軸に対する角度 / ZX平面での角度)
angle_rad = math.acos(v / c)
angle_deg = math.degrees(angle_rad)
# If 100% (そのままの角度で、半径が光速100%相当 t になるまで延長した場合の交点)
gamma = 1.0 / math.sqrt(1.0 - v**2) if v < 0.9999 else 1.0
if_r_rays = c * t
if_x_pos = p.emission_x + (v * t * gamma)
return {
"speed_pct": v * 100.0,
"x_pos": x_pos,
"r_rays": r_rays,
"angle_deg": angle_deg,
"if_x_pos": if_x_pos,
"if_r_rays": if_r_rays
}
# ==============================================================================
# マテリアル作成ロジック
# ==============================================================================
def create_unique_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 hasattr(mat, "shadow_method"): mat.shadow_method = 'NONE'
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]
if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 0.0
mat.diffuse_color = color
return mat
# ==============================================================================
# プレビュー用(ジオメトリ)ロジック
# ==============================================================================
OUTPUT_COL_NAME = f"{PREFIX}_Output"
TAG_C, TAG_T, TAG_O, TAG_I = f"{PREFIX}_c", f"{PREFIX}_t", f"{PREFIX}_o", f"{PREFIX}_i"
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 update_preview_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
info = get_physics_info(p)
R_circle = 1.0 * p.time_t
R_rays = info["r_rays"]
cone_origin = Vector((p.emission_x, 0, 0))
train_origin = Vector((info["x_pos"], 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'))
bmesh.ops.translate(bm, verts=bm.verts, vec=train_origin)
bm.to_mesh(m_c); bm.free()
o_c.data.materials.clear()
o_c.data.materials.append(create_unique_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_offset = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, train_origin, train_origin + tip_offset, p.ray_thickness)
bm.to_mesh(m_t); bm.free()
o_t.data.materials.clear()
o_t.data.materials.append(create_unique_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_offset = Vector((0, R_rays * math.cos(ang), R_rays * math.sin(ang)))
create_arrow_bm(bm, cone_origin, train_origin + tip_offset, p.cone_thickness)
bm.to_mesh(m_o); bm.free()
o_o.data.materials.clear()
o_o.data.materials.append(create_unique_material(p.cone_color, "Mat_Cone"))
# 4. If Train Rays (100% Extension)
o_i, m_i = sync_obj(f"YZ_If_Train_{PREFIX}", TAG_I)
o_i.hide_viewport = not p.show_if_train_rays
bm = bmesh.new()
if info["if_r_rays"] > 0.001:
train_if_origin = Vector((info["if_x_pos"], 0, 0))
for i in range(12):
ang = math.radians(i * 30)
tip_offset = Vector((0, info["if_r_rays"] * math.cos(ang), info["if_r_rays"] * math.sin(ang)))
create_arrow_bm(bm, train_if_origin, train_if_origin + tip_offset, p.if_train_thickness)
bm.to_mesh(m_i); bm.free()
o_i.data.materials.clear()
o_i.data.materials.append(create_unique_material(p.if_train_color, "Mat_If_Train"))
_timer = None
def delayed_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
update_preview_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)
show_if_train_rays: BoolProperty(name="Show If Train Rays", default=CURRENT_DEFAULTS['show_if_train_rays'], update=on_update)
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)
if_train_color: FloatVectorProperty(name="If Train Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['if_train_color'], update=on_update)
emission_x: FloatProperty(name="Cone Emission X", default=CURRENT_DEFAULTS['emission_x'], 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)
if_train_thickness: FloatProperty(name="If Train Thick", default=CURRENT_DEFAULTS['if_train_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_preview_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)
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()
# 安全な辞書構築 (エラーが起きないよう展開)
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'
new_dict += f' "show_if_train_rays": {props.show_if_train_rays},\n'
rc, cc = props.ray_color, props.cone_color
crc, itc = props.circle_color, props.if_train_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": ({crc[0]:.4f}, {crc[1]:.4f}, {crc[2]:.4f}, {crc[3]:.4f}),\n'
new_dict += f' "if_train_color": ({itc[0]:.4f}, {itc[1]:.4f}, {itc[2]:.4f}, {itc[3]:.4f}),\n'
new_dict += f' "emission_x": {props.emission_x:.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 += f' "if_train_thickness": {props.if_train_thickness:.4f},\n'
new_dict += "}\n"
# Sphereと同じ確実な文字列分割ロジック
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_CopyInfo(Operator):
bl_idname = f"{OP_PREFIX}.copy_info"
bl_label = "Copy Physics Info"
def execute(self, context):
p = getattr(context.scene, PROPS_NAME, None)
if not p: return {'CANCELLED'}
info = get_physics_info(p)
text = (
f"[ YZ Rays Physics Info ]\n"
f"Speed (v/c) : {info['speed_pct']:.1f} % ({p.speed_v:.2f}c)\n"
f"Time (t) : {p.time_t:.4f}\n"
f"Ray Angle to X : {info['angle_deg']:.2f} deg\n"
f"---------------------------------\n"
f"Real Reach X Pos : {info['x_pos']:.4f}\n"
f"Real Wave Radius : {info['r_rays']:.4f}\n"
f"---------------------------------\n"
f"If 100% Reach X : {info['if_x_pos']:.4f}\n"
f"If 100% Radius : {info['if_r_rays']:.4f}\n"
)
context.window_manager.clipboard = text
self.report({'INFO'}, "Physics Info Copied!")
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.emission_x = 0.0; 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 (V50)"
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()
# UI: Physics Properties
phys = layout.box()
phys.label(text="Physics Parameters", icon='PHYSICS')
phys.prop(props, "emission_x")
phys.prop(props, "speed_v")
phys.prop(props, "time_t")
phys.operator(OT_Reset.bl_idname, icon='LOOP_BACK', text="Reset Params")
# UI: Realtime Info
info_box = layout.box()
info_box.label(text="Realtime Info", icon='INFO')
info = get_physics_info(props)
info_box.label(text=f"Speed: {info['speed_pct']:.1f} % ({props.speed_v:.2f}c)")
info_box.label(text=f"Angle to X: {info['angle_deg']:.2f} °")
info_box.label(text=f"[Real] X: {info['x_pos']:.2f} | R: {info['r_rays']:.2f}")
info_box.label(text=f"[If 100%] X: {info['if_x_pos']:.2f} | R: {info['if_r_rays']:.2f}")
info_box.operator(OT_CopyInfo.bl_idname, icon='COPYDOWN', text="Copy Info to Clipboard")
layout.separator()
# UI: Visuals
col = layout.column()
col.operator(OT_ExecuteDraw.bl_idname, icon='PLAY', text="Force Update Draw")
box = layout.box()
box.prop(props, "show_circle")
if props.show_circle:
box.prop(props, "circle_color", text="")
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", text="")
box.prop(props, "ray_thickness")
box = layout.box()
box.prop(props, "show_cone_rays")
if props.show_cone_rays:
box.prop(props, "cone_color", text="")
box.prop(props, "cone_thickness")
box = layout.box()
box.prop(props, "show_if_train_rays")
if props.show_if_train_rays:
box.prop(props, "if_train_color", text="")
box.prop(props, "if_train_thickness")
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_CopyInfo,
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()