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