エディタに アドオン貼り 202260325

<aside> 💡

<END_DICT>

==============================================================================

【 内包する追加スクリプトの文字列定義 】

※以下の指定された場所に、対象のスクリプトをペーストしてください。

==============================================================================

① 図形ジェネレーター (b5200_zukkei...) のコードを以下に貼り付け

ZUKKEI_SCRIPT_CONTENT = r'''

▼▼▼ ここに「図形&配列ジェネレーター」の全コードを貼り付けてください ▼▼▼

▲▲▲ ここまで ▲▲▲

'''

② Viewport Color & Sun (view2026316) のコードを以下に貼り付け

VIEWPORT_SCRIPT_CONTENT = r'''

▼▼▼ ここに「3D Viewport Color & Sun」の全コードを貼り付けてください ▼▼▼

▲▲▲ ここまで ▲▲▲

'''

③ Fixed Camera & World (cam_kotei...) のコードを以下に貼り付け

CAMERA_SCRIPT_CONTENT = r'''

▼▼▼ ここに「v100 Fixed Camera & World」の全コードを貼り付けてください ▼▼▼

▲▲▲ ここまで ▲▲▲

'''

</aside>

AI Studio addonjack

https://aistudio.google.com/app/prompts/1sNBzv8m89j1qjgsQ-NTDIEg5zhpkRr5M

bl_info = {
    "name": "zionad 520[ Sq-Torus ] SquareTorus20260324",
    "author": "zionadchat",
    "version": (7, 0, 0),
    "blender": (3, 0, 0),
    "location": "3D View > Sidebar",
    "description": "Topology-Perfect Square Torus Generator & Script Loader",
    "category": "3D View",
}

import bpy
import bmesh
import webbrowser
import math
import mathutils
import time
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty, EnumProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup
from datetime import datetime

# ==============================================================================
#  【 基本設定エリア 】
# ==============================================================================
PREFIX       = "SquareTorus20260324"
ADDON_NAME   = "zionad 520[ Sq-Torus ]"
TAB_NAME     = "[ Sq Torus copy ]   "
PANEL_TITLE  = "Square Torus Generator"
AUTHOR       = "zionadchat"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: SQUARE_TORUS_2026_03_24_V7_FINAL ###"

OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"

ADDON_LINKS = (
    {"label": "Prefix トーラス正方形 20260324", "url": "<https://www.notion.so/Prefix-20260324-32df5dacaf4380528980db6a989d6306>"},
)

# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "show_preview": True,
    "show_square_guide": True,
    "torus_color": (0.0391, 0.8000, 0.1647, 0.8000),
    "torus_loc": (0.0000, 0.0000, 0.0000),
    "torus_rot": (0.0000, 30.0000, 0.0000),
    "square_size": 10.0000,
    "corner_radius": 0.0000,
    "minor_radius": 0.5000,
    "corner_segments": 8,
    "minor_segments": 16,
    "torus_plane": "XY",
}
# <END_DICT>

# ==============================================================================
#  【 内包する追加スクリプトの文字列定義 】
#   ※以下の指定された場所に、対象のスクリプトをペーストしてください。
# ==============================================================================

# ① 図形ジェネレーター (b5200_zukkei...) のコードを以下に貼り付け
ZUKKEI_SCRIPT_CONTENT = r'''
# ▼▼▼ ここに「図形&配列ジェネレーター」の全コードを貼り付けてください ▼▼▼

# ▲▲▲ ここまで ▲▲▲
'''

# ② Viewport Color & Sun (view2026316) のコードを以下に貼り付け
VIEWPORT_SCRIPT_CONTENT = r'''
# ▼▼▼ ここに「3D Viewport Color & Sun」の全コードを貼り付けてください ▼▼▼

# ▲▲▲ ここまで ▲▲▲
'''

# ③ Fixed Camera & World (cam_kotei...) のコードを以下に貼り付け
CAMERA_SCRIPT_CONTENT = r'''
# ▼▼▼ ここに「v100 Fixed Camera & World」の全コードを貼り付けてください ▼▼▼

# ▲▲▲ ここまで ▲▲▲
'''

# ==============================================================================
#  Square Torus システムロジック
# ==============================================================================

