基本アドオン設定 更新

rapture_20260711193437.png

bl_info = {
    "name": "Viewport & Automation Master",
    "author": "Your Name",
    "version": (2, 7),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "画面中央化、ビュー詳細情報取得、グリッド・背景色設定、各種リンク",
    "category": "3D View",
}

# 【作成日】 2026年7月11日 12:44

# ===================================
# 【初期設定・定数定義】 (1行ずつ定義)
# ===================================
PREFIX = "front_tools"
TAB_NAME = " back color "
GRID_DEFAULT_1 = 10.0
GRID_DEFAULT_2 = 100.0
LINK_NAME_1 = "基本アドオン設定 20260711b"
LINK_URL_1 = "<https://app.notion.com/p/20260711-39af5dacaf438027811dcd877db7c630>"
LINK_NAME_2 = "posfie"
LINK_URL_2 = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
LINK_NAME_3 = "zionadchat"
LINK_URL_3 = "<https://x.com/zionadchat>"

import bpy
import math
import mathutils
import webbrowser

# ===================================
# 【ビュー情報取得 関数】
# ===================================
def get_rv3d(context):
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            return a.spaces.active.region_3d
    return None

def get_view_info_data(context):
    rv3d = None
    area = None
    space = None
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            rv3d = a.spaces.active.region_3d
            area = a
            space = a.spaces.active
            break
            
    if not rv3d or not area or not space: return None
        
    mode = rv3d.view_perspective
    target_pos = rv3d.view_location
    view_dist = rv3d.view_distance
    cam_pos = rv3d.view_matrix.inverted().translation
    view_dir = rv3d.view_rotation @ mathutils.Vector((0.0, 0.0, -1.0))
    aspect = area.height / area.width if area.width > 0 else 1.0
    sensor = 32.0 
    
    if mode == 'PERSP':
        proj_text = "透視投影"
        is_persp = True
    elif mode == 'ORTHO':
        proj_text = "平行投影"
        is_persp = False
    elif mode == 'CAMERA':
        proj_text = "カメラビュー"
        is_persp = True
    else:
        proj_text = "不明"
        is_persp = False
    
    if is_persp:
        fov_h = 2.0 * math.atan(sensor / (2.0 * space.lens))
        w = 2.0 * view_dist * math.tan(fov_h / 2.0)
        h = w * aspect
    else: 
        w = view_dist * (sensor / space.lens)
        h = w * aspect
        
    return {
        'proj_text': proj_text,
        'cam_pos': cam_pos,
        'view_dir': view_dir,
        'target_pos': target_pos,
        'dist': view_dist,
        'w': w,
        'h': h,
        'is_persp': is_persp
    }

