blender まとめ20260821

bl_info = {
    "name": "選択オブジェクトの裏表色設定ツール",
    "author": "AI Assistant",
    "version": (1, 8, 0),
    "blender": (5, 0, 0), # Blender 5.0 以降専用
    "location": "View3D > Sidebar",
    "description": "選択した各オブジェクトに、独自の裏表カラー(透明度・発光・Bloom調整対応)を設定します。",
    "category": "3D View",
}

# 2026年07月16日 修正版(EEVEE/Cycles切り替え追加、適用ボタン上部固定、リンク更新)

import bpy
import addon_utils 

# ===================================================================
# 【定義・初期値】
# ===================================================================
ADDON_TITLE = "裏表カラー&発光設定"
PREFIX_VAL = "DICE_TOOLS_5"
ADDON_CATEGORY = ADDON_TITLE

DEFAULT_COLOR_FRONT = (0.9, 0.9, 0.9, 1.0)
DEFAULT_COLOR_BACK  = (0.8, 0.1, 0.1, 1.0)

DEFAULT_EMISSION_COLOR_FRONT = (0.0, 0.5, 1.0, 1.0) 
DEFAULT_EMISSION_COLOR_BACK  = (1.0, 0.2, 0.0, 1.0) 

# ============
# 【リンク定義】 
# =============
LINK_NAME_1 = "発光エフェクト"
LINK_URL_1 = "<https://note.com/zionadmillion/n/na41cbf370120>"
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 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"
# ===============

# ===================================
# 【コンポジター非破壊構築用関数】
# ===================================
def setup_compositor_bloom(scene):
    """既存のノード構成を壊さずに、必要なGlareノードを挿入する"""
    scene.use_nodes = True
    tree = scene.node_tree
    if not tree:
        return
    
    rl_node = None
    comp_node = None
    glare_node = None
    
    # 既存の主要ノードを走査
    for node in tree.nodes:
        if node.type == 'R_LAYERS':
            rl_node = node
        elif node.type == 'COMPOSITE':
            comp_node = node
        elif node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
            glare_node = node
            
    # 存在しない場合のみ各ノードを新規作成
    if not rl_node:
        rl_node = tree.nodes.new('CompositorNodeRLayers')
        rl_node.location = (-300, 0)
    if not comp_node:
        comp_node = tree.nodes.new('CompositorNodeComposite')
        comp_node.location = (300, 0)
    if not glare_node:
        glare_node = tree.nodes.new('CompositorNodeGlare')
        glare_node.location = (0, 0)
        glare_node.glare_type = 'BLOOM'
        
    # Render Layers と Composite の直接リンクを削除して割り込ませる
    for link in list(tree.links):
        if link.from_node == rl_node and link.to_node == comp_node:
            tree.links.remove(link)
            
    # Render Layers -> Glare
    link_exists1 = False
    for link in tree.links:
        if link.from_node == rl_node and link.to_node == glare_node:
            link_exists1 = True
            break
    if not link_exists1 and rl_node.outputs and glare_node.inputs:
        tree.links.new(rl_node.outputs[0], glare_node.inputs[0])
        
    # Glare -> Composite
    link_exists2 = False
    for link in tree.links:
        if link.from_node == glare_node and link.to_node == comp_node:
            link_exists2 = True
            break
    if not link_exists2 and glare_node.outputs and comp_node.inputs:
        tree.links.new(glare_node.outputs[0], comp_node.inputs[0])

# ===================================
# 【コールバック関数】 各種スライダーやトグルの連動
# ===================================
def update_world_brightness(self, context):
    """ワールド背景ノードの明るさをリアルタイムに変更する"""
    scene = context.scene
    world = scene.world
    if not world:
        return
    
    # ノード使用を有効化
    if not world.use_nodes:
        world.use_nodes = True
        
    tree = world.node_tree
    if tree:
        for node in tree.nodes:
            if node.type == 'BACKGROUND':
                try:
                    node.inputs['Strength'].default_value = self.world_brightness
                except KeyError:
                    # 予備のインデックス指定
                    node.inputs[1].default_value = self.world_brightness

