完成品2026

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