Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================"> Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================"> Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================">




bl_info = {
    "name": "Polygon Torus Generator Pro (Realtime & Detach)",
    "author": "Your Name",
    "version": (2, 0),
    "blender": (5, 0, 0), # Blender 5.0対応
    "location": "View3D > Sidebar (Nパネル)",
    "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能",
    "category": "Object",
}

import bpy
import webbrowser

# =========================================================================
# 【基本設定】
# =========================================================================
TAB_NAME    = "多角形ツール"
PREFIX_NAME = "poly_torus"

# --- リンク設定 (ドキュメント用) ---
ADDON_LINKS = [
    {
        "label": "地球儀 緯度経度 xyz (参考)",
        "url": "<https://note.com/zionadmillion/n/na0b25ce45488>",
        "icon": "URL"
    },
    {
        "label": "最新 posfie (参考)",
        "url": "<https://posfie.com/@timekagura/t/zionad2022?sort=0>",
        "icon": "URL"
    }
]

INIT_COLOR = (0.0, 1.0, 0.5)

# 無限ループ防止用のグローバル変数
IS_UPDATING = False

# =========================================================================
# 【リアルタイム更新関数】
# スライダーや数値を変更した瞬間に、対象のオブジェクトを自動更新します
# =========================================================================
def update_poly_torus(self, context):
    global IS_UPDATING
    if IS_UPDATING:
        return
    
    props = context.scene.poly_torus_props
    if not props.active_obj_name:
        return
        
    obj = bpy.data.objects.get(props.active_obj_name)
    # 対象が存在しない、または切り離し済みの場合は更新しない
    if not obj or not obj.get("pt_live"):
        return
        
    IS_UPDATING = True
    try:
        # 1. 位置・回転の更新
        obj.location = (props.loc_x, props.loc_y, props.loc_z)
        obj.rotation_euler = (props.rot_x, props.rot_y, props.rot_z)
        
        # 2. マテリアル・色の更新 (マテリアルはオブジェクトごとに独立しています)
        mat = obj.active_material
        if mat and mat.use_nodes:
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs["Base Color"].default_value = (*props.color, 1.0)
                bsdf.inputs["Alpha"].default_value = props.alpha
            mat.blend_method = 'BLEND' if props.alpha < 1.0 else 'OPAQUE'
        
        # 3. 形状(メッシュ)の更新
        # 3Dビューポートのコンテキストを取得して安全にメッシュを再生成
        area = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
        if area:
            region = next((r for r in area.regions if r.type == 'WINDOW'), None)
            old_active = context.view_layer.objects.active
            
            temp_obj = None
            with context.temp_override(area=area, region=region):
                bpy.ops.mesh.primitive_torus_add(
                    major_radius=props.radius, 
                    minor_radius=props.thick, 
                    major_segments=props.segments, 
                    minor_segments=32,
                    location=(0,0,0), rotation=(0,0,0)
                )
                temp_obj = context.active_object
            
            # メッシュデータをすり替える(オブジェクト本体は維持)
            if temp_obj:
                new_mesh = temp_obj.data
                old_mesh = obj.data
                
                obj.data = new_mesh
                if mat:
                    obj.data.materials.append(mat) # マテリアルを再割り当て
                    
                bpy.data.objects.remove(temp_obj)
                if old_mesh:
                    bpy.data.meshes.remove(old_mesh)
                    
            context.view_layer.objects.active = old_active
            obj.select_set(True)
            
    finally:
        IS_UPDATING = False