PREVIEW_COL_NAME = f"{PREFIX}_Preview_Zone"
PREVIEW_OBJ_NAME = f"[Preview] SqTorus_{PREFIX}"
PREVIEW_GUIDE_NAME = f"[Preview] SqGuide_{PREFIX}"
PREVIEW_MAT_NAME = f"PreviewMat_{PREFIX}"

def cleanup_preview_data():
    for name in[PREVIEW_OBJ_NAME, PREVIEW_GUIDE_NAME]:
        obj = bpy.data.objects.get(name)
        if obj:
            mesh = obj.data
            bpy.data.objects.remove(obj, do_unlink=True)
            if mesh and mesh.users == 0:
                bpy.data.meshes.remove(mesh)
    meshes_to_remove =[m for m in bpy.data.meshes if m.name.startswith(f"PreviewMesh_{PREFIX}")]
    for m in meshes_to_remove:
        if m.users == 0: bpy.data.meshes.remove(m)
    mat = bpy.data.materials.get(PREVIEW_MAT_NAME)
    if mat and mat.users == 0: bpy.data.materials.remove(mat)
    col = bpy.data.collections.get(PREVIEW_COL_NAME)
    if col and len(col.objects) == 0: bpy.data.collections.remove(col)

def cleanup_old_materials(prefix="Mat_UniqueSqTorus", limit=50):
    mats = [m for m in bpy.data.materials if m.name.startswith(prefix)]
    if len(mats) > limit:
        for m in mats[:-limit]:
            if m.users == 0: bpy.data.materials.remove(m)

def create_square_guide_bmesh(bm, square_size):
    S = square_size / 2.0
    v1 = bm.verts.new((S, S, 0))
    v2 = bm.verts.new((-S, S, 0))
    v3 = bm.verts.new((-S, -S, 0))
    v4 = bm.verts.new((S, -S, 0))
    bm.verts.ensure_lookup_table()
    bm.edges.new((v1, v2))
    bm.edges.new((v2, v3))
    bm.edges.new((v3, v4))
    bm.edges.new((v4, v1))
    return bm

def create_square_torus_bmesh(bm, square_size, corner_radius, minor_radius, corner_segments, minor_segments):
    square_size = min(max(square_size, 0.01), 10000.0)
    minor_radius = min(max(minor_radius, 0.001), square_size)
    minor_segments = max(minor_segments, 3)
    half_size = square_size / 2.0
    actual_corner_radius = min(max(corner_radius, 0.0), half_size)
    rings =[]
    EPS = 1e-6
    
    if actual_corner_radius < EPS:
        L = half_size
        corners =[
            (mathutils.Vector((L, L, 0)), mathutils.Vector((1, 1, 0)).normalized()),
            (mathutils.Vector((-L, L, 0)), mathutils.Vector((-1, 1, 0)).normalized()),
            (mathutils.Vector((-L, -L, 0)), mathutils.Vector((-1, -1, 0)).normalized()),
            (mathutils.Vector((L, -L, 0)), mathutils.Vector((1, -1, 0)).normalized())
        ]
        scale_xy = 1.0 / math.cos(math.pi / 4)
        for p, n in corners:
            b = mathutils.Vector((0, 0, 1))
            ring =[]
            for j in range(minor_segments):
                theta = j * 2.0 * math.pi / minor_segments
                offset = n * (minor_radius * math.cos(theta) * scale_xy) + b * (minor_radius * math.sin(theta))
                ring.append(bm.verts.new(p + offset))
            rings.append(ring)
    else:
        L = half_size - actual_corner_radius
        pts =[]
        for q in range(4):
            cx = L if q in [0, 3] else -L
            cy = L if q in [0, 1] else -L
            for i in range(corner_segments + 1):
                angle = q * (math.pi / 2) + i * (math.pi / 2) / corner_segments
                x = cx + actual_corner_radius * math.cos(angle)
                y = cy + actual_corner_radius * math.sin(angle)
                pts.append((mathutils.Vector((x, y, 0)), mathutils.Vector((math.cos(angle), math.sin(angle), 0))))
        
        unique_pts =[]
        for p, n in pts:
            if not unique_pts or (unique_pts[-1][0] - p).length > EPS:
                unique_pts.append((p, n))
        if len(unique_pts) > 1 and (unique_pts[-1][0] - unique_pts[0][0]).length < EPS:
            unique_pts.pop()
                
        for p, n in unique_pts:
            b = mathutils.Vector((0, 0, 1))
            ring =[]
            for j in range(minor_segments):
                theta = j * 2.0 * math.pi / minor_segments
                offset = n * (minor_radius * math.cos(theta)) + b * (minor_radius * math.sin(theta))
                ring.append(bm.verts.new(p + offset))
            rings.append(ring)
            
    bm.verts.ensure_lookup_table()
    total_rings = len(rings)
    if total_rings < 3: return bm

    edge_loops = []
    for ring in rings:
        edges =[]
        for j in range(minor_segments):
            v1 = ring[j]
            v2 = ring[(j + 1) % minor_segments]
            edges.append(bm.edges.new((v1, v2)))
        edge_loops.append(edges)
        
    bm.edges.ensure_lookup_table()
    for i in range(total_rings):
        next_i = (i + 1) % total_rings
        try: bmesh.ops.bridge_loops(bm, edges=edge_loops[i] + edge_loops[next_i])
        except Exception: pass
            
    bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
    for f in bm.faces: f.smooth = True
    if bm.faces: bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    return bm

