bl_info = {
"name": "Frame & Axis Arrow Creator",
"author": "Your Name",
"version": (3, 5),
"blender": (5, 0, 0),
"location": "View3D > Sidebar (Nパネル)",
"description": "3枚の直交半透明額縁(XY, ZX, YZ)と3軸矢印(円柱+円錐)の自動生成アドオン",
"category": "3D View",
}
# 【作成日】 2026年7月11日 12:44 (更新版)
# ===================================
# 【初期設定・定数定義】 (1行ずつ定義)
# ===================================
PREFIX = "windows_tools"
TAB_NAME = " windows 3axea "
# コレクション名・オブジェクト名初期値
INIT_COLL_MAIN = "枠額縁"
INIT_OBJ_FLOOR = "床面枠"
INIT_OBJ_WINDOW = "窓平面枠"
INIT_OBJ_X_PERS = "x軸遠近法 額縁"
# 額縁のパラメータ初期値 (4角を±30にするためサイズは60)
INIT_FRAME_SIZE = 60.0
INIT_FRAME_THICKNESS = 2.0 # 太さ初期値 2.0
INIT_COLOR_FLOOR = (0.0, 0.4, 1.0, 0.5) # 床面枠: Blue (透明度0.5)
INIT_COLOR_WINDOW = (0.0, 0.9, 0.2, 0.5) # 窓平面枠: Green (透明度0.5)
INIT_COLOR_X_PERS = (1.0, 0.1, 0.1, 0.5) # x軸遠近法 額縁: Red (透明度0.5)
# 矢印パラメータ初期値
INIT_ARROW_LENGTH = 30.0
INIT_ARROW_THICKNESS = 1.0
INIT_CONE_RADIUS = 2.0
INIT_CONE_HEIGHT = 4.0
INIT_ARROW_CENTER = True # 矢印中心を000にする設定 (デフォルト: True)
# 矢印カラー初期値 (CGのデファクトスタンダード基準)
INIT_ARROW_COLOR_X = (1.0, 0.1, 0.1, 0.5) # X軸: Red (透明度0.5)
INIT_ARROW_COLOR_Y = (0.1, 0.9, 0.1, 0.5) # Y軸: Green (透明度0.5)
INIT_ARROW_COLOR_Z = (0.1, 0.4, 1.0, 0.5) # Z軸: Blue (透明度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)
# サイズ値を文字列に整形
size_str = str(int(size)) if size.is_integer() else f"{size:.1f}"
if frame_type == 'FLOOR':
base_name = INIT_OBJ_FLOOR
mat_name = f"{INIT_OBJ_FLOOR}_{size_str}_半透明マテリアル"
color = props.floor_color
# Z=0 (XY平面)
rot_matrix = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
elif frame_type == 'WINDOW':
base_name = INIT_OBJ_WINDOW
mat_name = f"{INIT_OBJ_WINDOW}_{size_str}_半透明マテリアル"
color = props.window_color
# Y=0 (ZX平面) : 床面枠をX軸に90度回転
rot_z = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
rot_x = mathutils.Matrix.Rotation(math.radians(90), 4, 'X')
rot_matrix = rot_x @ rot_z
elif frame_type == 'X_PERS':
base_name = INIT_OBJ_X_PERS
mat_name = f"{INIT_OBJ_X_PERS}_{size_str}_半透明マテリアル"
color = props.x_pers_color
# X=0 (YZ平面) : 床面枠をY軸に90度回転
rot_z = mathutils.Matrix.Rotation(math.radians(45), 4, 'Z')
rot_y = mathutils.Matrix.Rotation(math.radians(90), 4, 'Y')
rot_matrix = rot_y @ rot_z
else:
return
name = f"{base_name}_{size_str}"
# 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'
major_r = (size / 2.0) * math.sqrt(2)
minor_r = thickness / 2.0
# 事前に選択を解除
bpy.ops.object.select_all(action='DESELECT')
# トーラスを生成
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
# 回転行列を直接適用
obj.rotation_mode = 'XYZ'
obj.matrix_world = rot_matrix
# マテリアル割り当て
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()
# ===================================
# 【矢印 作成用ヘルパー関数】
# ===================================
def create_arrow(context, axis_type):
props = getattr(context.scene, f"{PREFIX}_props")
length = props.arrow_length
thickness = props.arrow_thickness
cone_r = props.arrow_cone_radius
cone_h = props.arrow_cone_height
center_at_origin = props.arrow_center_at_origin
# メインコレクションの取得または新規作成
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)
if axis_type == 'X':
name = "X軸矢印"
color = props.arrow_color_x
# X軸方向への回転行列 (Y軸周り90度)
rot_matrix = mathutils.Matrix.Rotation(math.radians(90), 4, 'Y')
elif axis_type == 'Y':
name = "Y軸矢印"
color = props.arrow_color_y
# Y軸方向への回転行列 (X軸周り-90度)
rot_matrix = mathutils.Matrix.Rotation(math.radians(-90), 4, 'X')
elif axis_type == 'Z':
name = "Z軸矢印"
color = props.arrow_color_z
# Z軸方向 (回転なし)
rot_matrix = mathutils.Matrix.Identity(4)
else:
return
# 完全に独立した新規マテリアルを作成
mat_name = f"{name}_半透明マテリアル"
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]
if hasattr(mat, "surface_render_method"):
mat.surface_render_method = 'BLENDED'
else:
mat.blend_method = 'BLEND'
# 干渉防止のために全選択解除
bpy.ops.object.select_all(action='DESELECT')
# 1. 円柱作成 (Z軸方向に伸びるように配置)
bpy.ops.mesh.primitive_cylinder_add(
radius=thickness / 2.0,
depth=length,
location=(0.0, 0.0, length / 2.0)
)
cylinder = context.active_object
cylinder.select_set(False)
# 2. 円錐作成 (円柱の先端に配置)
bpy.ops.mesh.primitive_cone_add(
radius1=cone_r,
depth=cone_h,
location=(0.0, 0.0, length + cone_h / 2.0)
)
cone = context.active_object
# 3. 結合 (円錐と円柱を一体化)
cylinder.select_set(True)
cone.select_set(True)
context.view_layer.objects.active = cylinder
bpy.ops.object.join()
arrow_obj = cylinder
arrow_obj.name = name
# 中心点調整オフセット
dz = -(length + cone_h) / 2.0 if center_at_origin else 0.0
translation_matrix = mathutils.Matrix.Translation((0.0, 0.0, dz))
# 各軸方向にマトリクスで回転移動
arrow_obj.rotation_mode = 'XYZ'
arrow_obj.matrix_world = rot_matrix @ translation_matrix
# マテリアル適用
arrow_obj.data.materials.append(mat)
# コレクション整理 (メインコレクション「枠額縁」の直下に格納)
if main_col.name not in [c.name for c in arrow_obj.users_collection]:
main_col.objects.link(arrow_obj)
for c in list(arrow_obj.users_collection):
if c != main_col:
c.objects.unlink(arrow_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')
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')
return {'FINISHED'}
class VGM_OT_create_frame_x_pers(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_frame_x_pers"
bl_label = "x軸遠近法 額縁を作成"
def execute(self, context):
create_frame(context, 'X_PERS')
return {'FINISHED'}
class VGM_OT_create_arrow_x(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_arrow_x"
bl_label = "X軸矢印を作成"
def execute(self, context):
create_arrow(context, 'X')
return {'FINISHED'}
class VGM_OT_create_arrow_y(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_arrow_y"
bl_label = "Y軸矢印を作成"
def execute(self, context):
create_arrow(context, 'Y')
return {'FINISHED'}
class VGM_OT_create_arrow_z(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_arrow_z"
bl_label = "Z軸矢印を作成"
def execute(self, context):
create_arrow(context, 'Z')
return {'FINISHED'}
class VGM_OT_create_arrow_all(bpy.types.Operator):
bl_idname = f"view3d.{PREFIX}_create_arrow_all"
bl_label = "3軸の矢印を一括作成"
def execute(self, context):
create_arrow(context, 'X')
create_arrow(context, 'Y')
create_arrow(context, 'Z')
self.report({'INFO'}, "X, Y, Z軸の矢印を作成しました")
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
props.x_pers_color = INIT_COLOR_X_PERS
# 矢印リセット
props.arrow_length = INIT_ARROW_LENGTH
props.arrow_thickness = INIT_ARROW_THICKNESS
props.arrow_cone_radius = INIT_CONE_RADIUS
props.arrow_cone_height = INIT_CONE_HEIGHT
props.arrow_center_at_origin = INIT_ARROW_CENTER
props.arrow_color_x = INIT_ARROW_COLOR_X
props.arrow_color_y = INIT_ARROW_COLOR_Y
props.arrow_color_z = INIT_ARROW_COLOR_Z
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)
frame_thickness: bpy.props.FloatProperty(name="太さ", default=INIT_FRAME_THICKNESS, min=0.01)
floor_color: bpy.props.FloatVectorProperty(name="床面枠の色", subtype='COLOR', default=INIT_COLOR_FLOOR, size=4, min=0.0, max=1.0)
window_color: bpy.props.FloatVectorProperty(name="窓平面枠の色", subtype='COLOR', default=INIT_COLOR_WINDOW, size=4, min=0.0, max=1.0)
x_pers_color: bpy.props.FloatVectorProperty(name="x軸遠近法の色", subtype='COLOR', default=INIT_COLOR_X_PERS, size=4, min=0.0, max=1.0)
# 矢印プロパティ
arrow_length: bpy.props.FloatProperty(name="矢印 長さ", default=INIT_ARROW_LENGTH, min=0.01)
arrow_thickness: bpy.props.FloatProperty(name="矢印 太さ", default=INIT_ARROW_THICKNESS, min=0.01)
arrow_cone_radius: bpy.props.FloatProperty(name="円錐 太さ(半径)", default=INIT_CONE_RADIUS, min=0.01)
arrow_cone_height: bpy.props.FloatProperty(name="円錐 高さ", default=INIT_CONE_HEIGHT, min=0.01)
arrow_center_at_origin: bpy.props.BoolProperty(name="矢印中心を000にする", default=INIT_ARROW_CENTER)
arrow_color_x: bpy.props.FloatVectorProperty(name="X軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_X, size=4, min=0.0, max=1.0)
arrow_color_y: bpy.props.FloatVectorProperty(name="Y軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_Y, size=4, min=0.0, max=1.0)
arrow_color_z: bpy.props.FloatVectorProperty(name="Z軸の色", subtype='COLOR', default=INIT_ARROW_COLOR_Z, size=4, min=0.0, max=1.0)
# ===================================
# 【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.prop(props, "x_pers_color", text="x軸遠近法の色 (X=0)")
layout.separator()
# 額縁生成ボタン
col_buttons = layout.column(align=True)
col_buttons.scale_y = 1.2
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')
col_buttons.operator(f"view3d.{PREFIX}_create_frame_x_pers", text="x軸遠近法 額縁を作成 (X=0平面)", icon='OUTLINER_OB_FORCE_FIELD')
# ===================================
# 【UIパネル: 矢印作成設定パネル】
# ===================================
class VGM_PT_arrow_panel(bpy.types.Panel):
bl_idname = f"{PREFIX.upper()}_PT_arrow_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='CONE')
box.prop(props, "arrow_length")
box.prop(props, "arrow_thickness")
box.prop(props, "arrow_cone_radius")
box.prop(props, "arrow_cone_height")
box.prop(props, "arrow_center_at_origin")
box.separator()
box.prop(props, "arrow_color_x", text="X軸 矢印の色")
box.prop(props, "arrow_color_y", text="Y軸 矢印の色")
box.prop(props, "arrow_color_z", text="Z軸 矢印の色")
layout.separator()
# 個別生成ボタン
row = layout.row(align=True)
row.scale_y = 1.1
row.operator(f"view3d.{PREFIX}_create_arrow_x", text="X軸矢印")
row.operator(f"view3d.{PREFIX}_create_arrow_y", text="Y軸矢印")
row.operator(f"view3d.{PREFIX}_create_arrow_z", text="Z軸矢印")
# 一括生成ボタン
layout.operator(f"view3d.{PREFIX}_create_arrow_all", text="3軸の矢印を一括作成", icon='FORCE_VORTEX')
layout.separator(factor=1.5)
# 初期設定ボタン、アドオン登録解除
col_sys = layout.column(align=True)
col_sys.operator(f"view3d.{PREFIX}_reset_frame_props", text="すべてのパラメータを初期値に戻す", icon='LOOP_BACK')
col_sys.separator()
col_sys.operator(f"wm.{PREFIX}_remove_addon", text="アドオンの登録を解除", icon='CANCEL')
# ===================================
# 【登録・削除処理】
# ===================================
classes = [
VGM_Properties,
VGM_OT_create_frame_floor,
VGM_OT_create_frame_window,
VGM_OT_create_frame_x_pers,
VGM_OT_create_arrow_x,
VGM_OT_create_arrow_y,
VGM_OT_create_arrow_z,
VGM_OT_create_arrow_all,
VGM_OT_reset_frame_props,
VGM_OT_remove_addon,
VGM_PT_frame_panel,
VGM_PT_arrow_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()