def update_bloom_settings(self, context):
    """Bloomの状態やスライダー値が変更されたときに連動するコールバック"""
    scene = context.scene
    try:
        if self.enable_bloom:
            # 有効化時にノードがなければ作成、あれば有効化
            scene.use_nodes = True
            tree = getattr(scene, 'node_tree', None)
            if tree:
                glare_node = None
                for node in tree.nodes:
                    if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                        glare_node = node
                        break
                
                if not glare_node:
                    setup_compositor_bloom(scene)
                    for node in tree.nodes:
                        if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                            glare_node = node
                            break
                
                if glare_node:
                    glare_node.mute = False
                    glare_node.mix = self.bloom_mix
                    glare_node.threshold = self.bloom_threshold

            # 背景の明るさも指定値に同期
            update_world_brightness(self, context)

            # レンダービュー・コンポジター表示設定に切り替え
            for area in context.screen.areas:
                if area.type == 'VIEW_3D':
                    for space in area.spaces:
                        if space.type == 'VIEW_3D':
                            space.shading.type = 'RENDERED' 
                            space.shading.use_compositor = 'ALWAYS'
        else:
            # 無効化時はGlareノードをMute(消音)にする
            tree = getattr(scene, 'node_tree', None)
            if tree:
                for node in tree.nodes:
                    if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                        node.mute = True
    except Exception as e:
        print(f"Bloom更新エラー: {e}")

# ===================================
# 【プロパティ定義】
# ===================================
class FrontBackColorProperties(bpy.types.PropertyGroup):
    color_front: bpy.props.FloatVectorProperty(
        name="表面の色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_COLOR_FRONT
    )
    color_back: bpy.props.FloatVectorProperty(
        name="裏面の色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_COLOR_BACK
    )
    
    use_emission: bpy.props.BoolProperty(
        name="発光(Emission)を有効にする",
        default=False
    )
    
    emission_type: bpy.props.EnumProperty(
        name="発光タイプ",
        items=[
            ('CUSTOM', "カスタムカラー (Custom)", "独自のカラーを発光色として使用します"),
            ('BASE_COLOR', "ベースカラー同期 (Sync Base)", "ベースカラーと同じ色で発光します"),
        ],
        default='CUSTOM'
    )
    
    emission_color_front: bpy.props.FloatVectorProperty(
        name="表面の発光色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_EMISSION_COLOR_FRONT
    )
    emission_strength_front: bpy.props.FloatProperty(
        name="表面の発光強度", min=0.0, max=1000.0, default=10.0 
    )
    emission_color_back: bpy.props.FloatVectorProperty(
        name="裏面の発光色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_EMISSION_COLOR_BACK
    )
    emission_strength_back: bpy.props.FloatProperty(
        name="裏面の発光強度", min=0.0, max=1000.0, default=10.0 
    )

    enable_bloom: bpy.props.BoolProperty(
        name="Bloom を有効にする",
        description="コンポジターを使用してBloom効果のON/OFF状態を切り替えます",
        default=False,
        update=update_bloom_settings
    )

    bloom_threshold: bpy.props.FloatProperty(
        name="発光しきい値", 
        description="この数値より明るい部分だけが周囲に広がります(0で全て広がる)",
        min=0.0, max=10.0, default=0.0,
        update=update_bloom_settings
    )
    bloom_mix: bpy.props.FloatProperty(
        name="広がりの強さ(Mix)", 
        description="-1.0で発光なし、0.0で通常、1.0で光の広がりだけが表示されます",
        min=-1.0, max=1.0, default=0.0,
        update=update_bloom_settings
    )

    world_brightness: bpy.props.FloatProperty(
        name="背景の明るさ",
        description="レンダーモードプレビュー時の背景(ワールド輝度)の明るさを調整します(通常は暗い方が発光が目立ちます)",
        min=0.0, max=10.0, default=0.1,
        update=update_world_brightness
    )