def apply_auto_smooth(mesh):
    if bpy.app.version < (4, 1, 0):
        try:
            if hasattr(mesh, "use_auto_smooth"):
                mesh.use_auto_smooth = True
                mesh.auto_smooth_angle = math.radians(30)
        except AttributeError: pass

def create_unique_material(color, name_prefix="Mat_UniqueSqTorus"):
    timestamp = datetime.now().strftime('%M%S%f')[:5] 
    mat_name = f"{name_prefix}_{timestamp}"
    mat = bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    mat.blend_method = 'BLEND'
    if mat.use_nodes:
        tree = mat.node_tree
        tree.nodes.clear()
        bsdf = tree.nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.location = (0, 0)
        out = tree.nodes.new("ShaderNodeOutputMaterial")
        out.location = (300, 0)
        tree.links.new(bsdf.outputs[0], out.inputs[0])
        if "Base Color" in bsdf.inputs: bsdf.inputs['Base Color'].default_value = color
        if "Alpha" in bsdf.inputs: bsdf.inputs['Alpha'].default_value = color[3]
    cleanup_old_materials(name_prefix)
    return mat

def get_or_create_preview_material():
    mat = bpy.data.materials.get(PREVIEW_MAT_NAME)
    if not mat:
        mat = bpy.data.materials.new(name=PREVIEW_MAT_NAME)
        mat.use_nodes = True
        mat.blend_method = 'BLEND'
    return mat

def update_preview_material(mat, color):
    if mat.use_nodes:
        bsdf = None
        for node in mat.node_tree.nodes:
            if node.type == 'BSDF_PRINCIPLED':
                bsdf = node
                break
        if not bsdf:
            mat.node_tree.nodes.clear()
            bsdf = mat.node_tree.nodes.new("ShaderNodeBsdfPrincipled")
            out = mat.node_tree.nodes.new("ShaderNodeOutputMaterial")
            mat.node_tree.links.new(bsdf.outputs[0], out.inputs[0])
        if "Base Color" in bsdf.inputs: bsdf.inputs["Base Color"].default_value = color
        if "Alpha" in bsdf.inputs: bsdf.inputs["Alpha"].default_value = color[3]

def get_transform_matrix(props):
    rot_matrix = mathutils.Matrix.Identity(4)
    if props.torus_plane == 'YZ': rot_matrix = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Y')
    elif props.torus_plane == 'ZX': rot_matrix = mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'X')
    user_rot = mathutils.Euler((math.radians(props.torus_rot[0]), math.radians(props.torus_rot[1]), math.radians(props.torus_rot[2])), 'XYZ').to_matrix().to_4x4()
    loc_matrix = mathutils.Matrix.Translation(mathutils.Vector(props.torus_loc))
    return loc_matrix @ user_rot @ rot_matrix

