アドオン作成 プロンプト 呪文

rapture_20260714045514.png

簡易背景 20260714|zionadmillion

簡易背景
<https://note.com/zionadmillion/n/nb7ae4d18f7ba>

色設定収集

bl_info = {
    "name": "zionad_[ 簡易背景 ]",
    "author": "Your Name",
    "version": (1, 3),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar (Nパネル)",
    "description": "簡易背景設定、カラーピッカー(3x3)による色情報記憶・確実なコード書き出し",
    "category": "3D View",
}

import bpy
import math
import mathutils
import webbrowser
import re
import os

# ===================================
# 【定義】
# ===================================
ADDON_TITLE = "[ 簡易背景 ]"
PREFIX_VAL = "DICE_TOOLS_5"
ADDON_CATEGORY = ADDON_TITLE
PREFIX = PREFIX_VAL.lower()

# ============
# 【初期設定・リンク定義】 
# ============
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>"
LINK_NAME_4 = "blender コンテナ 20260713"
LINK_URL_4 = "<https://note.com/zionadmillion/n/n9a47d88fe7b4>"
LINK_NAME_5 = "簡易背景 20260714"
LINK_URL_5 = "<https://note.com/zionadmillion/n/nb7ae4d18f7ba>"

# ============
# 【コレクション・オブジェクト名の初期値】
# ============
INIT_COLL_MAIN = "MainCollection"
INIT_COLL_SUB = "SubCollection"
INIT_OBJ_NAME = "TargetObject"

# ============
# 【数値設定の初期値】
# ============
GRID_PRESET_1 = 10.0
GRID_PRESET_2 = 100.0
GRID_CUSTOM_INIT = 1.0

# ============
# 【初期設定の色 (リセット用)】
# ============
COLOR_GRID_INIT = (0.7176, 0.8, 0.8157, 1.0)
COLOR_BG_HIGH_INIT = (0.4784, 0.4431, 0.8588)
COLOR_BG_LOW_INIT = (0.5725, 0.5725, 0.5725)

# ============
# 【3セット(3x3)のカラー記憶用 初期値】
# エクスポート実行時、この部分が自動で書き換わります。
# ============
PRESET1_GRID = (0.5569, 0.6196, 0.6314, 1.0)
PRESET1_HIGH = (0.0, 0.8588, 0.6235)
PRESET1_LOW = (0.0824, 0.0824, 0.0824)

PRESET2_GRID = (0.6314, 0.0632, 0.5469, 1.0)
PRESET2_HIGH = (0.0922, 0.0355, 0.1006)
PRESET2_LOW = (0.0154, 0.0218, 0.0824)

PRESET3_GRID = (0.5569, 0.6196, 0.6314, 1.0)
PRESET3_HIGH = (0.4784, 0.4431, 0.8588)
PRESET3_LOW = (0.0824, 0.0824, 0.0824)