# ===================================
# 【オペレーター】
# ===================================
class VGM_OT_reset_view(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_view"
    bl_label = "選択オブジェクトを画面中央にする"
    def execute(self, context):
        rv3d = get_rv3d(context)
        if not rv3d: return {'CANCELLED'}
        target = context.active_object
        rv3d.view_location = target.matrix_world.to_translation() if target else rv3d.view_location.copy()
        rv3d.view_distance = max(target.dimensions.length * 1.5, 10.0) if target else 30.0
        rv3d.view_perspective = 'PERSP'
        context.view_layer.update()
        return {'FINISHED'}

class VGM_OT_update_info(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_update_info"
    bl_label = "情報を更新"
    def execute(self, context):
        for area in context.screen.areas:
            if area.type == 'VIEW_3D': area.tag_redraw()
        return {'FINISHED'}

class VGM_OT_copy_info(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_copy_info"
    bl_label = "情報をコピー"
    def execute(self, context):
        info = get_view_info_data(context)
        if not info: return {'CANCELLED'}
        
        lines = []
        lines.append(f"投影モード: {info['proj_text']}")
        if info['is_persp']:
            lines.append(f"架空の視座位置 X: {info['cam_pos'].x:.1f}, Y: {info['cam_pos'].y:.1f}, Z: {info['cam_pos'].z:.1f}")
        
        lines.append(f"画面中央への視線方向 X: {info['view_dir'].x:.1f}, Y: {info['view_dir'].y:.1f}, Z: {info['view_dir'].z:.1f}")
        lines.append(f"画面中央位置 X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}")
        lines.append(f"画面水平の大きさ: {info['w']:.1f}")
        lines.append(f"画面上下の大きさ: {info['h']:.1f}")
        
        if info['is_persp']:
            lines.append(f"視座からの距離: {info['dist']:.1f}")

        context.window_manager.clipboard = "\n".join(lines)
        self.report({'INFO'}, "ビュー情報をクリップボードにコピーしました")
        return {'FINISHED'}

class VGM_OT_set_grid_scale(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_set_grid_scale"
    bl_label = "Grid間隔を設定"
    scale_val: bpy.props.FloatProperty(name="Scale")
    use_custom: bpy.props.BoolProperty(name="カスタム値", default=False)
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        val = props.custom_grid_scale if self.use_custom else self.scale_val
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D': space.overlay.grid_scale = val
        return {'FINISHED'}

class VGM_OT_open_url(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    
    def execute(self, context):
        if self.url:
            webbrowser.open(self.url)
        return {'FINISHED'}

class VGM_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_remove_addon"
    bl_label = "アドオン削除"
    def execute(self, context): unregister(); return {'FINISHED'}

# ===================================
# 【プロパティ定義】
# ===================================
class VGM_Properties(bpy.types.PropertyGroup):
    custom_grid_scale: bpy.props.FloatProperty(name="指定値", default=50.0, min=0.001)

# ===================================
# 【UIパネル (1つに統合)】
# ===================================
class VGM_PT_main_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX.upper()}_PT_main_panel"
    bl_label = "Viewport & Grid Master"
    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}_props")
        info = get_view_info_data(context)
        
        # --- 1. ビュー操作 (先頭) ---
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択オブジェクトを画面中央にする", icon='ZOOM_ALL')
        layout.separator(factor=1.5)
        
        # --- 2. ビュー情報 ---
        box_info = layout.box()
        row_info = box_info.row(align=True)
        row_info.label(text=f"情報: [{info['proj_text'] if info else '不明'}]", icon='INFO')
        row_info.operator(f"view3d.{PREFIX}_update_info", text="更新", icon='FILE_REFRESH')
        
        if info:
            col_info = box_info.column(align=True)
            col_info.label(text="画面中央位置:")
            col_info.label(text=f"  X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}")
            col_info.separator()
            
            col_info.label(text="画面中央への視線方向:")
            col_info.label(text=f"  X: {info['view_dir'].x:.1f}, Y: {info['view_dir'].y:.1f}, Z: {info['view_dir'].z:.1f}")
            col_info.separator()
            
            col_info.label(text=f"画面水平の大きさ: {info['w']:.1f}")
            col_info.label(text=f"画面上下の大きさ: {info['h']:.1f}")
            
            if info['is_persp']:
                col_info.separator()
                col_info.label(text=f"視座からの距離: {info['dist']:.1f}")
                
            box_info.separator()
            box_info.operator(f"view3d.{PREFIX}_copy_info", text="情報をコピー (1行ずつペースト)", icon='COPYDOWN')
        else:
            box_info.label(text="3Dビューが見つかりません")
            
        layout.separator(factor=1.5)
        
        # --- 3. Grid / 背景 設定 ---
        box_grid = layout.box()
        box_grid.label(text="Grid / 背景 設定", icon='GRID')
        
        row_preset = box_grid.row(align=True)
        row_preset.operator(f"view3d.{PREFIX}_set_grid_scale", text=f"間隔 {GRID_DEFAULT_1}").scale_val = GRID_DEFAULT_1
        row_preset.operator(f"view3d.{PREFIX}_set_grid_scale", text=f"間隔 {GRID_DEFAULT_2}").scale_val = GRID_DEFAULT_2
        
        row_custom = box_grid.row(align=True)
        row_custom.prop(props, "custom_grid_scale")
        row_custom.operator(f"view3d.{PREFIX}_set_grid_scale", text="指定値にする").use_custom = True
        
        box_grid.separator()
        prefs = context.preferences
        if len(prefs.themes) > 0:
            theme_view3d = prefs.themes[0].view_3d
            col_theme = box_grid.column(align=True)
            col_theme.prop(theme_view3d, "grid", text="Grid 色変更")
            col_theme.separator()
            col_theme.prop(theme_view3d.space.gradients, "high_gradient", text="背景色 (上)")
            col_theme.prop(theme_view3d.space.gradients, "low_gradient", text="背景色 (下)")

        layout.separator(factor=1.5)
        
        # --- 4. リンク / アドオン削除 ---
        box_sys = layout.box()
        box_sys.label(text="システム / リンク", icon='PREFERENCES')
        
        op1 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_1, icon='URL')
        op1.url = LINK_URL_1
        
        op2 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_2, icon='URL')
        op2.url = LINK_URL_2
        
        op3 = box_sys.operator(f"wm.{PREFIX}_open_url", text=LINK_NAME_3, icon='URL')
        op3.url = LINK_URL_3
        
        box_sys.separator()
        box_sys.operator(f"wm.{PREFIX}_remove_addon", text="アドオンの登録を解除", icon='CANCEL')

# ===================================
# 【登録処理】
# ===================================
classes = [
    VGM_Properties,
    VGM_OT_reset_view,
    VGM_OT_update_info,
    VGM_OT_copy_info,
    VGM_OT_set_grid_scale,
    VGM_OT_open_url,
    VGM_OT_remove_addon,
    VGM_PT_main_panel
]

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

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

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