def update_preview_geometry(context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return
    col = bpy.data.collections.get(PREVIEW_COL_NAME)
    if not col: col = bpy.data.collections.new(PREVIEW_COL_NAME)
    if col.name not in context.scene.collection.children: context.scene.collection.children.link(col)
    obj = bpy.data.objects.get(PREVIEW_OBJ_NAME)
    guide_obj = bpy.data.objects.get(PREVIEW_GUIDE_NAME)
    if not props.show_preview:
        if obj: bpy.data.objects.remove(obj, do_unlink=True)
        if guide_obj: bpy.data.objects.remove(guide_obj, do_unlink=True)
        return
    final_matrix = get_transform_matrix(props)
    scene_mesh_name = f"PreviewMesh_{PREFIX}_{context.scene.name}"
    bm = bmesh.new()
    try:
        create_square_torus_bmesh(bm, square_size=props.square_size, corner_radius=props.corner_radius, minor_radius=props.minor_radius, corner_segments=props.corner_segments, minor_segments=props.minor_segments)
        bmesh.ops.transform(bm, matrix=final_matrix, verts=bm.verts)
        mesh = bpy.data.meshes.get(scene_mesh_name)
        if not mesh: mesh = bpy.data.meshes.new(scene_mesh_name)
        else: mesh.clear_geometry()
        bm.to_mesh(mesh)
        apply_auto_smooth(mesh)
        mesh.update()
    finally: bm.free()
    if not obj:
        obj = bpy.data.objects.new(PREVIEW_OBJ_NAME, mesh)
        col.objects.link(obj)
    elif obj.data != mesh: obj.data = mesh
    mat = get_or_create_preview_material()
    update_preview_material(mat, props.torus_color)
    if not obj.data.materials: obj.data.materials.append(mat)
    else: obj.data.materials[0] = mat
    if props.show_square_guide:
        bm_g = bmesh.new()
        try:
            create_square_guide_bmesh(bm_g, props.square_size)
            bmesh.ops.transform(bm_g, matrix=final_matrix, verts=bm_g.verts)
            guide_mesh_name = scene_mesh_name + "_Guide"
            mesh_g = bpy.data.meshes.get(guide_mesh_name)
            if not mesh_g: mesh_g = bpy.data.meshes.new(guide_mesh_name)
            else: mesh_g.clear_geometry()
            bm_g.to_mesh(mesh_g)
            mesh_g.update()
        finally: bm_g.free()
        if not guide_obj:
            guide_obj = bpy.data.objects.new(PREVIEW_GUIDE_NAME, mesh_g)
            col.objects.link(guide_obj)
        elif guide_obj.data != mesh_g: guide_obj.data = mesh_g
        guide_obj.display_type = 'WIRE'
        guide_obj.show_in_front = True
    else:
        if guide_obj: bpy.data.objects.remove(guide_obj, do_unlink=True)

_timer = None
_last_update_time = 0
def delayed_update():
    global _timer, _last_update_time
    _timer = None
    now = time.time()
    if now - _last_update_time < 0.05:
        if _timer is None: _timer = bpy.app.timers.register(delayed_update, first_interval=0.05)
        return None
    _last_update_time = now
    ctx = bpy.context
    if not ctx or not ctx.scene: return None
    if ctx.object and ctx.object.mode != 'OBJECT': return None
    update_preview_geometry(ctx)
    return None
def on_update(self, context):
    global _timer
    if _timer is None: _timer = bpy.app.timers.register(delayed_update, first_interval=0.05)

# ==============================================================================
#  PROPERTIES
# ==============================================================================

class PG_TorusProps(PropertyGroup):
    show_preview: BoolProperty(name="Show Preview", default=CURRENT_DEFAULTS['show_preview'], update=on_update)
    show_square_guide: BoolProperty(name="Show Square Guide", default=CURRENT_DEFAULTS['show_square_guide'], update=on_update)
    torus_color: FloatVectorProperty(name="Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['torus_color'], update=on_update)
    torus_plane: EnumProperty(name="Plane", items=[('XY', "XY Plane", ""), ('YZ', "YZ Plane", ""), ('ZX', "ZX Plane", "")], default=CURRENT_DEFAULTS['torus_plane'], update=on_update)
    torus_loc: FloatVectorProperty(name="Location", size=3, default=CURRENT_DEFAULTS['torus_loc'], update=on_update)
    torus_rot: FloatVectorProperty(name="Rotation (Deg)", size=3, default=CURRENT_DEFAULTS['torus_rot'], update=on_update)
    square_size: FloatProperty(name="Square Size", default=CURRENT_DEFAULTS['square_size'], min=0.1, max=10000.0, update=on_update)
    corner_radius: FloatProperty(name="Corner Radius", default=CURRENT_DEFAULTS['corner_radius'], min=0.0, max=5000.0, update=on_update)
    minor_radius: FloatProperty(name="Tube Thickness", default=CURRENT_DEFAULTS['minor_radius'], min=0.01, max=5000.0, update=on_update)
    corner_segments: IntProperty(name="Corner Segs", default=CURRENT_DEFAULTS['corner_segments'], min=1, soft_max=128, update=on_update)
    minor_segments: IntProperty(name="Tube Segs", default=CURRENT_DEFAULTS['minor_segments'], min=3, soft_max=128, update=on_update)

# ==============================================================================
#  OPERATORS
# ==============================================================================

class OT_CreateTorus(Operator):
    bl_idname = f"{OP_PREFIX}.create_torus"
    bl_label = "Create Square Torus"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME, None)
        bm = bmesh.new()
        create_square_torus_bmesh(bm, square_size=props.square_size, corner_radius=props.corner_radius, minor_radius=props.minor_radius, corner_segments=props.corner_segments, minor_segments=props.minor_segments)
        final_matrix = get_transform_matrix(props)
        bmesh.ops.transform(bm, matrix=final_matrix, verts=bm.verts)
        mesh = bpy.data.meshes.new(f"SquareTorus_Mesh")
        bm.to_mesh(mesh)
        bm.free()
        apply_auto_smooth(mesh)
        obj = bpy.data.objects.new(f"SqTorus_{datetime.now().strftime('%H%M%S')}", mesh)
        if context.collection: context.collection.objects.link(obj)
        else: context.scene.collection.objects.link(obj)
        unique_mat = create_unique_material(props.torus_color, "Mat_UniqueSqTorus")
        obj.data.materials.append(unique_mat)
        bpy.ops.object.select_all(action='DESELECT')
        obj.select_set(True)
        context.view_layer.objects.active = obj
        self.report({'INFO'}, "Created Topology-Perfect Square Torus!")
        return {'FINISHED'}

class OT_CopyFullScript(Operator):
    bl_idname = f"{OP_PREFIX}.copy_script"
    bl_label = "Copy Script"
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME, None)
        target_text = None
        for t in bpy.data.texts:
            if SOURCE_ID_TAG in t.as_string(): target_text = t; break
        if not target_text:
            self.report({'WARNING'}, "Source script not found in Text Editor.")
            return {'CANCELLED'}
        code = target_text.as_string()
        c, l, r = props.torus_color, props.torus_loc, props.torus_rot
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "show_preview": {props.show_preview},\n'
        new_dict += f'    "show_square_guide": {props.show_square_guide},\n'
        new_dict += f'    "torus_color": ({c[0]:.4f}, {c[1]:.4f}, {c[2]:.4f}, {c[3]:.4f}),\n'
        new_dict += f'    "torus_loc": ({l[0]:.4f}, {l[1]:.4f}, {l[2]:.4f}),\n'
        new_dict += f'    "torus_rot": ({r[0]:.4f}, {r[1]:.4f}, {r[2]:.4f}),\n'
        new_dict += f'    "square_size": {props.square_size:.4f},\n'
        new_dict += f'    "corner_radius": {props.corner_radius:.4f},\n'
        new_dict += f'    "minor_radius": {props.minor_radius:.4f},\n'
        new_dict += f'    "corner_segments": {props.corner_segments},\n'
        new_dict += f'    "minor_segments": {props.minor_segments},\n'
        new_dict += f'    "torus_plane": "{props.torus_plane}",\n'
        new_dict += "}\n"
        try:
            tag_start = "# <BEGIN" + "_DICT>"
            tag_end = "# <END" + "_DICT>"
            if tag_start not in code or tag_end not in code:
                self.report({'ERROR'}, "DICT tags missing! Script might be corrupted.")
                return {'CANCELLED'}
            pre_code, rest = code.split(tag_start, 1)
            _, post_code = rest.split(tag_end, 1)
            final_code = pre_code + tag_start + "\n" + new_dict + tag_end + post_code
            if SOURCE_ID_TAG not in final_code:
                self.report({'ERROR'}, "Critical Error: SOURCE_ID_TAG lost during copy.")
                return {'CANCELLED'}
            lines = final_code.split("\n")
            if len(lines) > 0 and lines[0].startswith("# Copied:"):
                time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                lines[0] = f"# Copied: {time_str}"
            final_code = "\n".join(lines)
            context.window_manager.clipboard = final_code
            self.report({'INFO'}, "Code copied with absolute safety!")
        except Exception as e: 
            self.report({'ERROR'}, f"Copy failed: {e}")
            return {'CANCELLED'}
        return {'FINISHED'}

