サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { "> サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { "> サイドバー", } # ============================================================================== # デフォルト値設定 (すべて小数2桁に整理済み) # ============================================================================== CURRENT_DEFAULTS = { ">
# Copied: 14:55:00
import bpy
import math
from bpy.props import FloatVectorProperty, FloatProperty, EnumProperty, BoolProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector, Euler
from math import radians
from datetime import datetime
# ==============================================================================
# 【 開発方針 】
# 本アドオンは Blender 5.x 専用です。Blender 4.x以前との互換性は維持しません。
# ==============================================================================
# アドオンのメタデータ
bl_info = {
"name": "[ 500 背景色 ]",
"author": "zionadchat",
"version": (3, 6, 0),
"blender": (5, 0, 0),
"category": "[ 500 背景色 ]",
"description": "【Blender 5.x 専用】3Dビューポートの色、太陽、透視投影視座位置をリアルタイム制御します",
"location": "3Dビュー > サイドバー",
}
# ==============================================================================
# デフォルト値設定 (すべて小数2桁に整理済み)
# ==============================================================================
CURRENT_DEFAULTS = {
"sun_control_mode": 'ANGLE',
"grid_preset": 'white',
"wire_preset": 'orange',
"camera_preset": 'Cam',
"background_type": 'LINEAR',
"header_preset": 'Dark Green',
"preset": 'Dark Green',
"render_preset": 'Blue',
"outliner_preset": 'Outliner 5.0.0',
"text_editor_preset": 'Text 5.0.0',
"sun_target_location": (0.00, 0.00, 0.00),
"sun_rotation": (0.79, 0.00, 0.79),
"sun_location": (0.00, 0.00, 10.00),
"sun_strength": 2.50,
"custom_grid_scale": 1.00,
"grid_color": (0.21, 0.21, 0.21, 1.00),
"wire_color": (0.00, 0.00, 0.00),
"camera_color": (0.47, 0.55, 1.00),
"header_color": (0.00, 0.03, 0.00, 1.00),
"custom_gradient_high": (0.16, 0.77, 1.00),
"custom_gradient_low": (0.01, 0.01, 0.15),
"reverse_gradient": False,
"render_color": (0.19, 0.60, 1.00, 1.00),
"render_environment_strength": 1.00,
"outliner_header_color": (0.19, 0.19, 0.19, 0.70),
"outliner_background_color": (0.14, 0.14, 0.14, 1.00),
"text_editor_header_color": (0.19, 0.19, 0.19, 0.70),
"text_editor_background_color": (0.14, 0.14, 0.14, 1.00),
"view_pos": (2.40, -13.26, 3.84),
}
# ==============================================================================
# コアロジック
# ==============================================================================
# 定数
ADDON_CATEGORY_NAME = bl_info["category"]
PREFIX = "backcolor2026"
VIEW_RESET_BTN_TEXT = "Reset View (0, -10, 10)"
VIEW_POS_INIT = (0.0, -10.0, 10.0)
# パネル定義
LINK_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_link_panel"
MAIN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_main_panel"
PERSP_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_persp_control"
BG_PANEL_IDNAME_1 = f"{PREFIX}_VIEW3D_PT_solid_background_panel"
HEADER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_header_panel"
RENDER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_render_panel"
SUN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_sun_panel"
GRID_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_gridpanel"
WIRE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_wirepanel"
CAMERA_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_camerapanel"
OUTLINER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_outliner_panel"
TEXT_EDITOR_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_text_editor_panel"
REMOVE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_remove"
# パネルラベル
PANEL_LABELS = {
"LINK": "リンク",
"MAIN": "メイン操作",
"PERSP": "透視投影 視座位置",
"BACKGROUND": "3D Viewport Color",
"HEADER": "Header Color",
"RENDER": "Render Color",
"SUN": "太陽 設定",
"GRID": "Grid Color",
"WIRE": "Wire Color",
"CAMERA": "Camera Color",
"OUTLINER": "Outliner Color",
"TEXT_EDITOR": "Text Editor Color",
"REMOVE": "アドオン削除",
}
# プリセット群
BASE_PRESETS =[
("Dark Green", "Dark Green", "Dark Green background", (0.00, 0.28, 0.02), (0.10, 0.15, 0.05)),
("purple", "purple", "purple background", (0.49, 0.45, 1.00), (0.74, 0.65, 0.88)),
("BlueGreen", "BlueGreen", "BlueGreen background", (0.14, 0.34, 0.83), (0.57, 0.88, 0.63)),
("DARK_BLUE", "Dark Blue", "Dark Blue background", (0.07, 0.13, 0.31), (0.05, 0.05, 0.15)),
("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 background", (0.24, 0.24, 0.24), (0.19, 0.19, 0.19)),
("FOREST_GREEN", "Forest Green", "Forest Green background", (0.50, 0.70, 0.50), (0.10, 0.15, 0.05)),
]
HEADER_PRESETS =[
("Dark Green", "Dark Green", "Dark Green header", (0.00, 0.03, 0.00, 1.00)),
("purple", "purple", "purple header", (0.00, 0.00, 0.00, 1.00)),
("BlueGreen", "BlueGreen", "BlueGreen header", (0.00, 0.00, 0.00, 1.00)),
("DARK_BLUE", "Dark Blue", "Dark Blue header", (0.10, 0.10, 0.30, 1.00)),
("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 header", (0.19, 0.19, 0.19, 0.70)),
("FOREST_GREEN", "Forest Green", "Forest Green header", (0.20, 0.30, 0.10, 1.00)),
]
RENDER_PRESETS =[
("Blue", "Blue", "Blue render color", (0.19, 0.60, 1.00, 1.00)),
("Render 5.0.0", "Render 5.0.0", "Render 5.0.0 color", (0.05, 0.05, 0.05, 1.00)),
("LIGHT_GRAY", "Light Gray", "Light gray render", (0.80, 0.80, 0.80, 1.00)),
]
GRID_PRESETS =[
("white", "white", "white grid color", (1.00, 1.00, 1.00, 1.00)),
("Grid 5.0.0", "Grid 5.0.0", "Grid 5.0.0 color", (0.33, 0.33, 0.33, 0.50)),
("DARK_GRAY", "Dark Gray", "Dark gray grid", (0.10, 0.10, 0.10, 1.00)),
]
WIRE_PRESETS =[
("orange", "orange", "orange wire", (0.71, 0.21, 0.05)),
("Wire 5.0.0", "Wire 5.0.0", "Wire 5.0.0 color", (0.00, 0.00, 0.00)),
("WHITE", "White", "White wire", (1.00, 1.00, 1.00)),
]
CAMERA_PRESETS =[
("Cam", "Cam", "Cam camera color", (0.47, 0.55, 1.00)),
("Cam 5.0.0", "Cam 5.0.0", "Cam 5.0.0 color", (0.00, 0.00, 0.00)),
("YELLOW", "Yellow", "Yellow camera", (1.00, 1.00, 0.00)),
]
OUTLINER_PRESETS =[
("Outliner 5.0.0", "Outliner 5.0.0", "Outliner 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
("DARK_TEAL", "Dark Teal", "Dark teal outliner", (0.00, 0.20, 0.20, 1.00), (0.00, 0.10, 0.10, 1.00)),
]
TEXT_EDITOR_PRESETS =[
("Text 5.0.0", "Text 5.0.0", "Text Editor 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
("DARK_GREEN", "Dark Green", "Dark green text editor", (0.00, 0.20, 0.00, 1.00), (0.00, 0.10, 0.00, 1.00)),
]
BACKGROUND_TYPES =[
('SINGLE_COLOR', "Single Color", "Uniform background color"),
('LINEAR', "Linear", "Linear gradient background"),
('RADIAL', "Vignette", "Radial gradient simulating a vignette effect"),
]
# ==============================================================================
# リアルタイム更新用コールバック関数(ビューポート色・太陽)
# ==============================================================================
def update_custom_grid_scale(self, context):
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.custom_grid_scale
def update_grid_color(self, context): bpy.context.preferences.themes[0].view_3d.grid = self.grid_color
def update_wire_color(self, context): bpy.context.preferences.themes[0].view_3d.wire = self.wire_color
def update_camera_color(self, context): bpy.context.preferences.themes[0].view_3d.camera = self.camera_color
def update_background_color(self, context):
gradients = bpy.context.preferences.themes[0].view_3d.space.gradients
if self.background_type == 'SINGLE_COLOR':
gradients.background_type = 'LINEAR'
gradients.high_gradient = gradients.gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
else:
gradients.background_type = self.background_type
gradients.high_gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
gradients.gradient = self.custom_gradient_high if self.reverse_gradient else self.custom_gradient_low
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
def update_header_color(self, context):
bpy.context.preferences.themes[0].view_3d.space.header = self.header_color
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
def update_render_color(self, context):
bpy.context.preferences.themes[0].image_editor.space.back = self.render_color[:3]
world = bpy.data.worlds.get('MyWorld') or bpy.data.worlds.new('MyWorld')
context.scene.world = world
world.use_nodes = True
bg_node = world.node_tree.nodes.get('Background') or world.node_tree.nodes.new(type='ShaderNodeBackground')
bg_node.name = 'Background'
bg_node.inputs[0].default_value = self.render_color
bg_node.inputs[1].default_value = self.render_environment_strength
output_node = world.node_tree.nodes.get('World Output') or world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
output_node.name = 'World Output'
world.node_tree.links.new(bg_node.outputs[0], output_node.inputs['Surface'])
def update_outliner_color(self, context):
space = bpy.context.preferences.themes[0].outliner.space
space.header = self.outliner_header_color
space.back = self.outliner_background_color[:3]
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'OUTLINER': area.tag_redraw()
def update_text_editor_color(self, context):
space = bpy.context.preferences.themes[0].text_editor.space
space.header = self.text_editor_header_color
space.back = self.text_editor_background_color[:3]
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'TEXT_EDITOR': area.tag_redraw()
def get_or_create_sun():
sun_obj = bpy.data.objects.get("Sun")
if sun_obj is None or sun_obj.type != 'LIGHT' or sun_obj.data.type != 'SUN':
if sun_obj:
try: bpy.data.objects.remove(sun_obj, do_unlink=True)
except: pass
bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 0))
sun_obj = bpy.context.active_object
sun_obj.name = "Sun"; sun_obj.data.name = "Sun"
return sun_obj
def update_sun(self, context):
sun = get_or_create_sun()
sun.location = self.sun_location
if self.sun_control_mode == 'ANGLE':
sun.rotation_euler = self.sun_rotation
else:
target_vec = Vector(self.sun_target_location)
sun_vec = Vector(self.sun_location)
if (target_vec - sun_vec).length_squared < 0.0001: return
direction = target_vec - sun_vec
sun.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
sun.data.energy = self.sun_strength
# ==============================================================================
# リアルタイム更新用コールバック関数(透視投影 視座位置)
# ==============================================================================
_is_updating_view = False
def update_view_position(self, context):
global _is_updating_view
if _is_updating_view: return
props = getattr(context.scene, "persp_view_props", None)
if not props: return
limit = props.slider_limit
v = list(props.view_pos)
clamped = False
for i in range(3):
if v[i] > limit: v[i] = limit; clamped = True
elif v[i] < -limit: v[i] = -limit; clamped = True
if clamped:
_is_updating_view = True
props.view_pos = v
_is_updating_view = False
_is_updating_view = True
try:
cam_pos = Vector(props.view_pos)
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
r3d = space.region_3d
r3d.view_perspective = 'PERSP'
target_pos = Vector(r3d.view_location)
rel_pos = cam_pos - target_pos
dist = rel_pos.length
if dist > 0.001:
r3d.view_distance = dist
r3d.view_rotation = rel_pos.to_track_quat('Z', 'Y')
finally:
_is_updating_view = False
def view_sync_timer():
global _is_updating_view
if _is_updating_view: return 0.05
context = bpy.context
if getattr(context, "scene", None) is None: return 0.05
props = getattr(context.scene, "persp_view_props", None)
if not props: return 0.05
r3d, target_area = None, None
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
r3d, target_area = space.region_3d, area
break
if r3d: break
if r3d: break
if r3d and target_area:
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
current_pos = Vector(props.view_pos)
if (current_pos - actual_cam_pos).length > 0.001:
_is_updating_view = True
max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
if max_val > props.slider_limit: props.slider_limit = max_val + 50.0
props.view_pos = actual_cam_pos
_is_updating_view = False
target_area.tag_redraw()
return 0.05
# ------------------------------------------------------------------------
# Property Groups
# ------------------------------------------------------------------------
class SunSettingsProperties(PropertyGroup):
sun_control_mode: EnumProperty(name="制御モード", items=[('ANGLE', "角度", "太陽の回転を直接指定"), ('TARGET', "ターゲット", "指定位置に太陽を向ける")], default=CURRENT_DEFAULTS["sun_control_mode"], update=update_sun)
sun_target_location: FloatVectorProperty(name="ターゲット位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_target_location"], update=update_sun)
sun_rotation: FloatVectorProperty(name="角度", subtype='EULER', unit='ROTATION', default=CURRENT_DEFAULTS["sun_rotation"], update=update_sun)
sun_location: FloatVectorProperty(name="位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_location"], update=update_sun)
sun_strength: FloatProperty(name="強さ", default=CURRENT_DEFAULTS["sun_strength"], min=0.0, update=update_sun)
class ViewportColorProperties(PropertyGroup):
custom_grid_scale: FloatProperty(name="Scale", default=CURRENT_DEFAULTS["custom_grid_scale"], min=0.001, update=update_custom_grid_scale)
grid_color: FloatVectorProperty(name="Grid Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["grid_color"], update=update_grid_color)
grid_preset: EnumProperty(name="Grid Preset", items=[(p[0], p[1], p[2]) for p in GRID_PRESETS], default=CURRENT_DEFAULTS["grid_preset"], update=lambda self, context: self.update_grid_preset(context))
wire_color: FloatVectorProperty(name="Wire Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["wire_color"], update=update_wire_color)
wire_preset: EnumProperty(name="Wire Preset", items=[(p[0], p[1], p[2]) for p in WIRE_PRESETS], default=CURRENT_DEFAULTS["wire_preset"], update=lambda self, context: self.update_wire_preset(context))
camera_color: FloatVectorProperty(name="Camera Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["camera_color"], update=update_camera_color)
camera_preset: EnumProperty(name="Camera Preset", items=[(p[0], p[1], p[2]) for p in CAMERA_PRESETS], default=CURRENT_DEFAULTS["camera_preset"], update=lambda self, context: self.update_camera_preset(context))
background_type: EnumProperty(name="Background Type", items=BACKGROUND_TYPES, default=CURRENT_DEFAULTS["background_type"], update=update_background_color)
header_color: FloatVectorProperty(name="Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["header_color"], update=update_header_color)
header_preset: EnumProperty(name="Header Preset", items=[(p[0], p[1], p[2]) for p in HEADER_PRESETS], default=CURRENT_DEFAULTS["header_preset"], update=lambda self, context: self.update_header_preset(context))
custom_gradient_high: FloatVectorProperty(name="Gradient High Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_high"], update=update_background_color)
custom_gradient_low: FloatVectorProperty(name="Gradient Low Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_low"], update=update_background_color)
reverse_gradient: BoolProperty(name="Reverse Gradient", default=CURRENT_DEFAULTS["reverse_gradient"], update=update_background_color)
preset: EnumProperty(name="Color Preset", items=[(p[0], p[1], p[2]) for p in BASE_PRESETS], default=CURRENT_DEFAULTS["preset"], update=lambda self, context: self.update_preset(context))
render_color: FloatVectorProperty(name="Render Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["render_color"], update=update_render_color)
render_preset: EnumProperty(name="Render Preset", items=[(p[0], p[1], p[2]) for p in RENDER_PRESETS], default=CURRENT_DEFAULTS["render_preset"], update=lambda self, context: self.update_render_preset(context))
render_environment_strength: FloatProperty(name="Render Environment Strength", default=CURRENT_DEFAULTS["render_environment_strength"], min=0.0, max=1900.0, update=update_render_color)
outliner_header_color: FloatVectorProperty(name="Outliner Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_header_color"], update=update_outliner_color)
outliner_background_color: FloatVectorProperty(name="Outliner Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_background_color"], update=update_outliner_color)
outliner_preset: EnumProperty(name="Outliner Preset", items=[(p[0], p[1], p[2]) for p in OUTLINER_PRESETS], default=CURRENT_DEFAULTS["outliner_preset"], update=lambda self, context: self.update_outliner_preset(context))
text_editor_header_color: FloatVectorProperty(name="Text Editor Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_header_color"], update=update_text_editor_color)
text_editor_background_color: FloatVectorProperty(name="Text Editor Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_background_color"], update=update_text_editor_color)
text_editor_preset: EnumProperty(name="Text Editor Preset", items=[(p[0], p[1], p[2]) for p in TEXT_EDITOR_PRESETS], default=CURRENT_DEFAULTS["text_editor_preset"], update=lambda self, context: self.update_text_editor_preset(context))
def update_grid_preset(self, context):
for p in GRID_PRESETS:
if p[0] == self.grid_preset: self.grid_color = p[3]; break
def update_wire_preset(self, context):
for p in WIRE_PRESETS:
if p[0] == self.wire_preset: self.wire_color = p[3]; break
def update_camera_preset(self, context):
for p in CAMERA_PRESETS:
if p[0] == self.camera_preset: self.camera_color = p[3]; break
def update_preset(self, context):
for p in BASE_PRESETS:
if p[0] == self.preset: self.custom_gradient_high = p[3]; self.custom_gradient_low = p[4]; break
def update_header_preset(self, context):
for p in HEADER_PRESETS:
if p[0] == self.header_preset: self.header_color = p[3]; break
def update_render_preset(self, context):
for p in RENDER_PRESETS:
if p[0] == self.render_preset: self.render_color = p[3]; break
def update_outliner_preset(self, context):
for p in OUTLINER_PRESETS:
if p[0] == self.outliner_preset: self.outliner_header_color = p[3]; self.outliner_background_color = p[4]; break
def update_text_editor_preset(self, context):
for p in TEXT_EDITOR_PRESETS:
if p[0] == self.text_editor_preset: self.text_editor_header_color = p[3]; self.text_editor_background_color = p[4]; break
class PerspViewProperties(PropertyGroup):
slider_limit: FloatProperty(name="Range Limit", default=300.0, min=10.0, max=10000.0)
view_pos: FloatVectorProperty(name="View Position", size=3, soft_min=-10000.0, soft_max=10000.0, default=CURRENT_DEFAULTS.get('view_pos', VIEW_POS_INIT), update=update_view_position)
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_ViewCenterFront(Operator):
bl_idname = f"{PREFIX}_wm.view_center_front"
bl_label = "Center 0,0,0 (Front View)"
bl_description = "原点(0,0,0)を画面中央に配置し、Yマイナス方向からの視点(正面)にします"
def execute(self, context):
for area in context.screen.areas:
if area.type == 'VIEW_3D':
rv3d = area.spaces.active.region_3d
if rv3d:
rv3d.view_location = (0.0, 0.0, 0.0)
rv3d.view_rotation = Euler((radians(90.0), 0.0, 0.0), 'XYZ').to_quaternion()
if rv3d.view_distance < 10.0: rv3d.view_distance = 60.0
return {'FINISHED'}
class OT_CopyFullScript(Operator):
bl_idname = f"{PREFIX}_wm.copy_script"
bl_label = "Copy Defaults Only"
def execute(self, context):
props = context.scene.viewport_color_props
sun_props = context.scene.sun_settings_props
persp_props = context.scene.persp_view_props
# すべての数値を小数2桁に丸めて出力
def fmt_vec(v): return "(" + ", ".join(f"{x:.2f}" for x in v) + ")"
def fmt_str(s): return f"'{s}'"
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "sun_control_mode": {fmt_str(sun_props.sun_control_mode)},\n'
new_dict += f' "grid_preset": {fmt_str(props.grid_preset)},\n'
new_dict += f' "wire_preset": {fmt_str(props.wire_preset)},\n'
new_dict += f' "camera_preset": {fmt_str(props.camera_preset)},\n'
new_dict += f' "background_type": {fmt_str(props.background_type)},\n'
new_dict += f' "header_preset": {fmt_str(props.header_preset)},\n'
new_dict += f' "preset": {fmt_str(props.preset)},\n'
new_dict += f' "render_preset": {fmt_str(props.render_preset)},\n'
new_dict += f' "outliner_preset": {fmt_str(props.outliner_preset)},\n'
new_dict += f' "text_editor_preset": {fmt_str(props.text_editor_preset)},\n'
new_dict += f' "sun_target_location": {fmt_vec(sun_props.sun_target_location)},\n'
new_dict += f' "sun_rotation": {fmt_vec(sun_props.sun_rotation)},\n'
new_dict += f' "sun_location": {fmt_vec(sun_props.sun_location)},\n'
new_dict += f' "sun_strength": {sun_props.sun_strength:.2f},\n'
new_dict += f' "custom_grid_scale": {props.custom_grid_scale:.2f},\n'
new_dict += f' "grid_color": {fmt_vec(props.grid_color)},\n'
new_dict += f' "wire_color": {fmt_vec(props.wire_color)},\n'
new_dict += f' "camera_color": {fmt_vec(props.camera_color)},\n'
new_dict += f' "header_color": {fmt_vec(props.header_color)},\n'
new_dict += f' "custom_gradient_high": {fmt_vec(props.custom_gradient_high)},\n'
new_dict += f' "custom_gradient_low": {fmt_vec(props.custom_gradient_low)},\n'
new_dict += f' "reverse_gradient": {props.reverse_gradient},\n'
new_dict += f' "render_color": {fmt_vec(props.render_color)},\n'
new_dict += f' "render_environment_strength": {props.render_environment_strength:.2f},\n'
new_dict += f' "outliner_header_color": {fmt_vec(props.outliner_header_color)},\n'
new_dict += f' "outliner_background_color": {fmt_vec(props.outliner_background_color)},\n'
new_dict += f' "text_editor_header_color": {fmt_vec(props.text_editor_header_color)},\n'
new_dict += f' "text_editor_background_color": {fmt_vec(props.text_editor_background_color)},\n'
new_dict += f' "view_pos": {fmt_vec(persp_props.view_pos)},\n'
new_dict += "}\n"
context.window_manager.clipboard = new_dict
self.report({'INFO'}, "最新数値を初期値コード(CURRENT_DEFAULTS)としてコピーしました!")
return {'FINISHED'}
class OVERLAY_OT_set_grid_scale(Operator):
bl_idname = f"{PREFIX}_overlay.set_grid_scale"; bl_label = "Set Grid Scale"
scale_value: FloatProperty()
def execute(self, context):
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.scale_value
return {'FINISHED'}
class SUN_OT_Create(Operator):
bl_idname = f"{PREFIX}.create_sun"; bl_label = "太陽を作成"
def execute(self, context):
get_or_create_sun(); self.report({'INFO'}, "太陽を作成しました。"); return {'FINISHED'}
class SUN_OT_Reset(Operator):
bl_idname = f"{PREFIX}.reset_sun"; bl_label = "太陽の設定を初期値にリセット"
def execute(self, context):
props = context.scene.sun_settings_props
props.sun_control_mode, props.sun_target_location = 'ANGLE', (0.0, 0.0, 0.0)
props.sun_rotation = (radians(45.0), radians(0.0), radians(45.0))
props.sun_location, props.sun_strength = (0.0, 0.0, 10.0), 2.5
update_sun(props, context)
self.report({'INFO'}, "太陽の設定をリセットしました。"); return {'FINISHED'}
class RemoveAllPanels(Operator):
bl_idname = f"{PREFIX}_wm.remove_all_panels"; bl_label = PANEL_LABELS["REMOVE"]
def execute(self, context): unregister(); return {'FINISHED'}
class PERSP_OT_GetCurrentView(Operator):
bl_idname = f"{PREFIX}_persp.get_current_view"; bl_label = "Get Current View & Update"; bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
r3d = context.space_data.region_3d if context.space_data else None
if not props or not r3d: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
if max_val > props.slider_limit: props.slider_limit = max_val + 50.0
props.view_pos = actual_cam_pos; return {'FINISHED'}
class PERSP_OT_ResetView(Operator):
bl_idname = f"{PREFIX}_persp.reset_view"; bl_label = VIEW_RESET_BTN_TEXT; bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
if props:
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D': space.region_3d.view_location = (0.0, 0.0, 0.0)
props.view_pos = VIEW_POS_INIT
return {'FINISHED'}
class PERSP_OT_CenterSelected(Operator):
bl_idname = f"{PREFIX}_persp.center_selected"; bl_label = "Center Selected Object"; bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
bpy.ops.view3d.view_selected()
r3d = context.space_data.region_3d if context.space_data else None
props = getattr(context.scene, "persp_view_props", None)
if r3d and props:
target_pos = Vector(r3d.view_location)
props.view_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
return {'FINISHED'}
class PERSP_OT_CopyActualViewPos(Operator):
bl_idname = f"{PREFIX}_persp.copy_actual_pos"; bl_label = "Copy Position Only"
def execute(self, context):
r3d = context.space_data.region_3d if context.space_data else None
if not r3d: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
p = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
context.window_manager.clipboard = f"Actual View Pos: ({p.x:.2f}, {p.y:.2f}, {p.z:.2f})"
self.report({'INFO'}, "視座位置をコピーしました"); return {'FINISHED'}
class PERSP_OT_CopyAngles(Operator):
bl_idname = f"{PREFIX}_persp.copy_angles"; bl_label = "Copy Full Info (Pos & Angles)"
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
r3d = context.space_data.region_3d if context.space_data else None
if not r3d or not props: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
vec = target_pos - actual_cam_pos; length = vec.length
if length < 0.0001: return {'CANCELLED'}
ang_x, ang_y, ang_z = math.degrees(math.acos(vec.x / length)), math.degrees(math.acos(vec.y / length)), math.degrees(math.acos(vec.z / length))
pl_x, pl_y, pl_z = math.degrees(math.asin(vec.x / length)), math.degrees(math.asin(vec.y / length)), math.degrees(math.asin(vec.z / length))
info_text = (
f"--- View Direction Info ---\n[ Actual 3D View Status ]\nActual View Pos : ({actual_cam_pos.x:.2f}, {actual_cam_pos.y:.2f}, {actual_cam_pos.z:.2f})\n"
f"Target Pos : ({target_pos.x:.2f}, {target_pos.y:.2f}, {target_pos.z:.2f})\nDistance : {length:.2f}\n\n"
f"[ Direction Angles (軸そのものとの角度 0〜180°) ]\nAngle from X Axis : {ang_x:.2f} deg\nAngle from Y Axis : {ang_y:.2f} deg\nAngle from Z Axis : {ang_z:.2f} deg\n\n"
f"[ Planar Angles (直感的な傾き・ズレ角 -90〜90°) ]\nX (横のズレ角) : {pl_x:.2f} deg\nY (前後の傾き) : {pl_y:.2f} deg\nZ (仰角・俯角) : {pl_z:.2f} deg\n"
)
context.window_manager.clipboard = info_text
self.report({'INFO'}, "情報全体をクリップボードにコピーしました"); return {'FINISHED'}
# ------------------------------------------------------------------------
# Panels
# ------------------------------------------------------------------------
class BasePanel(Panel): bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = ADDON_CATEGORY_NAME
class VIEW3D_PT_LinkPanel(BasePanel):
bl_label = PANEL_LABELS["LINK"]; bl_idname = LINK_PANEL_IDNAME; bl_order = 0
def draw(self, context):
layout = self.layout
layout.operator("wm.url_open", text="背景色 20250720", icon='URL').url = "<https://app.notion.com/p/20250720-3a3347b3454a80f78e8dc0a5cc52e649>"
layout.operator("wm.url_open", text="アドオン コンテナ 20260715", icon='URL').url = "<https://note.com/zionadmillion/n/n2158226ce616>"
layout.operator("wm.url_open", text="posfie", icon='URL').url = "<https://posfie.com/@timekagura/t/zionad2022?sort=0>"
layout.operator("wm.url_open", text="zionadchat", icon='URL').url = "<https://x.com/zionadchat>"
class VIEW3D_PT_MainPanel(BasePanel):
bl_label = PANEL_LABELS["MAIN"]; bl_idname = MAIN_PANEL_IDNAME; bl_order = 1
def draw(self, context):
layout = self.layout
# メイン操作群
layout.operator(f"{PREFIX}_persp.center_selected", icon='VIEWZOOM', text="選択Objectを画面中央に表示")
layout.separator()
row = layout.row(); row.scale_y = 1.2
row.operator(f"{PREFIX}_wm.copy_script", icon='COPY_ID', text="初期値コード(辞書)のみをコピー")
layout.row().operator(f"{PREFIX}_wm.view_center_front", icon='VIEWZOOM', text="0,0,0 を正面(Y-)から見る")
layout.separator()
# Overlays設定群(元のOverlayPanelから移動)
layout.label(text="Overlays", icon='OVERLAY')
props = context.scene.viewport_color_props
if context.space_data.type == 'VIEW_3D':
layout.prop(context.space_data.overlay, "show_floor", text="Floor")
layout.prop(props, "custom_grid_scale", text="Scale 数値入力")
row_grid = layout.row(align=True)
row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="入力値").scale_value = props.custom_grid_scale
row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="10.0").scale_value = 10.0
row_grid.operator(f"{PREFIX}_overlay.set_grid_scale", text="100.0").scale_value = 100.0
else:
layout.label(text="3D Viewport is required.")
class VIEW3D_PT_PerspControlPanel(BasePanel):
bl_label = PANEL_LABELS["PERSP"]; bl_idname = PERSP_PANEL_IDNAME; bl_order = 2
def draw(self, context):
layout = self.layout
props = getattr(context.scene, "persp_view_props", None)
if not props: return
box = layout.box(); box.label(text="Perspective Viewpoint", icon='VIEW_CAMERA'); box.prop(props, "slider_limit", text="Range Limit (+/-)")
col = box.column(align=True); col.prop(props, "view_pos", text="X", index=0); col.prop(props, "view_pos", text="Y", index=1); col.prop(props, "view_pos", text="Z", index=2)
box.separator(); box.operator(f"{PREFIX}_persp.get_current_view", icon='RESTRICT_VIEW_OFF'); box.operator(f"{PREFIX}_persp.reset_view", icon='LOOP_BACK')
layout.separator()
box_info = layout.box(); box_info.label(text="Actual View Status", icon='INFO')
r3d = context.space_data.region_3d if context.space_data else None
if r3d:
target_pos = Vector(r3d.view_location); actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
vec = target_pos - actual_cam_pos; length = vec.length
col_pos = box_info.column(align=True); col_pos.label(text="[ Actual Position ]", icon='VIEW_CAMERA')
col_pos.label(text=f" X: {actual_cam_pos.x:.2f}"); col_pos.label(text=f" Y: {actual_cam_pos.y:.2f}"); col_pos.label(text=f" Z: {actual_cam_pos.z:.2f}"); col_pos.label(text=f" Distance: {length:.2f}")
box_info.operator(f"{PREFIX}_persp.copy_actual_pos", icon='COPYDOWN'); box_info.separator()
col_ang = box_info.column(align=True)
if length > 0.0001:
a_x, a_y, a_z = math.degrees(math.acos(vec.x / length)), math.degrees(math.acos(vec.y / length)), math.degrees(math.acos(vec.z / length))
p_x, p_y, p_z = math.degrees(math.asin(vec.x / length)), math.degrees(math.asin(vec.y / length)), math.degrees(math.asin(vec.z / length))
col_ang.label(text="[ Direction Angles (軸との角度) ]", icon='ORIENTATION_GLOBAL')
col_ang.label(text=f" X: {a_x:.2f}°"); col_ang.label(text=f" Y: {a_y:.2f}°"); col_ang.label(text=f" Z: {a_z:.2f}°")
col_ang.separator(); col_ang.label(text="[ Planar Angles (直感的な傾き) ]", icon='DRIVER_ROTATIONAL_DIFFERENCE')
col_ang.label(text=f" X (ズレ角): {p_x:.2f}°"); col_ang.label(text=f" Y (ズレ角): {p_y:.2f}°"); col_ang.label(text=f" Z (仰俯角): {p_z:.2f}°")
else: col_ang.label(text=" Target is too close")
box_info.separator(); box_info.operator(f"{PREFIX}_persp.copy_angles", icon='COPYDOWN')
else: box_info.label(text="Please use in 3D View")
class VIEW3D_PT_solid_background_panel(BasePanel):
bl_label = PANEL_LABELS["BACKGROUND"]; bl_idname = BG_PANEL_IDNAME_1; bl_order = 3
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "preset", text="Background Preset"); layout.prop(props, "background_type", expand=True)
layout.prop(props, "custom_gradient_high", text="Color High" if props.background_type != 'SINGLE_COLOR' else "Color")
if props.background_type != 'SINGLE_COLOR': layout.prop(props, "custom_gradient_low")
layout.prop(props, "reverse_gradient", text="Reverse Gradient")
class VIEW3D_PT_HeaderPanel(BasePanel):
bl_label = PANEL_LABELS["HEADER"]; bl_idname = HEADER_PANEL_IDNAME; bl_order = 4
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "header_preset", text="Header Preset"); layout.prop(props, "header_color")
class VIEW3D_PT_RenderPanel(BasePanel):
bl_label = PANEL_LABELS["RENDER"]; bl_idname = RENDER_PANEL_IDNAME; bl_order = 5
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "render_preset"); layout.prop(props, "render_color"); layout.prop(props, "render_environment_strength")
class VIEW3D_PT_SunPanel(BasePanel):
bl_label = PANEL_LABELS["SUN"]; bl_idname = SUN_PANEL_IDNAME; bl_order = 6
def draw(self, context):
layout, props = self.layout, context.scene.sun_settings_props
row = layout.row(align=True); row.operator(f"{PREFIX}.create_sun", text="太陽作成ボタン"); row.operator(f"{PREFIX}.reset_sun", icon='FILE_REFRESH', text="")
layout.separator(); layout.prop(props, "sun_control_mode", expand=True)
if props.sun_control_mode == 'ANGLE': layout.prop(props, "sun_rotation")
else: layout.prop(props, "sun_target_location")
layout.prop(props, "sun_location"); layout.prop(props, "sun_strength")
class VIEW3D_PT_GridPanel(BasePanel):
bl_label = PANEL_LABELS["GRID"]; bl_idname = GRID_PANEL_IDNAME; bl_order = 7
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "grid_preset"); layout.prop(props, "grid_color")
class VIEW3D_PT_WirePanel(BasePanel):
bl_label = PANEL_LABELS["WIRE"]; bl_idname = WIRE_PANEL_IDNAME; bl_order = 8
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "wire_preset"); layout.prop(props, "wire_color")
class VIEW3D_PT_CameraPanel(BasePanel):
bl_label = PANEL_LABELS["CAMERA"]; bl_idname = CAMERA_PANEL_IDNAME; bl_order = 9
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "camera_preset"); layout.prop(props, "camera_color")
class VIEW3D_PT_OutlinerPanel(BasePanel):
bl_label = PANEL_LABELS["OUTLINER"]; bl_idname = OUTLINER_PANEL_IDNAME; bl_order = 10
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "outliner_preset"); layout.prop(props, "outliner_header_color"); layout.prop(props, "outliner_background_color")
class VIEW3D_PT_TextEditorPanel(BasePanel):
bl_label = PANEL_LABELS["TEXT_EDITOR"]; bl_idname = TEXT_EDITOR_PANEL_IDNAME; bl_order = 11
def draw(self, context): layout, props = self.layout, context.scene.viewport_color_props; layout.prop(props, "text_editor_preset"); layout.prop(props, "text_editor_header_color"); layout.prop(props, "text_editor_background_color")
class VIEW3D_PT_RemovePanel(BasePanel):
bl_label = PANEL_LABELS["REMOVE"]; bl_idname = REMOVE_PANEL_IDNAME; bl_order = 12
def draw(self, context): self.layout.operator(f"{PREFIX}_wm.remove_all_panels", text=PANEL_LABELS["REMOVE"])
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes =[
SunSettingsProperties, ViewportColorProperties, PerspViewProperties,
OT_CopyFullScript, OT_ViewCenterFront, OVERLAY_OT_set_grid_scale,
SUN_OT_Create, SUN_OT_Reset,
PERSP_OT_GetCurrentView, PERSP_OT_ResetView, PERSP_OT_CenterSelected, PERSP_OT_CopyActualViewPos, PERSP_OT_CopyAngles,
RemoveAllPanels, VIEW3D_PT_LinkPanel, VIEW3D_PT_MainPanel, VIEW3D_PT_PerspControlPanel,
VIEW3D_PT_solid_background_panel, VIEW3D_PT_HeaderPanel, VIEW3D_PT_RenderPanel,
VIEW3D_PT_SunPanel, VIEW3D_PT_GridPanel, VIEW3D_PT_WirePanel, VIEW3D_PT_CameraPanel,
VIEW3D_PT_OutlinerPanel, VIEW3D_PT_TextEditorPanel, VIEW3D_PT_RemovePanel
]
def register():
for cls in classes:
# 二重登録防止処理
try: bpy.utils.register_class(cls)
except ValueError:
try: bpy.utils.unregister_class(getattr(bpy.types, cls.__name__)); bpy.utils.register_class(cls)
except Exception: pass
bpy.types.Scene.viewport_color_props = PointerProperty(type=ViewportColorProperties)
bpy.types.Scene.sun_settings_props = PointerProperty(type=SunSettingsProperties)
bpy.types.Scene.persp_view_props = PointerProperty(type=PerspViewProperties)
def apply_initial_settings():
if bpy.context.scene and hasattr(bpy.context.scene, 'viewport_color_props'):
props = bpy.context.scene.viewport_color_props
sun_props = bpy.context.scene.sun_settings_props
persp_props = bpy.context.scene.persp_view_props
for key, val in CURRENT_DEFAULTS.items():
if hasattr(props, key): setattr(props, key, val)
elif hasattr(sun_props, key): setattr(sun_props, key, val)
elif hasattr(persp_props, key): setattr(persp_props, key, val)
update_background_color(props, bpy.context); update_header_color(props, bpy.context); update_render_color(props, bpy.context)
update_grid_color(props, bpy.context); update_wire_color(props, bpy.context); update_camera_color(props, bpy.context)
update_outliner_color(props, bpy.context); update_text_editor_color(props, bpy.context); update_sun(sun_props, bpy.context)
# アドオン有効化時の自動設定(サイドパネル展開&マテリアルプレビュー化)
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_region_ui = True
space.shading.type = 'MATERIAL'
bpy.app.timers.register(apply_initial_settings, first_interval=0.1)
if not bpy.app.timers.is_registered(view_sync_timer): bpy.app.timers.register(view_sync_timer)
def unregister():
if hasattr(bpy.types.Scene, 'viewport_color_props'): del bpy.types.Scene.viewport_color_props
if hasattr(bpy.types.Scene, 'sun_settings_props'): del bpy.types.Scene.sun_settings_props
if hasattr(bpy.types.Scene, 'persp_view_props'): del bpy.types.Scene.persp_view_props
for cls in reversed(classes):
try: bpy.utils.unregister_class(cls)
except RuntimeError: pass
if bpy.app.timers.is_registered(view_sync_timer): bpy.app.timers.unregister(view_sync_timer)
# ファイルとして直接実行された場合
if __name__ == "__main__":
if "unregister" in globals():
try: unregister()
except Exception: pass
if "register" in globals(): register()
# Copied: 12:40:00
import bpy
import os
import math
from bpy.props import FloatVectorProperty, FloatProperty, EnumProperty, StringProperty, BoolProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector, Euler
from math import radians
from datetime import datetime
# ==============================================================================
# 【 開発方針 】
# 本アドオンは Blender 5.x 専用です。
# Blender 4.x以前との互換性は維持しません。
# 最新APIを利用し、保守性・安定性・開発速度を優先します。
# ==============================================================================
# ★ このスクリプト自身のID (コピー機能で使用)
# ### ZIONAD_SOURCE_ID: VIEWPORT_COLOR_2026_03_16 ###
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: VIEWPORT_COLOR_2026_03_16 ###"
# アドオンのメタデータ
bl_info = {
"name": "zionad 5520[ 3D Viewport Color & Sun & Perspective ] 20260319",
"author": "zionadchat",
"version": (3, 0, 0),
"blender": (5, 0, 0),
"category": " 5520[ 3D Viewport ] ",
"description": "【Blender 5.x 専用】3Dビューポートの色、太陽、透視投影視座位置をリアルタイム制御します",
"location": "3Dビュー > サイドバー",
}
# 定数
ADDON_CATEGORY_NAME = bl_info["category"]
PREFIX = "view2026316"
VIEW_RESET_BTN_TEXT = "Reset View (0, -10, 10)"
VIEW_POS_INIT = (0.0, -10.0, 10.0)
# ==============================================================================
# デフォルト値設定 (コピー機能でここが書き換わります)
# ==============================================================================
# プリセットによる上書きを防ぐため、プリセット項目を先に読み込む順序にしています
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"sun_control_mode": 'ANGLE',
"grid_preset": 'white',
"wire_preset": 'orange',
"camera_preset": 'Cam',
"background_type": 'LINEAR',
"header_preset": 'Dark Green',
"preset": 'Dark Green',
"render_preset": 'Blue',
"outliner_preset": 'Outliner 5.0.0',
"text_editor_preset": 'Text 5.0.0',
"sun_target_location": (0.0000, 0.0000, 0.0000),
"sun_rotation": (0.7854, 0.0000, 0.7854),
"sun_location": (0.0000, 0.0000, 10.0000),
"sun_strength": 2.5000,
"custom_grid_scale": 1.0000,
"grid_color": (1.0000, 1.0000, 1.0000, 1.0000),
"wire_color": (0.0000, 0.0000, 0.0000),
"camera_color": (0.4700, 0.5500, 1.0000),
"header_color": (0.0000, 0.0300, 0.0000, 1.0000),
"custom_gradient_high": (0.1641, 0.7668, 1.0000),
"custom_gradient_low": (0.0088, 0.0098, 0.1500),
"reverse_gradient": False,
"render_color": (0.1900, 0.6000, 1.0000, 1.0000),
"render_environment_strength": 1.0000,
"outliner_header_color": (0.1900, 0.1900, 0.1900, 0.7000),
"outliner_background_color": (0.1400, 0.1400, 0.1400, 1.0000),
"text_editor_header_color": (0.1900, 0.1900, 0.1900, 0.7000),
"text_editor_background_color": (0.1400, 0.1400, 0.1400, 1.0000),
"view_pos": (0.0000, -10.0000, 10.0000),
}
# <END_DICT>
# パネル定義
COPY_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_copy_panel"
PERSP_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_persp_control"
OVERLAY_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_overlay_panel"
BG_PANEL_IDNAME_1 = f"{PREFIX}_VIEW3D_PT_solid_background_panel"
HEADER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_header_panel"
RENDER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_render_panel"
SUN_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_sun_panel"
GRID_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_gridpanel"
WIRE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_wirepanel"
CAMERA_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_camerapanel"
OUTLINER_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_outliner_panel"
TEXT_EDITOR_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_text_editor_panel"
LINK_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_link_panel"
REMOVE_PANEL_IDNAME = f"{PREFIX}_VIEW3D_PT_remove"
# パネルラベル
PANEL_LABELS = {
"COPY": "コードコピー",
"PERSP": "透視投影 視座位置",
"OVERLAY": "Overlays",
"BACKGROUND": "3D Viewport Color",
"HEADER": "Header Color",
"RENDER": "Render Color",
"SUN": "太陽 設定",
"GRID": "Grid Color",
"WIRE": "Wire Color",
"CAMERA": "Camera Color",
"OUTLINER": "Outliner Color",
"TEXT_EDITOR": "Text Editor Color",
"LINK": "リンク",
"REMOVE": "アドオン削除",
}
# プリセット群
BASE_PRESETS =[
("Dark Green", "Dark Green", "Dark Green background", (0.00, 0.28, 0.02), (0.10, 0.15, 0.05)),
("purple", "purple", "purple background", (0.49, 0.45, 1.00), (0.74, 0.65, 0.88)),
("BlueGreen", "BlueGreen", "BlueGreen background", (0.14, 0.34, 0.83), (0.57, 0.88, 0.63)),
("DARK_BLUE", "Dark Blue", "Dark Blue background", (0.07, 0.13, 0.31), (0.05, 0.05, 0.15)),
("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 background", (0.24, 0.24, 0.24), (0.19, 0.19, 0.19)),
("FOREST_GREEN", "Forest Green", "Forest Green background", (0.50, 0.70, 0.50), (0.10, 0.15, 0.05)),
]
HEADER_PRESETS =[
("Dark Green", "Dark Green", "Dark Green header", (0.00, 0.03, 0.00, 1.00)),
("purple", "purple", "purple header", (0.00, 0.00, 0.00, 1.00)),
("BlueGreen", "BlueGreen", "BlueGreen header", (0.00, 0.00, 0.00, 1.00)),
("DARK_BLUE", "Dark Blue", "Dark Blue header", (0.10, 0.10, 0.30, 1.00)),
("Viewport 5.0.0", "Viewport 5.0.0", "Viewport 5.0.0 header", (0.19, 0.19, 0.19, 0.70)),
("FOREST_GREEN", "Forest Green", "Forest Green header", (0.20, 0.30, 0.10, 1.00)),
]
RENDER_PRESETS =[
("Blue", "Blue", "Blue render color", (0.19, 0.60, 1.00, 1.00)),
("Render 5.0.0", "Render 5.0.0", "Render 5.0.0 color", (0.05, 0.05, 0.05, 1.00)),
("LIGHT_GRAY", "Light Gray", "Light gray render", (0.80, 0.80, 0.80, 1.00)),
]
GRID_PRESETS =[
("white", "white", "white grid color", (1.00, 1.00, 1.00, 1.00)),
("Grid 5.0.0", "Grid 5.0.0", "Grid 5.0.0 color", (0.33, 0.33, 0.33, 0.50)),
("DARK_GRAY", "Dark Gray", "Dark gray grid", (0.10, 0.10, 0.10, 1.00)),
]
WIRE_PRESETS =[
("orange", "orange", "orange wire", (0.71, 0.21, 0.05)),
("Wire 5.0.0", "Wire 5.0.0", "Wire 5.0.0 color", (0.00, 0.00, 0.00)),
("WHITE", "White", "White wire", (1.00, 1.00, 1.00)),
]
CAMERA_PRESETS =[
("Cam", "Cam", "Cam camera color", (0.47, 0.55, 1.00)),
("Cam 5.0.0", "Cam 5.0.0", "Cam 5.0.0 color", (0.00, 0.00, 0.00)),
("YELLOW", "Yellow", "Yellow camera", (1.00, 1.00, 0.00)),
]
OUTLINER_PRESETS =[
("Outliner 5.0.0", "Outliner 5.0.0", "Outliner 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
("DARK_TEAL", "Dark Teal", "Dark teal outliner", (0.00, 0.20, 0.20, 1.00), (0.00, 0.10, 0.10, 1.00)),
]
TEXT_EDITOR_PRESETS =[
("Text 5.0.0", "Text 5.0.0", "Text Editor 5.0.0 colors", (0.19, 0.19, 0.19, 0.70), (0.14, 0.14, 0.14, 1.00)),
("DARK_GREEN", "Dark Green", "Dark green text editor", (0.00, 0.20, 0.00, 1.00), (0.00, 0.10, 0.00, 1.00)),
]
BACKGROUND_TYPES =[
('SINGLE_COLOR', "Single Color", "Uniform background color"),
('LINEAR', "Linear", "Linear gradient background"),
('RADIAL', "Vignette", "Radial gradient simulating a vignette effect"),
]
# ==============================================================================
# リアルタイム更新用コールバック関数(ビューポート色・太陽)
# ==============================================================================
def format_tuple(t):
return '(' + ', '.join(f"{x:.3f}" for x in t) + ')'
def update_custom_grid_scale(self, context):
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
area.spaces.active.overlay.grid_scale = self.custom_grid_scale
def update_grid_color(self, context):
bpy.context.preferences.themes[0].view_3d.grid = self.grid_color
def update_wire_color(self, context):
bpy.context.preferences.themes[0].view_3d.wire = self.wire_color
def update_camera_color(self, context):
bpy.context.preferences.themes[0].view_3d.camera = self.camera_color
def update_background_color(self, context):
gradients = bpy.context.preferences.themes[0].view_3d.space.gradients
if self.background_type == 'SINGLE_COLOR':
gradients.background_type = 'LINEAR'
gradients.high_gradient = gradients.gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
else:
gradients.background_type = self.background_type
gradients.high_gradient = self.custom_gradient_low if self.reverse_gradient else self.custom_gradient_high
gradients.gradient = self.custom_gradient_high if self.reverse_gradient else self.custom_gradient_low
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
def update_header_color(self, context):
bpy.context.preferences.themes[0].view_3d.space.header = self.header_color
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
def update_render_color(self, context):
bpy.context.preferences.themes[0].image_editor.space.back = self.render_color[:3]
world = bpy.data.worlds.get('MyWorld') or bpy.data.worlds.new('MyWorld')
context.scene.world = world
world.use_nodes = True
bg_node = world.node_tree.nodes.get('Background') or world.node_tree.nodes.new(type='ShaderNodeBackground')
bg_node.name = 'Background'
bg_node.inputs[0].default_value = self.render_color
bg_node.inputs[1].default_value = self.render_environment_strength
output_node = world.node_tree.nodes.get('World Output') or world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
output_node.name = 'World Output'
world.node_tree.links.new(bg_node.outputs[0], output_node.inputs['Surface'])
def update_outliner_color(self, context):
space = bpy.context.preferences.themes[0].outliner.space
space.header = self.outliner_header_color
space.back = self.outliner_background_color[:3]
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'OUTLINER': area.tag_redraw()
def update_text_editor_color(self, context):
space = bpy.context.preferences.themes[0].text_editor.space
space.header = self.text_editor_header_color
space.back = self.text_editor_background_color[:3]
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'TEXT_EDITOR': area.tag_redraw()
def get_or_create_sun():
sun_obj = bpy.data.objects.get("Sun")
if sun_obj is None or sun_obj.type != 'LIGHT' or sun_obj.data.type != 'SUN':
if sun_obj:
try: bpy.data.objects.remove(sun_obj, do_unlink=True)
except: pass
bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 0))
sun_obj = bpy.context.active_object
sun_obj.name = "Sun"; sun_obj.data.name = "Sun"
return sun_obj
def update_sun(self, context):
sun = get_or_create_sun()
sun.location = self.sun_location
if self.sun_control_mode == 'ANGLE':
sun.rotation_euler = self.sun_rotation
else:
target_vec = Vector(self.sun_target_location)
sun_vec = Vector(self.sun_location)
if (target_vec - sun_vec).length_squared < 0.0001: return
direction = target_vec - sun_vec
sun.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
sun.data.energy = self.sun_strength
# ==============================================================================
# リアルタイム更新用コールバック関数(透視投影 視座位置)
# ==============================================================================
_is_updating_view = False
def update_view_position(self, context):
"""スライダーが操作されたときに視点を更新する"""
global _is_updating_view
if _is_updating_view: return
props = getattr(context.scene, "persp_view_props", None)
if not props: return
limit = props.slider_limit
v = list(props.view_pos)
clamped = False
for i in range(3):
if v[i] > limit: v[i] = limit; clamped = True
elif v[i] < -limit: v[i] = -limit; clamped = True
if clamped:
_is_updating_view = True
props.view_pos = v
_is_updating_view = False
_is_updating_view = True
try:
cam_pos = Vector(props.view_pos)
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
r3d = space.region_3d
r3d.view_perspective = 'PERSP'
target_pos = Vector(r3d.view_location)
rel_pos = cam_pos - target_pos
dist = rel_pos.length
if dist > 0.001:
r3d.view_distance = dist
r3d.view_rotation = rel_pos.to_track_quat('Z', 'Y')
finally:
_is_updating_view = False
def view_sync_timer():
"""マウス操作での視点移動を検知し、スライダー(UI)を同期するタイマー"""
global _is_updating_view
if _is_updating_view: return 0.05
context = bpy.context
if getattr(context, "scene", None) is None: return 0.05
props = getattr(context.scene, "persp_view_props", None)
if not props: return 0.05
r3d = None
target_area = None
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
r3d = space.region_3d
target_area = area
break
if r3d: break
if r3d: break
if r3d and target_area:
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
current_pos = Vector(props.view_pos)
if (current_pos - actual_cam_pos).length > 0.001:
_is_updating_view = True
max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
if max_val > props.slider_limit:
props.slider_limit = max_val + 50.0
props.view_pos = actual_cam_pos
_is_updating_view = False
target_area.tag_redraw()
return 0.05
# ------------------------------------------------------------------------
# Property Groups
# ------------------------------------------------------------------------
class SunSettingsProperties(PropertyGroup):
sun_control_mode: EnumProperty(name="制御モード", items=[('ANGLE', "角度", "太陽の回転を直接指定"), ('TARGET', "ターゲット", "指定位置に太陽を向ける")], default=CURRENT_DEFAULTS["sun_control_mode"], update=update_sun)
sun_target_location: FloatVectorProperty(name="ターゲット位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_target_location"], update=update_sun)
sun_rotation: FloatVectorProperty(name="角度", subtype='EULER', unit='ROTATION', default=CURRENT_DEFAULTS["sun_rotation"], update=update_sun)
sun_location: FloatVectorProperty(name="位置", subtype='XYZ', default=CURRENT_DEFAULTS["sun_location"], update=update_sun)
sun_strength: FloatProperty(name="強さ", default=CURRENT_DEFAULTS["sun_strength"], min=0.0, update=update_sun)
class ViewportColorProperties(PropertyGroup):
custom_grid_scale: FloatProperty(name="Scale", default=CURRENT_DEFAULTS["custom_grid_scale"], min=0.001, update=update_custom_grid_scale)
grid_color: FloatVectorProperty(name="Grid Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["grid_color"], update=update_grid_color)
grid_preset: EnumProperty(name="Grid Preset", items=[(p[0], p[1], p[2]) for p in GRID_PRESETS], default=CURRENT_DEFAULTS["grid_preset"], update=lambda self, context: self.update_grid_preset(context))
wire_color: FloatVectorProperty(name="Wire Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["wire_color"], update=update_wire_color)
wire_preset: EnumProperty(name="Wire Preset", items=[(p[0], p[1], p[2]) for p in WIRE_PRESETS], default=CURRENT_DEFAULTS["wire_preset"], update=lambda self, context: self.update_wire_preset(context))
camera_color: FloatVectorProperty(name="Camera Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["camera_color"], update=update_camera_color)
camera_preset: EnumProperty(name="Camera Preset", items=[(p[0], p[1], p[2]) for p in CAMERA_PRESETS], default=CURRENT_DEFAULTS["camera_preset"], update=lambda self, context: self.update_camera_preset(context))
background_type: EnumProperty(name="Background Type", items=BACKGROUND_TYPES, default=CURRENT_DEFAULTS["background_type"], update=update_background_color)
header_color: FloatVectorProperty(name="Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["header_color"], update=update_header_color)
header_preset: EnumProperty(name="Header Preset", items=[(p[0], p[1], p[2]) for p in HEADER_PRESETS], default=CURRENT_DEFAULTS["header_preset"], update=lambda self, context: self.update_header_preset(context))
custom_gradient_high: FloatVectorProperty(name="Gradient High Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_high"], update=update_background_color)
custom_gradient_low: FloatVectorProperty(name="Gradient Low Color", subtype='COLOR', size=3, min=0.0, max=1.0, default=CURRENT_DEFAULTS["custom_gradient_low"], update=update_background_color)
reverse_gradient: BoolProperty(name="Reverse Gradient", default=CURRENT_DEFAULTS["reverse_gradient"], update=update_background_color)
preset: EnumProperty(name="Color Preset", items=[(p[0], p[1], p[2]) for p in BASE_PRESETS], default=CURRENT_DEFAULTS["preset"], update=lambda self, context: self.update_preset(context))
render_color: FloatVectorProperty(name="Render Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["render_color"], update=update_render_color)
render_preset: EnumProperty(name="Render Preset", items=[(p[0], p[1], p[2]) for p in RENDER_PRESETS], default=CURRENT_DEFAULTS["render_preset"], update=lambda self, context: self.update_render_preset(context))
render_environment_strength: FloatProperty(name="Render Environment Strength", default=CURRENT_DEFAULTS["render_environment_strength"], min=0.0, max=1900.0, update=update_render_color)
outliner_header_color: FloatVectorProperty(name="Outliner Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_header_color"], update=update_outliner_color)
outliner_background_color: FloatVectorProperty(name="Outliner Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["outliner_background_color"], update=update_outliner_color)
outliner_preset: EnumProperty(name="Outliner Preset", items=[(p[0], p[1], p[2]) for p in OUTLINER_PRESETS], default=CURRENT_DEFAULTS["outliner_preset"], update=lambda self, context: self.update_outliner_preset(context))
text_editor_header_color: FloatVectorProperty(name="Text Editor Header Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_header_color"], update=update_text_editor_color)
text_editor_background_color: FloatVectorProperty(name="Text Editor Background Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=CURRENT_DEFAULTS["text_editor_background_color"], update=update_text_editor_color)
text_editor_preset: EnumProperty(name="Text Editor Preset", items=[(p[0], p[1], p[2]) for p in TEXT_EDITOR_PRESETS], default=CURRENT_DEFAULTS["text_editor_preset"], update=lambda self, context: self.update_text_editor_preset(context))
def update_grid_preset(self, context):
for p in GRID_PRESETS:
if p[0] == self.grid_preset: self.grid_color = p[3]; break
def update_wire_preset(self, context):
for p in WIRE_PRESETS:
if p[0] == self.wire_preset: self.wire_color = p[3]; break
def update_camera_preset(self, context):
for p in CAMERA_PRESETS:
if p[0] == self.camera_preset: self.camera_color = p[3]; break
def update_preset(self, context):
for p in BASE_PRESETS:
if p[0] == self.preset: self.custom_gradient_high = p[3]; self.custom_gradient_low = p[4]; break
def update_header_preset(self, context):
for p in HEADER_PRESETS:
if p[0] == self.header_preset: self.header_color = p[3]; break
def update_render_preset(self, context):
for p in RENDER_PRESETS:
if p[0] == self.render_preset: self.render_color = p[3]; break
def update_outliner_preset(self, context):
for p in OUTLINER_PRESETS:
if p[0] == self.outliner_preset: self.outliner_header_color = p[3]; self.outliner_background_color = p[4]; break
def update_text_editor_preset(self, context):
for p in TEXT_EDITOR_PRESETS:
if p[0] == self.text_editor_preset: self.text_editor_header_color = p[3]; self.text_editor_background_color = p[4]; break
class PerspViewProperties(PropertyGroup):
slider_limit: FloatProperty(name="Range Limit", default=300.0, min=10.0, max=10000.0)
view_pos: FloatVectorProperty(name="View Position", size=3, soft_min=-10000.0, soft_max=10000.0,
default=CURRENT_DEFAULTS.get('view_pos', VIEW_POS_INIT),
update=update_view_position)
# ------------------------------------------------------------------------
# Operators
# ------------------------------------------------------------------------
class OT_ViewCenterFront(Operator):
bl_idname = f"{PREFIX}_wm.view_center_front"
bl_label = "Center 0,0,0 (Front View)"
bl_description = "原点(0,0,0)を画面中央に配置し、Yマイナス方向からの視点(正面)にします"
def execute(self, context):
for area in context.screen.areas:
if area.type == 'VIEW_3D':
rv3d = area.spaces.active.region_3d
if rv3d:
rv3d.view_location = (0.0, 0.0, 0.0)
rv3d.view_rotation = Euler((radians(90.0), 0.0, 0.0), 'XYZ').to_quaternion()
if rv3d.view_distance < 10.0:
rv3d.view_distance = 60.0
return {'FINISHED'}
class OT_CopyFullScript(Operator):
bl_idname = f"{PREFIX}_wm.copy_script"
bl_label = "Copy Script"
def execute(self, context):
props = context.scene.viewport_color_props
sun_props = context.scene.sun_settings_props
persp_props = context.scene.persp_view_props
code = ""
file_path = globals().get('__file__')
if file_path:
if file_path.endswith('.pyc') or file_path.endswith('.pyo'):
file_path = file_path[:-1]
try:
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
code = f.read()
except Exception:
pass
if not code or SOURCE_ID_TAG not in code:
for t in bpy.data.texts:
if SOURCE_ID_TAG in t.as_string():
code = t.as_string()
break
if not code:
self.report({'ERROR'}, "スクリプトのソースが見つかりません。")
return {'CANCELLED'}
def fmt_vec(v): return "(" + ", ".join(f"{x:.4f}" for x in v) + ")"
def fmt_str(s): return f"'{s}'"
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "sun_control_mode": {fmt_str(sun_props.sun_control_mode)},\n'
new_dict += f' "grid_preset": {fmt_str(props.grid_preset)},\n'
new_dict += f' "wire_preset": {fmt_str(props.wire_preset)},\n'
new_dict += f' "camera_preset": {fmt_str(props.camera_preset)},\n'
new_dict += f' "background_type": {fmt_str(props.background_type)},\n'
new_dict += f' "header_preset": {fmt_str(props.header_preset)},\n'
new_dict += f' "preset": {fmt_str(props.preset)},\n'
new_dict += f' "render_preset": {fmt_str(props.render_preset)},\n'
new_dict += f' "outliner_preset": {fmt_str(props.outliner_preset)},\n'
new_dict += f' "text_editor_preset": {fmt_str(props.text_editor_preset)},\n'
new_dict += f' "sun_target_location": {fmt_vec(sun_props.sun_target_location)},\n'
new_dict += f' "sun_rotation": {fmt_vec(sun_props.sun_rotation)},\n'
new_dict += f' "sun_location": {fmt_vec(sun_props.sun_location)},\n'
new_dict += f' "sun_strength": {sun_props.sun_strength:.4f},\n'
new_dict += f' "custom_grid_scale": {props.custom_grid_scale:.4f},\n'
new_dict += f' "grid_color": {fmt_vec(props.grid_color)},\n'
new_dict += f' "wire_color": {fmt_vec(props.wire_color)},\n'
new_dict += f' "camera_color": {fmt_vec(props.camera_color)},\n'
new_dict += f' "header_color": {fmt_vec(props.header_color)},\n'
new_dict += f' "custom_gradient_high": {fmt_vec(props.custom_gradient_high)},\n'
new_dict += f' "custom_gradient_low": {fmt_vec(props.custom_gradient_low)},\n'
new_dict += f' "reverse_gradient": {props.reverse_gradient},\n'
new_dict += f' "render_color": {fmt_vec(props.render_color)},\n'
new_dict += f' "render_environment_strength": {props.render_environment_strength:.4f},\n'
new_dict += f' "outliner_header_color": {fmt_vec(props.outliner_header_color)},\n'
new_dict += f' "outliner_background_color": {fmt_vec(props.outliner_background_color)},\n'
new_dict += f' "text_editor_header_color": {fmt_vec(props.text_editor_header_color)},\n'
new_dict += f' "text_editor_background_color": {fmt_vec(props.text_editor_background_color)},\n'
new_dict += f' "view_pos": {fmt_vec(persp_props.view_pos)},\n'
new_dict += "}\n"
try:
start, end = "# <BEGIN" + "_DICT>", "# <END" + "_DICT>"
parts = code.split(start)
if len(parts) < 2: return {'CANCELLED'}
pre = parts[0]
post = code.split(end)[1]
lines = pre.split('\n')
if len(lines) > 0 and lines[0].startswith("# Copied:"): lines.pop(0)
pre = '\n'.join(lines).lstrip('\n')
final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n{pre}{start}\n{new_dict}{end}{post}"
context.window_manager.clipboard = final
self.report({'INFO'}, "現在の数値でコードをコピーしました!")
except Exception as e:
self.report({'ERROR'}, f"Failed to parse code: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class OVERLAY_OT_set_grid_scale(Operator):
bl_idname = f"{PREFIX}_overlay.set_grid_scale"
bl_label = "Set Grid Scale"
scale_value: FloatProperty()
def execute(self, context):
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D': area.spaces.active.overlay.grid_scale = self.scale_value
return {'FINISHED'}
class SUN_OT_Create(Operator):
bl_idname = f"{PREFIX}.create_sun"; bl_label = "太陽を作成"
def execute(self, context):
get_or_create_sun()
self.report({'INFO'}, "太陽を作成しました。"); return {'FINISHED'}
class SUN_OT_Reset(Operator):
bl_idname = f"{PREFIX}.reset_sun"; bl_label = "太陽の設定を初期値にリセット"
def execute(self, context):
props = context.scene.sun_settings_props
props.sun_control_mode, props.sun_target_location = 'ANGLE', (0.0, 0.0, 0.0)
props.sun_rotation = (radians(45.0), radians(0.0), radians(45.0))
props.sun_location, props.sun_strength = (0.0, 0.0, 10.0), 2.5
update_sun(props, context)
self.report({'INFO'}, "太陽の設定をリセットしました。"); return {'FINISHED'}
class RemoveAllPanels(Operator):
bl_idname = f"{PREFIX}_wm.remove_all_panels"; bl_label = PANEL_LABELS["REMOVE"]
def execute(self, context): unregister(); return {'FINISHED'}
# --- 透視投影用のオペレーター ---
class PERSP_OT_GetCurrentView(Operator):
bl_idname = f"{PREFIX}_persp.get_current_view"
bl_label = "Get Current View & Update"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
r3d = context.space_data.region_3d if context.space_data else None
if not props or not r3d: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
max_val = max(abs(actual_cam_pos.x), abs(actual_cam_pos.y), abs(actual_cam_pos.z))
if max_val > props.slider_limit: props.slider_limit = max_val + 50.0
props.view_pos = actual_cam_pos
return {'FINISHED'}
class PERSP_OT_ResetView(Operator):
bl_idname = f"{PREFIX}_persp.reset_view"
bl_label = VIEW_RESET_BTN_TEXT
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
if props:
for window in context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D': space.region_3d.view_location = (0.0, 0.0, 0.0)
props.view_pos = VIEW_POS_INIT
return {'FINISHED'}
class PERSP_OT_CenterSelected(Operator):
bl_idname = f"{PREFIX}_persp.center_selected"
bl_label = "Center Selected Object"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
bpy.ops.view3d.view_selected()
r3d = context.space_data.region_3d if context.space_data else None
props = getattr(context.scene, "persp_view_props", None)
if r3d and props:
target_pos = Vector(r3d.view_location)
props.view_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
return {'FINISHED'}
class PERSP_OT_CopyActualViewPos(Operator):
bl_idname = f"{PREFIX}_persp.copy_actual_pos"
bl_label = "Copy Position Only"
def execute(self, context):
r3d = context.space_data.region_3d if context.space_data else None
if not r3d: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
p = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
context.window_manager.clipboard = f"Actual View Pos: ({p.x:.4f}, {p.y:.4f}, {p.z:.4f})"
self.report({'INFO'}, "視座位置をコピーしました")
return {'FINISHED'}
class PERSP_OT_CopyAngles(Operator):
bl_idname = f"{PREFIX}_persp.copy_angles"
bl_label = "Copy Full Info (Pos & Angles)"
def execute(self, context):
props = getattr(context.scene, "persp_view_props", None)
r3d = context.space_data.region_3d if context.space_data else None
if not r3d or not props: return {'CANCELLED'}
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
vec = target_pos - actual_cam_pos
length = vec.length
if length < 0.0001: return {'CANCELLED'}
ang_x = math.degrees(math.acos(vec.x / length))
ang_y = math.degrees(math.acos(vec.y / length))
ang_z = math.degrees(math.acos(vec.z / length))
pl_x = math.degrees(math.asin(vec.x / length))
pl_y = math.degrees(math.asin(vec.y / length))
pl_z = math.degrees(math.asin(vec.z / length))
info_text = (
f"--- View Direction Info ---\n"
f"[ Actual 3D View Status ]\n"
f"Actual View Pos : ({actual_cam_pos.x:.4f}, {actual_cam_pos.y:.4f}, {actual_cam_pos.z:.4f})\n"
f"Target Pos : ({target_pos.x:.4f}, {target_pos.y:.4f}, {target_pos.z:.4f})\n"
f"Distance : {length:.4f}\n\n"
f"[ Direction Angles (軸そのものとの角度 0〜180°) ]\n"
f"Angle from X Axis : {ang_x:.2f} deg\n"
f"Angle from Y Axis : {ang_y:.2f} deg\n"
f"Angle from Z Axis : {ang_z:.2f} deg\n\n"
f"[ Planar Angles (直感的な傾き・ズレ角 -90〜90°) ]\n"
f"X (横のズレ角) : {pl_x:.2f} deg\n"
f"Y (前後の傾き) : {pl_y:.2f} deg\n"
f"Z (仰角・俯角) : {pl_z:.2f} deg\n"
)
context.window_manager.clipboard = info_text
self.report({'INFO'}, "情報全体をクリップボードにコピーしました")
return {'FINISHED'}
# ------------------------------------------------------------------------
# Panels
# ------------------------------------------------------------------------
class BasePanel(Panel):
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = ADDON_CATEGORY_NAME
class VIEW3D_PT_CopyPanel(BasePanel):
bl_label = PANEL_LABELS["COPY"]; bl_idname = COPY_PANEL_IDNAME; bl_order = 0
def draw(self, context):
layout = self.layout
row = layout.row()
row.scale_y = 1.2
row.operator(f"{PREFIX}_wm.copy_script", icon='COPY_ID', text="最新数値付きコードコピー")
row_view = layout.row()
row_view.operator(f"{PREFIX}_wm.view_center_front", icon='VIEWZOOM', text="0,0,0 を正面(Y-)から見る")
class VIEW3D_PT_PerspControlPanel(BasePanel):
bl_label = PANEL_LABELS["PERSP"]; bl_idname = PERSP_PANEL_IDNAME; bl_order = 1
def draw(self, context):
layout = self.layout
props = getattr(context.scene, "persp_view_props", None)
if not props: return
box = layout.box()
box.label(text="Perspective Viewpoint", icon='VIEW_CAMERA')
box.prop(props, "slider_limit", text="Range Limit (+/-)")
col = box.column(align=True)
col.prop(props, "view_pos", text="X", index=0)
col.prop(props, "view_pos", text="Y", index=1)
col.prop(props, "view_pos", text="Z", index=2)
box.separator()
box.operator(f"{PREFIX}_persp.get_current_view", icon='RESTRICT_VIEW_OFF')
box.operator(f"{PREFIX}_persp.reset_view", icon='LOOP_BACK')
layout.operator(f"{PREFIX}_persp.center_selected", icon='VIEWZOOM')
layout.separator()
box_info = layout.box()
box_info.label(text="Actual View Status", icon='INFO')
r3d = context.space_data.region_3d if context.space_data else None
if r3d:
target_pos = Vector(r3d.view_location)
actual_cam_pos = target_pos + r3d.view_rotation @ Vector((0.0, 0.0, r3d.view_distance))
vec = target_pos - actual_cam_pos
length = vec.length
col_pos = box_info.column(align=True)
col_pos.label(text="[ Actual Position ]", icon='VIEW_CAMERA')
col_pos.label(text=f" X: {actual_cam_pos.x:.4f}")
col_pos.label(text=f" Y: {actual_cam_pos.y:.4f}")
col_pos.label(text=f" Z: {actual_cam_pos.z:.4f}")
col_pos.label(text=f" Distance: {length:.4f}")
box_info.operator(f"{PREFIX}_persp.copy_actual_pos", icon='COPYDOWN')
box_info.separator()
col_ang = box_info.column(align=True)
if length > 0.0001:
a_x = math.degrees(math.acos(vec.x / length))
a_y = math.degrees(math.acos(vec.y / length))
a_z = math.degrees(math.acos(vec.z / length))
p_x = math.degrees(math.asin(vec.x / length))
p_y = math.degrees(math.asin(vec.y / length))
p_z = math.degrees(math.asin(vec.z / length))
col_ang.label(text="[ Direction Angles (軸との角度) ]", icon='ORIENTATION_GLOBAL')
col_ang.label(text=f" X: {a_x:.2f}°")
col_ang.label(text=f" Y: {a_y:.2f}°")
col_ang.label(text=f" Z: {a_z:.2f}°")
col_ang.separator()
col_ang.label(text="[ Planar Angles (直感的な傾き) ]", icon='DRIVER_ROTATIONAL_DIFFERENCE')
col_ang.label(text=f" X (ズレ角): {p_x:.2f}°")
col_ang.label(text=f" Y (ズレ角): {p_y:.2f}°")
col_ang.label(text=f" Z (仰俯角): {p_z:.2f}°")
else:
col_ang.label(text=" Target is too close")
box_info.separator()
box_info.operator(f"{PREFIX}_persp.copy_angles", icon='COPYDOWN')
else:
box_info.label(text="Please use in 3D View")
class VIEW3D_PT_OverlayPanel(BasePanel):
bl_label = PANEL_LABELS["OVERLAY"]; bl_idname = OVERLAY_PANEL_IDNAME; bl_order = 2
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
if context.space_data.type == 'VIEW_3D':
layout.prop(context.space_data.overlay, "show_floor", text="Floor")
layout.prop(props, "custom_grid_scale", text="Scale 数値入力")
row = layout.row(align=True)
row.operator(f"{PREFIX}_overlay.set_grid_scale", text="入力値").scale_value = props.custom_grid_scale
row.operator(f"{PREFIX}_overlay.set_grid_scale", text="10.0").scale_value = 10.0
row.operator(f"{PREFIX}_overlay.set_grid_scale", text="100.0").scale_value = 100.0
else:
layout.label(text="3D Viewport is required.")
class VIEW3D_PT_solid_background_panel(BasePanel):
bl_label = PANEL_LABELS["BACKGROUND"]; bl_idname = BG_PANEL_IDNAME_1; bl_order = 3
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "preset", text="Background Preset")
layout.prop(props, "background_type", expand=True)
layout.prop(props, "custom_gradient_high", text="Color High" if props.background_type != 'SINGLE_COLOR' else "Color")
if props.background_type != 'SINGLE_COLOR': layout.prop(props, "custom_gradient_low")
layout.prop(props, "reverse_gradient", text="Reverse Gradient")
class VIEW3D_PT_HeaderPanel(BasePanel):
bl_label = PANEL_LABELS["HEADER"]; bl_idname = HEADER_PANEL_IDNAME; bl_order = 4
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "header_preset", text="Header Preset")
layout.prop(props, "header_color")
class VIEW3D_PT_RenderPanel(BasePanel):
bl_label = PANEL_LABELS["RENDER"]; bl_idname = RENDER_PANEL_IDNAME; bl_order = 5
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "render_preset")
layout.prop(props, "render_color")
layout.prop(props, "render_environment_strength")
class VIEW3D_PT_SunPanel(BasePanel):
bl_label = PANEL_LABELS["SUN"]; bl_idname = SUN_PANEL_IDNAME; bl_order = 6
def draw(self, context):
layout, props = self.layout, context.scene.sun_settings_props
row = layout.row(align=True)
row.operator(f"{PREFIX}.create_sun", text="太陽作成ボタン")
row.operator(f"{PREFIX}.reset_sun", icon='FILE_REFRESH', text="")
layout.separator()
layout.prop(props, "sun_control_mode", expand=True)
if props.sun_control_mode == 'ANGLE': layout.prop(props, "sun_rotation")
else: layout.prop(props, "sun_target_location")
layout.prop(props, "sun_location"); layout.prop(props, "sun_strength")
class VIEW3D_PT_GridPanel(BasePanel):
bl_label = PANEL_LABELS["GRID"]; bl_idname = GRID_PANEL_IDNAME; bl_order = 7
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "grid_preset")
layout.prop(props, "grid_color")
class VIEW3D_PT_WirePanel(BasePanel):
bl_label = PANEL_LABELS["WIRE"]; bl_idname = WIRE_PANEL_IDNAME; bl_order = 8
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "wire_preset")
layout.prop(props, "wire_color")
class VIEW3D_PT_CameraPanel(BasePanel):
bl_label = PANEL_LABELS["CAMERA"]; bl_idname = CAMERA_PANEL_IDNAME; bl_order = 9
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "camera_preset")
layout.prop(props, "camera_color")
class VIEW3D_PT_OutlinerPanel(BasePanel):
bl_label = PANEL_LABELS["OUTLINER"]; bl_idname = OUTLINER_PANEL_IDNAME; bl_order = 10
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "outliner_preset")
layout.prop(props, "outliner_header_color")
layout.prop(props, "outliner_background_color")
class VIEW3D_PT_TextEditorPanel(BasePanel):
bl_label = PANEL_LABELS["TEXT_EDITOR"]; bl_idname = TEXT_EDITOR_PANEL_IDNAME; bl_order = 11
def draw(self, context):
layout, props = self.layout, context.scene.viewport_color_props
layout.prop(props, "text_editor_preset")
layout.prop(props, "text_editor_header_color")
layout.prop(props, "text_editor_background_color")
class VIEW3D_PT_LinkPanel(BasePanel):
bl_label = PANEL_LABELS["LINK"]; bl_idname = LINK_PANEL_IDNAME; bl_order = 12
def draw(self, context):
layout = self.layout
layout.operator("wm.url_open", text="進化版 画面中央 透視投影視座位置 20260319bb", icon='URL').url = "<https://www.notion.so/20260319bb-327f5dacaf43801e8e37ce489dc1d593>"
layout.operator("wm.url_open", text="5520 背景色 変更 20260316版", icon='URL').url = "<https://www.notion.so/5520-20260316-314f5dacaf4380da9be4c05551d40710>"
class VIEW3D_PT_RemovePanel(BasePanel):
bl_label = PANEL_LABELS["REMOVE"]; bl_idname = REMOVE_PANEL_IDNAME; bl_order = 13
def draw(self, context):
self.layout.operator(f"{PREFIX}_wm.remove_all_panels", text=PANEL_LABELS["REMOVE"])
# ------------------------------------------------------------------------
# Registration
# ------------------------------------------------------------------------
classes =[
SunSettingsProperties, ViewportColorProperties, PerspViewProperties,
OT_CopyFullScript, OT_ViewCenterFront,
OVERLAY_OT_set_grid_scale,
SUN_OT_Create, SUN_OT_Reset,
PERSP_OT_GetCurrentView, PERSP_OT_ResetView, PERSP_OT_CenterSelected, PERSP_OT_CopyActualViewPos, PERSP_OT_CopyAngles,
RemoveAllPanels,
VIEW3D_PT_CopyPanel, VIEW3D_PT_PerspControlPanel,
VIEW3D_PT_OverlayPanel, VIEW3D_PT_solid_background_panel,
VIEW3D_PT_HeaderPanel, VIEW3D_PT_RenderPanel, VIEW3D_PT_SunPanel, VIEW3D_PT_GridPanel,
VIEW3D_PT_WirePanel, VIEW3D_PT_CameraPanel, VIEW3D_PT_OutlinerPanel, VIEW3D_PT_TextEditorPanel,
VIEW3D_PT_LinkPanel, VIEW3D_PT_RemovePanel
]
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.viewport_color_props = PointerProperty(type=ViewportColorProperties)
bpy.types.Scene.sun_settings_props = PointerProperty(type=SunSettingsProperties)
bpy.types.Scene.persp_view_props = PointerProperty(type=PerspViewProperties)
def apply_initial_settings():
if bpy.context.scene and hasattr(bpy.context.scene, 'viewport_color_props'):
props = bpy.context.scene.viewport_color_props
sun_props = bpy.context.scene.sun_settings_props
persp_props = bpy.context.scene.persp_view_props
for key, val in CURRENT_DEFAULTS.items():
if hasattr(props, key): setattr(props, key, val)
elif hasattr(sun_props, key): setattr(sun_props, key, val)
elif hasattr(persp_props, key): setattr(persp_props, key, val)
update_background_color(props, bpy.context)
update_header_color(props, bpy.context)
update_render_color(props, bpy.context)
update_grid_color(props, bpy.context)
update_wire_color(props, bpy.context)
update_camera_color(props, bpy.context)
update_outliner_color(props, bpy.context)
update_text_editor_color(props, bpy.context)
update_sun(sun_props, bpy.context)
bpy.app.timers.register(apply_initial_settings, first_interval=0.1)
if not bpy.app.timers.is_registered(view_sync_timer):
bpy.app.timers.register(view_sync_timer)
def unregister():
if hasattr(bpy.types.Scene, 'viewport_color_props'): del bpy.types.Scene.viewport_color_props
if hasattr(bpy.types.Scene, 'sun_settings_props'): del bpy.types.Scene.sun_settings_props
if hasattr(bpy.types.Scene, 'persp_view_props'): del bpy.types.Scene.persp_view_props
for cls in reversed(classes):
try: bpy.utils.unregister_class(cls)
except RuntimeError: pass
if bpy.app.timers.is_registered(view_sync_timer):
bpy.app.timers.unregister(view_sync_timer)
if __name__ == "__main__":
register()