# ===================================
# 【オペレーター定義】
# ===================================
class OT_apply_front_back_color(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
    bl_label = "裏表の色と発光を適用する"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.front_back_color_props
        
        selected_objs = [obj for obj in context.selected_objects if obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}]
        
        if not selected_objs:
            self.report({'WARNING'}, "オブジェクトを選択してから実行してください")
            return {'CANCELLED'}
        
        for obj in selected_objs:
            mat_name = f"{PREFIX_VAL}_{obj.name}_Mat"
            mat = bpy.data.materials.get(mat_name)
            if not mat:
                mat = bpy.data.materials.new(mat_name)
                mat.use_nodes = True
            
            mat.diffuse_color = props.color_front
            
            try:
                mat.surface_render_method = 'BLENDED' 
                mat.use_transparency_overlap = True   
            except AttributeError:
                pass
            
            nodes = mat.node_tree.nodes
            links = mat.node_tree.links
            nodes.clear()
            
            n_out = nodes.new('ShaderNodeOutputMaterial')
            n_out.location = (300, 0)
            
            n_mix = nodes.new('ShaderNodeMixShader')
            n_mix.location = (100, 0)
            
            n_front = nodes.new('ShaderNodeBsdfPrincipled')
            n_front.location = (-250, 200)
            n_front.inputs['Base Color'].default_value = props.color_front
            n_front.inputs['Alpha'].default_value = props.color_front[3] 
            
            if props.use_emission:
                n_front.inputs['Emission Strength'].default_value = props.emission_strength_front
                if props.emission_type == 'CUSTOM':
                    n_front.inputs['Emission Color'].default_value = props.emission_color_front
                else:
                    n_front.inputs['Emission Color'].default_value = props.color_front
            else:
                n_front.inputs['Emission Strength'].default_value = 0.0

            n_back = nodes.new('ShaderNodeBsdfPrincipled')
            n_back.location = (-250, -250)
            n_back.inputs['Base Color'].default_value = props.color_back
            n_back.inputs['Alpha'].default_value = props.color_back[3]   
            
            if props.use_emission:
                n_back.inputs['Emission Strength'].default_value = props.emission_strength_back
                if props.emission_type == 'CUSTOM':
                    n_back.inputs['Emission Color'].default_value = props.emission_color_back
                else:
                    n_back.inputs['Emission Color'].default_value = props.color_back
            else:
                n_back.inputs['Emission Strength'].default_value = 0.0
            
            n_geo = nodes.new('ShaderNodeNewGeometry')
            n_geo.location = (-250, 500)
            
            links.new(n_geo.outputs['Backfacing'], n_mix.inputs[0]) 
            links.new(n_front.outputs[0], n_mix.inputs[1])          
            links.new(n_back.outputs[0], n_mix.inputs[2])           
            links.new(n_mix.outputs[0], n_out.inputs['Surface'])
            
            if not obj.material_slots:
                obj.data.materials.append(mat)
            else:
                for slot in obj.material_slots:
                    slot.material = mat
                
        self.report({'INFO'}, f"{len(selected_objs)}個のオブジェクトに適用しました")
        return {'FINISHED'}

