骨格 立方体作成リスト

# Code Created : 2024_0526_1200
bl_info = {
"name": "Skeleton Cube Generator (Pro)",
"author": "Your Name",
"version": (2, 4, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar",
"description": "Generates a skeletal cube and an independent sphere with join functionality.",
"category": "3D View",
}
import bpy
import bmesh
import math
import webbrowser
import time
from mathutils import Vector, Matrix
# =========================================================
# 共通定義
# =========================================================
ADDON_TITLE = "Skeleton Cube"
PREFIX_VAL = "SKELETON_CUBE"
PREFIX = PREFIX_VAL.lower()
ADDON_CATEGORY = "Skeleton Cube"
# =========================================================
# ユーザー設定パラメータ (初期値)
# =========================================================
TARGET_COLLECTION_NAME = "SkeletonCube_Collection"
DEFAULT_CENTER_X = 0.0
DEFAULT_CENTER_Y = 0.0
DEFAULT_CENTER_Z = 0.0
DEFAULT_ROTATION_X = 0.0
DEFAULT_ROTATION_Y = 0.0
DEFAULT_ROTATION_Z = 0.0
DEFAULT_EDGE_LENGTH = 2.0
DEFAULT_CYLINDER_RADIUS = 0.05
DEFAULT_COLOR_R = 0.2
DEFAULT_COLOR_G = 0.6
DEFAULT_COLOR_B = 1.0
DEFAULT_ALPHA = 1.0
DEFAULT_VERT_SPHERES_SHOW = True
DEFAULT_VERT_SPHERE_RADIUS = 0.08
DEFAULT_VERT_SPH_COLOR_R = 1.0
DEFAULT_VERT_SPH_COLOR_G = 0.3
DEFAULT_VERT_SPH_COLOR_B = 0.3
DEFAULT_VERT_SPH_ALPHA = 1.0
DEFAULT_SINGLE_SPHERE_SHOW = True
DEFAULT_SINGLE_SPHERE_LOC_X = 0.0
DEFAULT_SINGLE_SPHERE_LOC_Y = 0.0
DEFAULT_SINGLE_SPHERE_LOC_Z = 0.0
DEFAULT_SINGLE_SPHERE_RADIUS = 0.5
DEFAULT_SINGLE_SPH_COLOR_R = 0.2
DEFAULT_SINGLE_SPH_COLOR_G = 1.0
DEFAULT_SINGLE_SPH_COLOR_B = 0.2
DEFAULT_SINGLE_SPH_ALPHA = 1.0
# =========================================================
# リンク・補助関数
# =========================================================
def link_to_collection(context, obj, coll_name):
coll = bpy.data.collections.get(coll_name)
if not coll:
coll = bpy.data.collections.new(coll_name)
context.scene.collection.children.link(coll)
if obj.name not in coll.objects:
coll.objects.link(obj)
# 他のコレクションから除外
for c in obj.users_collection:
if c != coll:
c.objects.unlink(obj)
def setup_material(mat_name, color, alpha):
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(mat_name)
mat.use_nodes = True
mat.use_backface_culling = False
mat.diffuse_color = (color[0], color[1], color[2], alpha)
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
if hasattr(mat, "shadow_method"):
mat.shadow_method = 'NONE'
nodes = mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs['Base Color'].default_value = (color[0], color[1], color[2], alpha)
if 'Alpha' in bsdf.inputs:
bsdf.inputs['Alpha'].default_value = alpha
return mat
# =========================================================
# メイン生成・更新ロジック
# =========================================================
def update_skeleton_cube(context):
props = context.scene.skeleton_cube_props
obj_name = PREFIX_VAL
mesh_name = f"{PREFIX_VAL}_MESH"
mesh = bpy.data.meshes.get(mesh_name)
if not mesh:
mesh = bpy.data.meshes.new(mesh_name)
else:
mesh.clear_geometry()
obj = bpy.data.objects.get(obj_name)
if not obj:
obj = bpy.data.objects.new(obj_name, mesh)
link_to_collection(context, obj, props.target_collection_name)
bm = bmesh.new()
L = props.edge_length
h = L / 2.0
r_cyl = props.cylinder_radius
verts_pos = [
Vector((-h, -h, -h)), Vector((+h, -h, -h)), Vector((+h, +h, -h)), Vector((-h, +h, -h)),
Vector((-h, -h, +h)), Vector((+h, -h, +h)), Vector((+h, +h, +h)), Vector((-h, +h, +h))
]
edges = [(0,1), (1,2), (2,3), (3,0), (4,5), (5,6), (6,7), (7,4), (0,4), (1,5), (2,6), (3,7)]
# 円柱
for i, j in edges:
start, end = verts_pos[i], verts_pos[j]
center = (start + end) / 2.0
direction = (end - start).normalized()
length = (start - end).length
rot_quat = direction.to_track_quat('Z', 'Y')
matrix = Matrix.Translation(center) @ rot_quat.to_matrix().to_4x4()
bmesh.ops.create_cone(
bm, cap_ends=True, cap_tris=False, segments=16,
radius1=r_cyl, radius2=r_cyl, depth=length, matrix=matrix
)
# 頂点球体
if props.show_vert_spheres:
r_sph = props.vert_sphere_radius
for v in verts_pos:
start_face_count = len(bm.faces)
bmesh.ops.create_uvsphere(
bm, u_segments=16, v_segments=16, radius=r_sph, matrix=Matrix.Translation(v)
)
bm.faces.ensure_lookup_table()
for face_idx in range(start_face_count, len(bm.faces)):
bm.faces[face_idx].material_index = 1
bm.to_mesh(mesh)
bm.free()
mesh.update()
obj.location = props.loc
obj.rotation_euler = props.rot
mat_cyl = setup_material(f"{PREFIX_VAL}_CYL_MAT", props.color, props.alpha)
mat_vert_sph = setup_material(f"{PREFIX_VAL}_VERT_SPH_MAT", props.vert_sphere_color, props.vert_sphere_alpha)
obj.data.materials.clear()
obj.data.materials.append(mat_cyl) # Index 0
obj.data.materials.append(mat_vert_sph) # Index 1
def update_single_sphere(context):
props = context.scene.skeleton_cube_props
obj_name = f"{PREFIX_VAL}_SINGLE_SPH"
mesh_name = f"{obj_name}_MESH"
mesh = bpy.data.meshes.get(mesh_name)
if not mesh:
mesh = bpy.data.meshes.new(mesh_name)
else:
mesh.clear_geometry()
obj = bpy.data.objects.get(obj_name)
if not obj:
obj = bpy.data.objects.new(obj_name, mesh)
link_to_collection(context, obj, props.target_collection_name)
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=32, radius=props.single_sphere_radius)
bm.to_mesh(mesh)
bm.free()
mesh.update()
# 骨格立方体を親にして追従させる
cube_obj = bpy.data.objects.get(PREFIX_VAL)
if cube_obj:
obj.parent = cube_obj
obj.location = Vector(props.single_sphere_loc)
obj.rotation_euler = (0, 0, 0)
else:
obj.parent = None
obj.location = Vector(props.single_sphere_loc)
obj.rotation_euler = (0, 0, 0)
mat = setup_material(f"{obj_name}_MAT", props.single_sphere_color, props.single_sphere_alpha)
obj.data.materials.clear()
obj.data.materials.append(mat)
# =========================================================
# プロパティ
# =========================================================
def on_property_update(self, context):
if self.cube_created:
update_skeleton_cube(context)
if self.single_created:
update_single_sphere(context)
for area in context.screen.areas:
if area.type == 'VIEW_3D':
area.tag_redraw()
class SkeletonCubeProperties(bpy.types.PropertyGroup):
target_collection_name: bpy.props.StringProperty(name="保存コレクション名", default=TARGET_COLLECTION_NAME)
cube_created: bpy.props.BoolProperty(default=False)
single_created: bpy.props.BoolProperty(default=False)
loc: bpy.props.FloatVectorProperty(name="位置", size=3, default=(DEFAULT_CENTER_X, DEFAULT_CENTER_Y, DEFAULT_CENTER_Z), update=on_property_update)
rot: bpy.props.FloatVectorProperty(name="回転", size=3, subtype='EULER', default=(math.radians(DEFAULT_ROTATION_X), math.radians(DEFAULT_ROTATION_Y), math.radians(DEFAULT_ROTATION_Z)), update=on_property_update)
edge_length: bpy.props.FloatProperty(name="立方体1辺", default=DEFAULT_EDGE_LENGTH, min=0.001, update=on_property_update)
cylinder_radius: bpy.props.FloatProperty(name="円柱半径", default=DEFAULT_CYLINDER_RADIUS, min=0.001, update=on_property_update)
color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', size=3, min=0.0, max=1.0, default=(DEFAULT_COLOR_R, DEFAULT_COLOR_G, DEFAULT_COLOR_B), update=on_property_update)
alpha: bpy.props.FloatProperty(name="透明度", default=DEFAULT_ALPHA, min=0.0, max=1.0, update=on_property_update)
show_vert_spheres: bpy.props.BoolProperty(name="頂点に球体を表示", default=DEFAULT_VERT_SPHERES_SHOW, update=on_property_update)
vert_sphere_radius: bpy.props.FloatProperty(name="球体半径", default=DEFAULT_VERT_SPHERE_RADIUS, min=0.001, update=on_property_update)
vert_sphere_color: bpy.props.FloatVectorProperty(name="球体色", subtype='COLOR', size=3, min=0.0, max=1.0, default=(DEFAULT_VERT_SPH_COLOR_R, DEFAULT_VERT_SPH_COLOR_G, DEFAULT_VERT_SPH_COLOR_B), update=on_property_update)
vert_sphere_alpha: bpy.props.FloatProperty(name="球体透明度", default=DEFAULT_VERT_SPH_ALPHA, min=0.0, max=1.0, update=on_property_update)
show_single_sphere: bpy.props.BoolProperty(name="単独球体を生成する", default=DEFAULT_SINGLE_SPHERE_SHOW, update=on_property_update)
single_sphere_loc: bpy.props.FloatVectorProperty(name="相対位置", size=3, default=(DEFAULT_SINGLE_SPHERE_LOC_X, DEFAULT_SINGLE_SPHERE_LOC_Y, DEFAULT_SINGLE_SPHERE_LOC_Z), update=on_property_update)
single_sphere_radius: bpy.props.FloatProperty(name="球体の半径", default=DEFAULT_SINGLE_SPHERE_RADIUS, min=0.001, update=on_property_update)
single_sphere_color: bpy.props.FloatVectorProperty(name="球体の色", subtype='COLOR', size=3, min=0.0, max=1.0, default=(DEFAULT_SINGLE_SPH_COLOR_R, DEFAULT_SINGLE_SPH_COLOR_G, DEFAULT_SINGLE_SPH_COLOR_B), update=on_property_update)
single_sphere_alpha: bpy.props.FloatProperty(name="球体の透明度", default=DEFAULT_SINGLE_SPH_ALPHA, min=0.0, max=1.0, update=on_property_update)
# =========================================================
# Operators
# =========================================================
class SKELETON_CUBE_OT_CreateCube(bpy.types.Operator):
bl_idname = f"{PREFIX}.create_cube"
bl_label = "骨格立方体を作成"
def execute(self, context):
context.scene.skeleton_cube_props.cube_created = True
update_skeleton_cube(context)
return {'FINISHED'}
class SKELETON_CUBE_OT_DetachCube(bpy.types.Operator):
bl_idname = f"{PREFIX}.detach_cube"
bl_label = "骨格立方体を独立させる"
def execute(self, context):
obj = bpy.data.objects.get(PREFIX_VAL)
if obj:
suffix = str(int(time.time() % 100000))
obj.name = f"{PREFIX_VAL}_Fixed_{suffix}"
if obj.data:
obj.data.name = f"{PREFIX_VAL}_MESH_{suffix}"
for slot in obj.material_slots:
if slot.material:
new_mat = slot.material.copy()
new_mat.name = f"{slot.material.name}_{suffix}"
slot.material = new_mat
context.scene.skeleton_cube_props.cube_created = False
self.report({'INFO'}, f"立方体を独立させました: {obj.name}")
return {'FINISHED'}
class SKELETON_CUBE_OT_CreateSingle(bpy.types.Operator):
bl_idname = f"{PREFIX}.create_single"
bl_label = "単独球体を作成"
def execute(self, context):
context.scene.skeleton_cube_props.single_created = True
update_single_sphere(context)
return {'FINISHED'}
class SKELETON_CUBE_OT_DetachSingle(bpy.types.Operator):
bl_idname = f"{PREFIX}.detach_single"
bl_label = "単独球体を独立させる"
def execute(self, context):
obj = bpy.data.objects.get(f"{PREFIX_VAL}_SINGLE_SPH")
if obj:
suffix = str(int(time.time() % 100000))
obj.name = f"{PREFIX_VAL}_SINGLE_SPH_Fixed_{suffix}"
if obj.data:
obj.data.name = f"{obj.name}_MESH"
for slot in obj.material_slots:
if slot.material:
new_mat = slot.material.copy()
new_mat.name = f"{slot.material.name}_{suffix}"
slot.material = new_mat
# 親子関係の解除と位置の確定
obj.matrix_world = obj.matrix_world.copy()
obj.parent = None
context.scene.skeleton_cube_props.single_created = False
self.report({'INFO'}, f"単独球体を独立させました: {obj.name}")
return {'FINISHED'}
class SKELETON_CUBE_OT_JoinSelected(bpy.types.Operator):
bl_idname = f"{PREFIX}.join_selected"
bl_label = "選択したObjectを一体化"
bl_description = "2つ以上のオブジェクトを選択して1つにまとめます (Ctrl+Jと同じ機能)"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return len(context.selected_objects) >= 2 and context.active_object is not None
def execute(self, context):
bpy.ops.object.join()
self.report({'INFO'}, "選択されたオブジェクトを一体化しました")
return {'FINISHED'}
class SKELETON_CUBE_OT_RemoveAddon(bpy.types.Operator):
bl_idname = f"{PREFIX}.remove_addon"
bl_label = "アドオン機能を消去する"
def execute(self, context):
bpy.app.timers.register(lambda: unregister() or None, first_interval=0.01)
return {'FINISHED'}
# =========================================================
# Panels
# =========================================================
class SKELETON_CUBE_PT_Main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
props = context.scene.skeleton_cube_props
layout.prop(props, "target_collection_name", text="保存コレクション")
layout.separator()
col = layout.column(align=True)
col.prop(props, "loc", text="全体位置")
col.prop(props, "rot", text="全体回転")
col.separator()
col.label(text="【骨格 (円柱)】")
col.prop(props, "edge_length", text="立方体1辺")
col.prop(props, "cylinder_radius", text="円柱半径")
col.prop(props, "color", text="色")
col.prop(props, "alpha", text="透明度")
col.separator()
col.label(text="【頂点の球体】")
col.prop(props, "show_vert_spheres", text="頂点球体を生成")
sub = col.column(align=True)
sub.enabled = props.show_vert_spheres
sub.prop(props, "vert_sphere_radius", text="半径")
sub.prop(props, "vert_sphere_color", text="色")
sub.prop(props, "vert_sphere_alpha", text="透明度")
col.separator()
layout.operator(SKELETON_CUBE_OT_CreateCube.bl_idname, icon='CUBE')
layout.operator(SKELETON_CUBE_OT_DetachCube.bl_idname, icon='UNLINKED')
class SKELETON_CUBE_PT_SingleSphere(bpy.types.Panel):
bl_label = "単独球体の設定"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
props = context.scene.skeleton_cube_props
layout.prop(props, "show_single_sphere")
sub = layout.column(align=True)
sub.enabled = props.show_single_sphere
sub.prop(props, "single_sphere_loc", text="相対位置")
sub.prop(props, "single_sphere_radius", text="半径")
sub.prop(props, "single_sphere_color", text="色")
sub.prop(props, "single_sphere_alpha", text="透明度")
layout.separator()
layout.operator(SKELETON_CUBE_OT_CreateSingle.bl_idname, icon='SPHERE')
layout.operator(SKELETON_CUBE_OT_DetachSingle.bl_idname, icon='UNLINKED')
class SKELETON_CUBE_PT_Join(bpy.types.Panel):
bl_label = "オブジェクトの一体化"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
layout.label(text="※2つ以上のObjectを選択して実行", icon='INFO')
layout.operator(SKELETON_CUBE_OT_JoinSelected.bl_idname, icon='LINKED')
class SKELETON_CUBE_PT_System(bpy.types.Panel):
bl_label = "システム設定"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
layout.label(text="アドオンの完全消去を行います", icon='ERROR')
layout.operator(SKELETON_CUBE_OT_RemoveAddon.bl_idname, icon='CANCEL')
# =========================================================
# Register / Unregister
# =========================================================
classes = (
SkeletonCubeProperties,
SKELETON_CUBE_OT_CreateCube,
SKELETON_CUBE_OT_DetachCube,
SKELETON_CUBE_OT_CreateSingle,
SKELETON_CUBE_OT_DetachSingle,
SKELETON_CUBE_OT_JoinSelected,
SKELETON_CUBE_OT_RemoveAddon,
SKELETON_CUBE_PT_Main,
SKELETON_CUBE_PT_SingleSphere,
SKELETON_CUBE_PT_Join,
SKELETON_CUBE_PT_System,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.skeleton_cube_props = bpy.props.PointerProperty(type=SkeletonCubeProperties)
def unregister():
if hasattr(bpy.types.Scene, "skeleton_cube_props"):
del bpy.types.Scene.skeleton_cube_props
for cls in reversed(classes):
try:
bpy.utils.unregister_class(cls)
except RuntimeError:
pass
if __name__ == "__main__":
register()