# =========================================================================
# 【プロパティ定義】 (全項目に update コールバックを設定)
# =========================================================================
class PolyTorusProperties(bpy.types.PropertyGroup):
    active_obj_name: bpy.props.StringProperty(name="編集中のオブジェクト", default="")

    segments: bpy.props.IntProperty(name="角数 (3=三角, 4=四角)", default=3, min=3, max=256, update=update_poly_torus)
    radius: bpy.props.FloatProperty(name="半径 (R)", default=1.0, min=0.01, update=update_poly_torus)
    thick: bpy.props.FloatProperty(name="太さ (断面)", default=0.05, min=0.001, update=update_poly_torus)
    
    color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_COLOR, min=0.0, max=1.0, update=update_poly_torus)
    alpha: bpy.props.FloatProperty(name="透明度", default=1.0, min=0.0, max=1.0, update=update_poly_torus)
    
    loc_x: bpy.props.FloatProperty(name="位置 X", default=0.0, update=update_poly_torus)
    loc_y: bpy.props.FloatProperty(name="位置 Y", default=0.0, update=update_poly_torus)
    loc_z: bpy.props.FloatProperty(name="位置 Z", default=0.0, update=update_poly_torus)
    
    rot_x: bpy.props.FloatProperty(name="X軸", default=0.0, subtype='ANGLE', update=update_poly_torus)
    rot_y: bpy.props.FloatProperty(name="Y軸", default=0.0, subtype='ANGLE', update=update_poly_torus)
    rot_z: bpy.props.FloatProperty(name="Z軸", default=0.0, subtype='ANGLE', update=update_poly_torus)

# =========================================================================
# 【オペレーター:生成処理】
# =========================================================================
class POLY_OT_create_torus(bpy.types.Operator):
    bl_idname = f"mesh.{PREFIX_NAME.lower()}_create"
    bl_label = "多角形を生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global IS_UPDATING
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")

        # 以前編集していたオブジェクトがあれば、自動で切り離す
        if props.active_obj_name:
            old_obj = bpy.data.objects.get(props.active_obj_name)
            if old_obj and "pt_live" in old_obj:
                del old_obj["pt_live"]

        # アドオン削除後も完全に独立して残る標準マテリアルを新規作成
        mat = bpy.data.materials.new(name=f"{PREFIX_NAME}_Mat")
        mat.use_nodes = True
        
        # オブジェクト生成
        area = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
        region = next((r for r in area.regions if r.type == 'WINDOW'), None) if area else None
        
        IS_UPDATING = True
        try:
            if area and region:
                with context.temp_override(area=area, region=region):
                    bpy.ops.mesh.primitive_torus_add(
                        major_radius=props.radius, minor_radius=props.thick, 
                        major_segments=props.segments, minor_segments=32,
                        location=(props.loc_x, props.loc_y, props.loc_z), 
                        rotation=(props.rot_x, props.rot_y, props.rot_z)
                    )
            else:
                # 3Dビュー外で実行された場合のフォールバック
                bpy.ops.mesh.primitive_torus_add(
                    major_radius=props.radius, minor_radius=props.thick, major_segments=props.segments
                )
                
            obj = context.active_object
            obj.data.materials.append(mat)
            
            # トラッキング用のマーカーと名前を記録
            obj["pt_live"] = True
            props.active_obj_name = obj.name
            
        finally:
            IS_UPDATING = False
            
        # 色などを初期適用
        update_poly_torus(self, context)

        return {'FINISHED'}

# =========================================================================
# 【オペレーター:アドオンから切り離し (確定)】
# =========================================================================
class POLY_OT_detach_torus(bpy.types.Operator):
    bl_idname = f"object.{PREFIX_NAME.lower()}_detach"
    bl_label = "アドオンから切り離し (確定)"
    bl_description = "現在の図形を確定し、アドオンの連動から切り離します"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        global IS_UPDATING
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
        
        obj = bpy.data.objects.get(props.active_obj_name)
        if obj and "pt_live" in obj:
            del obj["pt_live"] # マーカーを削除
            
        IS_UPDATING = True
        props.active_obj_name = ""
        IS_UPDATING = False
        
        self.report({'INFO'}, "オブジェクトを確定し、切り離しました。")
        return {'FINISHED'}

# =========================================================================
# 【オペレーター:URL&アドオン削除機能】
# =========================================================================
class POLY_OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX_NAME.lower()}_open_url"
    bl_label = "URL"
    url: bpy.props.StringProperty()
    def execute(self, context):
        webbrowser.open(self.url)
        return {'FINISHED'}

