bl_info = {
"name": "zionad_ [ 骨格立方体 ] & Sine Wave",
"author": "zionad_ & ChatGPT",
"version": (2, 5, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar (N Panel) > 骨格立方体",
"description": "骨格立方体生成ツールと進行波・時間軌跡のリアルタイム描画ツールの統合アドオン",
"category": "3D View",
}
# 2026年07月14日 統合版(作成ボタン・パネル構造統一)
import bpy
import math
import webbrowser
import random
from bpy.props import (
FloatProperty, FloatVectorProperty, StringProperty,
IntProperty, EnumProperty, BoolProperty, PointerProperty
)
from bpy.types import Operator, Panel, PropertyGroup
# ==========================================
# 【初期設定・手動変更エリア】 (骨格立方体)
# ==========================================
LINK_NAME_1 = "基本アドオン設定 20260711b"
LINK_URL_1 = "<https://app.notion.com/p/20260711-39af5dacaf438027811dcd877db7c630>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"
LINK_NAME_4 = "blender コンテナ 20260713"
LINK_URL_4 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_5 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"
OBJ_NAME_BASE = "Skeleton_Dice"
COL_NAME = "Dice_Collection"
SUB_COL_NAME = "Skeleton_Cubes"
# 初期数値
INIT_SIZE = 60.0
INIT_THICKNESS = 1.0
INIT_RES = 32
INIT_POS = (0.0, 0.0, 0.0)
INIT_ROT = (0.0, 0.0, 0.0)
INIT_CLR_FRAME = (0.1, 0.6, 1.0, 1.0)
INIT_CLR_FACE = (1.0, 1.0, 1.0, 0.3)
# ==========================================
# 【初期設定・手動変更エリア】 (Sine Wave)
# ==========================================
SINE_OBJ_PREFIX = "SINE_WAVE_"
SINE_AMPLITUDE_DEFAULT = 2.0
SINE_FREQUENCY_DEFAULT = 1.0
SINE_LENGTH_DEFAULT = 10.0
SINE_TIME_MAX_DEFAULT = 10.0
SINE_TIME_DEFAULT = 1.0
SINE_DELAY_DEFAULT = 0.0
SINE_RESOLUTION_DEFAULT = 128
SINE_WAVE_SPEED_X_DEFAULT = 1.0
SINE_LOC_X_DEFAULT = 0.0
SINE_LOC_Y_DEFAULT = 0.0
SINE_LOC_Z_DEFAULT = 0.0
INIT_COLOR = (0.5, 0.5, 0.5, 0.8)
MAIN_COLOR = (0.2, 0.6, 1.0, 1.0)
TRAJ1_COLOR = (1.0, 0.8, 0.2, 1.0)
TRAJ2_COLOR = (1.0, 0.4, 0.8, 1.0)
TX_COLOR = (0.2, 1.0, 0.5, 1.0)
# ==========================================
# 【形状生成コア・ロジック】 (骨格立方体)
# ==========================================
def create_advanced_geometry(context):
scene = context.scene
import bmesh
bm_base = bmesh.new()
if scene.dice_shape == 'CUBE':
bmesh.ops.create_cube(bm_base, size=scene.dice_size)
else:
bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=scene.dice_size/2)
curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
curve_data.dimensions = '3D'
curve_data.fill_mode = 'FULL'
curve_data.bevel_depth = scene.dice_thick / 2
curve_data.bevel_resolution = scene.dice_res
for edge in bm_base.edges:
v1, v2 = edge.verts[0].co, edge.verts[1].co
polyline = curve_data.splines.new('POLY')
polyline.points.add(1)
polyline.points[0].co = (*v1, 1)
polyline.points[1].co = (*v2, 1)
temp_frame_obj = bpy.data.objects.new("temp_frame", curve_data)
context.collection.objects.link(temp_frame_obj)
depsgraph = context.evaluated_depsgraph_get()
mesh_frame = bpy.data.meshes.new_from_object(temp_frame_obj.evaluated_get(depsgraph))
bpy.data.objects.remove(temp_frame_obj)
bpy.data.curves.remove(curve_data)
bm_final = bmesh.new()
bm_final.from_mesh(mesh_frame)
for face in bm_final.faces:
face.material_index = 0
bpy.data.meshes.remove(mesh_frame)
if scene.dice_show_faces:
bm_face = bmesh.new()
if scene.dice_shape == 'CUBE':
bmesh.ops.create_cube(bm_face, size=scene.dice_size)
else:
bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=scene.dice_size/2)
for face in bm_face.faces:
face.material_index = 1
bm_face.to_mesh(bpy.data.meshes.new("temp_face"))
bm_final.from_mesh(bpy.data.meshes.get("temp_face"))
bpy.data.meshes.remove(bpy.data.meshes.get("temp_face"))
bm_face.free()
mesh_out = bpy.data.meshes.new(OBJ_NAME_BASE)
bm_final.to_mesh(mesh_out)
bm_final.free()
bm_base.free()
return mesh_out
def update_skeleton_live(self, context):
scene = context.scene
for obj in scene.objects:
if obj.name.startswith(OBJ_NAME_BASE) and obj.type == 'MESH':
obj.location = scene.dice_pos
obj.rotation_euler = [math.radians(a) for a in scene.dice_rot]
new_mesh = create_advanced_geometry(context)
old_mesh = obj.data
obj.data = new_mesh
if old_mesh: bpy.data.meshes.remove(old_mesh)
while len(obj.data.materials) < 2:
obj.data.materials.append(None)
if not obj.material_slots[0].material:
obj.material_slots[0].material = bpy.data.materials.new(name="Mat_Frame")
mat_frame = obj.material_slots[0].material
mat_frame.use_nodes = True
bsdf_frame = mat_frame.node_tree.nodes.get("Principled BSDF")
if bsdf_frame:
bsdf_frame.inputs['Base Color'].default_value = scene.dice_color_frame
if 'Alpha' in bsdf_frame.inputs: bsdf_frame.inputs['Alpha'].default_value = scene.dice_color_frame[3]
mat_frame.blend_method = 'BLEND'
if not obj.material_slots[1].material:
obj.material_slots[1].material = bpy.data.materials.new(name="Mat_Face")
mat_face = obj.material_slots[1].material
mat_face.use_nodes = True
bsdf_face = mat_face.node_tree.nodes.get("Principled BSDF")
if bsdf_face:
bsdf_face.inputs['Base Color'].default_value = scene.dice_color_face
if 'Alpha' in bsdf_face.inputs: bsdf_face.inputs['Alpha'].default_value = scene.dice_color_face[3]
mat_face.blend_method = 'BLEND'
# ==========================================
# 【形状生成コア・ロジック】 (Sine Wave)
# ==========================================
def clear_sine_wave_data():
for obj in list(bpy.data.objects):
if obj.name.startswith(SINE_OBJ_PREFIX):
bpy.data.objects.remove(obj, do_unlink=True)
for crv in list(bpy.data.curves):
if crv.name.startswith(SINE_OBJ_PREFIX):
bpy.data.curves.remove(crv)
for mat in list(bpy.data.materials):
if mat.name.startswith(SINE_OBJ_PREFIX):
bpy.data.materials.remove(mat)
def is_sine_wave_exists():
"""サイン波がすでに生成されているかチェック"""
for obj in bpy.data.objects:
if obj.name.startswith(SINE_OBJ_PREFIX):
return True
return False
def calculate_z_displacement(x, t, props):
f = props.frequency
v = props.wave_speed_x
if v == 0.0:
phase = 2.0 * math.pi * f * (t - props.delay)
else:
if props.wave_direction == 'SPACE':
phase = 2.0 * math.pi * f * (x / v - (t - props.delay))
else:
phase = 2.0 * math.pi * f * ((t - props.delay) - x / v)
amp = props.amplitude
if props.taper_x and props.length > 0.0:
amp = props.amplitude * (x / props.length)
return amp * math.sin(phase) + SINE_LOC_Z_DEFAULT
def create_curve_object(name, thickness, color, collection):
curve_data = bpy.data.curves.new(name=name, type='CURVE')
curve_data.dimensions = '3D'
curve_data.bevel_depth = thickness
curve_data.bevel_resolution = 4
spline = curve_data.splines.new(type='POLY')
obj = bpy.data.objects.new(name, curve_data)
collection.objects.link(obj)
mat = bpy.data.materials.new(name=f"{name}_Mat")
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 len(color) == 4 and color[3] < 1.0:
bsdf.inputs['Alpha'].default_value = color[3]
obj.data.materials.append(mat)
return spline
def generate_waves(context):
props = context.scene.sine_wave_props
clear_sine_wave_data()
coll_name = "SineWave_Collection"
if coll_name in bpy.data.collections:
coll = bpy.data.collections[coll_name]
else:
coll = bpy.data.collections.new(coll_name)
context.scene.collection.children.link(coll)
if props.init_show:
spline_init = create_curve_object(f"{SINE_OBJ_PREFIX}Init_t0", props.init_thickness, props.init_color, coll)
spline_init.points.add(props.resolution - 1)
for i in range(props.resolution):
x_local = (i / (props.resolution - 1)) * props.length
y = 0.0 + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, 0.0, props)
spline_init.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
if props.main_show:
spline_main = create_curve_object(f"{SINE_OBJ_PREFIX}Main", props.thickness, props.color_main, coll)
spline_main.points.add(props.resolution - 1)
for i in range(props.resolution):
x_local = (i / (props.resolution - 1)) * props.length
y = props.time_t + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, props.time_t, props)
spline_main.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
if props.traj1_show and props.time_max > 0.0:
spline_traj1 = create_curve_object(f"{SINE_OBJ_PREFIX}Trajectory1", props.traj1_thickness, props.traj1_color, coll)
spline_traj1.points.add(props.resolution - 1)
for i in range(props.resolution):
tau = (i / (props.resolution - 1)) * props.time_max
x_local = props.traj1_x
y = tau + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, tau, props)
spline_traj1.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
if props.traj2_show and props.time_max > 0.0:
spline_traj2 = create_curve_object(f"{SINE_OBJ_PREFIX}Trajectory2", props.traj2_thickness, props.traj2_color, coll)
spline_traj2.points.add(props.resolution - 1)
for i in range(props.resolution):
tau = (i / (props.resolution - 1)) * props.time_max
x_local = props.traj2_x
y = tau + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, tau, props)
spline_traj2.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
if props.tx_show:
spline_tx = create_curve_object(f"{SINE_OBJ_PREFIX}Wave_tx", props.tx_thickness, props.tx_color, coll)
spline_tx.points.add(props.resolution - 1)
for i in range(props.resolution):
if props.tx_slope_a == 0.0:
max_tx_length = props.length
else:
max_tx_length = min(props.length, props.time_max / props.tx_slope_a)
x_local = (i / (props.resolution - 1)) * max_tx_length
t_local = props.tx_slope_a * x_local
y = t_local + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, t_local, props)
spline_tx.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
def update_waves_cb(self, context):
"""プロパティ変更時、すでに波が生成されていればリアルタイム更新する"""
if context and hasattr(context.scene, 'sine_wave_props'):
if is_sine_wave_exists():
generate_waves(context)
# ==========================================
# 【プロパティ定義】 (Sine Wave)
# ==========================================
class SineWaveProperties(PropertyGroup):
amplitude: FloatProperty(name="最大振幅 (Z軸)", default=SINE_AMPLITUDE_DEFAULT, update=update_waves_cb)
frequency: FloatProperty(name="時間振動数 f (回/単位時間)", default=SINE_FREQUENCY_DEFAULT, update=update_waves_cb)
length: FloatProperty(name="空間の長さ (X最大)", default=SINE_LENGTH_DEFAULT, update=update_waves_cb)
time_max: FloatProperty(name="時間の長さ (Y最大)", default=SINE_TIME_MAX_DEFAULT, update=update_waves_cb)
time_t: FloatProperty(
name="現在の時間 t",
default=SINE_TIME_DEFAULT,
soft_min=0.0,
soft_max=10.0,
update=update_waves_cb
)
wave_speed_x: FloatProperty(name="X方向速度 (v)", default=SINE_WAVE_SPEED_X_DEFAULT, soft_min=-10.0, soft_max=10.0, update=update_waves_cb)
delay: FloatProperty(name="開始時間の遅れ", default=SINE_DELAY_DEFAULT, update=update_waves_cb)
resolution: IntProperty(name="解像度", default=SINE_RESOLUTION_DEFAULT, min=2, update=update_waves_cb)
wave_direction: EnumProperty(
name="波の開始基準",
items=[
('SPACE', "空間 (X軸) で山から開始", "t=0の初期波において、X軸正の方向へ『山』から始まります"),
('TIME', "時間 (Y軸) で山から開始", "原点X=0において、時間が進むと『山』から湧き出します")
],
default='SPACE',
update=update_waves_cb
)
taper_x: BoolProperty(
name="X軸方向へ徐々に山を高くする",
description="X=0で振幅を0にし、Xが進むにつれて山の高さを最大振幅まで徐々に大きくします",
default=False,
update=update_waves_cb
)
init_show: BoolProperty(name="初期波 (t=0)", default=True, update=update_waves_cb)
init_thickness: FloatProperty(name="初期波の太さ", default=0.05, min=0.0, update=update_waves_cb)
init_color: FloatVectorProperty(name="初期波の色", subtype='COLOR', size=4, default=INIT_COLOR, min=0.0, max=1.0, update=update_waves_cb)
main_show: BoolProperty(name="時間波形 (t=t)", default=True, update=update_waves_cb)
thickness: FloatProperty(name="時間波形の太さ", default=0.15, min=0.0, update=update_waves_cb)
color_main: FloatVectorProperty(name="時間波形の色", subtype='COLOR', size=4, default=MAIN_COLOR, min=0.0, max=1.0, update=update_waves_cb)
traj1_show: BoolProperty(name="軌跡 1", default=True, update=update_waves_cb)
traj1_x: FloatProperty(name="軌跡1 X位置", default=0.0, update=update_waves_cb)
traj1_thickness: FloatProperty(name="軌跡1の太さ", default=0.1, min=0.0, update=update_waves_cb)
traj1_color: FloatVectorProperty(name="軌跡1の色", subtype='COLOR', size=4, default=TRAJ1_COLOR, min=0.0, max=1.0, update=update_waves_cb)
traj2_show: BoolProperty(name="軌跡 2", default=True, update=update_waves_cb)
traj2_x: FloatProperty(name="軌跡2 X位置", default=2.0, update=update_waves_cb)
traj2_thickness: FloatProperty(name="軌跡2の太さ", default=0.1, min=0.0, update=update_waves_cb)
traj2_color: FloatVectorProperty(name="軌跡2の色", subtype='COLOR', size=4, default=TRAJ2_COLOR, min=0.0, max=1.0, update=update_waves_cb)
tx_show: BoolProperty(name="t=ax 波面", default=True, update=update_waves_cb)
tx_slope_a: FloatProperty(name="係数 (a)", default=1.0, min=0.0, max=1.0, update=update_waves_cb)
tx_thickness: FloatProperty(name="t=ax 波の太さ", default=0.12, min=0.0, update=update_waves_cb)
tx_color: FloatVectorProperty(name="t=ax 波の色", subtype='COLOR', size=4, default=TX_COLOR, min=0.0, max=1.0, update=update_waves_cb)
# ==========================================
# 【オペレーター】
# ==========================================
# 骨格立方体用
class DICE_TOOLS_5_OT_Create(Operator):
bl_idname = "dice_tools_5.create_skeleton"
bl_label = "骨格を新規生成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh_data = create_advanced_geometry(context)
obj = bpy.data.objects.new(f"{OBJ_NAME_BASE}_{random.randint(100,999)}", mesh_data)
main_col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(COL_NAME)
if main_col.name not in context.scene.collection.children:
context.scene.collection.children.link(main_col)
sub_col = bpy.data.collections.get(SUB_COL_NAME) or bpy.data.collections.new(SUB_COL_NAME)
if sub_col.name not in main_col.children:
main_col.children.link(sub_col)
sub_col.objects.link(obj)
context.view_layer.objects.active = obj
update_skeleton_live(self, context)
return {'FINISHED'}
class DICE_TOOLS_5_OT_Detach(Operator):
bl_idname = "dice_tools_5.detach_object"
bl_label = "アドオンから切り離す"
def execute(self, context):
obj = context.active_object
if obj and obj.name.startswith(OBJ_NAME_BASE):
obj.name = "Fixed_Dice_" + str(random.randint(100,999))
self.report({'INFO'}, "独立させました")
return {'FINISHED'}
# Sine Wave用オペレーター (新規追加)
class SINE_WAVE_OT_Create(Operator):
bl_idname = "sine_wave.create"
bl_label = "サイン波を新規生成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
# 色がわかりやすいようマテリアルプレビューに変更
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
generate_waves(context)
return {'FINISHED'}
class SINE_WAVE_OT_Clear(Operator):
bl_idname = "sine_wave.clear"
bl_label = "サイン波を削除"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
clear_sine_wave_data()
return {'FINISHED'}
# 共通・管理用
class DICE_TOOLS_5_OT_ViewCenter(Operator):
bl_idname = "dice_tools_5.view_center"
bl_label = "選択を中央へ"
def execute(self, context):
if context.active_object: bpy.ops.view3d.view_selected()
return {'FINISHED'}
class DICE_TOOLS_5_OT_OpenLink(Operator):
bl_idname = "dice_tools_5.open_link"
bl_label = "URLを開く"
url: StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class DICE_TOOLS_5_OT_RemoveAddon(Operator):
bl_idname = "dice_tools_5.remove_addon_ui"
bl_label = "アドオンUIを削除"
def execute(self, context):
unregister()
return {'FINISHED'}
# ==========================================
# 【UIパネル】
# ==========================================
# (すべてのパネルを bl_category = "骨格立方体" に統一)
class DICE_TOOLS_5_PT_ViewPanel(Panel):
bl_label = "ビュー情報"
bl_idname = "DICE_TOOLS_5_PT_ViewPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
def draw(self, context):
layout = self.layout
rv3d = context.region_data
layout.operator("dice_tools_5.view_center", icon='CENTER_ONLY')
layout.separator()
layout.label(text="モード: " + ("透視投影" if rv3d.view_perspective == 'PERSP' else "平行投影"))
box = layout.box()
loc = rv3d.view_location
box.label(text=f"中心: X:{loc.x:.1f} Y:{loc.y:.1f} Z:{loc.z:.1f}")
layout.label(text=f"画面: {context.region.width} x {context.region.height}")
if rv3d.view_perspective == 'PERSP':
layout.label(text=f"距離: {rv3d.view_distance:.1f}")
layout.prop(context.scene, "dice_info_str", text="", icon='COPY_ID')
class DICE_TOOLS_5_PT_CreationPanel(Panel):
bl_label = "骨格 調整"
bl_idname = "DICE_TOOLS_5_PT_CreationPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
def draw(self, context):
layout = self.layout
scene = context.scene
obj = context.active_object
layout.operator("dice_tools_5.create_skeleton", icon='MESH_CUBE')
layout.separator()
col = layout.column(align=True)
col.prop(scene, "dice_shape", text="基本形状")
col.prop(scene, "dice_pos", text="位置")
col.prop(scene, "dice_rot", text="回転")
col.prop(scene, "dice_size", text="サイズ")
col.prop(scene, "dice_thick", text="骨格の太さ")
col.prop(scene, "dice_res", text="分割数")
layout.separator()
layout.prop(scene, "dice_show_faces", text="表面を表示する", icon='FACESEL')
row = layout.row()
row.prop(scene, "dice_color_frame", text="骨格の色")
if scene.dice_show_faces:
row.prop(scene, "dice_color_face", text="表面の色")
layout.separator()
if obj and obj.name.startswith(OBJ_NAME_BASE):
layout.operator("dice_tools_5.detach_object", icon='UNLINKED')
else:
layout.label(text="※生成した骨格を選択して調整", icon='INFO')
class SINE_WAVE_PT_CreationPanel(Panel):
bl_label = "サイン波 調整"
bl_idname = "SINE_WAVE_PT_CreationPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
def draw(self, context):
layout = self.layout
props = context.scene.sine_wave_props
# 生成・削除のオペレーターボタン(骨格立方体と同じ構造)
row = layout.row(align=True)
row.operator("sine_wave.create", icon='MOD_WAVE')
row.operator("sine_wave.clear", icon='TRASH')
layout.separator()
# 1. 波の形と方向
box = layout.box()
box.label(text="波の形・方向の設定", icon='MOD_WAVE')
box.prop(props, "wave_direction")
box.prop(props, "taper_x")
# 2. 基本設定
box = layout.box()
box.label(text="物理パラメータ", icon='VIEW_ORTHO')
box.prop(props, "amplitude")
box.prop(props, "frequency")
box.prop(props, "length")
box.prop(props, "time_max")
box.separator()
box.prop(props, "time_t", slider=True)
box.prop(props, "wave_speed_x")
box.prop(props, "delay")
box.prop(props, "resolution")
# 3. 波の表示切り替え
box2 = layout.box()
box2.label(text="波の表示切り替え (ON/OFF)", icon='RESTRICT_VIEW_OFF')
row1 = box2.row(align=True)
row1.prop(props, "init_show", toggle=True, text="t=0")
row1.prop(props, "main_show", toggle=True, text="t=t")
row1.prop(props, "tx_show", toggle=True, text="t=ax")
row2 = box2.row(align=True)
row2.prop(props, "traj1_show", toggle=True, text="軌跡1")
row2.prop(props, "traj2_show", toggle=True, text="軌跡2")
# 4. 見た目の詳細設定
box3 = layout.box()
box3.label(text="色・太さ・位置の設定", icon='MATERIAL')
if props.init_show:
row_c = box3.row()
row_c.label(text="初期 (t=0):")
row_c.prop(props, "init_thickness", text="太さ")
row_c.prop(props, "init_color", text="")
if props.main_show:
row_c = box3.row()
row_c.label(text=f"現在 (t={props.time_t:.1f}):")
row_c.prop(props, "thickness", text="太さ")
row_c.prop(props, "color_main", text="")
if props.traj1_show:
box3.separator()
row_c = box3.row(align=True)
row_c.label(text="軌跡1:")
row_c.prop(props, "traj1_x", text="X座標")
row_c.prop(props, "traj1_thickness", text="太さ")
row_c.prop(props, "traj1_color", text="")
if props.traj2_show:
row_c = box3.row(align=True)
row_c.label(text="軌跡2:")
row_c.prop(props, "traj2_x", text="X座標")
row_c.prop(props, "traj2_thickness", text="太さ")
row_c.prop(props, "traj2_color", text="")
if props.tx_show:
box3.separator()
row_a = box3.row()
row_a.label(text="波面 (t=ax):")
row_a.prop(props, "tx_slope_a", text="係数 a", slider=True)
row_c = box3.row()
row_c.label(text="")
row_c.prop(props, "tx_thickness", text="太さ")
row_c.prop(props, "tx_color", text="")
class DICE_TOOLS_5_PT_Footer(Panel):
bl_label = "リンク・管理"
bl_idname = "DICE_TOOLS_5_PT_Footer"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
links = [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2), (LINK_NAME_3, LINK_URL_3), (LINK_NAME_4, LINK_URL_4), (LINK_NAME_5, LINK_URL_5)]
for name, url in links:
op = layout.operator("dice_tools_5.open_link", text=name, icon='WORLD')
op.url = url
layout.separator()
layout.operator("dice_tools_5.remove_addon_ui", icon='TRASH')
# ==========================================
# 【登録・解除】
# ==========================================
classes = (
# オペレーター (骨格)
DICE_TOOLS_5_OT_ViewCenter,
DICE_TOOLS_5_OT_Create,
DICE_TOOLS_5_OT_Detach,
DICE_TOOLS_5_OT_OpenLink,
DICE_TOOLS_5_OT_RemoveAddon,
# オペレーター (SineWave)
SINE_WAVE_OT_Create,
SINE_WAVE_OT_Clear,
# プロパティ (SineWave)
SineWaveProperties,
# パネル群 (全て骨格立方体タブ内)
DICE_TOOLS_5_PT_ViewPanel,
DICE_TOOLS_5_PT_CreationPanel,
SINE_WAVE_PT_CreationPanel,
DICE_TOOLS_5_PT_Footer,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
s = bpy.types.Scene
s.dice_shape = EnumProperty(items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], name="Shape", default='CUBE', update=update_skeleton_live)
s.dice_size = FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
s.dice_thick = FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
s.dice_res = IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
s.dice_pos = FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
s.dice_rot = FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
s.dice_show_faces = BoolProperty(name="Show Faces", default=True, update=update_skeleton_live)
s.dice_color_frame = FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
s.dice_color_face = FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)
s.dice_info_str = StringProperty(name="Info")
s.sine_wave_props = PointerProperty(type=SineWaveProperties)
def unregister():
for cls in reversed(classes):
if hasattr(bpy.utils, "unregister_class"):
try: bpy.utils.unregister_class(cls)
except: pass
s = bpy.types.Scene
del s.dice_shape, s.dice_size, s.dice_thick, s.dice_res, s.dice_pos, s.dice_rot
del s.dice_show_faces, s.dice_color_frame, s.dice_color_face, s.dice_info_str
del s.sine_wave_props
clear_sine_wave_data()
if __name__ == "__main__":
register()
bl_info = {
"name": "zionad_ [ 骨格立方体 ]",
"author": "zionad_",
"version": (2, 0, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > 骨格立方体",
"description": "表面・骨格別の色設定、表面有無の選択が可能な骨格生成ツール",
"category": "骨格立方体",
}
# 2026年07月14日 06時35分00秒
import bpy
import math
import webbrowser
import random
from bpy.props import FloatProperty, FloatVectorProperty, StringProperty, IntProperty, EnumProperty, BoolProperty
from bpy.types import Operator, Panel
# ==========================================
# 【初期設定・手動変更エリア】
# ==========================================
LINK_NAME_1 = "基本アドオン設定 20260711b"
LINK_URL_1 = "<https://app.notion.com/p/20260711-39af5dacaf438027811dcd877db7c630>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"
LINK_NAME_4 = "blender コンテナ 20260713"
LINK_URL_4 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_5 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"
OBJ_NAME_BASE = "Skeleton_Dice"
COL_NAME = "Dice_Collection"
SUB_COL_NAME = "Skeleton_Cubes"
# 初期数値
INIT_SIZE = 60.0
INIT_THICKNESS = 1.0
INIT_RES = 32 # 分割数初期値 32
INIT_POS = (0.0, 0.0, 0.0)
INIT_ROT = (0.0, 0.0, 0.0)
# 初期色 (骨格: フレーム用)
INIT_CLR_FRAME = (0.1, 0.6, 1.0, 1.0) # 青系
# 初期色 (表面: 面用)
INIT_CLR_FACE = (1.0, 1.0, 1.0, 0.3) # 白の透明
# ==========================================
# 【形状生成コア・ロジック】
# ==========================================
def create_advanced_geometry(context):
"""骨格(パイプ)と表面(面)を組み合わせたメッシュを構築"""
scene = context.scene
import bmesh
# 1. 元となる形状のデータを作成
bm_base = bmesh.new()
if scene.dice_shape == 'CUBE':
bmesh.ops.create_cube(bm_base, size=scene.dice_size)
else: # SQUARE
bmesh.ops.create_grid(bm_base, x_segments=1, y_segments=1, size=scene.dice_size/2)
# 2. パイプ骨格の作成
curve_data = bpy.data.curves.new("temp_curve", type='CURVE')
curve_data.dimensions = '3D'
curve_data.fill_mode = 'FULL'
curve_data.bevel_depth = scene.dice_thick / 2
curve_data.bevel_resolution = scene.dice_res
for edge in bm_base.edges:
v1, v2 = edge.verts[0].co, edge.verts[1].co
polyline = curve_data.splines.new('POLY')
polyline.points.add(1)
polyline.points[0].co = (*v1, 1)
polyline.points[1].co = (*v2, 1)
# 評価済みメッシュとして骨格を取得
temp_frame_obj = bpy.data.objects.new("temp_frame", curve_data)
context.collection.objects.link(temp_frame_obj)
depsgraph = context.evaluated_depsgraph_get()
mesh_frame = bpy.data.meshes.new_from_object(temp_frame_obj.evaluated_get(depsgraph))
# 後片付け
bpy.data.objects.remove(temp_frame_obj)
bpy.data.curves.remove(curve_data)
# 3. 結合用のBMesh作成
bm_final = bmesh.new()
# 骨格をBMeshに追加 (Material Index 0)
bm_final.from_mesh(mesh_frame)
for face in bm_final.faces:
face.material_index = 0
bpy.data.meshes.remove(mesh_frame)
# 表面を追加する場合 (Material Index 1)
if scene.dice_show_faces:
# 面だけを抽出して追加
bm_face = bmesh.new()
if scene.dice_shape == 'CUBE':
bmesh.ops.create_cube(bm_face, size=scene.dice_size)
else:
bmesh.ops.create_grid(bm_face, x_segments=1, y_segments=1, size=scene.dice_size/2)
# 既存のBMeshに結合
for face in bm_face.faces:
face.material_index = 1
bm_face.to_mesh(bpy.data.meshes.new("temp_face")) # 変換経由
bm_final.from_mesh(bpy.data.meshes.get("temp_face"))
bpy.data.meshes.remove(bpy.data.meshes.get("temp_face"))
bm_face.free()
# 4. 最終メッシュ出力
mesh_out = bpy.data.meshes.new(OBJ_NAME_BASE)
bm_final.to_mesh(mesh_out)
bm_final.free()
bm_base.free()
return mesh_out
def update_skeleton_live(self, context):
scene = context.scene
for obj in scene.objects:
if obj.name.startswith(OBJ_NAME_BASE) and obj.type == 'MESH':
# 変形
obj.location = scene.dice_pos
obj.rotation_euler = [math.radians(a) for a in scene.dice_rot]
# メッシュ更新
new_mesh = create_advanced_geometry(context)
old_mesh = obj.data
obj.data = new_mesh
if old_mesh: bpy.data.meshes.remove(old_mesh)
# マテリアル設定 (2スロット)
while len(obj.data.materials) < 2:
obj.data.materials.append(None)
# スロット0: 骨格用
if not obj.material_slots[0].material:
obj.material_slots[0].material = bpy.data.materials.new(name="Mat_Frame")
mat_frame = obj.material_slots[0].material
mat_frame.use_nodes = True
bsdf_frame = mat_frame.node_tree.nodes.get("Principled BSDF")
if bsdf_frame:
bsdf_frame.inputs['Base Color'].default_value = scene.dice_color_frame
if 'Alpha' in bsdf_frame.inputs: bsdf_frame.inputs['Alpha'].default_value = scene.dice_color_frame[3]
mat_frame.blend_method = 'BLEND'
# スロット1: 表面用
if not obj.material_slots[1].material:
obj.material_slots[1].material = bpy.data.materials.new(name="Mat_Face")
mat_face = obj.material_slots[1].material
mat_face.use_nodes = True
bsdf_face = mat_face.node_tree.nodes.get("Principled BSDF")
if bsdf_face:
bsdf_face.inputs['Base Color'].default_value = scene.dice_color_face
if 'Alpha' in bsdf_face.inputs: bsdf_face.inputs['Alpha'].default_value = scene.dice_color_face[3]
mat_face.blend_method = 'BLEND'
# ==========================================
# 【オペレーター】
# ==========================================
class DICE_TOOLS_5_OT_Create(Operator):
bl_idname = "dice_tools_5.create_skeleton"
bl_label = "骨格を新規生成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh_data = create_advanced_geometry(context)
obj = bpy.data.objects.new(f"{OBJ_NAME_BASE}_{random.randint(100,999)}", mesh_data)
main_col = bpy.data.collections.get(COL_NAME) or bpy.data.collections.new(COL_NAME)
if main_col.name not in context.scene.collection.children:
context.scene.collection.children.link(main_col)
sub_col = bpy.data.collections.get(SUB_COL_NAME) or bpy.data.collections.new(SUB_COL_NAME)
if sub_col.name not in main_col.children:
main_col.children.link(sub_col)
sub_col.objects.link(obj)
context.view_layer.objects.active = obj
update_skeleton_live(self, context)
return {'FINISHED'}
class DICE_TOOLS_5_OT_Detach(Operator):
bl_idname = "dice_tools_5.detach_object"
bl_label = "アドオンから切り離す"
def execute(self, context):
obj = context.active_object
if obj and obj.name.startswith(OBJ_NAME_BASE):
obj.name = "Fixed_Dice_" + str(random.randint(100,999))
self.report({'INFO'}, "独立させました")
return {'FINISHED'}
class DICE_TOOLS_5_OT_ViewCenter(Operator):
bl_idname = "dice_tools_5.view_center"
bl_label = "選択を中央へ"
def execute(self, context):
if context.active_object: bpy.ops.view3d.view_selected()
return {'FINISHED'}
class DICE_TOOLS_5_OT_OpenLink(Operator):
bl_idname = "dice_tools_5.open_link"
bl_label = "URLを開く"
url: StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class DICE_TOOLS_5_OT_RemoveAddon(Operator):
bl_idname = "dice_tools_5.remove_addon_ui"
bl_label = "アドオンUIを削除"
def execute(self, context):
unregister()
return {'FINISHED'}
# ==========================================
# 【UIパネル】
# ==========================================
class DICE_TOOLS_5_PT_ViewPanel(Panel):
bl_label = "ビュー情報"
bl_idname = "DICE_TOOLS_5_PT_ViewPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
def draw(self, context):
layout = self.layout
rv3d = context.region_data
layout.operator("dice_tools_5.view_center", icon='CENTER_ONLY')
layout.separator()
layout.label(text="モード: " + ("透視投影" if rv3d.view_perspective == 'PERSP' else "平行投影"))
box = layout.box()
loc = rv3d.view_location
box.label(text=f"中心: X:{loc.x:.1f} Y:{loc.y:.1f} Z:{loc.z:.1f}")
layout.label(text=f"画面: {context.region.width} x {context.region.height}")
if rv3d.view_perspective == 'PERSP':
layout.label(text=f"距離: {rv3d.view_distance:.1f}")
layout.prop(context.scene, "dice_info_str", text="", icon='COPY_ID')
class DICE_TOOLS_5_PT_CreationPanel(Panel):
bl_label = "骨格 調整"
bl_idname = "DICE_TOOLS_5_PT_CreationPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
def draw(self, context):
layout = self.layout
scene = context.scene
obj = context.active_object
layout.operator("dice_tools_5.create_skeleton", icon='MESH_CUBE')
layout.separator()
col = layout.column(align=True)
col.prop(scene, "dice_shape", text="基本形状")
col.prop(scene, "dice_pos", text="位置")
col.prop(scene, "dice_rot", text="回転")
col.prop(scene, "dice_size", text="サイズ")
col.prop(scene, "dice_thick", text="骨格の太さ")
col.prop(scene, "dice_res", text="分割数")
layout.separator()
layout.prop(scene, "dice_show_faces", text="表面を表示する", icon='FACESEL')
row = layout.row()
row.prop(scene, "dice_color_frame", text="骨格の色")
if scene.dice_show_faces:
row.prop(scene, "dice_color_face", text="表面の色")
layout.separator()
if obj and obj.name.startswith(OBJ_NAME_BASE):
layout.operator("dice_tools_5.detach_object", icon='UNLINKED')
else:
layout.label(text="※生成した骨格を選択して調整", icon='INFO')
class DICE_TOOLS_5_PT_Footer(Panel):
bl_label = "リンク・管理"
bl_idname = "DICE_TOOLS_5_PT_Footer"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "骨格立方体"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
links = [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2), (LINK_NAME_3, LINK_URL_3), (LINK_NAME_4, LINK_URL_4), (LINK_NAME_5, LINK_URL_5)]
for name, url in links:
op = layout.operator("dice_tools_5.open_link", text=name, icon='WORLD')
op.url = url
layout.separator()
layout.operator("dice_tools_5.remove_addon_ui", icon='TRASH')
# ==========================================
# 【登録・解除】
# ==========================================
classes = (
DICE_TOOLS_5_OT_ViewCenter, DICE_TOOLS_5_OT_Create, DICE_TOOLS_5_OT_Detach,
DICE_TOOLS_5_OT_OpenLink, DICE_TOOLS_5_OT_RemoveAddon,
DICE_TOOLS_5_PT_ViewPanel, DICE_TOOLS_5_PT_CreationPanel, DICE_TOOLS_5_PT_Footer,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
s = bpy.types.Scene
s.dice_shape = EnumProperty(items=[('CUBE', "立方体", ""), ('SQUARE', "正方形", "")], name="Shape", default='CUBE', update=update_skeleton_live)
s.dice_size = FloatProperty(name="Size", default=INIT_SIZE, min=0.1, update=update_skeleton_live)
s.dice_thick = FloatProperty(name="Thick", default=INIT_THICKNESS, min=0.01, update=update_skeleton_live)
s.dice_res = IntProperty(name="Res", default=INIT_RES, min=3, max=100, update=update_skeleton_live)
s.dice_pos = FloatVectorProperty(name="Pos", default=INIT_POS, update=update_skeleton_live)
s.dice_rot = FloatVectorProperty(name="Rot", default=INIT_ROT, update=update_skeleton_live)
s.dice_show_faces = BoolProperty(name="Show Faces", default=True, update=update_skeleton_live)
s.dice_color_frame = FloatVectorProperty(name="Color Frame", subtype='COLOR', size=4, default=INIT_CLR_FRAME, min=0.0, max=1.0, update=update_skeleton_live)
s.dice_color_face = FloatVectorProperty(name="Color Face", subtype='COLOR', size=4, default=INIT_CLR_FACE, min=0.0, max=1.0, update=update_skeleton_live)
s.dice_info_str = StringProperty(name="Info")
def unregister():
for cls in reversed(classes):
if hasattr(bpy.utils, "unregister_class"):
try: bpy.utils.unregister_class(cls)
except: pass
s = bpy.types.Scene
del s.dice_shape, s.dice_size, s.dice_thick, s.dice_res, s.dice_pos, s.dice_rot
del s.dice_show_faces, s.dice_color_frame, s.dice_color_face, s.dice_info_str
if __name__ == "__main__":
register()
bl_info = {
"name": "Sine Wave Generator (Space & Time Trajectory)",
"author": "ChatGPT",
"version": (1, 6, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar (N Panel)",
"description": "進行波、x=0の時間軌跡、t=xの波面などを描画します。",
"category": "3D View",
}
import bpy
import math
# ===================================
# 共通定義 & 初期設定
# ===================================
ADDON_TITLE = "Sine Wave Generator"
PREFIX = "sine_wave"
# 数値
SINE_AMPLITUDE_DEFAULT = 2.0
SINE_FREQUENCY_DEFAULT = 1.0
SINE_LENGTH_DEFAULT = 10.0
SINE_TIME_DEFAULT = 1.0 # デフォルトを t=1.0 に変更
SINE_DELAY_DEFAULT = 0.0
SINE_RESOLUTION_DEFAULT = 128
SINE_WAVE_SPEED_X_DEFAULT = 1.0
# 位置オフセット
SINE_LOC_X_DEFAULT = 0.0
SINE_LOC_Y_DEFAULT = 0.0
SINE_LOC_Z_DEFAULT = 0.0
# デフォルトの色
INIT_COLOR = (0.5, 0.5, 0.5, 0.8) # t=0 波形 (グレー)
MAIN_COLOR = (0.2, 0.6, 1.0, 1.0) # t=1 波形 (青)
TRAJ_COLOR = (1.0, 0.8, 0.2, 1.0) # x=0 軌跡 (黄色)
TX_COLOR = (0.2, 1.0, 0.5, 1.0) # t=x 波面 (緑)
# ===================================
# 共通の変位計算関数
# ===================================
def calculate_z_displacement(x, t, props):
"""
空間(x)と時間(t)から、Z軸方向の変位を計算する共通関数。
※ 波の式を (v*t - x) とすることで、t>0 (時間が進む) のとき、
x=0 における最初の変位が z>0 (山) になります。
"""
phase_x = props.wave_speed_x * (t - props.delay) - x
return props.amplitude * math.sin(props.frequency * phase_x) + SINE_LOC_Z_DEFAULT
# ===================================
# プロパティ定義
# ===================================
class SineWaveProperties(bpy.types.PropertyGroup):
# 基本パラメータ
amplitude: bpy.props.FloatProperty(name="振幅高さ (Z軸)", default=SINE_AMPLITUDE_DEFAULT)
frequency: bpy.props.FloatProperty(name="周波数 (k)", default=SINE_FREQUENCY_DEFAULT)
length: bpy.props.FloatProperty(name="波の長さ (X軸)", default=SINE_LENGTH_DEFAULT, description="x=0 から +X方向への描画距離")
time_t: bpy.props.FloatProperty(name="時間 t (Y座標)", default=SINE_TIME_DEFAULT, description="時間波形を描画する時刻")
wave_speed_x: bpy.props.FloatProperty(name="X方向速度 (v)", default=SINE_WAVE_SPEED_X_DEFAULT, soft_min=-10.0, soft_max=10.0)
delay: bpy.props.FloatProperty(name="開始時間の遅れ", default=SINE_DELAY_DEFAULT)
resolution: bpy.props.IntProperty(name="解像度", default=SINE_RESOLUTION_DEFAULT, min=2)
# 1. 初期波 (t=0)
init_show: bpy.props.BoolProperty(name="初期波 (t=0) を描画", default=True)
init_thickness: bpy.props.FloatProperty(name="初期波の太さ", default=0.05, min=0.0)
init_color: bpy.props.FloatVectorProperty(name="初期波の色", subtype='COLOR', size=4, default=INIT_COLOR, min=0.0, max=1.0)
# 2. メイン波 (t=t)
main_show: bpy.props.BoolProperty(name="時間波形 (t=t) を描画", default=True)
thickness: bpy.props.FloatProperty(name="時間波形の太さ", default=0.15, min=0.0)
color_main: bpy.props.FloatVectorProperty(name="時間波形の色", subtype='COLOR', size=4, default=MAIN_COLOR, min=0.0, max=1.0)
# 3. 時間軌跡 (x=0)
traj_show: bpy.props.BoolProperty(name="時間軌跡 (x=0) を描画", default=True)
traj_thickness: bpy.props.FloatProperty(name="軌跡の太さ", default=0.1, min=0.0)
traj_color: bpy.props.FloatVectorProperty(name="軌跡の色", subtype='COLOR', size=4, default=TRAJ_COLOR, min=0.0, max=1.0)
# 4. t=x 波面
tx_show: bpy.props.BoolProperty(name="t=x 波面を描画", default=True)
tx_thickness: bpy.props.FloatProperty(name="t=x 波の太さ", default=0.12, min=0.0)
tx_color: bpy.props.FloatVectorProperty(name="t=x 波の色", subtype='COLOR', size=4, default=TX_COLOR, min=0.0, max=1.0)
# ===================================
# オペレーター (生成処理)
# ===================================
class SINE_WAVE_OT_generate(bpy.types.Operator):
bl_idname = f"{PREFIX}.generate"
bl_label = "波と軌跡を生成"
def create_curve_object(self, name, thickness, color, collection):
"""カーブとマテリアルを生成するヘルパー関数"""
curve_data = bpy.data.curves.new(name=name, type='CURVE')
curve_data.dimensions = '3D'
curve_data.bevel_depth = thickness
curve_data.bevel_resolution = 4
spline = curve_data.splines.new(type='POLY')
obj = bpy.data.objects.new(name, curve_data)
collection.objects.link(obj)
mat = bpy.data.materials.new(name=f"{name}_Mat")
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 len(color) == 4 and color[3] < 1.0:
bsdf.inputs['Alpha'].default_value = color[3]
obj.data.materials.append(mat)
return spline
def execute(self, context):
props = context.scene.sine_wave_props
collection = context.collection
# ------------------------------------------------
# 1. 初期波形 (t=0 の空間波形: y = 0)
# ------------------------------------------------
if props.init_show:
spline_init = self.create_curve_object("Wave_Init_t0", props.init_thickness, props.init_color, collection)
spline_init.points.add(props.resolution - 1)
for i in range(props.resolution):
x_local = (i / (props.resolution - 1)) * props.length # x > 0 基準
y = 0.0 + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, 0.0, props)
spline_init.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
# ------------------------------------------------
# 2. メインの波 (空間波形: y = time_t)
# ------------------------------------------------
if props.main_show:
spline_main = self.create_curve_object(f"Wave_Main_t{props.time_t:.1f}", props.thickness, props.color_main, collection)
spline_main.points.add(props.resolution - 1)
for i in range(props.resolution):
x_local = (i / (props.resolution - 1)) * props.length # x > 0 基準
y = props.time_t + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, props.time_t, props)
spline_main.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
# ------------------------------------------------
# 3. 時間軌跡 (x=0 の時間軸変位: y = 0 to time_t)
# ------------------------------------------------
if props.traj_show and props.time_t > 0.0:
spline_traj = self.create_curve_object("Trajectory_x0", props.traj_thickness, props.traj_color, collection)
spline_traj.points.add(props.resolution - 1)
for i in range(props.resolution):
tau = (i / (props.resolution - 1)) * props.time_t
x_local = 0.0 # x=0 に固定
y = tau + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, tau, props)
spline_traj.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
# ------------------------------------------------
# 4. t=x 波面 (xとtが連動する斜めの面上の波: y = x)
# ------------------------------------------------
if props.tx_show:
spline_tx = self.create_curve_object("Wave_t_equals_x", props.tx_thickness, props.tx_color, collection)
spline_tx.points.add(props.resolution - 1)
for i in range(props.resolution):
x_local = (i / (props.resolution - 1)) * props.length
t_local = x_local # t = x
y = t_local + SINE_LOC_Y_DEFAULT
z = calculate_z_displacement(x_local, t_local, props)
spline_tx.points[i].co = (x_local + SINE_LOC_X_DEFAULT, y, z, 1.0)
return {'FINISHED'}
# ===================================
# UI パネル
# ===================================
class SINE_WAVE_PT_panel(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{PREFIX}_PT_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "SineWave"
def draw(self, context):
layout = self.layout
props = context.scene.sine_wave_props
# 基本設定
box = layout.box()
box.label(text="基本設定", icon='VIEW_ORTHO')
box.prop(props, "amplitude")
box.prop(props, "frequency")
box.prop(props, "length")
box.prop(props, "time_t")
box.prop(props, "wave_speed_x")
box.prop(props, "delay")
box.prop(props, "resolution")
# 各種描画のオンオフと見た目
box2 = layout.box()
box2.label(text="波の描画設定", icon='OUTLINER_OB_CURVE')
# 1. 初期波 (t=0)
box2.prop(props, "init_show", icon='TIME')
if props.init_show:
row = box2.row()
row.prop(props, "init_thickness", text="太さ")
row.prop(props, "init_color", text="")
# 2. メイン波 (t=t)
box2.prop(props, "main_show", text=f"時間波形 (t={props.time_t:.1f}) を描画", icon='MOD_WAVE')
if props.main_show:
row = box2.row()
row.prop(props, "thickness", text="太さ")
row.prop(props, "color_main", text="")
# 3. 軌跡 (x=0)
box2.prop(props, "traj_show", icon='ANIM')
if props.traj_show:
row = box2.row()
row.prop(props, "traj_thickness", text="太さ")
row.prop(props, "traj_color", text="")
# 4. t=x 波面
box2.prop(props, "tx_show", icon='CON_TRANSLIKE')
if props.tx_show:
row = box2.row()
row.prop(props, "tx_thickness", text="太さ")
row.prop(props, "tx_color", text="")
layout.separator()
row = layout.row()
row.scale_y = 1.5
row.operator(SINE_WAVE_OT_generate.bl_idname, text="波と軌跡を作成", icon='PLAY')
def setup_viewport_ui():
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
space.show_region_ui = True
return None
classes = (
SineWaveProperties,
SINE_WAVE_OT_generate,
SINE_WAVE_PT_panel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.sine_wave_props = bpy.props.PointerProperty(type=SineWaveProperties)
bpy.app.timers.register(setup_viewport_ui, first_interval=0.1)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.sine_wave_props
if __name__ == "__main__":
register()