額縁枠 窓面 床面 20260711

設定値 コピーなし元型 3窓面 20260711
bl_info = {
"name": "Frame Creator Pro",
"author": "Your Name",
"version": (3, 4),
"blender": (5, 0, 0),
"location": "View3D > Sidebar (Nパネル)",
"description": "窓枠(Y=0)・床面(Z=0)に特化した正確な半透明正方形枠の自動生成アドオン",
"category": "3D View",
}
# 【作成日】 2026年7月11日 12:44 (更新版)
# ===================================
# 【初期設定・定数定義】 (1行ずつ定義)
# ===================================
PREFIX = "frame_tools"
TAB_NAME = " frame "
# コレクション名・オブジェクト名
INIT_COLL_MAIN = "枠額縁"
INIT_OBJ_FLOOR = "床面枠"
INIT_OBJ_WINDOW = "窓平面枠"
# 枠額縁のパラメータ初期値 (窓の4つの角を正確に±30にするため、1辺の長さは60)
INIT_FRAME_SIZE = 60.0
INIT_FRAME_THICKNESS = 2.0 # 初期設定の太さを 2.0 に変更
INIT_COLOR_FLOOR = (0.2, 0.6, 1.0, 0.5) # 床面用のRGBA (透明度 0.5)
INIT_COLOR_WINDOW = (0.2, 0.9, 0.6, 0.5) # 窓枠用のRGBA (透明度 0.5)
import bpy
import math
import mathutils
import webbrowser
# ===================================
# 【枠額縁 作成用ヘルパー関数】
# ===================================
def create_frame(context, frame_type):
props = getattr(context.scene, f"{PREFIX}_props")
size = props.frame_size
thickness = props.frame_thickness
# メインコレクションの取得または新規作成
if INIT_COLL_MAIN in bpy.data.collections:
main_col = bpy.data.collections[INIT_COLL_MAIN]
else:
main_col = bpy.data.collections.new(INIT_COLL_MAIN)
context.scene.collection.children.link(main_col)
# 辺の長さを表す文字列のフォーマット (例: 60.0 -> "60", 60.5 -> "60.5")
size_str = str(int(size)) if size.is_integer() else f"{size:.1f}"
if frame_type == 'FLOOR':
base_name = INIT_OBJ_FLOOR
color = props.floor_color
# Z=0 (XY平面)。角を軸に合わせるため、あらかじめZ軸周りに45度回転させる
rot = (0.0, 0.0, math.radians(45))
elif frame_type == 'WINDOW':
base_name = INIT_OBJ_WINDOW
color = props.window_color
# Y=0 (ZX平面)。4つの角を(±30, 0, ±30)にするため、X軸90度、Z軸45度回転
rot = (math.radians(90), 0.0, math.radians(45))
else:
return
# 【オブジェクト名に辺の長さを追加】
name = f"{base_name}_{size_str}"
mat_name = f"{name}_半透明マテリアル"
# 作成ごとに1つ1つ完全に独立した新規マテリアルを生成
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
# パネルの色・透明度情報をノードに適用
if mat.use_nodes:
nodes = mat.node_tree.nodes
principled = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
if principled:
principled.inputs['Base Color'].default_value = (color[0], color[1], color[2], 1.0)
if 'Alpha' in principled.inputs:
principled.inputs['Alpha'].default_value = color[3]
# Blender 5.0 (EEVEE-Next) 対応:エラーを出さずに半透明を描画
if hasattr(mat, "surface_render_method"):
mat.surface_render_method = 'BLENDED'
else:
mat.blend_method = 'BLEND'
# パイプサイズと太さの決定
# 角を(±30)の位置に一致させるために、大半径 = (1辺の長さ / 2) * √2 とします。
major_r = (size / 2.0) * math.sqrt(2)
minor_r = thickness / 2.0
# 既存オブジェクトは削除せず維持(Blenderによって自動で「床面枠_60.001」のようにリネームされます)
# トーラスを一度回転なしで生成
bpy.ops.mesh.primitive_torus_add(
align='WORLD',
location=(0.0, 0.0, 0.0),
rotation=(0.0, 0.0, 0.0),
major_segments=4,
minor_segments=16,
major_radius=major_r,
minor_radius=minor_r
)
obj = context.active_object
obj.name = name
# 【行列(Matrix)による厳密配置】
# 正方形を維持するためのZ軸45度回転
rot_z = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
if frame_type == 'FLOOR':
# XY平面 (Z=0)
obj.matrix_world = rot_z
elif frame_type == 'WINDOW':
# ZX平面 (Y=0) : 床面枠(rot_z)をグローバルX軸に90度回転させた状態
rot_x = mathutils.Matrix.Rotation(math.radians(90), 4, 'X')
obj.matrix_world = rot_x @ rot_z # 4つの角が厳密に Y=0平面の(±30, 0, ±30)に一致します
# マテリアル割り当て
obj.data.materials.append(mat)
# コレクション整理 (メインコレクション「枠額縁」の直下に格納)
if main_col.name not in [c.name for c in obj.users_collection]:
main_col.objects.link(obj)
for c in list(obj.users_collection):
if c != main_col:
c.objects.unlink(obj)
context.view_layer.update()
# ===================================
# 【オペレーター】
# ===================================
class VGM_OT_create_frame_floor(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_frame_floor"
bl_label = "床面枠を作成"
def execute(self, context):
create_frame(context, 'FLOOR')
self.report({'INFO'}, f"床面枠 を新規追加しました")
return {'FINISHED'}
class VGM_OT_create_frame_window(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_frame_window"
bl_label = "窓平面枠を作成"
def execute(self, context):
create_frame(context, 'WINDOW')
self.report({'INFO'}, f"窓平面枠 を新規追加しました")
return {'FINISHED'}
class VGM_OT_reset_frame_props(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_reset_frame_props"
bl_label = "作成設定を初期値に戻す"
def execute(self, context):
props = getattr(context.scene, f"{PREFIX}_props")
props.frame_size = INIT_FRAME_SIZE
props.frame_thickness = INIT_FRAME_THICKNESS
props.floor_color = INIT_COLOR_FLOOR
props.window_color = INIT_COLOR_WINDOW
self.report({'INFO'}, "設定をコードの初期値に戻しました")
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):
frame_size: bpy.props.FloatProperty(
name="1辺の長さ",
default=INIT_FRAME_SIZE,
min=0.01,
description="正方形の1辺の長さ (4角を±30にする場合は「60」)"
)
frame_thickness: bpy.props.FloatProperty(
name="太さ",
default=INIT_FRAME_THICKNESS,
min=0.01,
description="パイプの太さ"
)
floor_color: bpy.props.FloatVectorProperty(
name="床面枠の色",
subtype='COLOR',
default=INIT_COLOR_FLOOR,
size=4,
min=0.0, max=1.0,
description="床面枠のRGBAカラー"
)
window_color: bpy.props.FloatVectorProperty(
name="窓平面枠の色",
subtype='COLOR',
default=INIT_COLOR_WINDOW,
size=4,
min=0.0, max=1.0,
description="窓平面枠のRGBAカラー"
)
# ===================================
# 【UIパネル: 枠額縁作成設定パネル】
# ===================================
class VGM_PT_frame_panel(bpy.types.Panel):
bl_idname = f"{PREFIX.upper()}_PT_frame_panel"
bl_label = "枠額縁 作成設定"
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")
# パラメータ設定
box = layout.box()
box.label(text="寸法・個別色設定", icon='MODIFIER')
box.prop(props, "frame_size")
box.prop(props, "frame_thickness")
box.separator()
box.prop(props, "floor_color", text="床面枠の色 (Z=0平面)")
box.prop(props, "window_color", text="窓平面枠の色 (Y=0平面)")
box.operator(f"view3d.{PREFIX}_reset_frame_props", text="パラメータを初期値に戻す", icon='LOOP_BACK')
layout.separator()
# 生成ボタン
col_buttons = layout.column(align=True)
col_buttons.scale_y = 1.3
col_buttons.operator(f"view3d.{PREFIX}_create_frame_floor", text="床面枠を作成 (Z=0平面)", icon='MESH_PLANE')
col_buttons.operator(f"view3d.{PREFIX}_create_frame_window", text="窓平面枠を作成 (Y=0平面)", icon='GRID')
layout.separator(factor=1.5)
# アドオン登録解除 (切り離し)
layout.operator(f"wm.{PREFIX}_remove_addon", text="アドオンの登録を解除", icon='CANCEL')
# ===================================
# 【登録・削除処理】
# ===================================
classes = [
VGM_Properties,
VGM_OT_create_frame_floor,
VGM_OT_create_frame_window,
VGM_OT_reset_frame_props,
VGM_OT_remove_addon,
VGM_PT_frame_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()