class OT_Reset(Operator):
    bl_idname = f"{OP_PREFIX}.reset"
    bl_label = "Reset Transform"
    def execute(self, context):
        p = getattr(context.scene, PROPS_NAME)
        p.torus_loc, p.torus_rot, p.torus_plane = (0,0,0), (0,0,0), 'XY'
        p.square_size, p.corner_radius, p.minor_radius = 10.0, 0.0, 0.5
        return {'FINISHED'}

class OT_OpenUrl(Operator):
    bl_idname = f"{OP_PREFIX}.open_url"; bl_label = "Open URL"; url: StringProperty()
    def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}

class OT_RemoveAddon(Operator):
    bl_idname = f"{OP_PREFIX}.remove_addon"; bl_label = "Remove Addon"
    def execute(self, context):
        bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
        return {'FINISHED'}

# ==============================================================================
#  追加スクリプト書き出しオペレーター
# ==============================================================================
class OT_AddZukkeiScript(Operator):
    bl_idname = f"{OP_PREFIX}.add_zukkei_script"
    bl_label = "Load Zukkei Script"
    bl_description = "図形&配列ジェネレータースクリプトをテキストエディターに読み込みます"
    def execute(self, context):
        text_name = "B5200_Zukkei_Array_View_20260319.py"
        if text_name in bpy.data.texts: text_data = bpy.data.texts[text_name]; text_data.clear()
        else: text_data = bpy.data.texts.new(name=text_name)
        text_data.write(ZUKKEI_SCRIPT_CONTENT)
        found_editor = False
        for area in context.screen.areas:
            if area.type == 'TEXT_EDITOR': area.spaces.active.text = text_data; found_editor = True; break
        if found_editor: self.report({'INFO'}, f"テキストエディターに '{text_name}' を読み込みました!")
        else: self.report({'INFO'}, f"'{text_name}' を作成しました。Text Editor を開いて確認してください。")
        return {'FINISHED'}

