addonjack
https://aistudio.google.com/app/prompts/1yvvrY8dGxh5u0ssaBw57LwKyKktKckc4
# Copied at: 20260320 15:33:26
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = " [ Minkowski ] "
# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V8 ###"
bl_info = {
"name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
"author": "zionadchat",
"version": (1, 6, 2),
"blender": (5, 0, 0),
"location": "3D View > Sidebar",
"description": "Stable Minkowski Diagram with Intersections",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
ADDON_LINKS = (
{"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
{"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
{"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)
# ==============================================================================
# デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_preview": True,
"beta": 0.6000, "thickness": 0.0300,
"precision": 4,
"vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
"vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
"vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
"vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
"vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
"vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>
# ==============================================================================
# 描画 & マテリアル制御
# ==============================================================================
def cleanup_minkowski_data():
"""PREFIXが付いた全データブロックを完全に削除"""
for obj in bpy.data.objects:
if obj.name.startswith(PREFIX):
bpy.data.objects.remove(obj, do_unlink=True)
for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
for block in data:
if block.name.startswith(PREFIX) and block.users == 0:
data.remove(block)
def get_or_create_material(name_suffix, color):
mat_name = f"{PREFIX}_Mat_{name_suffix}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
bsdf.inputs['Base Color'].default_value = color
if 'Alpha' in bsdf.inputs:
bsdf.inputs['Alpha'].default_value = color[3]
return mat
def draw_minkowski_diagram(context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return
cleanup_minkowski_data()
if not props.show_preview: return
target_col = context.scene.collection
beta = props.beta
thickness = props.thickness
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
def add_line(name, points, color, is_visible):
if not is_visible: return
curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
curve.dimensions = '3D'
curve.fill_mode = 'FULL'
curve.bevel_depth = thickness
spline = curve.splines.new('POLY')
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p[0], p[1], 0, 1)
obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
target_col.objects.link(obj)
obj.data.materials.append(get_or_create_material(name, color))
# 1. 静止系軸
add_line("Axis_S",[(0, -6), (0, 6)], props.col_axis, props.vis_axis)
add_line("Axis_X",[(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
# 2. 光円錐
add_line("Light_P",[(-6, -6), (6, 6)], props.col_light, props.vis_light)
add_line("Light_M",[(-6, 6), (6, -6)], props.col_light, props.vis_light)
# 3. 不変双曲線 (sinh/cosh)
q_range =[i * 0.1 - 2.5 for i in range(51)]
add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
add_line("Hyp_S",[(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)
# 4. 運動系軸
add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
add_line("Axis_XP",[(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)
# 5. メモリ校正用マーカー (交点)
if props.vis_axis or props.vis_ctp:
m_mat = get_or_create_material("Marker", props.col_axis)
for loc in[(beta*gamma, gamma), (gamma, beta*gamma)]:
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
marker = context.active_object
marker.name = f"{PREFIX}_Marker"
marker.data.materials.append(m_mat)
# ==============================================================================
# タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
draw_minkowski_diagram(bpy.context)
return None
def on_property_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(trigger_update, first_interval=0.03)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class PG_MinkowskiProps(PropertyGroup):
show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
precision: IntProperty(name="Decimals", default=CURRENT_DEFAULTS.get('precision', 4), min=0, max=9, description="Decimal places for intersections")
vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)
# ==============================================================================
# OPERATORS
# ==============================================================================
class OT_CopyMinkowskiFullCode(Operator):
bl_idname = f"{OP_PREFIX}.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
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 ID not found in Text Editor.")
return {'CANCELLED'}
code = target_text.as_string()
def f4(v): return tuple(round(x, 4) for x in v)
p = props.precision
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_preview": {props.show_preview},\n'
new_dict += f' "beta": {props.beta:.{p}f}, "thickness": {props.thickness:.4f},\n'
new_dict += f' "precision": {props.precision},\n'
new_dict += f' "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
new_dict += f' "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
new_dict += f' "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
new_dict += f' "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
new_dict += f' "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
new_dict += f' "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
new_dict += "}\n"
try:
start_tag = "".join(["#", " <BEGIN_DICT>"])
end_tag = "".join(["#", " <END_DICT>"])
if start_tag not in code or end_tag not in code:
self.report({'ERROR'}, "Dictionary tags missing.")
return {'CANCELLED'}
pre_part = code.split(start_tag, 1)[0]
post_part = code.split(end_tag, 1)[1]
if pre_part.startswith("# Copied at:"):
parts = pre_part.split("\n", 1)
if len(parts) > 1:
pre_part = parts[1]
# 年月日を YYYYMMDD HH:MM:SS の形式で付与
final_code = (
f"# Copied at: {datetime.now().strftime('%Y%m%d %H:%M:%S')}\n" +
pre_part + start_tag + "\n" +
new_dict +
end_tag + post_part
)
context.window_manager.clipboard = final_code
self.report({'INFO'}, "Full script copied to clipboard!")
except Exception as e:
self.report({'ERROR'}, f"Copy failed: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class OT_CopyMinkowskiIntersections(Operator):
bl_idname = f"{OP_PREFIX}.copy_intersections"
bl_label = "Copy Intersections"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
beta = props.beta
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
p = props.precision
t_x, t_ct = beta * gamma, gamma
s_x, s_ct = gamma, beta * gamma
text = (
f"Beta (v/c): {beta:.{p}f}\n"
f"Time Intersection (x, ct): ({t_x:.{p}f}, {t_ct:.{p}f})\n"
f"Space Intersection (x, ct): ({s_x:.{p}f}, {s_ct:.{p}f})"
)
context.window_manager.clipboard = text
self.report({'INFO'}, "Intersections copied to clipboard!")
return {'FINISHED'}
class OT_OpenMinkowskiUrl(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_RemoveMinkowskiAddon(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_MinkowskiPanel(Panel):
bl_label = "Minkowski Stable V7"
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)
p = props.precision
row = layout.row()
row.scale_y = 1.2
row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
# 物理パラメータ (Beta表示を指定桁数に連動)
box = layout.box()
box.label(text="Physical Parameters", icon='PHYSICS')
box.prop(props, "beta", slider=True, text=f"Beta (v/c) : {props.beta:.{p}f}")
box.prop(props, "thickness", slider=True)
# 交点情報エリア
box = layout.box()
box.label(text="Intersections (x, ct)", icon='MESH_GRID')
box.prop(props, "precision", slider=True)
beta = props.beta
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
t_x, t_ct = beta * gamma, gamma
s_x, s_ct = gamma, beta * gamma
col = box.column(align=True)
col.label(text=f"Time (ct' axis): ({t_x:.{p}f}, {t_ct:.{p}f})")
col.label(text=f"Space (x' axis): ({s_x:.{p}f}, {s_ct:.{p}f})")
row = box.row()
row.operator(OT_CopyMinkowskiIntersections.bl_idname, icon='COPYDOWN', text="Copy Intersections")
# 色・表示設定
box = layout.box()
box.label(text="Layers & Colors", icon='COLOR')
layers =[
("vis_axis", "col_axis", "Main Axes"),
("vis_light", "col_light", "Light Cone"),
("vis_thyp", "col_thyp", "Timelike Hyp"),
("vis_shyp", "col_shyp", "Spacelike Hyp"),
("vis_ctp", "col_ctp", "ct' Axis"),
("vis_xp", "col_xp", "x' Axis"),
]
for vis, col, label in layers:
row = box.row(align=True)
row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
row.prop(props, col, text=label)
class PT_MinkowskiLinks(Panel):
bl_label = "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_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]
class PT_MinkowskiSys(Panel):
bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; 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_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_CopyMinkowskiIntersections, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))
def unregister():
cleanup_minkowski_data()
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()
Blenderの仕様上、スライダーの右側にある入力ボックスの表示桁数はシステムで固定されていて動的に変更することができません。
そのため、「スライダーの左側に表示されるラベル名(Beta (v/c))」に、指定した小数点桁数の値を連動させて表示する ように修正しました。
また、「Copy Intersections」ボタンでコピーされるテキストの中にも、指定桁数に合わせたベータ(Beta)の値を一緒に含める ように改良しています。
# Copied at: 15:19:09
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = " [ Minkowski ] "
# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V6 ###"
bl_info = {
"name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
"author": "zionadchat",
"version": (1, 6, 0),
"blender": (5, 0, 0),
"location": "3D View > Sidebar",
"description": "Stable Minkowski Diagram with Intersections",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
ADDON_LINKS = (
{"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
{"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
{"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)
# ==============================================================================
# デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_preview": True,
"beta": 0.6009, "thickness": 0.0300,
"precision": 4,
"vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
"vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
"vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
"vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
"vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
"vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>
# ==============================================================================
# 描画 & マテリアル制御
# ==============================================================================
def cleanup_minkowski_data():
"""PREFIXが付いた全データブロックを完全に削除"""
for obj in bpy.data.objects:
if obj.name.startswith(PREFIX):
bpy.data.objects.remove(obj, do_unlink=True)
for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
for block in data:
if block.name.startswith(PREFIX) and block.users == 0:
data.remove(block)
def get_or_create_material(name_suffix, color):
mat_name = f"{PREFIX}_Mat_{name_suffix}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
bsdf.inputs['Base Color'].default_value = color
if 'Alpha' in bsdf.inputs:
bsdf.inputs['Alpha'].default_value = color[3]
return mat
def draw_minkowski_diagram(context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return
cleanup_minkowski_data()
if not props.show_preview: return
target_col = context.scene.collection
beta = props.beta
thickness = props.thickness
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
def add_line(name, points, color, is_visible):
if not is_visible: return
curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
curve.dimensions = '3D'
curve.fill_mode = 'FULL'
curve.bevel_depth = thickness
spline = curve.splines.new('POLY')
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p[0], p[1], 0, 1)
obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
target_col.objects.link(obj)
obj.data.materials.append(get_or_create_material(name, color))
# 1. 静止系軸
add_line("Axis_S",[(0, -6), (0, 6)], props.col_axis, props.vis_axis)
add_line("Axis_X", [(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
# 2. 光円錐
add_line("Light_P", [(-6, -6), (6, 6)], props.col_light, props.vis_light)
add_line("Light_M",[(-6, 6), (6, -6)], props.col_light, props.vis_light)
# 3. 不変双曲線 (sinh/cosh)
q_range =[i * 0.1 - 2.5 for i in range(51)]
add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
add_line("Hyp_S",[(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)
# 4. 運動系軸
add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
add_line("Axis_XP",[(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)
# 5. メモリ校正用マーカー (交点)
if props.vis_axis or props.vis_ctp:
m_mat = get_or_create_material("Marker", props.col_axis)
for loc in [(beta*gamma, gamma), (gamma, beta*gamma)]:
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
marker = context.active_object
marker.name = f"{PREFIX}_Marker"
marker.data.materials.append(m_mat)
# ==============================================================================
# タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
draw_minkowski_diagram(bpy.context)
return None
def on_property_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(trigger_update, first_interval=0.03)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class PG_MinkowskiProps(PropertyGroup):
show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
precision: IntProperty(name="Decimals", default=CURRENT_DEFAULTS.get('precision', 4), min=0, max=9, description="Decimal places for intersections")
vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)
# ==============================================================================
# OPERATORS
# ==============================================================================
class OT_CopyMinkowskiFullCode(Operator):
bl_idname = f"{OP_PREFIX}.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
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 ID not found in Text Editor.")
return {'CANCELLED'}
code = target_text.as_string()
def f4(v): return tuple(round(x, 4) for x in v)
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_preview": {props.show_preview},\n'
new_dict += f' "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
new_dict += f' "precision": {props.precision},\n'
new_dict += f' "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
new_dict += f' "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
new_dict += f' "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
new_dict += f' "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
new_dict += f' "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
new_dict += f' "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
new_dict += "}\n"
try:
start_tag = "".join(["#", " <BEGIN_DICT>"])
end_tag = "".join(["#", " <END_DICT>"])
if start_tag not in code or end_tag not in code:
self.report({'ERROR'}, "Dictionary tags missing.")
return {'CANCELLED'}
pre_part = code.split(start_tag, 1)[0]
post_part = code.split(end_tag, 1)[1]
if pre_part.startswith("# Copied at:"):
parts = pre_part.split("\n", 1)
if len(parts) > 1:
pre_part = parts[1]
final_code = (
f"# Copied at: {datetime.now().strftime('%H:%M:%S')}\n" +
pre_part + start_tag + "\n" +
new_dict +
end_tag + post_part
)
context.window_manager.clipboard = final_code
self.report({'INFO'}, "Full script copied to clipboard!")
except Exception as e:
self.report({'ERROR'}, f"Copy failed: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class OT_CopyMinkowskiIntersections(Operator):
bl_idname = f"{OP_PREFIX}.copy_intersections"
bl_label = "Copy Intersections"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
beta = props.beta
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
p = props.precision
t_x, t_ct = beta * gamma, gamma
s_x, s_ct = gamma, beta * gamma
text = (
f"Time Intersection (x, ct): ({t_x:.{p}f}, {t_ct:.{p}f})\n"
f"Space Intersection (x, ct): ({s_x:.{p}f}, {s_ct:.{p}f})"
)
context.window_manager.clipboard = text
self.report({'INFO'}, "Intersections copied to clipboard!")
return {'FINISHED'}
class OT_OpenMinkowskiUrl(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_RemoveMinkowskiAddon(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_MinkowskiPanel(Panel):
bl_label = "Minkowski Stable V6"
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)
row = layout.row()
row.scale_y = 1.2
row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
# 物理パラメータ
box = layout.box()
box.label(text="Physical Parameters", icon='PHYSICS')
box.prop(props, "beta", slider=True)
box.prop(props, "thickness", slider=True)
# 交点情報エリア
box = layout.box()
box.label(text="Intersections (x, ct)", icon='MESH_GRID')
box.prop(props, "precision", slider=True)
beta = props.beta
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
p = props.precision
t_x, t_ct = beta * gamma, gamma
s_x, s_ct = gamma, beta * gamma
col = box.column(align=True)
col.label(text=f"Time (ct' axis): ({t_x:.{p}f}, {t_ct:.{p}f})")
col.label(text=f"Space (x' axis): ({s_x:.{p}f}, {s_ct:.{p}f})")
row = box.row()
row.operator(OT_CopyMinkowskiIntersections.bl_idname, icon='COPYDOWN', text="Copy Intersections")
# 色・表示設定
box = layout.box()
box.label(text="Layers & Colors", icon='COLOR')
layers =[
("vis_axis", "col_axis", "Main Axes"),
("vis_light", "col_light", "Light Cone"),
("vis_thyp", "col_thyp", "Timelike Hyp"),
("vis_shyp", "col_shyp", "Spacelike Hyp"),
("vis_ctp", "col_ctp", "ct' Axis"),
("vis_xp", "col_xp", "x' Axis"),
]
for vis, col, label in layers:
row = box.row(align=True)
row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
row.prop(props, col, text=label)
class PT_MinkowskiLinks(Panel):
bl_label = "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_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]
class PT_MinkowskiSys(Panel):
bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; 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_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_CopyMinkowskiIntersections, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))
def unregister():
cleanup_minkowski_data()
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()
# Copied at: 15:12:46
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
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
PREFIX = "Minkowski20260320_Fixed"
TAB_NAME = " [ Minkowski ] "
# スクリプトが自分自身を特定するための固有ID
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_STABLE_V5 ###"
bl_info = {
"name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
"author": "zionadchat",
"version": (1, 5, 2),
"blender": (5, 0, 0),
"location": "3D View > Sidebar",
"description": "Stable Minkowski Diagram with Full Script Copy Support",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
ADDON_LINKS = (
{"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
{"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
{"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)
# ==============================================================================
# デフォルト値設定 (Copy Script機能でここが動的に書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_preview": True,
"beta": 0.6000, "thickness": 0.0200,
"vis_axis": True, "col_axis": (0.1495, 0.1495, 0.1495, 1.0),
"vis_light": True, "col_light": (0.1109, 0.0904, 0.0, 0.5),
"vis_thyp": True, "col_thyp": (1.0, 0.2, 0.2, 1.0),
"vis_shyp": True, "col_shyp": (0.2, 0.2, 1.0, 1.0),
"vis_ctp": True, "col_ctp": (0.0, 1.0, 0.2, 1.0),
"vis_xp": True, "col_xp": (1.0, 0.0, 1.0, 1.0),
}
# <END_DICT>
# ==============================================================================
# 描画 & マテリアル制御
# ==============================================================================
def cleanup_minkowski_data():
"""PREFIXが付いた全データブロックを完全に削除"""
for obj in bpy.data.objects:
if obj.name.startswith(PREFIX):
bpy.data.objects.remove(obj, do_unlink=True)
# メッシュ、カーブ、マテリアルの掃除
for data in[bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
for block in data:
if block.name.startswith(PREFIX) and block.users == 0:
data.remove(block)
def get_or_create_material(name_suffix, color):
mat_name = f"{PREFIX}_Mat_{name_suffix}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
bsdf.inputs['Base Color'].default_value = color
if 'Alpha' in bsdf.inputs:
bsdf.inputs['Alpha'].default_value = color[3]
return mat
def draw_minkowski_diagram(context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return
cleanup_minkowski_data()
if not props.show_preview: return
target_col = context.scene.collection
beta = props.beta
thickness = props.thickness
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
def add_line(name, points, color, is_visible):
if not is_visible: return
curve = bpy.data.curves.new(f"{PREFIX}_Curve_{name}", type='CURVE')
curve.dimensions = '3D'
curve.fill_mode = 'FULL'
curve.bevel_depth = thickness
spline = curve.splines.new('POLY')
spline.points.add(len(points) - 1)
for i, p in enumerate(points):
spline.points[i].co = (p[0], p[1], 0, 1)
obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve)
target_col.objects.link(obj)
obj.data.materials.append(get_or_create_material(name, color))
# 1. 静止系軸
add_line("Axis_S", [(0, -6), (0, 6)], props.col_axis, props.vis_axis)
add_line("Axis_X",[(-6, 0), (6, 0)], props.col_axis, props.vis_axis)
# 2. 光円錐
add_line("Light_P",[(-6, -6), (6, 6)], props.col_light, props.vis_light)
add_line("Light_M", [(-6, 6), (6, -6)], props.col_light, props.vis_light)
# 3. 不変双曲線 (sinh/cosh)
q_range =[i * 0.1 - 2.5 for i in range(51)]
add_line("Hyp_T",[(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
add_line("Hyp_S", [(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)
# 4. 運動系軸
add_line("Axis_CTP",[(-6*beta, -6), (6*beta, 6)], props.col_ctp, props.vis_ctp)
add_line("Axis_XP", [(-6, -6*beta), (6, 6*beta)], props.col_xp, props.vis_xp)
# 5. メモリ校正用マーカー
if props.vis_axis or props.vis_ctp:
m_mat = get_or_create_material("Marker", props.col_axis)
for loc in[(beta*gamma, gamma), (gamma, beta*gamma)]:
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
marker = context.active_object
marker.name = f"{PREFIX}_Marker"
marker.data.materials.append(m_mat)
# ==============================================================================
# タイマー更新制御
# ==============================================================================
_timer = None
def trigger_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
draw_minkowski_diagram(bpy.context)
return None
def on_property_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(trigger_update, first_interval=0.03)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class PG_MinkowskiProps(PropertyGroup):
show_preview: BoolProperty(name="Live Update", default=CURRENT_DEFAULTS['show_preview'], update=on_property_update)
beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.999, update=on_property_update)
thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_property_update)
vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_property_update)
col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_property_update)
vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_property_update)
col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_property_update)
vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_property_update)
col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_property_update)
vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_property_update)
col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_property_update)
vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_property_update)
col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_property_update)
vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_property_update)
col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_property_update)
# ==============================================================================
# OPERATORS
# ==============================================================================
class OT_CopyMinkowskiFullCode(Operator):
bl_idname = f"{OP_PREFIX}.copy_full_script"
bl_label = "Copy Full Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
# テキストエディタ内を全探索して自分自身を探す
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 ID not found in Text Editor.")
return {'CANCELLED'}
code = target_text.as_string()
def f4(v): return tuple(round(x, 4) for x in v)
# 新しい辞書文字列の作成
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_preview": {props.show_preview},\n'
new_dict += f' "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
new_dict += f' "vis_axis": {props.vis_axis}, "col_axis": {f4(props.col_axis)},\n'
new_dict += f' "vis_light": {props.vis_light}, "col_light": {f4(props.col_light)},\n'
new_dict += f' "vis_thyp": {props.vis_thyp}, "col_thyp": {f4(props.col_thyp)},\n'
new_dict += f' "vis_shyp": {props.vis_shyp}, "col_shyp": {f4(props.col_shyp)},\n'
new_dict += f' "vis_ctp": {props.vis_ctp}, "col_ctp": {f4(props.col_ctp)},\n'
new_dict += f' "vis_xp": {props.vis_xp}, "col_xp": {f4(props.col_xp)},\n'
new_dict += "}\n"
try:
# ここがエラー箇所だったので、文字の囲みが欠落しても問題ない結合方式に修正
start_tag = "".join(["#", " <BEGIN_DICT>"])
end_tag = "".join(["#", " <END_DICT>"])
if start_tag not in code or end_tag not in code:
self.report({'ERROR'}, "Dictionary tags missing.")
return {'CANCELLED'}
# 第2引数「1」を指定し、最初のタグのみで分割
pre_part = code.split(start_tag, 1)[0]
post_part = code.split(end_tag, 1)[1]
# 既にCopied atが先頭にあれば削除して増殖を防ぐ
if pre_part.startswith("# Copied at:"):
parts = pre_part.split("\n", 1)
if len(parts) > 1:
pre_part = parts[1]
final_code = (
f"# Copied at: {datetime.now().strftime('%H:%M:%S')}\n" +
pre_part + start_tag + "\n" +
new_dict +
end_tag + post_part
)
context.window_manager.clipboard = final_code
self.report({'INFO'}, "Full script copied to clipboard!")
except Exception as e:
self.report({'ERROR'}, f"Copy failed: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class OT_OpenMinkowskiUrl(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_RemoveMinkowskiAddon(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_MinkowskiPanel(Panel):
bl_label = "Minkowski Stable V5"
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)
row = layout.row()
row.scale_y = 1.2
row.operator(OT_CopyMinkowskiFullCode.bl_idname, icon='COPY_ID', text="Copy Setup as Script")
layout.prop(props, "show_preview", toggle=True, icon='HIDE_OFF' if props.show_preview else 'HIDE_ON')
box = layout.box()
box.label(text="Physical Parameters", icon='PHYSICS')
box.prop(props, "beta", slider=True)
box.prop(props, "thickness", slider=True)
box = layout.box()
box.label(text="Layers & Colors (Alpha supported)", icon='COLOR')
layers =[
("vis_axis", "col_axis", "Main Axes"),
("vis_light", "col_light", "Light Cone"),
("vis_thyp", "col_thyp", "Timelike Hyp"),
("vis_shyp", "col_shyp", "Spacelike Hyp"),
("vis_ctp", "col_ctp", "ct' Axis"),
("vis_xp", "col_xp", "x' Axis"),
]
for vis, col, label in layers:
row = box.row(align=True)
row.prop(props, vis, text="", icon='HIDE_OFF' if getattr(props, vis) else 'HIDE_ON')
row.prop(props, col, text=label)
class PT_MinkowskiLinks(Panel):
bl_label = "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_OpenMinkowskiUrl.bl_idname, text=l["label"]).url = l["url"]
class PT_MinkowskiSys(Panel):
bl_label = "System"; bl_idname = f"{PREFIX}_PT_sys"; 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_RemoveMinkowskiAddon.bl_idname, icon='CANCEL')
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (PG_MinkowskiProps, OT_CopyMinkowskiFullCode, OT_OpenMinkowskiUrl, OT_RemoveMinkowskiAddon, PT_MinkowskiPanel, PT_MinkowskiLinks, PT_MinkowskiSys)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))
def unregister():
cleanup_minkowski_data()
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()
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
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
PREFIX = "Minkowski20260320_v4"
TAB_NAME = " [ Minkowski ] "
# ### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_V4_STABLE ###
bl_info = {
"name": f"zionad 520 [ Minkowski Gen ] {PREFIX}",
"author": "zionadchat",
"version": (1, 3, 0),
"blender": (5, 0, 0),
"location": "3D View > Sidebar",
"description": "Stable Minkowski Diagram Generator with Alpha",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: MINKOWSKI_2026_03_20_V4_STABLE ###"
ADDON_LINKS = (
{"label": "時空図 20260320版", "url": "<https://www.notion.so/20260320-329f5dacaf4380e0b192fb449676db13>"},
{"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
)
# ==============================================================================
# デフォルト値
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_preview": True,
"beta": 0.6000,
"thickness": 0.0200,
"vis_axis": True, "col_axis": (1.00, 1.00, 1.00, 1.00),
"vis_light": True, "col_light": (1.00, 0.80, 0.00, 0.50),
"vis_thyp": True, "col_thyp": (1.00, 0.20, 0.20, 1.00),
"vis_shyp": True, "col_shyp": (0.20, 0.20, 1.00, 1.00),
"vis_ctp": True, "col_ctp": (0.00, 1.00, 0.20, 1.00),
"vis_xp": True, "col_xp": (1.00, 0.00, 1.00, 1.00),
}
# <END_DICT>
# ==============================================================================
# 描画 & クリーンアップロジック
# ==============================================================================
def cleanup_minkowski_data():
"""PREFIXが付いたオブジェクト、メッシュ、カーブ、マテリアルをすべて削除"""
for obj in bpy.data.objects:
if obj.name.startswith(PREFIX):
bpy.data.objects.remove(obj, do_unlink=True)
# 未使用のデータブロックも掃除(名前がPREFIXで始まるもの)
for data in [bpy.data.curves, bpy.data.meshes, bpy.data.materials]:
for block in data:
if block.name.startswith(PREFIX):
if block.users == 0:
data.remove(block)
def create_minkowski_material(name_suffix, color):
mat_name = f"{PREFIX}_Mat_{name_suffix}"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF") or nodes.new("ShaderNodeBsdfPrincipled")
bsdf.inputs['Base Color'].default_value = color
if 'Alpha' in bsdf.inputs:
bsdf.inputs['Alpha'].default_value = color[3]
return mat
def update_geometry(context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return
cleanup_minkowski_data()
if not props.show_preview: return
scene_collection = context.scene.collection
beta = props.beta
thickness = props.thickness
gamma = 1 / math.sqrt(1 - beta**2) if beta < 0.999 else 20.0
def draw_line(name, points, color, visible):
if not visible: return
curve_name = f"{PREFIX}_Curve_{name}"
curve_data = bpy.data.curves.new(curve_name, type='CURVE')
curve_data.dimensions = '3D'
curve_data.fill_mode = 'FULL'
curve_data.bevel_depth = thickness
polyline = curve_data.splines.new('POLY')
polyline.points.add(len(points) - 1)
for i, p in enumerate(points):
polyline.points[i].co = (p[0], p[1], 0, 1)
obj = bpy.data.objects.new(f"{PREFIX}_{name}", curve_data)
scene_collection.objects.link(obj)
obj.data.materials.append(create_minkowski_material(name, color))
# 1. メイン軸 (x, ct)
draw_line("Axis_S", [(0, -5), (0, 5)], props.col_axis, props.vis_axis)
draw_line("Axis_X", [(-5, 0), (5, 0)], props.col_axis, props.vis_axis)
# 2. 光円錐
draw_line("Light_P", [(-5, -5), (5, 5)], props.col_light, props.vis_light)
draw_line("Light_M", [(-5, 5), (5, -5)], props.col_light, props.vis_light)
# 3. 不変双曲線
q_range = [i * 0.1 - 2.0 for i in range(41)]
draw_line("Hyp_T", [(math.sinh(q), math.cosh(q)) for q in q_range], props.col_thyp, props.vis_thyp)
draw_line("Hyp_S", [(math.cosh(q), math.sinh(q)) for q in q_range], props.col_shyp, props.vis_shyp)
# 4. 移動系軸 (ct', x')
draw_line("Axis_CTP", [(-5*beta, -5), (5*beta, 5)], props.col_ctp, props.vis_ctp)
draw_line("Axis_XP", [(-5, -5*beta), (5, 5*beta)], props.col_xp, props.vis_xp)
# 5. メモリマーカー(球体)
if props.vis_axis or props.vis_ctp:
marker_mat = create_minkowski_material("Marker", props.col_axis)
for loc in [(beta*gamma, gamma), (gamma, beta*gamma)]:
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*2.5, location=(loc[0], loc[1], 0))
marker = context.active_object
marker.name = f"{PREFIX}_Marker"
marker.data.materials.append(marker_mat)
# タイマーによる更新制御
_timer = None
def delayed_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
update_geometry(bpy.context)
return None
def on_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(delayed_update, first_interval=0.03)
# ==============================================================================
# PROPERTIES / UI
# ==============================================================================
class PG_MinkowskiProps(PropertyGroup):
show_preview: BoolProperty(name="Live Preview", default=CURRENT_DEFAULTS['show_preview'], update=on_update)
beta: FloatProperty(name="Beta (v/c)", default=CURRENT_DEFAULTS['beta'], min=0, max=0.99, update=on_update)
thickness: FloatProperty(name="Thickness", default=CURRENT_DEFAULTS['thickness'], min=0.001, max=0.2, update=on_update)
vis_axis: BoolProperty(default=CURRENT_DEFAULTS['vis_axis'], update=on_update)
col_axis: FloatVectorProperty(name="Main Axes", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_axis'], update=on_update)
vis_light: BoolProperty(default=CURRENT_DEFAULTS['vis_light'], update=on_update)
col_light: FloatVectorProperty(name="Light Cone", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_light'], update=on_update)
vis_thyp: BoolProperty(default=CURRENT_DEFAULTS['vis_thyp'], update=on_update)
col_thyp: FloatVectorProperty(name="Timelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_thyp'], update=on_update)
vis_shyp: BoolProperty(default=CURRENT_DEFAULTS['vis_shyp'], update=on_update)
col_shyp: FloatVectorProperty(name="Spacelike Hyp", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_shyp'], update=on_update)
vis_ctp: BoolProperty(default=CURRENT_DEFAULTS['vis_ctp'], update=on_update)
col_ctp: FloatVectorProperty(name="ct' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_ctp'], update=on_update)
vis_xp: BoolProperty(default=CURRENT_DEFAULTS['vis_xp'], update=on_update)
col_xp: FloatVectorProperty(name="x' Axis", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['col_xp'], update=on_update)
class OT_CopyMinkowskiScript(Operator):
bl_idname = f"{OP_PREFIX}.copy_script"
bl_label = "Copy Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME)
target_text = next((t for t in bpy.data.texts if SOURCE_ID_TAG in t.as_string()), None)
if not target_text: return {'CANCELLED'}
code = target_text.as_string()
def f2(v): return tuple(round(x, 2) for x in v)
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_preview": {props.show_preview},\n'
new_dict += f' "beta": {props.beta:.4f}, "thickness": {props.thickness:.4f},\n'
new_dict += f' "vis_axis": {props.vis_axis}, "col_axis": {f2(props.col_axis)},\n'
new_dict += f' "vis_light": {props.vis_light}, "col_light": {f2(props.col_light)},\n'
new_dict += f' "vis_thyp": {props.vis_thyp}, "col_thyp": {f2(props.col_thyp)},\n'
new_dict += f' "vis_shyp": {props.vis_shyp}, "col_shyp": {f2(props.col_shyp)},\n'
new_dict += f' "vis_ctp": {props.vis_ctp}, "col_ctp": {f2(props.col_ctp)},\n'
new_dict += f' "vis_xp": {props.vis_xp}, "col_xp": {f2(props.col_xp)},\n'
new_dict += "}\n"
try:
start, end = "# <BEGIN_DICT>", "# <END_DICT>"
pre, post = code.split(start)[0], code.split(end)[1]
final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n{pre}{start}\n{new_dict}{end}{post}"
context.window_manager.clipboard = final
self.report({'INFO'}, "Setup Copied!")
except: return {'CANCELLED'}
return {'FINISHED'}
class PT_MinkowskiPanel(Panel):
bl_label = "Minkowski Stable Control"
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)
layout.operator(OT_CopyMinkowskiScript.bl_idname, icon='COPY_ID')
layout.prop(props, "show_preview", toggle=True)
box = layout.box()
box.prop(props, "beta", slider=True)
box.prop(props, "thickness", slider=True)
box = layout.box()
box.label(text="Layers & Colors (Alpha supported)")
layers = [("vis_axis", "col_axis", "Main Axes"), ("vis_light", "col_light", "Light Cone"),
("vis_thyp", "col_thyp", "Timelike Hyp"), ("vis_shyp", "col_shyp", "Spacelike Hyp"),
("vis_ctp", "col_ctp", "ct' Axis"), ("vis_xp", "col_xp", "x' Axis")]
for v, c, l in layers:
row = box.row(align=True)
row.prop(props, v, text="", icon='HIDE_OFF' if getattr(props, v) else 'HIDE_ON')
row.prop(props, c, text=l)
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (PG_MinkowskiProps, OT_CopyMinkowskiScript, PT_MinkowskiPanel)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_MinkowskiProps))
def unregister():
cleanup_minkowski_data()
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()
import bpy
import math
bl_info = {
"name": "Minkowski Diagram Generator",
"author": "zionadchat Assistant",
"version": (1, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > Physics",
"description": "ミンコフスキー時空図を生成するアドオン",
"category": "Add Mesh",
}
# ------------------------------------------------------------------
# 1. 描画ロジック本体
# ------------------------------------------------------------------
def create_minkowski(context):
props = context.scene.minkowski_props
beta = props.beta
thickness = props.thickness
# 既存の図形を削除(名前で管理)
for obj in bpy.data.objects:
if obj.name.startswith("Minkowski_"):
bpy.data.objects.remove(obj, do_unlink=True)
def draw_curve(name, points, color, is_cyclic=False):
curve_data = bpy.data.curves.new(name, type='CURVE')
curve_data.dimensions = '3D'
curve_data.fill_mode = 'FULL'
curve_data.bevel_depth = thickness # 線の太さ
polyline = curve_data.splines.new('POLY')
polyline.points.add(len(points) - 1)
for i, p in enumerate(points):
polyline.points[i].co = (p[0], p[1], 0, 1)
obj = bpy.data.objects.new(f"Minkowski_{name}", curve_data)
context.collection.objects.link(obj)
# マテリアル設定
mat = bpy.data.materials.new(name=f"Mat_{name}")
mat.use_nodes = True
nodes = mat.node_tree.nodes
nodes["Principled BSDF"].inputs[0].default_value = color
obj.data.materials.append(mat)
# 静止系軸
draw_curve("Axis_CT", [(0, -5), (0, 5)], (1, 1, 1, 1))
draw_curve("Axis_X", [(-5, 0), (5, 0)], (1, 1, 1, 1))
# 光円錐
draw_curve("Light_1", [(-5, -5), (5, 5)], (1, 1, 0, 1))
draw_curve("Light_2", [(-5, 5), (5, -5)], (1, 1, 0, 1))
# 双曲線の生成
steps = 40
q_range = [i * 0.1 - 2.0 for i in range(41)] # -2.0 to 2.0
# 時間的 (ct^2 - x^2 = 1)
t_points = [(math.sinh(q), math.cosh(q)) for q in q_range]
draw_curve("Hyp_Time", t_points, (1, 0.2, 0.2, 1))
# 空間的 (x^2 - ct^2 = 1)
s_points = [(math.cosh(q), math.sinh(q)) for q in q_range]
draw_curve("Hyp_Space", s_points, (0.2, 0.2, 1, 1))
# 動いている系の軸 (ct', x')
gamma = 1 / math.sqrt(1 - beta**2)
draw_curve("Axis_CT_P", [(-5*beta, -5), (5*beta, 5)], (0, 1, 0, 1))
draw_curve("Axis_X_P", [(-5, -5*beta), (5, 5*beta)], (1, 0, 1, 1))
# メモリ点
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*3, location=(beta*gamma, gamma, 0))
bpy.context.active_object.name = "Minkowski_Marker_T"
bpy.ops.mesh.primitive_uv_sphere_add(radius=thickness*3, location=(gamma, beta*gamma, 0))
bpy.context.active_object.name = "Minkowski_Marker_S"
# ------------------------------------------------------------------
# 2. UI定義とプロパティ
# ------------------------------------------------------------------
class MinkowskiProperties(bpy.types.PropertyGroup):
beta: bpy.props.FloatProperty(
name="Beta (v/c)",
description="観測者の速度",
default=0.6,
min=0.0,
max=0.99,
update=lambda self, context: create_minkowski(context)
)
thickness: bpy.props.FloatProperty(
name="Line Thickness",
description="線の太さ",
default=0.02,
min=0.001,
max=0.2,
update=lambda self, context: create_minkowski(context)
)
class MINKOWSKI_PT_panel(bpy.types.Panel):
bl_label = "Minkowski Diagram"
bl_idname = "MINKOWSKI_PT_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Physics'
def draw(self, context):
layout = self.layout
props = context.scene.minkowski_props
col = layout.column(align=True)
col.prop(props, "beta", slider=True)
col.prop(props, "thickness", slider=True)
layout.operator("minkowski.generate", text="Refresh Diagram")
class MINKOWSKI_OT_generate(bpy.types.Operator):
bl_label = "Generate Minkowski"
bl_idname = "minkowski.generate"
def execute(self, context):
create_minkowski(context)
return {'FINISHED'}
# ------------------------------------------------------------------
# 3. 登録
# ------------------------------------------------------------------
classes = [MinkowskiProperties, MINKOWSKI_PT_panel, MINKOWSKI_OT_generate]
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.minkowski_props = bpy.props.PointerProperty(type=MinkowskiProperties)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.minkowski_props
if __name__ == "__main__":
register()