class OT_setup_scene_bloom(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.setup_bloom"
    bl_label = "Bloomを有効化"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.front_back_color_props
        props.enable_bloom = True
        self.report({'INFO'}, "Bloomを有効にしました")
        return {'FINISHED'}

class OT_switch_render_engine(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.switch_render_engine"
    bl_label = "レンダーエンジン切り替え"
    bl_options = {'REGISTER', 'UNDO'}
    
    engine: bpy.props.StringProperty()
    
    def execute(self, context):
        try:
            context.scene.render.engine = self.engine
        except TypeError:
            # 古いバージョンのBlender用フォールバック
            if self.engine == 'BLENDER_EEVEE_NEXT':
                context.scene.render.engine = 'BLENDER_EEVEE'
        
        self.report({'INFO'}, f"レンダーエンジンを切り替えました")
        return {'FINISHED'}

class OT_open_link(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.open_link"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()

    def execute(self, context):
        bpy.ops.wm.url_open(url=self.url)
        return {'FINISHED'}

class OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.remove_addon"
    bl_label = "アドオン削除(無効化)実行"

    def execute(self, context):
        addon_name = __package__ if __package__ else __name__
        if addon_name == "__main__":
            bpy.app.timers.register(unregister, first_interval=0.1)
            self.report({'INFO'}, f"{ADDON_TITLE} を無効化しました")
            return {'FINISHED'}
            
        try:
            addon_utils.disable(addon_name, default_set=True)
            self.report({'INFO'}, f"{ADDON_TITLE} を無効化しました")
        except Exception as e:
            self.report({'ERROR'}, f"無効化エラー: {e}")
            return {'CANCELLED'}
        return {'FINISHED'}

# ===================================
# 【UIパネル】
# ===================================
class PT_MainPanel(bpy.types.Panel):
    bl_idname = f"{PREFIX_VAL}_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 = context.scene.front_back_color_props
        
        # --- レンダーエンジン切り替えボタン ---
        row = layout.row(align=True)
        is_eevee = context.scene.render.engine in {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
        is_cycles = context.scene.render.engine == 'CYCLES'
        
        op_eevee = row.operator(OT_switch_render_engine.bl_idname, text="EEVEE", depress=is_eevee)
        op_eevee.engine = 'BLENDER_EEVEE_NEXT'
        
        op_cycles = row.operator(OT_switch_render_engine.bl_idname, text="Cycles", depress=is_cycles)
        op_cycles.engine = 'CYCLES'
        
        layout.separator()
        
        # --- 適用ボタン(スクロールアウト防止のため常に一番上に配置) ---
        layout.operator(OT_apply_front_back_color.bl_idname, icon='SHADING_RENDERED', text="選択オブジェクトに適用")
        
        layout.separator()
        
        # --- ベースカラー設定 ---
        box = layout.box()
        box.label(text="【 ベースカラー(透明度対応)】", icon='COLOR')
        col = box.column(align=True)
        col.prop(props, "color_front")
        col.prop(props, "color_back")
        
        # --- 発光・Bloom設定 ---
        box2 = layout.box()
        box2.prop(props, "use_emission", toggle=True, icon='LIGHT')
        
        if props.use_emission:
            col2 = box2.column(align=True)
            col2.prop(props, "emission_type", text="")
            col2.separator()
            
            if props.emission_type == 'CUSTOM':
                col2.prop(props, "emission_color_front")
            col2.prop(props, "emission_strength_front")
            
            col2.separator()
            
            if props.emission_type == 'CUSTOM':
                col2.prop(props, "emission_color_back")
            col2.prop(props, "emission_strength_back")
            
            layout.separator()
            box_bloom = layout.box()
            box_bloom.label(text="【 Bloom(光の広がり) 設定 】", icon='SOLO_ON')
            
            box_bloom.prop(props, "enable_bloom", toggle=True, icon='NODE_COMPOSITING')
            
            if props.enable_bloom:
                box_bloom.separator()
                col_b = box_bloom.column(align=True)
                col_b.prop(props, "bloom_mix", slider=True)
                col_b.prop(props, "bloom_threshold", slider=True)
                col_b.prop(props, "world_brightness", slider=True)

class PT_LinkPanel(bpy.types.Panel):
    bl_label = "リンクパネル"
    bl_idname = f"{PREFIX_VAL}_PT_link_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_parent_id = f"{PREFIX_VAL}_PT_main_panel"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_1, icon='URL')
        op.url = LINK_URL_1
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_2, icon='URL')
        op.url = LINK_URL_2
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_3, icon='URL')
        op.url = LINK_URL_3
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_4, icon='URL')
        op.url = LINK_URL_4
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_5, icon='URL')
        op.url = LINK_URL_5

class PT_RemovePanel(bpy.types.Panel):
    bl_label = "アドオン削除"
    bl_idname = f"{PREFIX_VAL}_PT_remove_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_parent_id = f"{PREFIX_VAL}_PT_main_panel"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        layout.label(text="※このアドオンを無効化します", icon='ERROR')
        layout.operator(OT_remove_addon.bl_idname, icon='TRASH')

