Prefix 追加 20260227 Code copy

リンク パネル付き

rapture_20260318150631.png

# Copied: 15:00:01
import bpy
from bpy.props import FloatVectorProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime

# ==============================================================================
#  設定エリア & ID管理
# ==============================================================================

PREFIX = "Sphere20260227"
TAB_NAME = "[ Sphere copy ]   "

# --- 透視投影 視座関連の定数パラメーター ---
VIEW_RESET_BTN_TEXT = "Reset View (0, -10, 10)"  
VIEW_POS_INIT = (0.0, -10.0, 10.0)               

# ### ZIONAD_SOURCE_ID: SPHERE_2026_02_27_FIXED ###

bl_info = {
    "name": f"zionad 520 [ Sphere Gen ] {PREFIX}",
    "author": "zionadchat",
    "version": (3, 3, 0),
    "blender": (3, 0, 0),
    "location": "3D View > Sidebar",
    "description": "View Control Addon (Simplified)",
    "category": "3D View",
}

# 内部変数
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: SPHERE_2026_02_27_FIXED ###"

# ==============================================================================
#  デフォルト値設定 (コピー機能でここが書き換わります)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
    "view_pos": (0.0000, -10.0000, 10.0000),
}
# <END_DICT>

# ==============================================================================
#  透視投影 視点更新ロジック
# ==============================================================================

def update_view_position(self, context):
    props = getattr(context.scene, PROPS_NAME, None)
    if not props: return
    
    cam_pos = Vector(props.view_pos)
    
    # ウィンドウ内の全ての3Dビューポートの視点を更新
    for window in 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':
                        r3d = space.region_3d
                        r3d.view_perspective = 'PERSP'          # 透視投影にする
                        
                        # 現在の注視点を基準にカメラ位置を計算
                        target_pos = Vector(r3d.view_location)
                        
                        rel_pos = cam_pos - target_pos
                        dist = rel_pos.length
                        
                        if dist > 0.001:
                            r3d.view_distance = dist
                            # 注視点へ向けるための回転行列を設定
                            r3d.view_rotation = rel_pos.to_track_quat('Z', 'Y')

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

class PG_SphereProps(PropertyGroup):
    # 視座位置
    view_pos: FloatVectorProperty(
        name="View Position", 
        size=3, 
        soft_min=-100.0, soft_max=100.0, 
        default=CURRENT_DEFAULTS.get('view_pos', VIEW_POS_INIT), 
        update=update_view_position
    )

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

class OT_ResetView(Operator):
    bl_idname = f"{OP_PREFIX}.reset_view"
    bl_label = VIEW_RESET_BTN_TEXT
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        props = getattr(context.scene, PROPS_NAME, None)
        if props:
            # リセット時は全3Dビューの注視点(フォーカス位置)も (0, 0, 0) に戻す
            for window in 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.region_3d.view_location = (0.0, 0.0, 0.0)
                                
            props.view_pos = VIEW_POS_INIT # 初期値定数を適用
        return {'FINISHED'}

class OT_CenterSelected(Operator):
    bl_idname = f"{OP_PREFIX}.center_selected"
    bl_label = "Center Selected Object"
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        if context.area.type != 'VIEW_3D':
            self.report({'WARNING'}, "Please run in 3D Viewport")
            return {'CANCELLED'}
            
        # 1. 選択オブジェクトを注視点としてフォーカス
        bpy.ops.view3d.view_selected()
        
        # 2. フォーカス後のカメラのワールド座標を取得する
        r3d = None
        if hasattr(context, "region_data") and context.region_data:
            r3d = context.region_data
        else:
            for space in context.area.spaces:
                if space.type == 'VIEW_3D':
                    r3d = space.region_3d
                    break
                    
        if r3d:
            cam_matrix = r3d.view_matrix.inverted()
            cam_pos = cam_matrix.translation
            
            # 3. 取得したカメラ座標を view_pos にセット (スライダーと同期させる)
            props = getattr(context.scene, PROPS_NAME, None)
            if props:
                props.view_pos = cam_pos
                
        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({'ERROR'}, "Script source not found.")
            return {'CANCELLED'}

        code = target_text.as_string()
        v = props.view_pos
        
        new_dict = "CURRENT_DEFAULTS = {\n"
        new_dict += f'    "view_pos": ({v[0]:.4f}, {v[1]:.4f}, {v[2]:.4f}),\n'
        new_dict += "}\n"

        try:
            start, end = "# <BEGIN" + "_DICT>", "# <END" + "_DICT>"
            pre, post = code.split(start)[0], code.split(end)[1]
            final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n" + pre + start + "\n" + new_dict + end + post
            context.window_manager.clipboard = final
            self.report({'INFO'}, "Code copied!")
        except: return {'CANCELLED'}
        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'}

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

class PT_ViewControlPanel(Panel):
    bl_label = "View Control"
    bl_idname = f"{PREFIX}_PT_view_control"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = TAB_NAME
    bl_order = 0

    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, PROPS_NAME, None)
        if not props: 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()

        box = layout.box()
        box.label(text="Perspective Viewpoint", icon='VIEW_CAMERA')
        
        col = box.column(align=True)
        col.prop(props, "view_pos", text="X", index=0)
        col.prop(props, "view_pos", text="Y", index=1)
        col.prop(props, "view_pos", text="Z", index=2)
        
        box.separator()
        box.operator(OT_ResetView.bl_idname, icon='LOOP_BACK', text=VIEW_RESET_BTN_TEXT)
        
        layout.separator()
        layout.operator(OT_CenterSelected.bl_idname, icon='VIEWZOOM', text="Center Selected Object")

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'}
    bl_order = 1
    
    def draw(self, context): 
        self.layout.operator(OT_RemoveAddon.bl_idname, icon='CANCEL', text="Remove Addon")

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

classes = (
    PG_SphereProps, 
    OT_CopyFullScript, 
    OT_ResetView, 
    OT_CenterSelected, 
    OT_RemoveAddon, 
    PT_ViewControlPanel, 
    PT_RemovePanel
)

def open_sidebar():
    if not bpy.context:
        return None
    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.show_region_ui = True # パネルを展開
    return None

def register():
    for c in classes: bpy.utils.register_class(c)
    setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_SphereProps))
    bpy.app.timers.register(open_sidebar, first_interval=0.1)

def unregister():
    if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
    for c in reversed(classes): bpy.utils.unregister_class(c)

if __name__ == "__main__": register()