blender Million 2026
# <https://www.notion.so/20260305-31af5dacaf4380729b34e3136c3649aa>
# 2026-03-05 Michelson Interferometer Update
# Blender 4.3+ Exclusive
bl_info = {
"name": "Symmetric Spacetime (v5.48 Michelson)",
"author": "zionadchat Gemini",
"version": (5, 48),
"blender": (4, 3, 0),
"location": "View3D > Sidebar",
"description": "Michelson Interferometer in Spacetime Diagram 20260305",
"category": "Physics",
}
import bpy
import webbrowser
import math
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"calc_mode": "VEL", "velocity": 0.6000, "radius": 10.0000, "target_x": 15.0000,
"base_angle": 0.0000, "arm_b_angle": 90.0000, "ray_mode": "2",
"show_ray_classic": True, "show_ray_converge": True, "show_ray_ell_conv": True,
"show_ray_a_out": True, "show_ray_a_in": True, "show_ray_b_out": True, "show_ray_b_in": True,
"color_ray_a_out": (1.000, 0.500, 0.000, 1.000), "thick_ray_a_out": 1.41,
"color_ray_a_in": (0.005, 0.008, 0.271, 1.000), "thick_ray_a_in": 0.73,
"color_ray_b_out": (0.400, 0.000, 0.000, 1.000), "thick_ray_b_out": 0.80,
"color_ray_b_in": (0.000, 0.500, 0.000, 1.000), "thick_ray_b_in": 0.30,
"show_spheroid": True, "color_spheroid": (0.200, 0.100, 0.500, 0.10),
"show_st_ellipse": True, "color_st_ellipse": (1.000, 1.000, 1.000, 0.80), "thick_st_ellipse": 0.05,
"show_wavefronts": True, "color_wavefronts": (0.160, 0.000, 0.050, 0.40), "thick_wavefronts": 0.40,
"show_circle_rings": True, "color_circle_rings": (0.100, 0.200, 0.300, 0.30), "thick_circle_rings": 0.03,
"show_skeleton": True, "color_skeleton": (0.500, 0.500, 0.500, 0.20), "skel_thick": 0.01,
}
# <END_DICT>
TAB_NAME = "Relativity_Sym_v5"
COLLECTION_NAME = "Relativity_Sym_Output"
ADDON_LINKS = (
{"label": "時空図のマイケルソン干渉計 20260305版", "url": "<https://www.notion.so/20260305-31af5dacaf4380729b34e3136c3649aa>"},
{"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
{"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)
# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
mat_name = f"Mat_{part_id}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
mat.diffuse_color = color
return mat
# ------------------------------------------------------------------------
# Properties & Physics
# ------------------------------------------------------------------------
_is_updating = False
def update_physics(self, context):
global _is_updating
if _is_updating:
return
_is_updating = True
try:
p = self
if p.calc_mode == 'X':
p.velocity = p.target_x / math.sqrt(p.target_x**2 + (2*p.radius)**2)
else:
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - p.velocity**2))
p.target_x = p.velocity * 2 * p.radius * gamma
finally:
_is_updating = False
update_view(self, context)
def update_view(self, context):
try: bpy.ops.object.draw_spacetime_sym_v5('INVOKE_DEFAULT')
except: pass
class PG_RelativitySymV5(bpy.types.PropertyGroup):
calc_mode: bpy.props.EnumProperty(items=[('VEL', "Velocity Mode", ""), ('X', "Target X Mode", "")], default=CURRENT_DEFAULTS["calc_mode"], update=update_view)
velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_physics)
target_x: bpy.props.FloatProperty(name="Target X", default=CURRENT_DEFAULTS["target_x"], min=0.001, soft_max=1000.0, update=update_physics)
radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, soft_max=100.0, update=update_physics)
base_angle: bpy.props.FloatProperty(name="Base Angle", default=CURRENT_DEFAULTS["base_angle"], min=0, max=360, update=update_view)
arm_b_angle: bpy.props.FloatProperty(name="Arm B Angle", default=CURRENT_DEFAULTS["arm_b_angle"], min=0, max=360, update=update_view)
ray_mode: bpy.props.EnumProperty(items=[('2', "2 Rays", ""), ('12', "12 Rays", "")], default=CURRENT_DEFAULTS["ray_mode"], update=update_view)
show_ray_classic: bpy.props.BoolProperty(name="Classic", default=CURRENT_DEFAULTS["show_ray_classic"], update=update_view)
show_ray_converge: bpy.props.BoolProperty(name="Converge", default=CURRENT_DEFAULTS["show_ray_converge"], update=update_view)
show_ray_ell_conv: bpy.props.BoolProperty(name="Ellipse Conv", default=CURRENT_DEFAULTS["show_ray_ell_conv"], update=update_view)
color_ray_a_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_out"], update=update_view)
color_ray_a_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_in"], update=update_view)
color_ray_b_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_out"], update=update_view)
color_ray_b_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_in"], update=update_view)
thick_ray_a_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_out"], min=0, max=2.0, update=update_view)
thick_ray_a_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_in"], min=0, max=2.0, update=update_view)
thick_ray_b_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_out"], min=0, max=2.0, update=update_view)
thick_ray_b_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_in"], min=0, max=2.0, update=update_view)
show_ray_a_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_out"], update=update_view)
show_ray_a_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_in"], update=update_view)
show_ray_b_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_out"], update=update_view)
show_ray_b_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_in"], update=update_view)
show_spheroid: bpy.props.BoolProperty(name="Spheroid Surface", default=CURRENT_DEFAULTS["show_spheroid"], update=update_view)
color_spheroid: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_spheroid"], update=update_view)
show_st_ellipse: bpy.props.BoolProperty(name="Longit. Ellipse", default=CURRENT_DEFAULTS["show_st_ellipse"], update=update_view)
color_st_ellipse: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_st_ellipse"], update=update_view)
thick_st_ellipse: bpy.props.FloatProperty(name="Thick Ellipse", default=CURRENT_DEFAULTS["thick_st_ellipse"], min=0.0, max=1.0, update=update_view)
show_wavefronts: bpy.props.BoolProperty(name="Wavefronts (Horiz.)", default=CURRENT_DEFAULTS["show_wavefronts"], update=update_view)
color_wavefronts: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_wavefronts"], update=update_view)
thick_wavefronts: bpy.props.FloatProperty(name="Thick WF", default=CURRENT_DEFAULTS["thick_wavefronts"], min=0.0, max=2.0, update=update_view)
show_circle_rings: bpy.props.BoolProperty(name="Tube Rings", default=CURRENT_DEFAULTS["show_circle_rings"], update=update_view)
color_circle_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_circle_rings"], update=update_view)
thick_circle_rings: bpy.props.FloatProperty(name="Thick Rings", default=CURRENT_DEFAULTS["thick_circle_rings"], min=0.0, max=1.0, update=update_view)
show_skeleton: bpy.props.BoolProperty(name="Skeleton", default=CURRENT_DEFAULTS["show_skeleton"], update=update_view)
color_skeleton: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_skeleton"], update=update_view)
skel_thick: bpy.props.FloatProperty(name="Thick Skeleton", default=CURRENT_DEFAULTS["skel_thick"], min=0.0, max=1.0, update=update_view)
# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
def create_curve(col, name, points, thickness, part_id, color, circular=False):
curve = bpy.data.curves.new(name, 'CURVE')
curve.dimensions = '3D'
obj = bpy.data.objects.new(name, curve)
col.objects.link(obj)
spline = curve.splines.new('POLY')
spline.use_cyclic_u = circular
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p.x, p.y, p.z, 1)
curve.bevel_depth = thickness
obj.data.materials.append(get_fixed_material(part_id, color))
return obj
class OBJECT_OT_DrawSpacetimeSymV5(bpy.types.Operator):
bl_idname = "object.draw_spacetime_sym_v5"
bl_label = "Refresh View"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
p = context.scene.rel_sym_v5
v, R_val = p.velocity, p.radius
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
A = 2.0 * R_val * gamma
offset = Vector((v * A / 2.0, 0, A / 2.0))
F1 = Vector((0,0,0)) - offset
Mid = Vector((v*A/2,0,A/2)) - offset
F2 = Vector((v*A,0,A)) - offset
def get_P_locus(deg):
rad = math.radians(deg)
return Vector((gamma*(R_val*math.cos(rad)+v*R_val), R_val*math.sin(rad), gamma*(R_val+v*(R_val*math.cos(rad))))) - offset
def get_P_rigid(deg, z_v):
t_p = z_v + offset.z
rad = math.radians(deg)
return Vector((v*t_p - offset.x + R_val*math.cos(rad), R_val*math.sin(rad), z_v))
col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
if COLLECTION_NAME not in context.scene.collection.children:
context.scene.collection.children.link(col)
for obj in col.objects:
bpy.data.objects.remove(obj, do_unlink=True)
# Longitudinal Ellipse & Spheroid
major_2a = math.sqrt(A**2 * (v**2 + 1) + 4 * R_val**2)
dir_vec = F2 - F1
rot_quat = Vector((1,0,0)).rotation_difference(dir_vec)
if p.show_st_ellipse:
v_pts =[Mid + rot_quat @ Vector((major_2a/2 * math.cos(math.radians(d)), 0, R_val * math.sin(math.radians(d)))) for d in range(0, 365, 5)]
create_curve(col, "ST_Ellipse", v_pts, p.thick_st_ellipse, "ST_Ellipse", p.color_st_ellipse, True)
if p.show_spheroid:
mesh = bpy.data.meshes.new("Spheroid")
obj = bpy.data.objects.new("Spheroid", mesh)
col.objects.link(obj)
import bmesh
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
for vb in bm.verts:
vb.co.x *= (major_2a/2.0)
vb.co.y *= R_val
vb.co.z *= R_val
bmesh.ops.rotate(bm, cent=(0,0,0), matrix=rot_quat.to_matrix(), verts=bm.verts)
bmesh.ops.translate(bm, vec=Mid, verts=bm.verts)
bm.to_mesh(mesh)
bm.free()
obj.data.materials.append(get_fixed_material("Spheroid", p.color_spheroid))
# Wavefronts
if p.show_wavefronts:
for i, name in[(0, "Mid"), (-1, "Low"), (1, "Up")]:
trans = (F2 - Mid) * i
pts =[get_P_locus(d*5) + trans for d in range(73)]
create_curve(col, f"WF_{name}", pts, p.thick_wavefronts, "Wavefront", p.color_wavefronts, True)
# Rays
if p.ray_mode == '2':
PtA = get_P_locus(p.base_angle)
if p.show_ray_a_out: create_curve(col, "ArmA_O",[F1, PtA], p.thick_ray_a_out, "ArmA_Out", p.color_ray_a_out)
if p.show_ray_a_in: create_curve(col, "ArmA_I",[PtA, F2], p.thick_ray_a_in, "ArmA_In", p.color_ray_a_in)
PtB = get_P_locus(p.base_angle + p.arm_b_angle)
if p.show_ray_b_out: create_curve(col, "ArmB_O",[F1, PtB], p.thick_ray_b_out, "ArmB_Out", p.color_ray_b_out)
if p.show_ray_b_in: create_curve(col, "ArmB_I",[PtB, F2], p.thick_ray_b_in, "ArmB_In", p.color_ray_b_in)
# --- 2本の反射位置での円周(Tube Rings)を描画 ---
if p.show_circle_rings:
pts_A =[get_P_rigid(i*5, PtA.z) for i in range(73)]
create_curve(col, "Ring_ArmA", pts_A, p.thick_circle_rings, "ArmA_Out", p.color_ray_a_out, True)
pts_B =[get_P_rigid(i*5, PtB.z) for i in range(73)]
create_curve(col, "Ring_ArmB", pts_B, p.thick_circle_rings, "ArmB_Out", p.color_ray_b_out, True)
else:
for i in range(12):
deg = p.base_angle + (i*30)
Pt = get_P_locus(deg)
if p.show_ray_classic:
create_curve(col, f"Cl_{i}_O",[F1, Pt], p.thick_ray_a_out, "Classic_Out", p.color_ray_a_out)
create_curve(col, f"Cl_{i}_I",[Pt, F2], p.thick_ray_a_in, "Classic_In", p.color_ray_a_in)
if p.show_ray_converge:
Ps, Pe = get_P_rigid(deg, F1.z), get_P_rigid(deg, F2.z)
create_curve(col, f"Cv_{i}_O", [Ps, Mid], p.thick_ray_b_out, "Conv_Out", p.color_ray_b_out)
create_curve(col, f"Cv_{i}_I", [Mid, Pe], p.thick_ray_b_in, "Conv_In", p.color_ray_b_in)
if p.show_ray_ell_conv:
PtL, PtU = Pt+(F1-Mid), Pt+(F2-Mid)
create_curve(col, f"ElCv_{i}_O",[PtL, Mid], p.thick_ray_b_out, "EllConv_Out", p.color_ray_b_out)
create_curve(col, f"ElCv_{i}_I",[Mid, PtU], p.thick_ray_b_in, "EllConv_In", p.color_ray_b_in)
# Helpers
if p.show_skeleton:
for i in range(12):
Pl = get_P_locus(i*30) + offset
Ms = Vector((Pl.x+v*(0-Pl.z), Pl.y, 0)) - offset
Me = Vector((Pl.x+v*(A-Pl.z), Pl.y, A)) - offset
create_curve(col, f"Sk_{i}", [Ms, Me], p.skel_thick, "Skeleton", p.color_skeleton)
if p.show_circle_rings:
for z_v in[F1.z, 0.0, F2.z]:
pts =[get_P_rigid(i*5, z_v) for i in range(73)]
create_curve(col, f"T_{z_v}", pts, p.thick_circle_rings, "Tube", p.color_circle_rings, True)
# --- 共有情報の構築(Time Accounting用) ---
info = f"Velocity: {v:.6f} c\nTarget X: {F2.x - F1.x:.4f}\nA (Invariant): {A:.4f}"
if p.ray_mode == '2':
# 変数が未定義にならないよう再度取得
PtA = get_P_locus(p.base_angle)
PtB = get_P_locus(p.base_angle + p.arm_b_angle)
info += f"\n "
info += f"\n[Arm A Reflection]"
info += f"\n Time(Δt): {PtA.z - F1.z:.4f} / z: {PtA.z:.4f}"
info += f"\n Pos: ({PtA.x:.4f}, {PtA.y:.4f}, {PtA.z:.4f})"
info += f"\n "
info += f"\n[Arm B Reflection]"
info += f"\n Time(Δt): {PtB.z - F1.z:.4f} / z: {PtB.z:.4f}"
info += f"\n Pos: ({PtB.x:.4f}, {PtB.y:.4f}, {PtB.z:.4f})"
context.scene["rel_v5_info"] = info
return {'FINISHED'}
# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class VIEW3D_PT_RelSymV5(bpy.types.Panel):
bl_label = "Symmetric 4D Controller"
bl_idname = "VIEW3D_PT_rel_sym_v5"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
p = context.scene.rel_sym_v5
layout.operator("wm.copy_full_script", text="全コードをコピー", icon='COPY_ID')
# Physics Setup
box = layout.box()
row = box.row(align=True)
row.prop(p, "calc_mode", expand=True)
setup = box.box()
setup.prop(p, "radius")
if p.calc_mode == 'X':
setup.prop(p, "target_x", text="Target X")
else:
setup.prop(p, "velocity", text="Velocity (v/c)")
# ==========================================
# Time Accounting (Velocityの直下へ移動)
# ==========================================
acc = layout.box()
acc.label(text="Time Accounting", icon='TIME')
info_text = context.scene.get("rel_v5_info", "")
for line in info_text.split("\n"):
if line == " ":
acc.separator(factor=0.5)
elif line:
acc.label(text=line)
acc.operator("wm.copy_rel_info", text="数値を一括コピー", icon='COPYDOWN')
# ==========================================
# Rays Configuration
cfg = layout.box()
cfg.label(text="Rays Configuration", icon='LIGHT_SUN')
row = cfg.row(align=True)
row.prop(p, "ray_mode", text="")
row.prop(p, "base_angle", text="Base")
# Ray Visual Details
det = layout.box()
det.label(text="Ray Details", icon='NODE_MATERIAL')
if p.ray_mode == '2':
configs =[
("Arm A (Ref)", "show_ray_a_out", "show_ray_a_in", "thick_ray_a_out", "color_ray_a_out", "thick_ray_a_in", "color_ray_a_in", None),
("Arm B (Adj)", "show_ray_b_out", "show_ray_b_in", "thick_ray_b_out", "color_ray_b_out", "thick_ray_b_in", "color_ray_b_in", "arm_b_angle")
]
for lbl, so, si, to, co, ti, ci, ang in configs:
b = det.box()
r = b.row()
r.label(text=lbl)
r.prop(p, so, text="", icon='HIDE_OFF')
r.prop(p, si, text="", icon='HIDE_OFF')
if ang:
b.prop(p, ang, text="Offset")
c = b.column(align=True)
r = c.row(align=True)
r.prop(p, to, text="Thick Out")
r.prop(p, co, text="")
r = c.row(align=True)
r.prop(p, ti, text="Thick In")
r.prop(p, ci, text="")
# Wavefronts
wf = layout.box()
wf.label(text="Wavefronts", icon='SPHERE')
row = wf.row()
row.prop(p, "show_wavefronts", text="Show Rings")
row.prop(p, "color_wavefronts", text="")
wf.prop(p, "thick_wavefronts", text="Thickness")
# Spacetime Structure
st = layout.box()
st.label(text="Spacetime Structure", icon='WORLD')
row = st.row(align=True)
row.prop(p, "show_st_ellipse", text="Longit. Ellipse")
row.prop(p, "color_st_ellipse", text="")
if p.show_st_ellipse:
st.prop(p, "thick_st_ellipse", text="Thickness")
row = st.row(align=True)
row.prop(p, "show_spheroid", text="3D Spheroid")
row.prop(p, "color_spheroid", text="")
# Visual Aids
vbox = layout.box()
vbox.label(text="Visual Aids", icon='MESH_GRID')
row = vbox.row(align=True)
row.prop(p, "show_circle_rings", text="Tube Rings")
row.prop(p, "color_circle_rings", text="")
if p.show_circle_rings:
vbox.prop(p, "thick_circle_rings", text="Thickness")
row = vbox.row(align=True)
row.prop(p, "show_skeleton", text="Skeleton")
row.prop(p, "color_skeleton", text="")
if p.show_skeleton:
vbox.prop(p, "skel_thick", text="Thickness")
layout.operator("object.draw_spacetime_sym_v5", text="Refresh View", icon='FILE_REFRESH')
class VIEW3D_PT_RelLinks(bpy.types.Panel):
bl_label = "Theory Links"
bl_idname = "VIEW3D_PT_rel_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_rel_url", text=l["label"])
op.url = l["url"]
class VIEW3D_PT_RelSystem(bpy.types.Panel):
bl_label = "System"
bl_idname = "VIEW3D_PT_rel_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_rel_addon", icon='CANCEL', text="アドオン削除")
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class WM_OT_CopyFullScript(bpy.types.Operator):
bl_idname = "wm.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.rel_sym_v5
M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
if not texts:
self.report({'ERROR'}, "ソースコードが見つかりません。")
return {'CANCELLED'}
d_str = "CURRENT_DEFAULTS = {\n"
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if isinstance(val, str):
d_str += f' "{k}": "{val}",\n'
elif hasattr(val, "__len__"):
d_str += f' "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
elif isinstance(val, float):
d_str += f' "{k}": {val:.4f},\n'
else:
d_str += f' "{k}": {val},\n'
d_str += "}\n"
target_text = texts[-1]
new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
lines = new_code.split('\n')
if lines and lines[0].startswith("# "):
lines[0] = f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)"
context.window_manager.clipboard = '\n'.join(lines)
self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
return {'FINISHED'}
class WM_OT_OpenRelUrl(bpy.types.Operator):
bl_idname = "wm.open_rel_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class WM_OT_CopyRelInfo(bpy.types.Operator):
bl_idname = "wm.copy_rel_info"
bl_label = "Copy Info"
def execute(self, context):
context.window_manager.clipboard = context.scene.get("rel_v5_info", "")
self.report({'INFO'}, "数値をクリップボードにコピーしました!")
return {'FINISHED'}
class WM_OT_RemoveRelAddon(bpy.types.Operator):
bl_idname = "wm.remove_rel_addon"
bl_label = "Remove"
def execute(self, context):
unregister()
return {'FINISHED'}
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
PG_RelativitySymV5,
OBJECT_OT_DrawSpacetimeSymV5,
WM_OT_CopyFullScript,
WM_OT_OpenRelUrl,
WM_OT_CopyRelInfo,
WM_OT_RemoveRelAddon,
VIEW3D_PT_RelSymV5,
VIEW3D_PT_RelLinks,
VIEW3D_PT_RelSystem
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.rel_sym_v5 = bpy.props.PointerProperty(type=PG_RelativitySymV5)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if hasattr(bpy.types.Scene, "rel_sym_v5"):
del bpy.types.Scene.rel_sym_v5
if __name__ == "__main__":
register()
# <https://www.notion.so/20260220-30df5dacaf43805fa6ccd4ef2b068488>
# 2026-02-21 09:00:00 Thickness Integrated Master good
# Blender 4.3+ Exclusive
bl_info = {
"name": "Symmetric Spacetime (v5.46 Dynamic Defaults)",
"author": "zionadchat Gemini",
"version": (5, 46),
"blender": (4, 3, 0),
"location": "View3D > Sidebar",
"description": "Full thickness control & Dynamic copy function fixed",
"category": "Physics",
}
import bpy
import webbrowser
import math
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"calc_mode": "VEL", "velocity": 0.9600, "radius": 10.0000, "target_x": 68.5714,
"base_angle": 0.0000, "arm_b_angle": 90.0000, "ray_mode": "2",
"show_ray_classic": True, "show_ray_converge": True, "show_ray_ell_conv": True,
"show_ray_a_out": True, "show_ray_a_in": True, "show_ray_b_out": True, "show_ray_b_in": True,
"color_ray_a_out": (1.000, 0.500, 0.000, 1.000), "thick_ray_a_out": 1.41,
"color_ray_a_in": (0.005, 0.008, 0.271, 1.000), "thick_ray_a_in": 0.73,
"color_ray_b_out": (0.400, 0.000, 0.000, 1.000), "thick_ray_b_out": 0.80,
"color_ray_b_in": (0.000, 0.500, 0.000, 1.000), "thick_ray_b_in": 0.30,
"show_spheroid": True, "color_spheroid": (0.200, 0.100, 0.500, 0.10),
"show_st_ellipse": True, "color_st_ellipse": (1.000, 1.000, 1.000, 0.80), "thick_st_ellipse": 0.05,
"show_wavefronts": True, "color_wavefronts": (0.160, 0.000, 0.050, 0.40), "thick_wavefronts": 0.40,
"show_circle_rings": True, "color_circle_rings": (0.100, 0.200, 0.300, 0.30), "thick_circle_rings": 0.03,
"show_skeleton": True, "color_skeleton": (0.500, 0.500, 0.500, 0.20), "skel_thick": 0.01,
}
# <END_DICT>
TAB_NAME = "Relativity_Sym_v5"
COLLECTION_NAME = "Relativity_Sym_Output"
ADDON_LINKS = (
{"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
{"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)
# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
mat_name = f"Mat_{part_id}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
mat.diffuse_color = color
return mat
# ------------------------------------------------------------------------
# Properties & Physics
# ------------------------------------------------------------------------
_is_updating = False
def update_physics(self, context):
global _is_updating
if _is_updating:
return
_is_updating = True
try:
p = self
if p.calc_mode == 'X':
p.velocity = p.target_x / math.sqrt(p.target_x**2 + (2*p.radius)**2)
else:
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - p.velocity**2))
p.target_x = p.velocity * 2 * p.radius * gamma
finally:
_is_updating = False
update_view(self, context)
def update_view(self, context):
try: bpy.ops.object.draw_spacetime_sym_v5('INVOKE_DEFAULT')
except: pass
class PG_RelativitySymV5(bpy.types.PropertyGroup):
calc_mode: bpy.props.EnumProperty(items=[('VEL', "Velocity Mode", ""), ('X', "Target X Mode", "")], default=CURRENT_DEFAULTS["calc_mode"], update=update_view)
velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_physics)
target_x: bpy.props.FloatProperty(name="Target X", default=CURRENT_DEFAULTS["target_x"], min=0.001, soft_max=1000.0, update=update_physics)
radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, soft_max=100.0, update=update_physics)
base_angle: bpy.props.FloatProperty(name="Base Angle", default=CURRENT_DEFAULTS["base_angle"], min=0, max=360, update=update_view)
arm_b_angle: bpy.props.FloatProperty(name="Arm B Angle", default=CURRENT_DEFAULTS["arm_b_angle"], min=0, max=360, update=update_view)
ray_mode: bpy.props.EnumProperty(items=[('2', "2 Rays", ""), ('12', "12 Rays", "")], default=CURRENT_DEFAULTS["ray_mode"], update=update_view)
show_ray_classic: bpy.props.BoolProperty(name="Classic", default=CURRENT_DEFAULTS["show_ray_classic"], update=update_view)
show_ray_converge: bpy.props.BoolProperty(name="Converge", default=CURRENT_DEFAULTS["show_ray_converge"], update=update_view)
show_ray_ell_conv: bpy.props.BoolProperty(name="Ellipse Conv", default=CURRENT_DEFAULTS["show_ray_ell_conv"], update=update_view)
color_ray_a_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_out"], update=update_view)
color_ray_a_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_in"], update=update_view)
color_ray_b_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_out"], update=update_view)
color_ray_b_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_in"], update=update_view)
thick_ray_a_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_out"], min=0, max=2.0, update=update_view)
thick_ray_a_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_in"], min=0, max=2.0, update=update_view)
thick_ray_b_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_out"], min=0, max=2.0, update=update_view)
thick_ray_b_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_in"], min=0, max=2.0, update=update_view)
show_ray_a_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_out"], update=update_view)
show_ray_a_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_in"], update=update_view)
show_ray_b_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_out"], update=update_view)
show_ray_b_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_in"], update=update_view)
show_spheroid: bpy.props.BoolProperty(name="Spheroid Surface", default=CURRENT_DEFAULTS["show_spheroid"], update=update_view)
color_spheroid: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_spheroid"], update=update_view)
show_st_ellipse: bpy.props.BoolProperty(name="Longit. Ellipse", default=CURRENT_DEFAULTS["show_st_ellipse"], update=update_view)
color_st_ellipse: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_st_ellipse"], update=update_view)
thick_st_ellipse: bpy.props.FloatProperty(name="Thick Ellipse", default=CURRENT_DEFAULTS["thick_st_ellipse"], min=0.0, max=1.0, update=update_view)
show_wavefronts: bpy.props.BoolProperty(name="Wavefronts (Horiz.)", default=CURRENT_DEFAULTS["show_wavefronts"], update=update_view)
color_wavefronts: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_wavefronts"], update=update_view)
thick_wavefronts: bpy.props.FloatProperty(name="Thick WF", default=CURRENT_DEFAULTS["thick_wavefronts"], min=0.0, max=2.0, update=update_view)
show_circle_rings: bpy.props.BoolProperty(name="Tube Rings", default=CURRENT_DEFAULTS["show_circle_rings"], update=update_view)
color_circle_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_circle_rings"], update=update_view)
thick_circle_rings: bpy.props.FloatProperty(name="Thick Rings", default=CURRENT_DEFAULTS["thick_circle_rings"], min=0.0, max=1.0, update=update_view)
show_skeleton: bpy.props.BoolProperty(name="Skeleton", default=CURRENT_DEFAULTS["show_skeleton"], update=update_view)
color_skeleton: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_skeleton"], update=update_view)
skel_thick: bpy.props.FloatProperty(name="Thick Skeleton", default=CURRENT_DEFAULTS["skel_thick"], min=0.0, max=1.0, update=update_view)
# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
def create_curve(col, name, points, thickness, part_id, color, circular=False):
curve = bpy.data.curves.new(name, 'CURVE')
curve.dimensions = '3D'
obj = bpy.data.objects.new(name, curve)
col.objects.link(obj)
spline = curve.splines.new('POLY')
spline.use_cyclic_u = circular
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p.x, p.y, p.z, 1)
curve.bevel_depth = thickness
obj.data.materials.append(get_fixed_material(part_id, color))
return obj
class OBJECT_OT_DrawSpacetimeSymV5(bpy.types.Operator):
bl_idname = "object.draw_spacetime_sym_v5"
bl_label = "Refresh View"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
p = context.scene.rel_sym_v5
v, R_val = p.velocity, p.radius
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
A = 2.0 * R_val * gamma
offset = Vector((v * A / 2.0, 0, A / 2.0))
F1 = Vector((0,0,0)) - offset
Mid = Vector((v*A/2,0,A/2)) - offset
F2 = Vector((v*A,0,A)) - offset
def get_P_locus(deg):
rad = math.radians(deg)
return Vector((gamma*(R_val*math.cos(rad)+v*R_val), R_val*math.sin(rad), gamma*(R_val+v*(R_val*math.cos(rad))))) - offset
def get_P_rigid(deg, z_v):
t_p = z_v + offset.z
rad = math.radians(deg)
return Vector((v*t_p - offset.x + R_val*math.cos(rad), R_val*math.sin(rad), z_v))
col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
if COLLECTION_NAME not in context.scene.collection.children:
context.scene.collection.children.link(col)
for obj in col.objects:
bpy.data.objects.remove(obj, do_unlink=True)
# Longitudinal Ellipse & Spheroid
major_2a = math.sqrt(A**2 * (v**2 + 1) + 4 * R_val**2)
dir_vec = F2 - F1
rot_quat = Vector((1,0,0)).rotation_difference(dir_vec)
if p.show_st_ellipse:
v_pts =[Mid + rot_quat @ Vector((major_2a/2 * math.cos(math.radians(d)), 0, R_val * math.sin(math.radians(d)))) for d in range(0, 365, 5)]
create_curve(col, "ST_Ellipse", v_pts, p.thick_st_ellipse, "ST_Ellipse", p.color_st_ellipse, True)
if p.show_spheroid:
mesh = bpy.data.meshes.new("Spheroid")
obj = bpy.data.objects.new("Spheroid", mesh)
col.objects.link(obj)
import bmesh
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
for vb in bm.verts:
vb.co.x *= (major_2a/2.0)
vb.co.y *= R_val
vb.co.z *= R_val
bmesh.ops.rotate(bm, cent=(0,0,0), matrix=rot_quat.to_matrix(), verts=bm.verts)
bmesh.ops.translate(bm, vec=Mid, verts=bm.verts)
bm.to_mesh(mesh)
bm.free()
obj.data.materials.append(get_fixed_material("Spheroid", p.color_spheroid))
# Wavefronts
if p.show_wavefronts:
for i, name in[(0, "Mid"), (-1, "Low"), (1, "Up")]:
trans = (F2 - Mid) * i
pts =[get_P_locus(d*5) + trans for d in range(73)]
create_curve(col, f"WF_{name}", pts, p.thick_wavefronts, "Wavefront", p.color_wavefronts, True)
# Rays
if p.ray_mode == '2':
PtA = get_P_locus(p.base_angle)
if p.show_ray_a_out: create_curve(col, "ArmA_O",[F1, PtA], p.thick_ray_a_out, "ArmA_Out", p.color_ray_a_out)
if p.show_ray_a_in: create_curve(col, "ArmA_I", [PtA, F2], p.thick_ray_a_in, "ArmA_In", p.color_ray_a_in)
PtB = get_P_locus(p.base_angle + p.arm_b_angle)
if p.show_ray_b_out: create_curve(col, "ArmB_O",[F1, PtB], p.thick_ray_b_out, "ArmB_Out", p.color_ray_b_out)
if p.show_ray_b_in: create_curve(col, "ArmB_I", [PtB, F2], p.thick_ray_b_in, "ArmB_In", p.color_ray_b_in)
else:
for i in range(12):
deg = p.base_angle + (i*30)
Pt = get_P_locus(deg)
if p.show_ray_classic:
create_curve(col, f"Cl_{i}_O", [F1, Pt], p.thick_ray_a_out, "Classic_Out", p.color_ray_a_out)
create_curve(col, f"Cl_{i}_I", [Pt, F2], p.thick_ray_a_in, "Classic_In", p.color_ray_a_in)
if p.show_ray_converge:
Ps, Pe = get_P_rigid(deg, F1.z), get_P_rigid(deg, F2.z)
create_curve(col, f"Cv_{i}_O", [Ps, Mid], p.thick_ray_b_out, "Conv_Out", p.color_ray_b_out)
create_curve(col, f"Cv_{i}_I", [Mid, Pe], p.thick_ray_b_in, "Conv_In", p.color_ray_b_in)
if p.show_ray_ell_conv:
PtL, PtU = Pt+(F1-Mid), Pt+(F2-Mid)
create_curve(col, f"ElCv_{i}_O",[PtL, Mid], p.thick_ray_b_out, "EllConv_Out", p.color_ray_b_out)
create_curve(col, f"ElCv_{i}_I",[Mid, PtU], p.thick_ray_b_in, "EllConv_In", p.color_ray_b_in)
# Helpers
if p.show_skeleton:
for i in range(12):
Pl = get_P_locus(i*30) + offset
Ms = Vector((Pl.x+v*(0-Pl.z), Pl.y, 0)) - offset
Me = Vector((Pl.x+v*(A-Pl.z), Pl.y, A)) - offset
create_curve(col, f"Sk_{i}", [Ms, Me], p.skel_thick, "Skeleton", p.color_skeleton)
if p.show_circle_rings:
for z_v in[F1.z, 0.0, F2.z]:
pts =[get_P_rigid(i*5, z_v) for i in range(73)]
create_curve(col, f"T_{z_v}", pts, p.thick_circle_rings, "Tube", p.color_circle_rings, True)
context.scene["rel_v5_info"] = f"Velocity: {v:.6f} c\nTarget X: {F2.x+offset.x:.4f}\nA (Invariant): {A:.4f}"
return {'FINISHED'}
# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class VIEW3D_PT_RelSymV5(bpy.types.Panel):
bl_label = "Symmetric 4D Controller"
bl_idname = "VIEW3D_PT_rel_sym_v5"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
p = context.scene.rel_sym_v5
layout.operator("wm.copy_full_script", text="全コードをコピー", icon='COPY_ID')
# Physics Setup
box = layout.box()
row = box.row(align=True)
row.prop(p, "calc_mode", expand=True)
setup = box.box()
setup.prop(p, "radius")
if p.calc_mode == 'X':
setup.prop(p, "target_x", text="Target X")
else:
setup.prop(p, "velocity", text="Velocity (v/c)")
# Rays Configuration
cfg = layout.box()
cfg.label(text="Rays Configuration", icon='LIGHT_SUN')
row = cfg.row(align=True)
row.prop(p, "ray_mode", text="")
row.prop(p, "base_angle", text="Base")
# Ray Visual Details
det = layout.box()
det.label(text="Ray Details", icon='NODE_MATERIAL')
if p.ray_mode == '2':
configs =[
("Arm A (Ref)", "show_ray_a_out", "show_ray_a_in", "thick_ray_a_out", "color_ray_a_out", "thick_ray_a_in", "color_ray_a_in", None),
("Arm B (Adj)", "show_ray_b_out", "show_ray_b_in", "thick_ray_b_out", "color_ray_b_out", "thick_ray_b_in", "color_ray_b_in", "arm_b_angle")
]
for lbl, so, si, to, co, ti, ci, ang in configs:
b = det.box()
r = b.row()
r.label(text=lbl)
r.prop(p, so, text="", icon='HIDE_OFF')
r.prop(p, si, text="", icon='HIDE_OFF')
if ang:
b.prop(p, ang, text="Offset")
c = b.column(align=True)
r = c.row(align=True)
r.prop(p, to, text="Thick Out")
r.prop(p, co, text="")
r = c.row(align=True)
r.prop(p, ti, text="Thick In")
r.prop(p, ci, text="")
# Wavefronts
wf = layout.box()
wf.label(text="Wavefronts", icon='SPHERE')
row = wf.row()
row.prop(p, "show_wavefronts", text="Show Rings")
row.prop(p, "color_wavefronts", text="")
wf.prop(p, "thick_wavefronts", text="Thickness")
# Spacetime Structure
st = layout.box()
st.label(text="Spacetime Structure", icon='WORLD')
row = st.row(align=True)
row.prop(p, "show_st_ellipse", text="Longit. Ellipse")
row.prop(p, "color_st_ellipse", text="")
if p.show_st_ellipse:
st.prop(p, "thick_st_ellipse", text="Thickness")
row = st.row(align=True)
row.prop(p, "show_spheroid", text="3D Spheroid")
row.prop(p, "color_spheroid", text="")
# Visual Aids
vbox = layout.box()
vbox.label(text="Visual Aids", icon='MESH_GRID')
row = vbox.row(align=True)
row.prop(p, "show_circle_rings", text="Tube Rings")
row.prop(p, "color_circle_rings", text="")
if p.show_circle_rings:
vbox.prop(p, "thick_circle_rings", text="Thickness")
row = vbox.row(align=True)
row.prop(p, "show_skeleton", text="Skeleton")
row.prop(p, "color_skeleton", text="")
if p.show_skeleton:
vbox.prop(p, "skel_thick", text="Thickness")
layout.operator("object.draw_spacetime_sym_v5", text="Refresh View", icon='FILE_REFRESH')
class VIEW3D_PT_RelAccounting(bpy.types.Panel):
bl_label = "Time Accounting"
bl_idname = "VIEW3D_PT_rel_accounting"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
layout.label(text="Calculated Results", icon='TIME')
info_text = context.scene.get("rel_v5_info", "")
for line in info_text.split("\n"):
if line.strip():
layout.label(text=line)
layout.operator("wm.copy_rel_info", text="数値をコピー", icon='COPYDOWN')
class VIEW3D_PT_RelLinks(bpy.types.Panel):
bl_label = "Theory Links"
bl_idname = "VIEW3D_PT_rel_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_rel_url", text=l["label"])
op.url = l["url"]
class VIEW3D_PT_RelSystem(bpy.types.Panel):
bl_label = "System"
bl_idname = "VIEW3D_PT_rel_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_rel_addon", icon='CANCEL', text="アドオン削除")
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class WM_OT_CopyFullScript(bpy.types.Operator):
bl_idname = "wm.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.rel_sym_v5
M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
if not texts:
self.report({'ERROR'}, "ソースコードが見つかりません。")
return {'CANCELLED'}
d_str = "CURRENT_DEFAULTS = {\n"
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if isinstance(val, str):
d_str += f' "{k}": "{val}",\n'
elif hasattr(val, "__len__"):
d_str += f' "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
elif isinstance(val, float):
d_str += f' "{k}": {val:.4f},\n'
else:
d_str += f' "{k}": {val},\n'
d_str += "}\n"
# 複数テキストがある場合、一番新しいもの(リストの最後)を基準にする
target_text = texts[-1]
new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
# 先頭のタイムスタンプを更新
lines = new_code.split('\n')
if lines and lines[0].startswith("# "):
lines[0] = f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)"
context.window_manager.clipboard = '\n'.join(lines)
self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
return {'FINISHED'}
class WM_OT_OpenRelUrl(bpy.types.Operator):
bl_idname = "wm.open_rel_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class WM_OT_CopyRelInfo(bpy.types.Operator):
bl_idname = "wm.copy_rel_info"
bl_label = "Copy Info"
def execute(self, context):
context.window_manager.clipboard = context.scene.get("rel_v5_info", "")
return {'FINISHED'}
class WM_OT_RemoveRelAddon(bpy.types.Operator):
bl_idname = "wm.remove_rel_addon"
bl_label = "Remove"
def execute(self, context):
unregister()
return {'FINISHED'}
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
PG_RelativitySymV5,
OBJECT_OT_DrawSpacetimeSymV5,
WM_OT_CopyFullScript,
WM_OT_OpenRelUrl,
WM_OT_CopyRelInfo,
WM_OT_RemoveRelAddon,
VIEW3D_PT_RelSymV5,
VIEW3D_PT_RelAccounting,
VIEW3D_PT_RelLinks,
VIEW3D_PT_RelSystem
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.rel_sym_v5 = bpy.props.PointerProperty(type=PG_RelativitySymV5)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if hasattr(bpy.types.Scene, "rel_sym_v5"):
del bpy.types.Scene.rel_sym_v5
if __name__ == "__main__":
register()
# 2026-03-05 11:06:51 Version (Updated Defaults)
# 2026-02-21 09:00:00 Thickness Integrated Master good
# Blender 4.3+ Exclusive
bl_info = {
"name": "Symmetric Spacetime (v5.46 Dynamic Defaults)",
"author": "zionadchat Gemini",
"version": (5, 46),
"blender": (4, 3, 0),
"location": "View3D > Sidebar",
"description": "Full thickness control & Dynamic copy function fixed",
"category": "Physics",
}
import bpy
import webbrowser
import math
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# DYNAMIC DEFAULTS
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"calc_mode": "VEL",
"velocity": 0.6000,
"radius": 10.0000,
"target_x": 15.0000,
"base_angle": 0.0000,
"arm_b_angle": 90.0000,
"ray_mode": "2",
"show_ray_classic": True,
"show_ray_converge": True,
"show_ray_ell_conv": True,
"show_ray_a_out": True,
"show_ray_a_in": True,
"show_ray_b_out": True,
"show_ray_b_in": True,
"color_ray_a_out": (1.000, 0.500, 0.000, 1.000),
"thick_ray_a_out": 1.4100,
"color_ray_a_in": (0.005, 0.008, 0.271, 1.000),
"thick_ray_a_in": 0.7300,
"color_ray_b_out": (0.400, 0.000, 0.000, 1.000),
"thick_ray_b_out": 0.8000,
"color_ray_b_in": (0.000, 0.500, 0.000, 1.000),
"thick_ray_b_in": 0.3000,
"show_spheroid": True,
"color_spheroid": (0.200, 0.100, 0.500, 0.100),
"show_st_ellipse": True,
"color_st_ellipse": (1.000, 1.000, 1.000, 0.800),
"thick_st_ellipse": 0.0500,
"show_wavefronts": True,
"color_wavefronts": (0.160, 0.000, 0.050, 0.400),
"thick_wavefronts": 0.4000,
"show_circle_rings": True,
"color_circle_rings": (0.100, 0.200, 0.300, 0.300),
"thick_circle_rings": 0.1800,
"show_skeleton": True,
"color_skeleton": (0.500, 0.500, 0.500, 0.200),
"skel_thick": 0.0100,
}
# <END_DICT>
TAB_NAME = "Relativity_Sym_v5"
COLLECTION_NAME = "Relativity_Sym_Output"
ADDON_LINKS = (
{"label": "理論背景: Notion 資料", "url": "<https://www.notion.so/Einstein-from-20260119-main-2edc563be1b080bb94d9f6e5b667fdec>"},
{"label": "Blender シミュレーション解説", "url": "<https://www.notion.so/blender-deviationtokyo-30c293bfbb2980118c25dfc02259b096>"},
)
# ------------------------------------------------------------------------
# Material Fix
# ------------------------------------------------------------------------
def get_fixed_material(part_id, color):
mat_name = f"Mat_{part_id}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]
if "Emission Color" in bsdf.inputs: bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
if "Emission Strength" in bsdf.inputs: bsdf.inputs["Emission Strength"].default_value = 1.0
mat.diffuse_color = color
return mat
# ------------------------------------------------------------------------
# Properties & Physics
# ------------------------------------------------------------------------
_is_updating = False
def update_physics(self, context):
global _is_updating
if _is_updating:
return
_is_updating = True
try:
p = self
if p.calc_mode == 'X':
p.velocity = p.target_x / math.sqrt(p.target_x**2 + (2*p.radius)**2)
else:
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - p.velocity**2))
p.target_x = p.velocity * 2 * p.radius * gamma
finally:
_is_updating = False
update_view(self, context)
def update_view(self, context):
try: bpy.ops.object.draw_spacetime_sym_v5('INVOKE_DEFAULT')
except: pass
class PG_RelativitySymV5(bpy.types.PropertyGroup):
calc_mode: bpy.props.EnumProperty(items=[('VEL', "Velocity Mode", ""), ('X', "Target X Mode", "")], default=CURRENT_DEFAULTS["calc_mode"], update=update_view)
velocity: bpy.props.FloatProperty(name="Velocity", default=CURRENT_DEFAULTS["velocity"], min=0.0, max=0.999, update=update_physics)
target_x: bpy.props.FloatProperty(name="Target X", default=CURRENT_DEFAULTS["target_x"], min=0.001, soft_max=1000.0, update=update_physics)
radius: bpy.props.FloatProperty(name="Radius", default=CURRENT_DEFAULTS["radius"], min=0.001, soft_max=100.0, update=update_physics)
base_angle: bpy.props.FloatProperty(name="Base Angle", default=CURRENT_DEFAULTS["base_angle"], min=0, max=360, update=update_view)
arm_b_angle: bpy.props.FloatProperty(name="Arm B Angle", default=CURRENT_DEFAULTS["arm_b_angle"], min=0, max=360, update=update_view)
ray_mode: bpy.props.EnumProperty(items=[('2', "2 Rays", ""), ('12', "12 Rays", "")], default=CURRENT_DEFAULTS["ray_mode"], update=update_view)
show_ray_classic: bpy.props.BoolProperty(name="Classic", default=CURRENT_DEFAULTS["show_ray_classic"], update=update_view)
show_ray_converge: bpy.props.BoolProperty(name="Converge", default=CURRENT_DEFAULTS["show_ray_converge"], update=update_view)
show_ray_ell_conv: bpy.props.BoolProperty(name="Ellipse Conv", default=CURRENT_DEFAULTS["show_ray_ell_conv"], update=update_view)
color_ray_a_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_out"], update=update_view)
color_ray_a_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_a_in"], update=update_view)
color_ray_b_out: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_out"], update=update_view)
color_ray_b_in: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_ray_b_in"], update=update_view)
thick_ray_a_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_out"], min=0, max=2.0, update=update_view)
thick_ray_a_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_a_in"], min=0, max=2.0, update=update_view)
thick_ray_b_out: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_out"], min=0, max=2.0, update=update_view)
thick_ray_b_in: bpy.props.FloatProperty(default=CURRENT_DEFAULTS["thick_ray_b_in"], min=0, max=2.0, update=update_view)
show_ray_a_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_out"], update=update_view)
show_ray_a_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_a_in"], update=update_view)
show_ray_b_out: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_out"], update=update_view)
show_ray_b_in: bpy.props.BoolProperty(default=CURRENT_DEFAULTS["show_ray_b_in"], update=update_view)
show_spheroid: bpy.props.BoolProperty(name="Spheroid Surface", default=CURRENT_DEFAULTS["show_spheroid"], update=update_view)
color_spheroid: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_spheroid"], update=update_view)
show_st_ellipse: bpy.props.BoolProperty(name="Longit. Ellipse", default=CURRENT_DEFAULTS["show_st_ellipse"], update=update_view)
color_st_ellipse: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_st_ellipse"], update=update_view)
thick_st_ellipse: bpy.props.FloatProperty(name="Thick Ellipse", default=CURRENT_DEFAULTS["thick_st_ellipse"], min=0.0, max=1.0, update=update_view)
show_wavefronts: bpy.props.BoolProperty(name="Wavefronts (Horiz.)", default=CURRENT_DEFAULTS["show_wavefronts"], update=update_view)
color_wavefronts: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_wavefronts"], update=update_view)
thick_wavefronts: bpy.props.FloatProperty(name="Thick WF", default=CURRENT_DEFAULTS["thick_wavefronts"], min=0.0, max=2.0, update=update_view)
show_circle_rings: bpy.props.BoolProperty(name="Tube Rings", default=CURRENT_DEFAULTS["show_circle_rings"], update=update_view)
color_circle_rings: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_circle_rings"], update=update_view)
thick_circle_rings: bpy.props.FloatProperty(name="Thick Rings", default=CURRENT_DEFAULTS["thick_circle_rings"], min=0.0, max=1.0, update=update_view)
show_skeleton: bpy.props.BoolProperty(name="Skeleton", default=CURRENT_DEFAULTS["show_skeleton"], update=update_view)
color_skeleton: bpy.props.FloatVectorProperty(subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["color_skeleton"], update=update_view)
skel_thick: bpy.props.FloatProperty(name="Thick Skeleton", default=CURRENT_DEFAULTS["skel_thick"], min=0.0, max=1.0, update=update_view)
# ------------------------------------------------------------------------
# Drawing logic
# ------------------------------------------------------------------------
def create_curve(col, name, points, thickness, part_id, color, circular=False):
curve = bpy.data.curves.new(name, 'CURVE')
curve.dimensions = '3D'
obj = bpy.data.objects.new(name, curve)
col.objects.link(obj)
spline = curve.splines.new('POLY')
spline.use_cyclic_u = circular
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p.x, p.y, p.z, 1)
curve.bevel_depth = thickness
obj.data.materials.append(get_fixed_material(part_id, color))
return obj
class OBJECT_OT_DrawSpacetimeSymV5(bpy.types.Operator):
bl_idname = "object.draw_spacetime_sym_v5"
bl_label = "Refresh View"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
p = context.scene.rel_sym_v5
v, R_val = p.velocity, p.radius
gamma = 1.0 / math.sqrt(max(0.0001, 1.0 - v**2))
A = 2.0 * R_val * gamma
offset = Vector((v * A / 2.0, 0, A / 2.0))
F1 = Vector((0,0,0)) - offset
Mid = Vector((v*A/2,0,A/2)) - offset
F2 = Vector((v*A,0,A)) - offset
def get_P_locus(deg):
rad = math.radians(deg)
return Vector((gamma*(R_val*math.cos(rad)+v*R_val), R_val*math.sin(rad), gamma*(R_val+v*(R_val*math.cos(rad))))) - offset
def get_P_rigid(deg, z_v):
t_p = z_v + offset.z
rad = math.radians(deg)
return Vector((v*t_p - offset.x + R_val*math.cos(rad), R_val*math.sin(rad), z_v))
col = bpy.data.collections.get(COLLECTION_NAME) or bpy.data.collections.new(COLLECTION_NAME)
if COLLECTION_NAME not in context.scene.collection.children:
context.scene.collection.children.link(col)
for obj in col.objects:
bpy.data.objects.remove(obj, do_unlink=True)
# Longitudinal Ellipse & Spheroid
major_2a = math.sqrt(A**2 * (v**2 + 1) + 4 * R_val**2)
dir_vec = F2 - F1
rot_quat = Vector((1,0,0)).rotation_difference(dir_vec)
if p.show_st_ellipse:
v_pts =[Mid + rot_quat @ Vector((major_2a/2 * math.cos(math.radians(d)), 0, R_val * math.sin(math.radians(d)))) for d in range(0, 365, 5)]
create_curve(col, "ST_Ellipse", v_pts, p.thick_st_ellipse, "ST_Ellipse", p.color_st_ellipse, True)
if p.show_spheroid:
mesh = bpy.data.meshes.new("Spheroid")
obj = bpy.data.objects.new("Spheroid", mesh)
col.objects.link(obj)
import bmesh
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
for vb in bm.verts:
vb.co.x *= (major_2a/2.0)
vb.co.y *= R_val
vb.co.z *= R_val
bmesh.ops.rotate(bm, cent=(0,0,0), matrix=rot_quat.to_matrix(), verts=bm.verts)
bmesh.ops.translate(bm, vec=Mid, verts=bm.verts)
bm.to_mesh(mesh)
bm.free()
obj.data.materials.append(get_fixed_material("Spheroid", p.color_spheroid))
# Wavefronts
if p.show_wavefronts:
for i, name in[(0, "Mid"), (-1, "Low"), (1, "Up")]:
trans = (F2 - Mid) * i
pts =[get_P_locus(d*5) + trans for d in range(73)]
create_curve(col, f"WF_{name}", pts, p.thick_wavefronts, "Wavefront", p.color_wavefronts, True)
# Rays
if p.ray_mode == '2':
PtA = get_P_locus(p.base_angle)
if p.show_ray_a_out: create_curve(col, "ArmA_O",[F1, PtA], p.thick_ray_a_out, "ArmA_Out", p.color_ray_a_out)
if p.show_ray_a_in: create_curve(col, "ArmA_I", [PtA, F2], p.thick_ray_a_in, "ArmA_In", p.color_ray_a_in)
PtB = get_P_locus(p.base_angle + p.arm_b_angle)
if p.show_ray_b_out: create_curve(col, "ArmB_O",[F1, PtB], p.thick_ray_b_out, "ArmB_Out", p.color_ray_b_out)
if p.show_ray_b_in: create_curve(col, "ArmB_I", [PtB, F2], p.thick_ray_b_in, "ArmB_In", p.color_ray_b_in)
else:
for i in range(12):
deg = p.base_angle + (i*30)
Pt = get_P_locus(deg)
if p.show_ray_classic:
create_curve(col, f"Cl_{i}_O", [F1, Pt], p.thick_ray_a_out, "Classic_Out", p.color_ray_a_out)
create_curve(col, f"Cl_{i}_I", [Pt, F2], p.thick_ray_a_in, "Classic_In", p.color_ray_a_in)
if p.show_ray_converge:
Ps, Pe = get_P_rigid(deg, F1.z), get_P_rigid(deg, F2.z)
create_curve(col, f"Cv_{i}_O", [Ps, Mid], p.thick_ray_b_out, "Conv_Out", p.color_ray_b_out)
create_curve(col, f"Cv_{i}_I", [Mid, Pe], p.thick_ray_b_in, "Conv_In", p.color_ray_b_in)
if p.show_ray_ell_conv:
PtL, PtU = Pt+(F1-Mid), Pt+(F2-Mid)
create_curve(col, f"ElCv_{i}_O",[PtL, Mid], p.thick_ray_b_out, "EllConv_Out", p.color_ray_b_out)
create_curve(col, f"ElCv_{i}_I",[Mid, PtU], p.thick_ray_b_in, "EllConv_In", p.color_ray_b_in)
# Helpers
if p.show_skeleton:
for i in range(12):
Pl = get_P_locus(i*30) + offset
Ms = Vector((Pl.x+v*(0-Pl.z), Pl.y, 0)) - offset
Me = Vector((Pl.x+v*(A-Pl.z), Pl.y, A)) - offset
create_curve(col, f"Sk_{i}", [Ms, Me], p.skel_thick, "Skeleton", p.color_skeleton)
if p.show_circle_rings:
for z_v in[F1.z, 0.0, F2.z]:
pts =[get_P_rigid(i*5, z_v) for i in range(73)]
create_curve(col, f"T_{z_v}", pts, p.thick_circle_rings, "Tube", p.color_circle_rings, True)
context.scene["rel_v5_info"] = f"Velocity: {v:.6f} c\nTarget X: {F2.x+offset.x:.4f}\nA (Invariant): {A:.4f}"
return {'FINISHED'}
# ------------------------------------------------------------------------
# UI Panels
# ------------------------------------------------------------------------
class VIEW3D_PT_RelSymV5(bpy.types.Panel):
bl_label = "Symmetric 4D Controller"
bl_idname = "VIEW3D_PT_rel_sym_v5"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
p = context.scene.rel_sym_v5
layout.operator("wm.copy_full_script", text="全コードをコピー", icon='COPY_ID')
# Physics Setup
box = layout.box()
row = box.row(align=True)
row.prop(p, "calc_mode", expand=True)
setup = box.box()
setup.prop(p, "radius")
if p.calc_mode == 'X':
setup.prop(p, "target_x", text="Target X")
else:
setup.prop(p, "velocity", text="Velocity (v/c)")
# Rays Configuration
cfg = layout.box()
cfg.label(text="Rays Configuration", icon='LIGHT_SUN')
row = cfg.row(align=True)
row.prop(p, "ray_mode", text="")
row.prop(p, "base_angle", text="Base")
# Ray Visual Details
det = layout.box()
det.label(text="Ray Details", icon='NODE_MATERIAL')
if p.ray_mode == '2':
configs =[
("Arm A (Ref)", "show_ray_a_out", "show_ray_a_in", "thick_ray_a_out", "color_ray_a_out", "thick_ray_a_in", "color_ray_a_in", None),
("Arm B (Adj)", "show_ray_b_out", "show_ray_b_in", "thick_ray_b_out", "color_ray_b_out", "thick_ray_b_in", "color_ray_b_in", "arm_b_angle")
]
for lbl, so, si, to, co, ti, ci, ang in configs:
b = det.box()
r = b.row()
r.label(text=lbl)
r.prop(p, so, text="", icon='HIDE_OFF')
r.prop(p, si, text="", icon='HIDE_OFF')
if ang:
b.prop(p, ang, text="Offset")
c = b.column(align=True)
r = c.row(align=True)
r.prop(p, to, text="Thick Out")
r.prop(p, co, text="")
r = c.row(align=True)
r.prop(p, ti, text="Thick In")
r.prop(p, ci, text="")
# Wavefronts
wf = layout.box()
wf.label(text="Wavefronts", icon='SPHERE')
row = wf.row()
row.prop(p, "show_wavefronts", text="Show Rings")
row.prop(p, "color_wavefronts", text="")
wf.prop(p, "thick_wavefronts", text="Thickness")
# Spacetime Structure
st = layout.box()
st.label(text="Spacetime Structure", icon='WORLD')
row = st.row(align=True)
row.prop(p, "show_st_ellipse", text="Longit. Ellipse")
row.prop(p, "color_st_ellipse", text="")
if p.show_st_ellipse:
st.prop(p, "thick_st_ellipse", text="Thickness")
row = st.row(align=True)
row.prop(p, "show_spheroid", text="3D Spheroid")
row.prop(p, "color_spheroid", text="")
# Visual Aids
vbox = layout.box()
vbox.label(text="Visual Aids", icon='MESH_GRID')
row = vbox.row(align=True)
row.prop(p, "show_circle_rings", text="Tube Rings")
row.prop(p, "color_circle_rings", text="")
if p.show_circle_rings:
vbox.prop(p, "thick_circle_rings", text="Thickness")
row = vbox.row(align=True)
row.prop(p, "show_skeleton", text="Skeleton")
row.prop(p, "color_skeleton", text="")
if p.show_skeleton:
vbox.prop(p, "skel_thick", text="Thickness")
layout.operator("object.draw_spacetime_sym_v5", text="Refresh View", icon='FILE_REFRESH')
class VIEW3D_PT_RelAccounting(bpy.types.Panel):
bl_label = "Time Accounting"
bl_idname = "VIEW3D_PT_rel_accounting"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
layout.label(text="Calculated Results", icon='TIME')
info_text = context.scene.get("rel_v5_info", "")
for line in info_text.split("\n"):
if line.strip():
layout.label(text=line)
layout.operator("wm.copy_rel_info", text="数値をコピー", icon='COPYDOWN')
class VIEW3D_PT_RelLinks(bpy.types.Panel):
bl_label = "Theory Links"
bl_idname = "VIEW3D_PT_rel_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_rel_url", text=l["label"])
op.url = l["url"]
class VIEW3D_PT_RelSystem(bpy.types.Panel):
bl_label = "System"
bl_idname = "VIEW3D_PT_rel_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_rel_addon", icon='CANCEL', text="アドオン削除")
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class WM_OT_CopyFullScript(bpy.types.Operator):
bl_idname = "wm.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
p = context.scene.rel_sym_v5
M_START, M_END = "# <BEG" + "IN_DICT>", "# <EN" + "D_DICT>"
texts =[t.as_string() for t in bpy.data.texts if M_START in t.as_string()]
if not texts:
self.report({'ERROR'}, "ソースコードが見つかりません。")
return {'CANCELLED'}
d_str = "CURRENT_DEFAULTS = {\n"
for k in CURRENT_DEFAULTS.keys():
val = getattr(p, k)
if isinstance(val, str):
d_str += f' "{k}": "{val}",\n'
elif hasattr(val, "__len__"):
d_str += f' "{k}": ({val[0]:.3f}, {val[1]:.3f}, {val[2]:.3f}, {val[3]:.3f}),\n'
elif isinstance(val, float):
d_str += f' "{k}": {val:.4f},\n'
else:
d_str += f' "{k}": {val},\n'
d_str += "}\n"
# 複数テキストがある場合、一番新しいもの(リストの最後)を基準にする
target_text = texts[-1]
new_code = target_text.split(M_START)[0] + M_START + "\n" + d_str + M_END + target_text.split(M_END)[1]
# 先頭のタイムスタンプを更新
lines = new_code.split('\n')
if lines and lines[0].startswith("# "):
lines[0] = f"# {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Version (Updated Defaults)"
context.window_manager.clipboard = '\n'.join(lines)
self.report({'INFO'}, "現在のUI設定を反映したコードをコピーしました!")
return {'FINISHED'}
class WM_OT_OpenRelUrl(bpy.types.Operator):
bl_idname = "wm.open_rel_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class WM_OT_CopyRelInfo(bpy.types.Operator):
bl_idname = "wm.copy_rel_info"
bl_label = "Copy Info"
def execute(self, context):
context.window_manager.clipboard = context.scene.get("rel_v5_info", "")
return {'FINISHED'}
class WM_OT_RemoveRelAddon(bpy.types.Operator):
bl_idname = "wm.remove_rel_addon"
bl_label = "Remove"
def execute(self, context):
unregister()
return {'FINISHED'}
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes = (
PG_RelativitySymV5,
OBJECT_OT_DrawSpacetimeSymV5,
WM_OT_CopyFullScript,
WM_OT_OpenRelUrl,
WM_OT_CopyRelInfo,
WM_OT_RemoveRelAddon,
VIEW3D_PT_RelSymV5,
VIEW3D_PT_RelAccounting,
VIEW3D_PT_RelLinks,
VIEW3D_PT_RelSystem
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.rel_sym_v5 = bpy.props.PointerProperty(type=PG_RelativitySymV5)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if hasattr(bpy.types.Scene, "rel_sym_v5"):
del bpy.types.Scene.rel_sym_v5
if __name__ == "__main__":
register()
Blenderが落ちてしまう(クラッシュ・フリーズする)原因は、**プロパティの「上限値の制限」による無限ループ(RecursionError)**です。
Radius(半径)を大きくすると、内部で計算される Target X の値が100を超えます。しかし、コード内で Target X の最大値が max=100.0 と設定されていたため、Blenderが強制的に100に書き換えてしまい、「値が変更されたので再計算する」という処理が無限に繰り返されてクラッシュしていました。
修正内容
target_x の物理的な上限(max=100.0)を撤廃し、UI上の目安(soft_max)に変更しました。
更新処理(update_physics)が互いに呼び出し合って無限ループしないよう、**安全フラグ(_is_updating)**を導入しました。
Velocity のスライダーを動かした際にも Target X が連動して再計算されるように改善しました。
以下が修正済みの全コードです。既存のスクリプトをこちらに丸ごと置き換えて実行してください。