# ===================================
# 【登録・解除】
# ===================================
classes = [
    FrontBackColorProperties,
    OT_apply_front_back_color,
    OT_setup_scene_bloom,
    OT_switch_render_engine,
    OT_open_link,
    OT_remove_addon,
    PT_MainPanel,
    PT_LinkPanel,
    PT_RemovePanel,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.Scene.front_back_color_props = bpy.props.PointerProperty(type=FrontBackColorProperties)

def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    if hasattr(bpy.types.Scene, "front_back_color_props"):
        del bpy.types.Scene.front_back_color_props

if __name__ == "__main__":
    register()
bl_info = {
    "name": "選択オブジェクトの裏表色設定ツール",
    "author": "AI Assistant",
    "version": (1, 8, 0),
    "blender": (5, 0, 0), # Blender 5.0 以降専用
    "location": "View3D > Sidebar",
    "description": "選択した各オブジェクトに、独自の裏表カラー(透明度・発光・Bloom調整対応)を設定します。",
    "category": "3D View",
}

# 2026年07月16日 修正版(EEVEE/Cycles切り替え追加、適用ボタン上部固定、リンク更新)

import bpy
import addon_utils 

# ===================================================================
# 【定義・初期値】
# ===================================================================
ADDON_TITLE = "裏表カラー&発光設定"
PREFIX_VAL = "DICE_TOOLS_5"
ADDON_CATEGORY = ADDON_TITLE

DEFAULT_COLOR_FRONT = (0.9, 0.9, 0.9, 1.0)
DEFAULT_COLOR_BACK  = (0.8, 0.1, 0.1, 1.0)

DEFAULT_EMISSION_COLOR_FRONT = (0.0, 0.5, 1.0, 1.0) 
DEFAULT_EMISSION_COLOR_BACK  = (1.0, 0.2, 0.0, 1.0) 

# ============
# 【リンク定義】 
# =============
LINK_NAME_1 = "発光エフェクト"
LINK_URL_1 = "<https://note.com/zionadmillion/n/na41cbf370120>"
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 = "骨格立方体"
LINK_URL_5 = "<https://app.notion.com/p/39cf5dacaf4380b7bda4cb5977f04606>"
# ===============

# ===================================
# 【コンポジター非破壊構築用関数】
# ===================================
def setup_compositor_bloom(scene):
    """既存のノード構成を壊さずに、必要なGlareノードを挿入する"""
    scene.use_nodes = True
    tree = scene.node_tree
    if not tree:
        return
    
    rl_node = None
    comp_node = None
    glare_node = None
    
    # 既存の主要ノードを走査
    for node in tree.nodes:
        if node.type == 'R_LAYERS':
            rl_node = node
        elif node.type == 'COMPOSITE':
            comp_node = node
        elif node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
            glare_node = node
            
    # 存在しない場合のみ各ノードを新規作成
    if not rl_node:
        rl_node = tree.nodes.new('CompositorNodeRLayers')
        rl_node.location = (-300, 0)
    if not comp_node:
        comp_node = tree.nodes.new('CompositorNodeComposite')
        comp_node.location = (300, 0)
    if not glare_node:
        glare_node = tree.nodes.new('CompositorNodeGlare')
        glare_node.location = (0, 0)
        glare_node.glare_type = 'BLOOM'
        
    # Render Layers と Composite の直接リンクを削除して割り込ませる
    for link in list(tree.links):
        if link.from_node == rl_node and link.to_node == comp_node:
            tree.links.remove(link)
            
    # Render Layers -> Glare
    link_exists1 = False
    for link in tree.links:
        if link.from_node == rl_node and link.to_node == glare_node:
            link_exists1 = True
            break
    if not link_exists1 and rl_node.outputs and glare_node.inputs:
        tree.links.new(rl_node.outputs[0], glare_node.inputs[0])
        
    # Glare -> Composite
    link_exists2 = False
    for link in tree.links:
        if link.from_node == glare_node and link.to_node == comp_node:
            link_exists2 = True
            break
    if not link_exists2 and glare_node.outputs and comp_node.inputs:
        tree.links.new(glare_node.outputs[0], comp_node.inputs[0])

# ===================================
# 【コールバック関数】 各種スライダーやトグルの連動
# ===================================
def update_world_brightness(self, context):
    """ワールド背景ノードの明るさをリアルタイムに変更する"""
    scene = context.scene
    world = scene.world
    if not world:
        return
    
    # ノード使用を有効化
    if not world.use_nodes:
        world.use_nodes = True
        
    tree = world.node_tree
    if tree:
        for node in tree.nodes:
            if node.type == 'BACKGROUND':
                try:
                    node.inputs['Strength'].default_value = self.world_brightness
                except KeyError:
                    # 予備のインデックス指定
                    node.inputs[1].default_value = self.world_brightness

def update_bloom_settings(self, context):
    """Bloomの状態やスライダー値が変更されたときに連動するコールバック"""
    scene = context.scene
    try:
        if self.enable_bloom:
            # 有効化時にノードがなければ作成、あれば有効化
            scene.use_nodes = True
            tree = getattr(scene, 'node_tree', None)
            if tree:
                glare_node = None
                for node in tree.nodes:
                    if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                        glare_node = node
                        break
                
                if not glare_node:
                    setup_compositor_bloom(scene)
                    for node in tree.nodes:
                        if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                            glare_node = node
                            break
                
                if glare_node:
                    glare_node.mute = False
                    glare_node.mix = self.bloom_mix
                    glare_node.threshold = self.bloom_threshold

            # 背景の明るさも指定値に同期
            update_world_brightness(self, context)

            # レンダービュー・コンポジター表示設定に切り替え
            for area in context.screen.areas:
                if area.type == 'VIEW_3D':
                    for space in area.spaces:
                        if space.type == 'VIEW_3D':
                            space.shading.type = 'RENDERED' 
                            space.shading.use_compositor = 'ALWAYS'
        else:
            # 無効化時はGlareノードをMute(消音)にする
            tree = getattr(scene, 'node_tree', None)
            if tree:
                for node in tree.nodes:
                    if node.type == 'GLARE' and getattr(node, 'glare_type', '') == 'BLOOM':
                        node.mute = True
    except Exception as e:
        print(f"Bloom更新エラー: {e}")

# ===================================
# 【プロパティ定義】
# ===================================
class FrontBackColorProperties(bpy.types.PropertyGroup):
    color_front: bpy.props.FloatVectorProperty(
        name="表面の色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_COLOR_FRONT
    )
    color_back: bpy.props.FloatVectorProperty(
        name="裏面の色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_COLOR_BACK
    )
    
    use_emission: bpy.props.BoolProperty(
        name="発光(Emission)を有効にする",
        default=False
    )
    
    emission_type: bpy.props.EnumProperty(
        name="発光タイプ",
        items=[
            ('CUSTOM', "カスタムカラー (Custom)", "独自のカラーを発光色として使用します"),
            ('BASE_COLOR', "ベースカラー同期 (Sync Base)", "ベースカラーと同じ色で発光します"),
        ],
        default='CUSTOM'
    )
    
    emission_color_front: bpy.props.FloatVectorProperty(
        name="表面の発光色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_EMISSION_COLOR_FRONT
    )
    emission_strength_front: bpy.props.FloatProperty(
        name="表面の発光強度", min=0.0, max=1000.0, default=10.0 
    )
    emission_color_back: bpy.props.FloatVectorProperty(
        name="裏面の発光色", subtype='COLOR', size=4, min=0, max=1, default=DEFAULT_EMISSION_COLOR_BACK
    )
    emission_strength_back: bpy.props.FloatProperty(
        name="裏面の発光強度", min=0.0, max=1000.0, default=10.0 
    )

    enable_bloom: bpy.props.BoolProperty(
        name="Bloom を有効にする",
        description="コンポジターを使用してBloom効果のON/OFF状態を切り替えます",
        default=False,
        update=update_bloom_settings
    )

    bloom_threshold: bpy.props.FloatProperty(
        name="発光しきい値", 
        description="この数値より明るい部分だけが周囲に広がります(0で全て広がる)",
        min=0.0, max=10.0, default=0.0,
        update=update_bloom_settings
    )
    bloom_mix: bpy.props.FloatProperty(
        name="広がりの強さ(Mix)", 
        description="-1.0で発光なし、0.0で通常、1.0で光の広がりだけが表示されます",
        min=-1.0, max=1.0, default=0.0,
        update=update_bloom_settings
    )

    world_brightness: bpy.props.FloatProperty(
        name="背景の明るさ",
        description="レンダーモードプレビュー時の背景(ワールド輝度)の明るさを調整します(通常は暗い方が発光が目立ちます)",
        min=0.0, max=10.0, default=0.1,
        update=update_world_brightness
    )

# ===================================
# 【オペレーター定義】
# ===================================
class OT_apply_front_back_color(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
    bl_label = "裏表の色と発光を適用する"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.front_back_color_props
        
        selected_objs = [obj for obj in context.selected_objects if obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}]
        
        if not selected_objs:
            self.report({'WARNING'}, "オブジェクトを選択してから実行してください")
            return {'CANCELLED'}
        
        for obj in selected_objs:
            mat_name = f"{PREFIX_VAL}_{obj.name}_Mat"
            mat = bpy.data.materials.get(mat_name)
            if not mat:
                mat = bpy.data.materials.new(mat_name)
                mat.use_nodes = True
            
            mat.diffuse_color = props.color_front
            
            try:
                mat.surface_render_method = 'BLENDED' 
                mat.use_transparency_overlap = True   
            except AttributeError:
                pass
            
            nodes = mat.node_tree.nodes
            links = mat.node_tree.links
            nodes.clear()
            
            n_out = nodes.new('ShaderNodeOutputMaterial')
            n_out.location = (300, 0)
            
            n_mix = nodes.new('ShaderNodeMixShader')
            n_mix.location = (100, 0)
            
            n_front = nodes.new('ShaderNodeBsdfPrincipled')
            n_front.location = (-250, 200)
            n_front.inputs['Base Color'].default_value = props.color_front
            n_front.inputs['Alpha'].default_value = props.color_front[3] 
            
            if props.use_emission:
                n_front.inputs['Emission Strength'].default_value = props.emission_strength_front
                if props.emission_type == 'CUSTOM':
                    n_front.inputs['Emission Color'].default_value = props.emission_color_front
                else:
                    n_front.inputs['Emission Color'].default_value = props.color_front
            else:
                n_front.inputs['Emission Strength'].default_value = 0.0

            n_back = nodes.new('ShaderNodeBsdfPrincipled')
            n_back.location = (-250, -250)
            n_back.inputs['Base Color'].default_value = props.color_back
            n_back.inputs['Alpha'].default_value = props.color_back[3]   
            
            if props.use_emission:
                n_back.inputs['Emission Strength'].default_value = props.emission_strength_back
                if props.emission_type == 'CUSTOM':
                    n_back.inputs['Emission Color'].default_value = props.emission_color_back
                else:
                    n_back.inputs['Emission Color'].default_value = props.color_back
            else:
                n_back.inputs['Emission Strength'].default_value = 0.0
            
            n_geo = nodes.new('ShaderNodeNewGeometry')
            n_geo.location = (-250, 500)
            
            links.new(n_geo.outputs['Backfacing'], n_mix.inputs[0]) 
            links.new(n_front.outputs[0], n_mix.inputs[1])          
            links.new(n_back.outputs[0], n_mix.inputs[2])           
            links.new(n_mix.outputs[0], n_out.inputs['Surface'])
            
            if not obj.material_slots:
                obj.data.materials.append(mat)
            else:
                for slot in obj.material_slots:
                    slot.material = mat
                
        self.report({'INFO'}, f"{len(selected_objs)}個のオブジェクトに適用しました")
        return {'FINISHED'}

class OT_setup_scene_bloom(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.setup_bloom"
    bl_label = "Bloomを有効化"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.front_back_color_props
        props.enable_bloom = True
        self.report({'INFO'}, "Bloomを有効にしました")
        return {'FINISHED'}

class OT_switch_render_engine(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.switch_render_engine"
    bl_label = "レンダーエンジン切り替え"
    bl_options = {'REGISTER', 'UNDO'}
    
    engine: bpy.props.StringProperty()
    
    def execute(self, context):
        try:
            context.scene.render.engine = self.engine
        except TypeError:
            # 古いバージョンのBlender用フォールバック
            if self.engine == 'BLENDER_EEVEE_NEXT':
                context.scene.render.engine = 'BLENDER_EEVEE'
        
        self.report({'INFO'}, f"レンダーエンジンを切り替えました")
        return {'FINISHED'}

class OT_open_link(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.open_link"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()

    def execute(self, context):
        bpy.ops.wm.url_open(url=self.url)
        return {'FINISHED'}

class OT_remove_addon(bpy.types.Operator):
    bl_idname = f"{PREFIX_VAL.lower()}.remove_addon"
    bl_label = "アドオン削除(無効化)実行"

    def execute(self, context):
        addon_name = __package__ if __package__ else __name__
        if addon_name == "__main__":
            bpy.app.timers.register(unregister, first_interval=0.1)
            self.report({'INFO'}, f"{ADDON_TITLE} を無効化しました")
            return {'FINISHED'}
            
        try:
            addon_utils.disable(addon_name, default_set=True)
            self.report({'INFO'}, f"{ADDON_TITLE} を無効化しました")
        except Exception as e:
            self.report({'ERROR'}, f"無効化エラー: {e}")
            return {'CANCELLED'}
        return {'FINISHED'}

# ===================================
# 【UIパネル】
# ===================================
class PT_MainPanel(bpy.types.Panel):
    bl_idname = f"{PREFIX_VAL}_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 = context.scene.front_back_color_props
        
        # --- レンダーエンジン切り替えボタン ---
        row = layout.row(align=True)
        is_eevee = context.scene.render.engine in {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
        is_cycles = context.scene.render.engine == 'CYCLES'
        
        op_eevee = row.operator(OT_switch_render_engine.bl_idname, text="EEVEE", depress=is_eevee)
        op_eevee.engine = 'BLENDER_EEVEE_NEXT'
        
        op_cycles = row.operator(OT_switch_render_engine.bl_idname, text="Cycles", depress=is_cycles)
        op_cycles.engine = 'CYCLES'
        
        layout.separator()
        
        # --- 適用ボタン(スクロールアウト防止のため常に一番上に配置) ---
        layout.operator(OT_apply_front_back_color.bl_idname, icon='SHADING_RENDERED', text="選択オブジェクトに適用")
        
        layout.separator()
        
        # --- ベースカラー設定 ---
        box = layout.box()
        box.label(text="【 ベースカラー(透明度対応)】", icon='COLOR')
        col = box.column(align=True)
        col.prop(props, "color_front")
        col.prop(props, "color_back")
        
        # --- 発光・Bloom設定 ---
        box2 = layout.box()
        box2.prop(props, "use_emission", toggle=True, icon='LIGHT')
        
        if props.use_emission:
            col2 = box2.column(align=True)
            col2.prop(props, "emission_type", text="")
            col2.separator()
            
            if props.emission_type == 'CUSTOM':
                col2.prop(props, "emission_color_front")
            col2.prop(props, "emission_strength_front")
            
            col2.separator()
            
            if props.emission_type == 'CUSTOM':
                col2.prop(props, "emission_color_back")
            col2.prop(props, "emission_strength_back")
            
            layout.separator()
            box_bloom = layout.box()
            box_bloom.label(text="【 Bloom(光の広がり) 設定 】", icon='SOLO_ON')
            
            box_bloom.prop(props, "enable_bloom", toggle=True, icon='NODE_COMPOSITING')
            
            if props.enable_bloom:
                box_bloom.separator()
                col_b = box_bloom.column(align=True)
                col_b.prop(props, "bloom_mix", slider=True)
                col_b.prop(props, "bloom_threshold", slider=True)
                col_b.prop(props, "world_brightness", slider=True)

class PT_LinkPanel(bpy.types.Panel):
    bl_label = "リンクパネル"
    bl_idname = f"{PREFIX_VAL}_PT_link_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_parent_id = f"{PREFIX_VAL}_PT_main_panel"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_1, icon='URL')
        op.url = LINK_URL_1
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_2, icon='URL')
        op.url = LINK_URL_2
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_3, icon='URL')
        op.url = LINK_URL_3
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_4, icon='URL')
        op.url = LINK_URL_4
        op = layout.operator(OT_open_link.bl_idname, text=LINK_NAME_5, icon='URL')
        op.url = LINK_URL_5

class PT_RemovePanel(bpy.types.Panel):
    bl_label = "アドオン削除"
    bl_idname = f"{PREFIX_VAL}_PT_remove_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY
    bl_parent_id = f"{PREFIX_VAL}_PT_main_panel"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        layout = self.layout
        layout.label(text="※このアドオンを無効化します", icon='ERROR')
        layout.operator(OT_remove_addon.bl_idname, icon='TRASH')

# ===================================
# 【登録・解除】
# ===================================
classes = [
    FrontBackColorProperties,
    OT_apply_front_back_color,
    OT_setup_scene_bloom,
    OT_switch_render_engine,
    OT_open_link,
    OT_remove_addon,
    PT_MainPanel,
    PT_LinkPanel,
    PT_RemovePanel,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.Scene.front_back_color_props = bpy.props.PointerProperty(type=FrontBackColorProperties)

def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    if hasattr(bpy.types.Scene, "front_back_color_props"):
        del bpy.types.Scene.front_back_color_props

if __name__ == "__main__":
    register()