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()