class POLY_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX_NAME.lower()}_remove_addon"
    bl_label = "アドオン削除"
    bl_description = "アドオンをUIから消去します (作成したオブジェクト・マテリアルはそのまま残ります)"
    def execute(self, context):
        unregister()
        return {'FINISHED'}

# =========================================================================
# 【UIパネル群】
# =========================================================================
class POLY_PT_main_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_main_panel"
    bl_label = "多角形トーラス (リアルタイム生成)"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
        
        # ドキュメント(説明)
        box_doc = layout.box()
        box_doc.label(text="【使い方】", icon='HELP')
        box_doc.label(text="数値を変更するとリアルタイムに変形します")
        box_doc.label(text="「角数」3で三角形、4で四角形")
        
        layout.separator()
        
        # 状態表示と切り離しボタン
        if props.active_obj_name and bpy.data.objects.get(props.active_obj_name):
            box_live = layout.box()
            box_live.label(text=f"🔄 リアルタイム編集中: {props.active_obj_name}", icon='EDITMODE_HLT')
            box_live.scale_y = 1.3
            box_live.operator(f"object.{PREFIX_NAME.lower()}_detach", text="アドオンから切り離し (確定)", icon='UNLINKED')
            layout.separator()
        else:
            # 生成ボタン
            layout.scale_y = 1.3
            layout.operator(f"mesh.{PREFIX_NAME.lower()}_create", text="新しく多角形を生成", icon='ADD')
            layout.scale_y = 1.0
            layout.separator()
        
        # 形状設定
        box_s = layout.box()
        box_s.label(text="形状と色 (リアルタイム連動)", icon='MESH_TORUS')
        col_s = box_s.column(align=True)
        col_s.prop(props, "segments")
        col_s.separator()
        col_s.prop(props, "radius")
        col_s.prop(props, "thick")
        col_s.separator()
        row_c = col_s.row(align=True)
        row_c.prop(props, "color", text="")
        row_c.prop(props, "alpha", text="透明")

class POLY_PT_transform_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_transform_panel"
    bl_label = "配置 (位置と回転)"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
        c1, c2 = self.layout.column(align=True), self.layout.column(align=True)
        
        c1.label(text="位置 (リアルタイム連動):")
        c1.prop(props, "loc_x")
        c1.prop(props, "loc_y")
        c1.prop(props, "loc_z")
        
        self.layout.separator()
        
        c2.label(text="回転 (リアルタイム連動):")
        c2.prop(props, "rot_x")
        c2.prop(props, "rot_y")
        c2.prop(props, "rot_z")

class POLY_PT_footer_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX_NAME.upper()}_PT_footer_panel"
    bl_label = "ドキュメント / 管理"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_order = 100
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        
        box_links = layout.box()
        box_links.label(text="関連リンク:", icon='BOOKMARKS')
        for link in ADDON_LINKS:
            box_links.operator(f"wm.{PREFIX_NAME.lower()}_open_url", text=link["label"], icon='URL').url = link["url"]
            
        layout.separator(factor=2.0)
        
        # このボタンを押すとアドオン自体がUIから消えます
        layout.operator(f"wm.{PREFIX_NAME.lower()}_remove_addon", text="アドオンを無効化して閉じる", icon='CANCEL')

# =========================================================================
# 【登録処理】
# =========================================================================
classes = [
    PolyTorusProperties, 
    POLY_OT_create_torus, 
    POLY_OT_detach_torus,
    POLY_OT_open_url, 
    POLY_OT_remove_addon,
    POLY_PT_main_panel, 
    POLY_PT_transform_panel, 
    POLY_PT_footer_panel
]

def register():
    for c in classes: 
        bpy.utils.register_class(c)
    setattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props", bpy.props.PointerProperty(type=PolyTorusProperties))

def unregister():
    if hasattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props"): 
        delattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props")
    for c in reversed(classes):
        try: bpy.utils.unregister_class(c)
        except: pass

if __name__ == "__main__":
    try: unregister()
    except: pass
    register()