blender Million 2026

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

https://x.com/zionadchat

基本オブジェクト操作

ローレンツ氏の 光速 予算配分 再掲載 20260316

作業場

作業場 (1)

rapture_20260318195349.png

# 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()