20260604 blender million

非リアルタイム版

トーラス額縁2つの 投影面|zionadmillion

テスト

rapture_20260702081639.png

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

import bpy
import webbrowser
import math # √nの計算用に追加

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

# --- リンク設定 (ドキュメント用) ---
ADDON_LINKS = [
    {
        "label": "トーラス作成",
        "url": "<https://app.notion.com/p/390f5dacaf43801d8f08c695979e60e1>",
        "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. 形状(メッシュ)の更新
        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=props.minor_segments, # ★追加:太さの面数
                    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

# =========================================================================
# 【プロパティ定義】
# =========================================================================
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)
    minor_segments: bpy.props.IntProperty(name="太さの面数 (断面)", default=12, min=3, max=256, update=update_poly_torus) # ★追加
    
    radius: bpy.props.FloatProperty(name="半径 (R)", default=1.0, min=0.0001, update=update_poly_torus)
    thick: bpy.props.FloatProperty(name="太さ (断面)", default=0.05, min=0.001, update=update_poly_torus)
    
    # √n倍計算用の変数 n を追加
    multiplier_n: bpy.props.IntProperty(name="n =", default=2, min=1, description="√n倍計算のための基準値 n")
    
    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=props.minor_segments, # ★追加
                        location=(props.loc_x, props.loc_y, props.loc_z), 
                        rotation=(props.rot_x, props.rot_y, props.rot_z)
                    )
            else:
                bpy.ops.mesh.primitive_torus_add(
                    major_radius=props.radius, minor_radius=props.thick, major_segments=props.segments,
                    minor_segments=props.minor_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_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'}

class POLY_OT_modify_radius(bpy.types.Operator):
    bl_idname = f"object.{PREFIX_NAME.lower()}_modify_radius"
    bl_label = "半径を数値修飾"
    bl_description = "現在の半径を √n倍、または 1/√n倍 に計算し直します"
    bl_options = {'REGISTER', 'UNDO'}

    action: bpy.props.EnumProperty(
        items=[
            ('MULTIPLY', "Multiply", ""),
            ('DIVIDE', "Divide", "")
        ]
    )

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
        n = props.multiplier_n
        if n <= 0:
            return {'CANCELLED'}
        
        factor = math.sqrt(n)
        
        # プロパティ値を変更すると update コールバックが呼ばれリアルタイムに変形します
        if self.action == 'MULTIPLY':
            props.radius *= factor
        elif self.action == 'DIVIDE':
            props.radius /= factor
            
        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 = "アドオン削除"
    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.prop(props, "minor_segments") # ★追加:太さの面数
        col_s.separator()
        
        # 半径のプロパティ
        col_s.prop(props, "radius")
        
        # √n 倍の数値修飾UI
        row_n = col_s.row(align=True)
        row_n.prop(props, "multiplier_n")
        op_mult = row_n.operator(f"object.{PREFIX_NAME.lower()}_modify_radius", text="× √n")
        op_mult.action = 'MULTIPLY'
        op_div = row_n.operator(f"object.{PREFIX_NAME.lower()}_modify_radius", text="× 1/√n")
        op_div.action = 'DIVIDE'
        
        col_s.separator()
        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)
        layout.operator(f"wm.{PREFIX_NAME.lower()}_remove_addon", text="アドオンを無効化して閉じる", icon='CANCEL')

# =========================================================================
# 【登録処理】
# =========================================================================
classes = [
    PolyTorusProperties, 
    POLY_OT_create_torus, 
    POLY_OT_detach_torus,
    POLY_OT_modify_radius,
    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()