# ===================================
# 【ビュー情報取得 関数】
# ===================================
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, area, space = None, None, None
    for a in context.window.screen.areas:
        if a.type == 'VIEW_3D':
            rv3d, area, space = a.spaces.active.region_3d, a, 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 
    
    is_persp = mode in {'PERSP', 'CAMERA'}
    proj_text = "透視投影" if mode == 'PERSP' else "平行投影" if mode == 'ORTHO' else "カメラビュー" if mode == 'CAMERA' else "不明"
    
    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)
    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 DICE_TOOLS_5_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 DICE_TOOLS_5_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 DICE_TOOLS_5_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 = [
            f"投影モード: {info['proj_text']}",
            f"画面中央位置: X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}",
            f"視座位置: X: {info['cam_pos'].x:.1f}, Y: {info['cam_pos'].y:.1f}, Z: {info['cam_pos'].z:.1f}",
            f"視線方向: X: {info['view_dir'].x:.1f}, Y: {info['view_dir'].y:.1f}, Z: {info['view_dir'].z:.1f}",
            f"画面水平の大きさ: {info['w']:.1f}",
            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 DICE_TOOLS_5_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 DICE_TOOLS_5_OT_reset_settings(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_reset_settings"
    bl_label = "初期値に戻す"
    def set_theme_color(self, obj, attr, color):
        try:
            target_len = len(getattr(obj, attr))
            setattr(obj, attr, tuple(color) + (1.0,) if target_len == 4 and len(color) == 3 else tuple(color)[:target_len])
        except: pass
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        props.custom_grid_scale = GRID_CUSTOM_INIT
        if context.preferences.themes:
            theme_view3d = context.preferences.themes[0].view_3d
            self.set_theme_color(theme_view3d, "grid", COLOR_GRID_INIT)
            self.set_theme_color(theme_view3d.space.gradients, "high_gradient", COLOR_BG_HIGH_INIT)
            self.set_theme_color(theme_view3d.space.gradients, "gradient", COLOR_BG_LOW_INIT)
        return {'FINISHED'}

# --- 3x3 カラー記憶用 オペレーター ---
class DICE_TOOLS_5_OT_apply_preset(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_apply_preset"
    bl_label = "適用"
    preset_idx: bpy.props.IntProperty()
    def set_theme_color(self, obj, attr, color):
        try:
            target_len = len(getattr(obj, attr))
            setattr(obj, attr, tuple(color) + (1.0,) if target_len == 4 and len(color) == 3 else tuple(color)[:target_len])
        except: pass
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        theme_view3d = context.preferences.themes[0].view_3d
        self.set_theme_color(theme_view3d, "grid", getattr(props, f"preset{self.preset_idx}_grid"))
        self.set_theme_color(theme_view3d.space.gradients, "high_gradient", getattr(props, f"preset{self.preset_idx}_high"))
        self.set_theme_color(theme_view3d.space.gradients, "gradient", getattr(props, f"preset{self.preset_idx}_low"))
        self.report({'INFO'}, f"セット {self.preset_idx} を適用しました")
        return {'FINISHED'}

class DICE_TOOLS_5_OT_store_preset(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_store_preset"
    bl_label = "記憶"
    preset_idx: bpy.props.IntProperty()
    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        theme_view3d = context.preferences.themes[0].view_3d
        cg, ch, cl = theme_view3d.grid, theme_view3d.space.gradients.high_gradient, theme_view3d.space.gradients.gradient
        # 色の配列長を強制補正して保存 (エラー回避)
        setattr(props, f"preset{self.preset_idx}_grid", list(cg) + [1.0] if len(cg) == 3 else list(cg)[:4])
        setattr(props, f"preset{self.preset_idx}_high", list(ch)[:3])
        setattr(props, f"preset{self.preset_idx}_low", list(cl)[:3])
        self.report({'INFO'}, f"セット {self.preset_idx} に記憶しました")
        return {'FINISHED'}

class DICE_TOOLS_5_OT_export_code(bpy.types.Operator):
    bl_idname = f"view3d.{PREFIX}_export_code"
    bl_label = "現在の設定・記憶色で全体コードを出力"

    def get_valid_source_code(self, context):
        # 確実に「3x3のピッカー変数が存在する最新のコード」を探す処理
        code = ""
        if context.space_data and context.space_data.type == 'TEXT_EDITOR':
            code = context.space_data.text.as_string()
        if code and "PRESET3_LOW" in code: return code
        
        valid_texts = []
        for text in bpy.data.texts:
            s = text.as_string()
            if "zionad_[ 簡易背景 ]" in s and "PRESET3_LOW" in s:
                valid_texts.append(text)
        if valid_texts: return valid_texts[-1].as_string() # 最新のものを返す
        return ""

    def execute(self, context):
        props = getattr(context.scene, f"{PREFIX}_props")
        grid_custom = round(props.custom_grid_scale, 4)
        if not context.preferences.themes: return {'CANCELLED'}
        theme_view3d = context.preferences.themes[0].view_3d
        
        # エラー防止のため長さを固定して丸める関数
        def round_c(c, size):
            lst = list(c)
            while len(lst) < size: lst.append(1.0)
            return tuple(round(v, 4) for v in lst[:size])

        code = self.get_valid_source_code(context)
        if not code:
            self.report({'ERROR'}, "正しいソースコードが見つかりません。")
            return {'CANCELLED'}

        # 数値・初期値置換
        code = re.sub(r"GRID_CUSTOM_INIT\s*=\s*[\d\.]+", f"GRID_CUSTOM_INIT = {grid_custom}", code)
        code = re.sub(r"COLOR_GRID_INIT\s*=\s*\(.*?\)", f"COLOR_GRID_INIT = {round_c(theme_view3d.grid, 4)}", code)
        code = re.sub(r"COLOR_BG_HIGH_INIT\s*=\s*\(.*?\)", f"COLOR_BG_HIGH_INIT = {round_c(theme_view3d.space.gradients.high_gradient, 3)}", code)
        code = re.sub(r"COLOR_BG_LOW_INIT\s*=\s*\(.*?\)", f"COLOR_BG_LOW_INIT = {round_c(theme_view3d.space.gradients.gradient, 3)}", code)

        # 3セット記憶色の置換
        for i in range(1, 4):
            cg = round_c(getattr(props, f"preset{i}_grid"), 4)
            ch = round_c(getattr(props, f"preset{i}_high"), 3)
            cl = round_c(getattr(props, f"preset{i}_low"), 3)
            code = re.sub(rf"PRESET{i}_GRID\s*=\s*\(.*?\)", f"PRESET{i}_GRID = {cg}", code)
            code = re.sub(rf"PRESET{i}_HIGH\s*=\s*\(.*?\)", f"PRESET{i}_HIGH = {ch}", code)
            code = re.sub(rf"PRESET{i}_LOW\s*=\s*\(.*?\)",  f"PRESET{i}_LOW = {cl}", code)

        new_text = bpy.data.texts.new(name="ZIONAD_Updated_Code.py")
        new_text.from_string(code)
        
        # 生成後、テキストエディタを切り替える
        for area in context.screen.areas:
            if area.type == 'TEXT_EDITOR':
                area.spaces.active.text = new_text
                break
                
        self.report({'INFO'}, f"新しいコード '{new_text.name}' を生成しました。こちらを実行してください。")
        return {'FINISHED'}

class DICE_TOOLS_5_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 DICE_TOOLS_5_OT_remove_addon(bpy.types.Operator):
    bl_idname = f"wm.{PREFIX}_remove_addon"
    bl_label = "アドオン削除実行"
    def execute(self, context): 
        try: unregister(); self.report({'INFO'}, "アドオンを削除しました")
        except: pass
        return {'FINISHED'}

# ===================================
# 【プロパティ定義】
# ===================================
class DICE_TOOLS_5_Properties(bpy.types.PropertyGroup):
    custom_grid_scale: bpy.props.FloatProperty(name="指定数値", default=GRID_CUSTOM_INIT, min=0.001)
    
    # サイズ(3, 4)を厳密に固定
    preset1_grid: bpy.props.FloatVectorProperty(name="G", subtype='COLOR', size=4, default=PRESET1_GRID, min=0.0, max=1.0)
    preset1_high: bpy.props.FloatVectorProperty(name="H", subtype='COLOR', size=3, default=PRESET1_HIGH, min=0.0, max=1.0)
    preset1_low:  bpy.props.FloatVectorProperty(name="L", subtype='COLOR', size=3, default=PRESET1_LOW, min=0.0, max=1.0)
    
    preset2_grid: bpy.props.FloatVectorProperty(name="G", subtype='COLOR', size=4, default=PRESET2_GRID, min=0.0, max=1.0)
    preset2_high: bpy.props.FloatVectorProperty(name="H", subtype='COLOR', size=3, default=PRESET2_HIGH, min=0.0, max=1.0)
    preset2_low:  bpy.props.FloatVectorProperty(name="L", subtype='COLOR', size=3, default=PRESET2_LOW, min=0.0, max=1.0)
    
    preset3_grid: bpy.props.FloatVectorProperty(name="G", subtype='COLOR', size=4, default=PRESET3_GRID, min=0.0, max=1.0)
    preset3_high: bpy.props.FloatVectorProperty(name="H", subtype='COLOR', size=3, default=PRESET3_HIGH, min=0.0, max=1.0)
    preset3_low:  bpy.props.FloatVectorProperty(name="L", subtype='COLOR', size=3, default=PRESET3_LOW, min=0.0, max=1.0)

# ===================================
# 【UIパネル (アドオン構造)】
# ===================================
class DICE_TOOLS_5_PT_main_panel(bpy.types.Panel):
    bl_idname = f"{PREFIX.upper()}_PT_main_panel"
    bl_label = ADDON_TITLE
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    
    def draw(self, context):
        layout = self.layout
        props = getattr(context.scene, f"{PREFIX}_props")
        info = get_view_info_data(context)
        
        layout.operator(f"view3d.{PREFIX}_reset_view", text="選択オブジェクトを画面中央にする", icon='ZOOM_ALL')
        layout.separator(factor=1.5)
        
        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=f"画面中央位置: X: {info['target_pos'].x:.1f}, Y: {info['target_pos'].y:.1f}, Z: {info['target_pos'].z:.1f}")
            col_info.label(text=f"視座位置: X: {info['cam_pos'].x:.1f}, Y: {info['cam_pos'].y:.1f}, Z: {info['cam_pos'].z:.1f}")
            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", icon='COPYDOWN')
        else: box_info.label(text="3Dビューが見つかりません")
            
        layout.separator(factor=1.5)
        
        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_PRESET_1}").scale_val = GRID_PRESET_1
        row_preset.operator(f"view3d.{PREFIX}_set_grid_scale", text=f"間隔 {GRID_PRESET_2}").scale_val = GRID_PRESET_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()
        
        if context.preferences.themes:
            theme_view3d = context.preferences.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, "gradient", text="画面背景色 (下)")

        box_grid.separator()
        box_grid.operator(f"view3d.{PREFIX}_reset_settings", icon='LOOP_BACK')
        layout.separator(factor=1.5)

        box_mem = layout.box()
        box_mem.label(text="カラー記憶スロット (3セット)", icon='COLOR')
        
        for i in range(1, 4):
            row_btn = box_mem.row(align=True)
            row_btn.label(text=f"セット {i}:")
            row_btn.operator(f"view3d.{PREFIX}_apply_preset", text="適用", icon='CHECKMARK').preset_idx = i
            row_btn.operator(f"view3d.{PREFIX}_store_preset", text="記憶", icon='EYEDROPPER').preset_idx = i
            
            row_color = box_mem.row(align=True)
            row_color.prop(props, f"preset{i}_grid", text="G")
            row_color.prop(props, f"preset{i}_high", text="H")
            row_color.prop(props, f"preset{i}_low",  text="L")
            box_mem.separator()
        
        box_mem.operator(f"view3d.{PREFIX}_export_code", icon='TEXT')
        layout.separator(factor=1.5)
        
        box_sys = layout.box()
        box_sys.label(text="システム / リンク", icon='PREFERENCES')
        
        for name, url in [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2), (LINK_NAME_3, LINK_URL_3), (LINK_NAME_4, LINK_URL_4), (LINK_NAME_5, LINK_URL_5)]:
            if name and url:
                op = box_sys.operator(f"wm.{PREFIX}_open_url", text=name, icon='URL')
                op.url = url
                
        box_sys.separator()
        box_sys.operator(f"wm.{PREFIX}_remove_addon", icon='CANCEL')

# ===================================
# 【登録・削除処理】
# ===================================
classes = [
    DICE_TOOLS_5_Properties,
    DICE_TOOLS_5_OT_reset_view,
    DICE_TOOLS_5_OT_update_info,
    DICE_TOOLS_5_OT_copy_info,
    DICE_TOOLS_5_OT_set_grid_scale,
    DICE_TOOLS_5_OT_reset_settings,
    DICE_TOOLS_5_OT_apply_preset,
    DICE_TOOLS_5_OT_store_preset,
    DICE_TOOLS_5_OT_export_code,
    DICE_TOOLS_5_OT_open_url,
    DICE_TOOLS_5_OT_remove_addon,
    DICE_TOOLS_5_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=DICE_TOOLS_5_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()

元型: