Sidebar", "description": "トーラス、円錐、球体を連動生成(Blender 5 完全安定版)", "category": "3D View" } # ========================================================= # 2. コード作成日時 # ========================================================= # Code Created : 2026_0725_1330 # ========================================================= # 3. import # ========================================================= import bpy import bmesh import math import webbrowser import addon_utils from mathutils import Vector, Euler # ========================================================= # 4. 共通定義 # ========================================================= ADDON_TITLE = "TCS Generator" PREFIX_VAL = "TCS_GEN" ADDON_CATEGORY = "TCS Gen" PREFIX = "> Sidebar", "description": "トーラス、円錐、球体を連動生成(Blender 5 完全安定版)", "category": "3D View" } # ========================================================= # 2. コード作成日時 # ========================================================= # Code Created : 2026_0725_1330 # ========================================================= # 3. import # ========================================================= import bpy import bmesh import math import webbrowser import addon_utils from mathutils import Vector, Euler # ========================================================= # 4. 共通定義 # ========================================================= ADDON_TITLE = "TCS Generator" PREFIX_VAL = "TCS_GEN" ADDON_CATEGORY = "TCS Gen" PREFIX = "> Sidebar", "description": "トーラス、円錐、球体を連動生成(Blender 5 完全安定版)", "category": "3D View" } # ========================================================= # 2. コード作成日時 # ========================================================= # Code Created : 2026_0725_1330 # ========================================================= # 3. import # ========================================================= import bpy import bmesh import math import webbrowser import addon_utils from mathutils import Vector, Euler # ========================================================= # 4. 共通定義 # ========================================================= ADDON_TITLE = "TCS Generator" PREFIX_VAL = "TCS_GEN" ADDON_CATEGORY = "TCS Gen" PREFIX = ">
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "TCS Generator",
"author": "Your Name",
"version": (1, 0, 2),
"blender": (5, 0, 0),
"location": "View3D > Sidebar",
"description": "トーラス、円錐、球体を連動生成(Blender 5 完全安定版)",
"category": "3D View"
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Created : 2026_0725_1330
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import webbrowser
import addon_utils
from mathutils import Vector, Euler
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "TCS Generator"
PREFIX_VAL = "TCS_GEN"
ADDON_CATEGORY = "TCS Gen"
PREFIX = PREFIX_VAL.lower()
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "TCS Generator マニュアル"
LINK_URL_1 = "<https://example.com/manual>"
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>"
LINK_NAME_5 = ""
LINK_URL_5 = ""
# =========================================================
# 6. 初期設定
# =========================================================
# 数値
TORUS_MAJOR_RADIUS_DEFAULT = 2.0
TORUS_MINOR_RADIUS_DEFAULT = 0.2
CONE_HEIGHT_DEFAULT = 3.0
SPHERE_RADIUS_DEFAULT = 0.5
# ベクトル
LOC_X_DEFAULT = 0.0
LOC_Y_DEFAULT = 0.0
LOC_Z_DEFAULT = 0.0
ROT_X_DEFAULT = 0.0
ROT_Y_DEFAULT = 0.0
ROT_Z_DEFAULT = 0.0
# 色
COLOR_MAIN = (0.2, 0.6, 1.0, 1.0)
# =========================================================
# 7. 共通クラス
# =========================================================
class TCS_GEN_OT_open_url(bpy.types.Operator):
bl_idname = f"{PREFIX}.open_url"
bl_label = "リンクを開く"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class TCS_GEN_OT_remove_addon(bpy.types.Operator):
bl_idname = f"{PREFIX}.remove_addon"
bl_label = "Unregister Addon"
bl_description = "このアドオンを完全に無効化し、UIから消去します"
def execute(self, context):
# アドオン管理システムから安全に無効化するための遅延処理
def disable_addon():
addon_utils.disable(__name__)
return None
bpy.app.timers.register(disable_addon, first_interval=0.1)
self.report({'INFO'}, f"{ADDON_TITLE} を削除しました")
return {'FINISHED'}
class TCS_GEN_Utility:
@staticmethod
def get_or_create_collection(name):
col = bpy.data.collections.get(name)
if not col:
col = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(col)
return col
@staticmethod
def get_or_create_material(name, color):
mat = bpy.data.materials.get(name)
if not mat:
mat = bpy.data.materials.new(name)
mat.use_nodes = True
if mat.use_nodes:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
return mat
# =========================================================
# 8. アドオン固有機能
# =========================================================
def update_geometry(self, context):
"""プロパティ変更時にオブジェクトを再生成・更新する(安定版)"""
if not self.is_active:
return
col = TCS_GEN_Utility.get_or_create_collection(f"{PREFIX_VAL}_Collection")
mat = TCS_GEN_Utility.get_or_create_material(f"{PREFIX_VAL}_Material", self.color)
def apply_mesh(obj_name, mesh_func):
obj = bpy.data.objects.get(obj_name)
if obj:
old_mesh = obj.data
obj.data = mesh_func()
# メモリーリークを防ぐため古いメッシュを完全に破棄 (do_unlink=True)
if old_mesh.users == 0:
bpy.data.meshes.remove(old_mesh, do_unlink=True)
else:
mesh = mesh_func()
obj = bpy.data.objects.new(obj_name, mesh)
col.objects.link(obj)
if len(obj.data.materials) == 0:
obj.data.materials.append(mat)
else:
obj.data.materials[0] = mat
return obj
# 1. Torus(API依存を避ける完全な数学的生成)
def make_torus():
major_r = self.torus_major_radius
minor_r = self.torus_minor_radius
segments = 48
minor_segments = 16
verts = []
faces = []
for i in range(segments):
a1 = (i / segments) * 2.0 * math.pi
c1, s1 = math.cos(a1), math.sin(a1)
for j in range(minor_segments):
a2 = (j / minor_segments) * 2.0 * math.pi
c2, s2 = math.cos(a2), math.sin(a2)
x = (major_r + minor_r * c2) * c1
y = (major_r + minor_r * c2) * s1
z = minor_r * s2
verts.append((x, y, z))
for i in range(segments):
for j in range(minor_segments):
i_next = (i + 1) % segments
j_next = (j + 1) % minor_segments
v0 = i * minor_segments + j
v1 = i_next * minor_segments + j
v2 = i_next * minor_segments + j_next
v3 = i * minor_segments + j_next
faces.append((v0, v1, v2, v3))
me = bpy.data.meshes.new(f"{PREFIX_VAL}_Torus_Mesh")
me.from_pydata(verts, [], faces)
me.update()
return me
obj_torus = apply_mesh(f"{PREFIX_VAL}_Torus", make_torus)
obj_torus.location = self.loc
obj_torus.rotation_euler = self.rot
obj_torus.hide_viewport = not self.show_torus
obj_torus.hide_render = not self.show_torus
# 2. Cone
def make_cone():
bm = bmesh.new()
bmesh.ops.create_cone(
bm, cap_ends=True, cap_tris=True, segments=48,
radius1=self.torus_major_radius, radius2=0, depth=self.cone_height
)
bmesh.ops.translate(bm, verts=bm.verts, vec=(0, 0, self.cone_height / 2))
me = bpy.data.meshes.new(f"{PREFIX_VAL}_Cone_Mesh")
bm.to_mesh(me)
bm.free()
return me
obj_cone = apply_mesh(f"{PREFIX_VAL}_Cone", make_cone)
obj_cone.location = self.loc
obj_cone.rotation_euler = self.rot
obj_cone.hide_viewport = not self.show_cone
obj_cone.hide_render = not self.show_cone
# 3. Sphere
def make_sphere():
bm = bmesh.new()
bmesh.ops.create_uvsphere(
bm, u_segments=32, v_segments=16, radius=self.sphere_radius
)
me = bpy.data.meshes.new(f"{PREFIX_VAL}_Sphere_Mesh")
bm.to_mesh(me)
bm.free()
return me
obj_sphere = apply_mesh(f"{PREFIX_VAL}_Sphere", make_sphere)
rot_mat = Euler(self.rot).to_matrix()
obj_sphere.location = Vector(self.loc) + rot_mat @ Vector((0, 0, self.cone_height))
obj_sphere.rotation_euler = self.rot
obj_sphere.hide_viewport = not self.show_sphere
obj_sphere.hide_render = not self.show_sphere
class TCS_GEN_PropertyGroup(bpy.types.PropertyGroup):
is_active: bpy.props.BoolProperty(default=False)
loc: bpy.props.FloatVectorProperty(
name="Location",
default=(LOC_X_DEFAULT, LOC_Y_DEFAULT, LOC_Z_DEFAULT),
update=update_geometry
)
rot: bpy.props.FloatVectorProperty(
name="Rotation",
subtype='EULER',
default=(math.radians(ROT_X_DEFAULT), math.radians(ROT_Y_DEFAULT), math.radians(ROT_Z_DEFAULT)),
update=update_geometry
)
color: bpy.props.FloatVectorProperty(
name="Color",
subtype='COLOR',
size=4,
default=COLOR_MAIN,
min=0.0, max=1.0,
update=update_geometry
)
torus_major_radius: bpy.props.FloatProperty(
name="Radius (Torus & Cone)",
default=TORUS_MAJOR_RADIUS_DEFAULT,
min=0.01,
update=update_geometry
)
torus_minor_radius: bpy.props.FloatProperty(
name="Torus Thickness",
default=TORUS_MINOR_RADIUS_DEFAULT,
min=0.01,
update=update_geometry
)
cone_height: bpy.props.FloatProperty(
name="Cone Height",
default=CONE_HEIGHT_DEFAULT,
min=0.01,
update=update_geometry
)
sphere_radius: bpy.props.FloatProperty(
name="Sphere Radius",
default=SPHERE_RADIUS_DEFAULT,
min=0.01,
update=update_geometry
)
show_torus: bpy.props.BoolProperty(name="Torus", default=True, update=update_geometry)
show_cone: bpy.props.BoolProperty(name="Cone", default=True, update=update_geometry)
show_sphere: bpy.props.BoolProperty(name="Sphere", default=True, update=update_geometry)
class TCS_GEN_OT_create(bpy.types.Operator):
bl_idname = f"{PREFIX}.create"
bl_label = "オブジェクトを生成"
bl_description = "トーラス・円錐・球体を生成します"
def execute(self, context):
props = context.scene.tcs_gen_props
props.is_active = True
update_geometry(props, context)
self.report({'INFO'}, "オブジェクトを生成しました")
return {'FINISHED'}
class TCS_GEN_OT_copy_info(bpy.types.Operator):
bl_idname = f"{PREFIX}.copy_info"
bl_label = "情報をコピー"
bl_description = "各オブジェクトの位置・サイズ情報をクリップボードにコピーします"
def execute(self, context):
props = context.scene.tcs_gen_props
loc_str = f"X:{props.loc[0]:.2f} Y:{props.loc[1]:.2f} Z:{props.loc[2]:.2f}"
rot_str = f"X:{math.degrees(props.rot[0]):.1f}° Y:{math.degrees(props.rot[1]):.1f}° Z:{math.degrees(props.rot[2]):.1f}°"
text = f"【{ADDON_TITLE} Object Info】\n\n"
text += "[Torus]\n"
text += f"Location: {loc_str}\nRotation: {rot_str}\n"
text += f"Major Radius: {props.torus_major_radius:.2f}\n"
text += f"Minor Radius: {props.torus_minor_radius:.2f}\n\n"
text += "[Cone]\n"
text += f"Location: {loc_str}\nRotation: {rot_str}\n"
text += f"Radius: {props.torus_major_radius:.2f}\n"
text += f"Height: {props.cone_height:.2f}\n\n"
sphere_loc = Vector(props.loc) + Euler(props.rot).to_matrix() @ Vector((0, 0, props.cone_height))
sp_loc_str = f"X:{sphere_loc[0]:.2f} Y:{sphere_loc[1]:.2f} Z:{sphere_loc[2]:.2f}"
text += "[Sphere]\n"
text += f"Location: {sp_loc_str}\n"
text += f"Radius: {props.sphere_radius:.2f}\n"
context.window_manager.clipboard = text
self.report({'INFO'}, "情報をクリップボードにコピーしました")
return {'FINISHED'}
# 安全なUIロード待機とセットアップ
def setup_workspace_on_activation():
"""Nパネル展開とマテリアルプレビュー切替(エラー防止回路付き)"""
if not hasattr(bpy.context, 'window_manager') or not bpy.context.window_manager.windows:
# UIがまだロードされていない場合は0.1秒後にリトライ
return 0.1
try:
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
except Exception as e:
print(f"[{ADDON_TITLE}] Setup Error:", e)
return None # 正常終了後はタイマー停止
# =========================================================
# 9. パネル構成
# =========================================================
class TCS_GEN_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.tcs_gen_props
if not props.is_active:
layout.operator(f"{PREFIX}.create", icon='MESH_TORUS', text="初期生成")
return
box = layout.box()
box.label(text="Transform & Color", icon='OBJECT_DATA')
box.prop(props, "loc")
box.prop(props, "rot")
box.prop(props, "color")
box = layout.box()
box.label(text="Shape Parameters", icon='MODIFIER')
box.prop(props, "torus_major_radius")
box.prop(props, "torus_minor_radius")
box.prop(props, "cone_height")
box.prop(props, "sphere_radius")
box = layout.box()
box.label(text="Visibility", icon='RESTRICT_VIEW_OFF')
row = box.row(align=True)
row.prop(props, "show_torus", icon='HIDE_OFF' if props.show_torus else 'HIDE_ON', toggle=True)
row.prop(props, "show_cone", icon='HIDE_OFF' if props.show_cone else 'HIDE_ON', toggle=True)
row.prop(props, "show_sphere", icon='HIDE_OFF' if props.show_sphere else 'HIDE_ON', toggle=True)
class TCS_GEN_PT_info(bpy.types.Panel):
bl_label = "Information"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
props = context.scene.tcs_gen_props
if not props.is_active:
layout.label(text="オブジェクトが生成されていません")
return
box = layout.box()
box.label(text="Torus:", icon='MESH_TORUS')
box.label(text=f" Major R: {props.torus_major_radius:.2f} / Minor r: {props.torus_minor_radius:.2f}")
box = layout.box()
box.label(text="Cone:", icon='MESH_CONE')
box.label(text=f" Base R: {props.torus_major_radius:.2f} / Height: {props.cone_height:.2f}")
box = layout.box()
box.label(text="Sphere:", icon='MESH_UVSPHERE')
sphere_loc = Vector(props.loc) + Euler(props.rot).to_matrix() @ Vector((0, 0, props.cone_height))
box.label(text=f" Loc: X:{sphere_loc[0]:.1f} Y:{sphere_loc[1]:.1f} Z:{sphere_loc[2]:.1f}")
box.label(text=f" Radius: {props.sphere_radius:.2f}")
layout.separator()
layout.operator(f"{PREFIX}.copy_info", icon='COPYDOWN')
class TCS_GEN_PT_links(bpy.types.Panel):
bl_label = "Links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
box = layout.box()
if LINK_NAME_1:
box.operator(f"{PREFIX}.open_url", text=LINK_NAME_1, icon='URL').url = LINK_URL_1
if LINK_NAME_2:
box.operator(f"{PREFIX}.open_url", text=LINK_NAME_2, icon='URL').url = LINK_URL_2
if LINK_NAME_3:
box.operator(f"{PREFIX}.open_url", text=LINK_NAME_3, icon='URL').url = LINK_URL_3
if LINK_NAME_4:
box.operator(f"{PREFIX}.open_url", text=LINK_NAME_4, icon='URL').url = LINK_URL_4
if LINK_NAME_5:
box.operator(f"{PREFIX}.open_url", text=LINK_NAME_5, icon='URL').url = LINK_URL_5
class TCS_GEN_PT_remove(bpy.types.Panel):
bl_label = "Addon Remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="このアドオンを消去します", icon='ERROR')
box.operator(f"{PREFIX}.remove_addon", icon='CANCEL')
# =========================================================
# 10. register()
# =========================================================
classes = [
TCS_GEN_OT_open_url,
TCS_GEN_OT_remove_addon,
TCS_GEN_PropertyGroup,
TCS_GEN_OT_create,
TCS_GEN_OT_copy_info,
TCS_GEN_PT_main,
TCS_GEN_PT_info,
TCS_GEN_PT_links,
TCS_GEN_PT_remove,
]
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.tcs_gen_props = bpy.props.PointerProperty(type=TCS_GEN_PropertyGroup)
# アドオン有効化時の初期処理(タイマーによる安全な実行)
if not bpy.app.timers.is_registered(setup_workspace_on_activation):
bpy.app.timers.register(setup_workspace_on_activation, first_interval=0.1)
# =========================================================
# 11. unregister()
# =========================================================
def unregister():
if hasattr(bpy.types.Scene, "tcs_gen_props"):
del bpy.types.Scene.tcs_gen_props
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__):
bpy.utils.unregister_class(cls)
if bpy.app.timers.is_registered(setup_workspace_on_activation):
bpy.app.timers.unregister(setup_workspace_on_activation)
if __name__ == "__main__":
register()