class OT_AddViewportScript(Operator):
    bl_idname = f"{OP_PREFIX}.add_viewport_script"
    bl_label = "Load Viewport & Sun Script"
    bl_description = "3D Viewport Color & Sun スクリプトをテキストエディターに読み込みます"
    def execute(self, context):
        text_name = "Viewport_Color_Sun_20260316.py"
        if text_name in bpy.data.texts: text_data = bpy.data.texts[text_name]; text_data.clear()
        else: text_data = bpy.data.texts.new(name=text_name)
        text_data.write(VIEWPORT_SCRIPT_CONTENT)
        found_editor = False
        for area in context.screen.areas:
            if area.type == 'TEXT_EDITOR': area.spaces.active.text = text_data; found_editor = True; break
        if found_editor: self.report({'INFO'}, f"テキストエディターに '{text_name}' を読み込みました!")
        else: self.report({'INFO'}, f"'{text_name}' を作成しました。")
        return {'FINISHED'}

class OT_AddCameraScript(Operator):
    bl_idname = f"{OP_PREFIX}.add_camera_script"
    bl_label = "Load Fixed Camera Script"
    bl_description = "Fixed Camera & World スクリプトをテキストエディターに読み込みます"
    def execute(self, context):
        text_name = "Fixed_Camera_World_2026.py"
        if text_name in bpy.data.texts: text_data = bpy.data.texts[text_name]; text_data.clear()
        else: text_data = bpy.data.texts.new(name=text_name)
        text_data.write(CAMERA_SCRIPT_CONTENT)
        found_editor = False
        for area in context.screen.areas:
            if area.type == 'TEXT_EDITOR': area.spaces.active.text = text_data; found_editor = True; break
        if found_editor: self.report({'INFO'}, f"テキストエディターに '{text_name}' を読み込みました!")
        else: self.report({'INFO'}, f"'{text_name}' を作成しました。")
        return {'FINISHED'}

