骨格 立方体作成リスト
# SKELETON_CUBE_Generator_Signature_v2
# Code Created : 2024_0525_1600
bl_info = {
"name": "Skeleton Cube Generator (Pro)",
"author": "Your Name",
"version": (2, 0, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar",
"description": "Generates a skeletal cube. Features object detachment and code export.",
"category": "3D View",
}
import bpy
import bmesh
import math
import webbrowser
import base64
import time
from mathutils import Vector, Matrix
# =========================================================
# 共通定義
# =========================================================
ADDON_TITLE = "Skeleton Cube"
PREFIX_VAL = "SKELETON_CUBE"
PREFIX = PREFIX_VAL.lower()
ADDON_CATEGORY = "Skeleton Cube"
# =========================================================
# ユーザー設定パラメータ (初期値)
# ブラウザ翻訳回避のため、以下のブロックは厳密に保護されます
# =========================================================
# --- PARAM_START ---
DEFAULT_CENTER_X = 0.0000
DEFAULT_CENTER_Y = 0.0000
DEFAULT_CENTER_Z = 0.0000
DEFAULT_ROTATION_X = 0.0000
DEFAULT_ROTATION_Y = 0.0000
DEFAULT_ROTATION_Z = 0.0000
DEFAULT_EDGE_LENGTH = 2.0000
DEFAULT_CYLINDER_RADIUS = 0.0500
DEFAULT_COLOR_R = 0.2000
DEFAULT_COLOR_G = 0.6000
DEFAULT_COLOR_B = 1.0000
DEFAULT_ALPHA = 1.0000
DEFAULT_SHOW_SPHERES = True
DEFAULT_SPHERE_RADIUS = 0.0800
DEFAULT_SPH_COLOR_R = 1.0000
DEFAULT_SPH_COLOR_G = 0.3000
DEFAULT_SPH_COLOR_B = 0.3000
DEFAULT_SPH_ALPHA = 1.0000
# --- PARAM_END ---
# =========================================================
# リンク定義
# =========================================================
LINK_NAME_2 = "コンテナ builder"
LINK_URL_2 = "<https://note.com/zionad2017/n/ndb6dbdbc844a>"
LINK_NAME_3 = "posfie"
LINK_URL_3 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_4 = "zionadchat"
LINK_URL_4 = "<https://x.com/zionadchat>"
# =========================================================
# マテリアル設定補助関数
# =========================================================
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)
context.collection.objects.link(obj)
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))
]
v1, v2, v3, v4, v5, v6, v7, v8 = verts_pos
edges = [
(v1, v2), (v2, v3), (v3, v4), (v4, v1),
(v5, v6), (v6, v7), (v7, v8), (v8, v5),
(v1, v5), (v2, v6), (v3, v7), (v4, v8)
]
# 円柱 (Material Index 0)
for start, end in edges:
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
)
# 球体 (Material Index 1)
if props.show_spheres:
r_sph = props.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)
)
for i in range(start_face_count, len(bm.faces)):
bm.faces[i].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_sph = setup_material(f"{PREFIX_VAL}_SPH_MAT", props.sphere_color, props.sphere_alpha)
obj.data.materials.clear()
obj.data.materials.append(mat_cyl)
obj.data.materials.append(mat_sph)
for area in context.screen.areas:
if area.type == 'VIEW_3D':
area.tag_redraw()
# =========================================================
# プロパティ
# =========================================================
def on_property_update(self, context):
if self.is_created:
update_skeleton_cube(context)
class SkeletonCubeProperties(bpy.types.PropertyGroup):
is_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_spheres: bpy.props.BoolProperty(name="頂点に球体を表示", default=DEFAULT_SHOW_SPHERES, update=on_property_update)
sphere_radius: bpy.props.FloatProperty(name="球体半径", default=DEFAULT_SPHERE_RADIUS, min=0.001, update=on_property_update)
sphere_color: bpy.props.FloatVectorProperty(name="球体色", subtype='COLOR', size=3, min=0.0, max=1.0, default=(DEFAULT_SPH_COLOR_R, DEFAULT_SPH_COLOR_G, DEFAULT_SPH_COLOR_B), update=on_property_update)
sphere_alpha: bpy.props.FloatProperty(name="球体透明度", default=DEFAULT_SPH_ALPHA, min=0.0, max=1.0, update=on_property_update)
# =========================================================
# Operators
# =========================================================
class SKELETON_CUBE_OT_Create(bpy.types.Operator):
bl_idname = f"{PREFIX}.create"
bl_label = "骨格立方体を作成"
def execute(self, context):
props = context.scene.skeleton_cube_props
props.is_created = True
update_skeleton_cube(context)
return {'FINISHED'}
class SKELETON_CUBE_OT_Detach(bpy.types.Operator):
bl_idname = f"{PREFIX}.detach"
bl_label = "作成したObjectを独立させる"
bl_description = "現在のオブジェクトをアドオンから切り離し、色とサイズを固定します"
def execute(self, context):
obj = bpy.data.objects.get(PREFIX_VAL)
if obj:
suffix = str(int(time.time() % 100000))
# 名前を変更してアドオンの追跡から外す
obj.name = f"{PREFIX_VAL}_Independent_{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.is_created = False
self.report({'INFO'}, f"オブジェクトを独立させました: {obj.name}")
else:
self.report({'WARNING'}, "独立させるオブジェクトが見つかりません。")
return {'FINISHED'}
def get_own_source_code():
# 自身のアドオンコードを取得(TextEditorまたはファイルから)
sig = "SKELETON_CUBE_Generator_Signature_v2"
for t in bpy.data.texts:
if sig in t.as_string():
return t.as_string()
try:
import os
if os.path.exists(__file__):
with open(__file__, 'r', encoding='utf-8') as f:
return f.read()
except:
pass
return ""
class SKELETON_CUBE_OT_ExportCode(bpy.types.Operator):
bl_idname = f"{PREFIX}.export_code"
bl_label = "最新数値でコードを書き出す"
bl_description = "現在のサイズ・色の数値を初期値として埋め込んだ新しいコードを作成します"
def execute(self, context):
props = context.scene.skeleton_cube_props
source = get_own_source_code()
if not source:
self.report({'ERROR'}, "ソースコードが見つかりません。")
return {'CANCELLED'}
# [文字化け対策] 自動翻訳や正規表現エラーを回避するため、目印を暗号(Base64)から復元
mark_start = base64.b64decode(b'IyAtLS0gUEFSQU1fU1RBUlQgLS0t').decode('utf-8')
mark_end = base64.b64decode(b'IyAtLS0gUEFSQU1fRU5EIC0tLQ==').decode('utf-8')
lines = source.split("\n")
new_lines = []
in_param_block = False
for line in lines:
if line.strip() == mark_start:
in_param_block = True
new_lines.append(line)
# 現在の値を安全な文字列形式で直接書き込む(正規表現不使用)
new_lines.append(f"DEFAULT_CENTER_X = {props.loc[0]:.4f}")
new_lines.append(f"DEFAULT_CENTER_Y = {props.loc[1]:.4f}")
new_lines.append(f"DEFAULT_CENTER_Z = {props.loc[2]:.4f}")
new_lines.append(f"DEFAULT_ROTATION_X = {math.degrees(props.rot[0]):.4f}")
new_lines.append(f"DEFAULT_ROTATION_Y = {math.degrees(props.rot[1]):.4f}")
new_lines.append(f"DEFAULT_ROTATION_Z = {math.degrees(props.rot[2]):.4f}")
new_lines.append(f"DEFAULT_EDGE_LENGTH = {props.edge_length:.4f}")
new_lines.append(f"DEFAULT_CYLINDER_RADIUS = {props.cylinder_radius:.4f}")
new_lines.append(f"DEFAULT_COLOR_R = {props.color[0]:.4f}")
new_lines.append(f"DEFAULT_COLOR_G = {props.color[1]:.4f}")
new_lines.append(f"DEFAULT_COLOR_B = {props.color[2]:.4f}")
new_lines.append(f"DEFAULT_ALPHA = {props.alpha:.4f}")
new_lines.append(f"DEFAULT_SHOW_SPHERES = {props.show_spheres}")
new_lines.append(f"DEFAULT_SPHERE_RADIUS = {props.sphere_radius:.4f}")
new_lines.append(f"DEFAULT_SPH_COLOR_R = {props.sphere_color[0]:.4f}")
new_lines.append(f"DEFAULT_SPH_COLOR_G = {props.sphere_color[1]:.4f}")
new_lines.append(f"DEFAULT_SPH_COLOR_B = {props.sphere_color[2]:.4f}")
new_lines.append(f"DEFAULT_SPH_ALPHA = {props.sphere_alpha:.4f}")
continue
if line.strip() == mark_end:
in_param_block = False
new_lines.append(line)
continue
if not in_param_block:
new_lines.append(line)
# Text Editorに新しいテキストとして出力
t_str = str(int(time.time() % 100000))
new_text = bpy.data.texts.new(f"SkeletonCube_Exported_{t_str}.py")
new_text.from_string("\n".join(new_lines))
self.report({'INFO'}, f"Text Editorに {new_text.name} を書き出しました")
return {'FINISHED'}
class SKELETON_CUBE_OT_OpenLink(bpy.types.Operator):
bl_idname = f"{PREFIX}.open_link"
bl_label = "Open Link"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url:
webbrowser.open(self.url)
return {'FINISHED'}
def unregister_delayed():
try:
unregister()
except Exception:
pass
return None
class SKELETON_CUBE_OT_RemoveAddon(bpy.types.Operator):
bl_idname = f"{PREFIX}.remove_addon"
bl_label = "アドオン機能を消去する"
bl_description = "このアドオンの全機能を停止・解除します"
def execute(self, context):
bpy.app.timers.register(unregister_delayed, 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
col = layout.column(align=True)
col.label(text="中心位置")
col.prop(props, "loc", index=0, text="X")
col.prop(props, "loc", index=1, text="Y")
col.prop(props, "loc", index=2, text="Z")
col.separator()
col.label(text="回転")
col.prop(props, "rot", index=0, text="X")
col.prop(props, "rot", index=1, text="Y")
col.prop(props, "rot", index=2, text="Z")
col.separator()
col.prop(props, "edge_length", text="立方体1辺")
col.prop(props, "cylinder_radius", text="円柱半径")
col.separator()
col.prop(props, "color", text="円柱の色")
col.prop(props, "alpha", text="円柱の透明度")
col.separator()
layout.operator(SKELETON_CUBE_OT_Create.bl_idname, text="骨格立方体を作成", icon='CUBE')
class SKELETON_CUBE_PT_Sphere(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_spheres", text="球体を生成する")
sub = layout.column(align=True)
sub.enabled = props.show_spheres
sub.prop(props, "sphere_radius", text="球体の半径")
sub.prop(props, "sphere_color", text="球体の色")
sub.prop(props, "sphere_alpha", text="球体の透明度")
class SKELETON_CUBE_PT_Detach(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="色・サイズを完全に固定させます")
layout.operator(SKELETON_CUBE_OT_Detach.bl_idname, text="作成したObjectを独立させる", icon='UNLINKED')
class SKELETON_CUBE_PT_Export(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="現在の数値を記憶したコードを作成")
layout.operator(SKELETON_CUBE_OT_ExportCode.bl_idname, text="最新数値でコードを書き出す", icon='TEXT')
class SKELETON_CUBE_PT_Links(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.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text="カスタムリンク (未設定)").url = ""
layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_2).url = LINK_URL_2
layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_3).url = LINK_URL_3
layout.operator(SKELETON_CUBE_OT_OpenLink.bl_idname, text=LINK_NAME_4).url = LINK_URL_4
class SKELETON_CUBE_PT_Remove(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="※パネル自体を非表示にします")
layout.operator(SKELETON_CUBE_OT_RemoveAddon.bl_idname, text="アドオン機能を消去する", icon='CANCEL')
# =========================================================
# Register / Unregister / 起動時設定
# =========================================================
classes = (
SkeletonCubeProperties,
SKELETON_CUBE_OT_Create,
SKELETON_CUBE_OT_Detach,
SKELETON_CUBE_OT_ExportCode,
SKELETON_CUBE_OT_OpenLink,
SKELETON_CUBE_OT_RemoveAddon,
SKELETON_CUBE_PT_Main,
SKELETON_CUBE_PT_Sphere,
SKELETON_CUBE_PT_Detach,
SKELETON_CUBE_PT_Export,
SKELETON_CUBE_PT_Links,
SKELETON_CUBE_PT_Remove,
)
def setup_viewport_on_load():
context = bpy.context
if context and context.window_manager:
for window in 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.show_region_ui = True
space.shading.type = 'MATERIAL'
return None
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.skeleton_cube_props = bpy.props.PointerProperty(type=SkeletonCubeProperties)
bpy.app.timers.register(setup_viewport_on_load, first_interval=0.1)
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()