# ==============================================================================
#  PANELS
# ==============================================================================

class PT_MainPanel(Panel):
    bl_label = PANEL_TITLE
    bl_idname = f"{PREFIX}_PT_main"
    bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROPS_NAME, None)
        if not props: layout.label(text="Reload Script"); return

        row = layout.row()
        row.scale_y = 1.2
        row.operator(OT_CopyFullScript.bl_idname, icon='COPY_ID', text="Copy Code with Values")
        layout.separator()

        layout.prop(props, "show_preview", icon='RESTRICT_VIEW_OFF' if props.show_preview else 'RESTRICT_VIEW_ON')
        
        box = layout.box()
        if not props.show_preview: box.label(text="Preview is Hidden", icon='INFO')
        box.prop(props, "torus_color")
        col = box.column(align=True)
        col.prop(props, "torus_plane")
        col.prop(props, "torus_loc")
        col.prop(props, "torus_rot")
        box.separator()
        box.prop(props, "show_square_guide", icon='MESH_PLANE')
        col_s = box.column(align=True)
        col_s.prop(props, "square_size")
        row_cr = col_s.row()
        row_cr.prop(props, "corner_radius")
        if props.corner_radius <= 0.001: row_cr.label(text="[90° Mode]", icon='SNAP_VERTEX')
        col_s.prop(props, "minor_radius")
        row_seg = box.row()
        row_seg.prop(props, "corner_segments")
        row_seg.prop(props, "minor_segments")
        box.operator(OT_Reset.bl_idname, icon='LOOP_BACK')

        layout.separator()
        col_exec = layout.column()
        col_exec.scale_y = 1.5
        col_exec.operator(OT_CreateTorus.bl_idname, icon='MESH_TORUS', text="Create Square Torus")

class PT_LinksPanel(Panel):
    bl_label = "Links"; bl_idname = f"{PREFIX}_PT_links"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context):
        for l in ADDON_LINKS: self.layout.operator(OT_OpenUrl.bl_idname, text=l["label"]).url = l["url"]

class PT_RemovePanel(Panel):
    bl_label = "System"; bl_idname = f"{PREFIX}_PT_remove"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
    def draw(self, context): self.layout.operator(OT_RemoveAddon.bl_idname, icon='CANCEL', text="Remove Addon")

class PT_ScriptPanel(Panel):
    bl_label = "Additional Scripts"
    bl_idname = f"{PREFIX}_PT_script"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        layout.label(text="B5200 図形ジェネレーターを追加:")
        layout.operator(OT_AddZukkeiScript.bl_idname, icon='TEXT', text="Load Zukkei Script")
        layout.separator()
        layout.label(text="5520 Viewport & Sun を追加:")
        layout.operator(OT_AddViewportScript.bl_idname, icon='TEXT', text="Load Viewport Script")
        layout.separator()
        layout.label(text="v100 Fixed Camera を追加:")
        layout.operator(OT_AddCameraScript.bl_idname, icon='TEXT', text="Load Camera Script")

# ==============================================================================
#  REGISTER
# ==============================================================================

classes = (
    PG_TorusProps, 
    OT_CreateTorus, 
    OT_CopyFullScript, 
    OT_Reset, 
    OT_OpenUrl, 
    OT_RemoveAddon, 
    OT_AddZukkeiScript,
    OT_AddViewportScript,
    OT_AddCameraScript,
    PT_MainPanel, 
    PT_LinksPanel, 
    PT_RemovePanel, 
    PT_ScriptPanel
)

def auto_open_sidebar():
    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':
                            if not space.show_region_ui: space.show_region_ui = True
    except: pass
    return None

def register():
    for c in classes: 
        try: bpy.utils.register_class(c)
        except ValueError: pass
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_TorusProps))
    bpy.app.timers.register(auto_open_sidebar, first_interval=0.1)

def unregister():
    global _timer
    if _timer is not None:
        try: bpy.app.timers.unregister(_timer)
        except Exception: pass
        _timer = None
    cleanup_preview_data()
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): 
        try: bpy.utils.unregister_class(c)
        except ValueError: pass

if __name__ == "__main__": 
    register()