20260604 blender million
1号完成
2号成功
3号成功

bl_info = {
"name": "Globe Addon",
"author": "AI",
"version": (1, 2, 8),
"blender": (5, 0, 0), # Blender 5.x 専用
"location": "View3D > Sidebar",
"description": "Parametric Globe Generator for Blender 5.x",
"category": "3D View",
}
import bpy
import bmesh
import math
from mathutils import Matrix, Vector
# ==========================================
# 1. コード先頭の設定項目
# ==========================================
TITLE = "Globe Addon"
TAB_NAME = "GlobeTab"
PANEL_TITLE = "Globe Panel"
PREFIX_NAME = "globe_addon"
COLLECTION_MAIN = "Globe_Main"
COLLECTION_SUB = "Globe_Sub"
OBJ_NAME = "GlobeObj"
LINK_URL = "<https://app.notion.com/p/20260708-397f5dacaf438022ad34c17f3d743608>"
# 初期値 (分割数)
SEG_U_MAIN = 64
SEG_V_MAIN = 32
SEG_CIRCLE = 64
SEG_U_SUB = 32
SEG_V_SUB = 16
# 初期値 (サイズ・色など)
SPHERE_RADIUS = 10.0
SPHERE_THICKNESS = 1.0
SPHERE_COLOR = (0.2, 0.5, 0.8, 1.0)
SPHERE_INNER_COLOR = (0.8, 0.4, 0.1, 1.0)
LATITUDE_COUNT = 3
LONGITUDE_COUNT = 4
HOLE_RADIUS = 1.0
PLANE_COLOR = (0.9, 0.9, 0.9, 1.0)
CAP_COLOR = (0.2, 0.8, 0.4, 1.0)
CENTER_SPHERE_RADIUS = 1.0
CENTER_SPHERE_COLOR = (0.9, 0.1, 0.1, 1.0)
CONE_RADIUS = 2.0
CONE_DEPTH = 5.0
CONE_COLOR = (0.8, 0.2, 0.2, 1.0)
# ==========================================
# 2. 内部用定数
# ==========================================
NAME_SPHERE = f"{OBJ_NAME}_Sphere"
NAME_CUTTERS = f"{OBJ_NAME}_Cutters"
NAME_PLANES = f"{OBJ_NAME}_Planes"
NAME_CAPS = f"{OBJ_NAME}_Caps"
NAME_CENTER_SPHERES = f"{OBJ_NAME}_CenterSpheres"
NAME_CONE = f"{OBJ_NAME}_Cone"
MAT_SPHERE = f"{PREFIX_NAME}_Mat_Sphere"
MAT_SPHERE_INNER = f"{PREFIX_NAME}_Mat_Sphere_Inner"
MAT_PLANE = f"{PREFIX_NAME}_Mat_Plane"
MAT_CAP = f"{PREFIX_NAME}_Mat_Cap"
MAT_CENTER_SPHERE = f"{PREFIX_NAME}_Mat_Center_Sphere"
MAT_CONE = f"{PREFIX_NAME}_Mat_Cone"
PROP_ID = f"{PREFIX_NAME}_props"
# ==========================================
# 3. ユーティリティ関数群
# ==========================================
def ensure_layer_visibility(context, coll_name):
def traverse(layer_coll):
if layer_coll.collection.name == coll_name:
return layer_coll
for child in layer_coll.children:
res = traverse(child)
if res:
return res
return None
lc = traverse(context.view_layer.layer_collection)
if lc:
lc.exclude = False
lc.hide_viewport = False
def get_or_create_collection(coll_name, context):
coll = bpy.data.collections.get(coll_name)
if not coll:
coll = bpy.data.collections.new(coll_name)
if coll.name not in context.scene.collection.children.keys():
try:
context.scene.collection.children.link(coll)
except Exception:
pass
ensure_layer_visibility(context, coll_name)
return coll
def get_or_create_mesh_object(obj_name, coll):
obj = bpy.data.objects.get(obj_name)
if not obj:
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
try:
coll.objects.link(obj)
except Exception:
pass
else:
if obj.name not in coll.objects:
try:
coll.objects.link(obj)
except Exception:
pass
if not obj.data or type(obj.data).__name__ != 'Mesh':
mesh = bpy.data.meshes.new(obj_name)
obj.data = mesh
return obj
def ensure_material(mat_name):
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(mat_name)
mat.use_nodes = True
return mat
def assign_material(obj, mat, slot_index=0):
if len(obj.data.materials) <= slot_index:
while len(obj.data.materials) <= slot_index:
obj.data.materials.append(None)
obj.data.materials[slot_index] = mat
def update_material_color(mat_name, color):
mat = bpy.data.materials.get(mat_name)
if not mat or not mat.use_nodes:
return mat
# 透過表示のための設定(Eevee等)
if hasattr(mat, "blend_method"):
mat.blend_method = 'BLEND'
if hasattr(mat, "shadow_method"):
mat.shadow_method = 'HASHED'
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
if "Base Color" in node.inputs:
node.inputs["Base Color"].default_value = color
# 透明度(Alpha)の適用
if "Alpha" in node.inputs:
node.inputs["Alpha"].default_value = color[3]
break
return mat
def apply_smooth_shade(bm, use_smooth):
for face in bm.faces:
face.smooth = use_smooth
# --- メッシュ生成 ---
def create_cutters_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
bm = bmesh.new()
length = sphere_radius * 2.5
dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
for i in range(1, lat_count + 1):
theta = math.pi * i / (lat_count + 1)
for j in range(lon_count):
phi = 2 * math.pi * j / lon_count
x = math.sin(theta) * math.cos(phi)
y = math.sin(theta) * math.sin(phi)
z = math.cos(theta)
dir_vec = Vector((x, y, z))
rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
pos = dir_vec * dist
bmesh.ops.create_cone(
bm, cap_ends=True, segments=segments,
radius1=hole_radius, radius2=hole_radius, depth=length,
matrix=Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
)
apply_smooth_shade(bm, use_smooth)
bm.to_mesh(mesh)
bm.free()
def create_planes_mesh(mesh, sphere_radius, lat_count, lon_count, hole_radius, segments, use_smooth):
bm = bmesh.new()
dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
for i in range(1, lat_count + 1):
theta = math.pi * i / (lat_count + 1)
for j in range(lon_count):
phi = 2 * math.pi * j / lon_count
x = math.sin(theta) * math.cos(phi)
y = math.sin(theta) * math.sin(phi)
z = math.cos(theta)
dir_vec = Vector((x, y, z))
rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
pos = dir_vec * dist
mat_transform = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
geom = bmesh.ops.create_circle(
bm, segments=segments, radius=hole_radius, matrix=mat_transform
)
verts = geom['verts']
if len(verts) >= 3:
try:
bm.faces.new(verts)
except Exception:
try:
bmesh.ops.contextual_create(bm, geom=verts)
except:
pass
apply_smooth_shade(bm, use_smooth)
bm.to_mesh(mesh)
bm.free()
def create_center_spheres_mesh(mesh, sphere_radius, lat_count, lon_count, center_radius, hole_radius, seg_u, seg_v, use_smooth, mode):
bm = bmesh.new()
dist = math.sqrt(max(0.0, sphere_radius**2 - hole_radius**2))
# 1. テンプレートとなる1つの球体(または半球)を作成
bmesh.ops.create_uvsphere(bm, u_segments=seg_u, v_segments=seg_v, radius=center_radius)
if mode == 'OUTER':
# Z < 0 の頂点を削除(外側半球を残す)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.0001], context='VERTS')
# 切断面のフタを閉じる
bmesh.ops.holes_fill(bm, edges=[e for e in bm.edges if e.is_boundary])
elif mode == 'INNER':
# Z > 0 の頂点を削除(内側半球を残す)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z > 0.0001], context='VERTS')
bmesh.ops.holes_fill(bm, edges=[e for e in bm.edges if e.is_boundary])
base_geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
# 2. テンプレートを複製して各位置に配置
for i in range(1, lat_count + 1):
theta = math.pi * i / (lat_count + 1)
for j in range(lon_count):
phi = 2 * math.pi * j / lon_count
x = math.sin(theta) * math.cos(phi)
y = math.sin(theta) * math.sin(phi)
z = math.cos(theta)
dir_vec = Vector((x, y, z))
pos = dir_vec * dist
rot_quat = Vector((0, 0, 1)).rotation_difference(dir_vec)
# 回転と移動の行列
mat = Matrix.Translation(pos) @ rot_quat.to_matrix().to_4x4()
# 複製して変形
res = bmesh.ops.duplicate(bm, geom=base_geom)
new_verts = [elem for elem in res['geom'] if isinstance(elem, bmesh.types.BMVert)]
bmesh.ops.transform(bm, matrix=mat, verts=new_verts)
# 3. 最初に作成したテンプレート本体(原点にあるもの)を削除
bmesh.ops.delete(bm, geom=base_geom, context='VERTS')
# 法線の再計算とスムーズシェード適用
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
apply_smooth_shade(bm, use_smooth)
bm.to_mesh(mesh)
bm.free()
def setup_base_objects(context):
main_coll = get_or_create_collection(COLLECTION_MAIN, context)
sub_coll = get_or_create_collection(COLLECTION_SUB, context)
mat_sphere = ensure_material(MAT_SPHERE)
mat_sphere_inner = ensure_material(MAT_SPHERE_INNER)
mat_plane = ensure_material(MAT_PLANE)
mat_cap = ensure_material(MAT_CAP)
mat_center = ensure_material(MAT_CENTER_SPHERE)
mat_cone = ensure_material(MAT_CONE)
# 1. メイン球体
obj_sphere = get_or_create_mesh_object(NAME_SPHERE, main_coll)
assign_material(obj_sphere, mat_sphere, 0)
assign_material(obj_sphere, mat_sphere_inner, 1)
mod_solid = obj_sphere.modifiers.get("Solidify") or obj_sphere.modifiers.new(name="Solidify", type='SOLIDIFY')
mod_solid.material_offset = 1
mod_bool = obj_sphere.modifiers.get("Boolean") or obj_sphere.modifiers.new(name="Boolean", type='BOOLEAN')
mod_bool.operation = 'DIFFERENCE'
mod_bool.solver = 'EXACT'
# 2. カッター
obj_cutters = get_or_create_mesh_object(NAME_CUTTERS, sub_coll)
obj_cutters.display_type = 'WIRE'
obj_cutters.hide_render = True
obj_cutters.hide_viewport = True
mod_bool.object = obj_cutters
# 3. 穴平面
obj_planes = get_or_create_mesh_object(NAME_PLANES, main_coll)
assign_material(obj_planes, mat_plane, 0)
# 4. 球冠
obj_caps = get_or_create_mesh_object(NAME_CAPS, main_coll)
assign_material(obj_caps, mat_cap, 0)
c_mod_solid = obj_caps.modifiers.get("Solidify") or obj_caps.modifiers.new(name="Solidify", type='SOLIDIFY')
c_mod_bool = obj_caps.modifiers.get("Boolean") or obj_caps.modifiers.new(name="Boolean", type='BOOLEAN')
c_mod_bool.operation = 'INTERSECT'
c_mod_bool.solver = 'EXACT'
c_mod_bool.object = obj_cutters
# 5. 中心の球体
obj_center = get_or_create_mesh_object(NAME_CENTER_SPHERES, main_coll)
assign_material(obj_center, mat_center, 0)
# 6. 円錐
obj_cone = get_or_create_mesh_object(NAME_CONE, main_coll)
assign_material(obj_cone, mat_cone, 0)
def update_meshes_and_props(context):
props = getattr(context.scene, PROP_ID)
# --- 球体 ---
obj_sphere = bpy.data.objects.get(NAME_SPHERE)
if obj_sphere and obj_sphere.type == 'MESH':
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
apply_smooth_shade(bm, props.use_smooth_shade)
bm.to_mesh(obj_sphere.data)
bm.free()
mod_solid = obj_sphere.modifiers.get("Solidify")
if mod_solid:
mod_solid.thickness = props.sphere_thickness
obj_sphere.location = props.sphere_location
obj_sphere.rotation_euler = props.sphere_rotation
obj_sphere.hide_viewport = not props.show_sphere
obj_sphere.hide_render = not props.show_sphere
update_material_color(MAT_SPHERE, props.sphere_color)
update_material_color(MAT_SPHERE_INNER, props.sphere_inner_color)
# --- カッター ---
obj_cutters = bpy.data.objects.get(NAME_CUTTERS)
if obj_cutters and obj_cutters.type == 'MESH':
create_cutters_mesh(
obj_cutters.data, props.sphere_radius,
props.hole_latitude, props.hole_longitude, props.hole_radius,
props.segments_circle, props.use_smooth_shade
)
obj_cutters.location = props.sphere_location
obj_cutters.rotation_euler = props.sphere_rotation
# --- 穴平面 ---
obj_planes = bpy.data.objects.get(NAME_PLANES)
if obj_planes and obj_planes.type == 'MESH':
create_planes_mesh(
obj_planes.data, props.sphere_radius,
props.hole_latitude, props.hole_longitude, props.hole_radius,
props.segments_circle, props.use_smooth_shade
)
obj_planes.location = props.sphere_location
obj_planes.rotation_euler = props.sphere_rotation
obj_planes.hide_viewport = not props.show_plane
obj_planes.hide_render = not props.show_plane
update_material_color(MAT_PLANE, props.plane_color)
# --- 球冠(フタ) ---
obj_caps = bpy.data.objects.get(NAME_CAPS)
if obj_caps and obj_caps.type == 'MESH':
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=props.segments_main_u, v_segments=props.segments_main_v, radius=props.sphere_radius)
apply_smooth_shade(bm, props.use_smooth_shade)
bm.to_mesh(obj_caps.data)
bm.free()
mod_solid = obj_caps.modifiers.get("Solidify")
if mod_solid:
mod_solid.thickness = props.sphere_thickness
obj_caps.location = props.sphere_location
obj_caps.rotation_euler = props.sphere_rotation
obj_caps.hide_viewport = not props.show_cap
obj_caps.hide_render = not props.show_cap
update_material_color(MAT_CAP, props.cap_color)
# --- 穴球体 (中心球体) ---
obj_center_spheres = bpy.data.objects.get(NAME_CENTER_SPHERES)
if obj_center_spheres and obj_center_spheres.type == 'MESH':
create_center_spheres_mesh(
obj_center_spheres.data, props.sphere_radius,
props.hole_latitude, props.hole_longitude,
props.center_sphere_radius, props.hole_radius,
props.segments_sub_u, props.segments_sub_v, props.use_smooth_shade,
props.center_sphere_mode
)
obj_center_spheres.location = props.sphere_location
obj_center_spheres.rotation_euler = props.sphere_rotation
obj_center_spheres.hide_viewport = not props.show_center_sphere
obj_center_spheres.hide_render = not props.show_center_sphere
update_material_color(MAT_CENTER_SPHERE, props.center_sphere_color)
# --- 中央円錐 ---
obj_cone = bpy.data.objects.get(NAME_CONE)
if obj_cone and obj_cone.type == 'MESH':
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=props.segments_circle, radius1=props.cone_radius, radius2=0, depth=props.cone_depth)
apply_smooth_shade(bm, props.use_smooth_shade)
bm.to_mesh(obj_cone.data)
bm.free()
obj_cone.location = props.cone_location
obj_cone.rotation_euler = props.cone_rotation
update_material_color(MAT_CONE, props.cone_color)
# ==========================================
# 4. プロパティ定義
# ==========================================
is_updating = False
def trigger_update(self, context):
global is_updating
if is_updating:
return
is_updating = True
try:
update_meshes_and_props(context)
finally:
is_updating = False
class ADDON_PG_props(bpy.types.PropertyGroup):
# 品質設定
use_smooth_shade: bpy.props.BoolProperty(name="スムーズシェード適用", default=True, update=trigger_update)
segments_main_u: bpy.props.IntProperty(name="メイン球 分割 U", default=SEG_U_MAIN, min=3, soft_max=128, update=trigger_update)
segments_main_v: bpy.props.IntProperty(name="メイン球 分割 V", default=SEG_V_MAIN, min=2, soft_max=64, update=trigger_update)
segments_circle: bpy.props.IntProperty(name="穴・円錐 分割数", default=SEG_CIRCLE, min=3, soft_max=128, update=trigger_update)
segments_sub_u: bpy.props.IntProperty(name="穴球体 分割 U", default=SEG_U_SUB, min=3, soft_max=64, update=trigger_update)
segments_sub_v: bpy.props.IntProperty(name="穴球体 分割 V", default=SEG_V_SUB, min=2, soft_max=32, update=trigger_update)
show_sphere: bpy.props.BoolProperty(name="球体を表示", default=True, update=trigger_update)
sphere_radius: bpy.props.FloatProperty(name="半径", default=SPHERE_RADIUS, min=0.01, update=trigger_update)
sphere_thickness: bpy.props.FloatProperty(name="厚み", default=SPHERE_THICKNESS, update=trigger_update)
sphere_color: bpy.props.FloatVectorProperty(name="表面色 (Aで透明度)", subtype='COLOR', default=SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
sphere_inner_color: bpy.props.FloatVectorProperty(name="裏面色 (Aで透明度)", subtype='COLOR', default=SPHERE_INNER_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
sphere_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
sphere_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
hole_latitude: bpy.props.IntProperty(name="緯度方向個数", default=LATITUDE_COUNT, min=1, update=trigger_update)
hole_longitude: bpy.props.IntProperty(name="経度方向個数", default=LONGITUDE_COUNT, min=1, update=trigger_update)
hole_radius: bpy.props.FloatProperty(name="穴半径", default=HOLE_RADIUS, min=0.01, update=trigger_update)
show_plane: bpy.props.BoolProperty(name="穴平面を表示", default=True, update=trigger_update)
plane_color: bpy.props.FloatVectorProperty(name="穴平面色 (Aで透明度)", subtype='COLOR', default=PLANE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
show_cap: bpy.props.BoolProperty(name="球冠(フタ)を表示", default=True, update=trigger_update)
cap_color: bpy.props.FloatVectorProperty(name="球冠色 (Aで透明度)", subtype='COLOR', default=CAP_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
# 穴球体のプロパティ
show_center_sphere: bpy.props.BoolProperty(name="穴球体を表示", default=True, update=trigger_update)
center_sphere_mode: bpy.props.EnumProperty(
name="表示範囲",
items=[
('FULL', "全球 (両方)", "球全体を表示します"),
('OUTER', "外側半球のみ (凸)", "外側に膨らむ半球のみ表示します"),
('INNER', "内側半球のみ (凹)", "内側に凹む半球のみ表示します"),
],
default='FULL',
update=trigger_update
)
center_sphere_radius: bpy.props.FloatProperty(name="半径", default=CENTER_SPHERE_RADIUS, min=0.01, update=trigger_update)
center_sphere_color: bpy.props.FloatVectorProperty(name="色 (Aで透明度)", subtype='COLOR', default=CENTER_SPHERE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
cone_radius: bpy.props.FloatProperty(name="半径", default=CONE_RADIUS, min=0.01, update=trigger_update)
cone_depth: bpy.props.FloatProperty(name="高さ", default=CONE_DEPTH, min=0.01, update=trigger_update)
cone_color: bpy.props.FloatVectorProperty(name="色 (Aで透明度)", subtype='COLOR', default=CONE_COLOR, size=4, min=0.0, max=1.0, update=trigger_update)
cone_location: bpy.props.FloatVectorProperty(name="位置", default=(0,0,0), update=trigger_update)
cone_rotation: bpy.props.FloatVectorProperty(name="回転", default=(0,0,0), update=trigger_update)
# ==========================================
# 5. オペレーター群
# ==========================================
class ADDON_OT_sync_center_radius(bpy.types.Operator):
"""穴球体の半径を「穴平面の半径」に合わせます"""
bl_idname = f"{PREFIX_NAME}.sync_center_radius"
bl_label = "穴半径と同期"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, PROP_ID)
props.center_sphere_radius = props.hole_radius
return {'FINISHED'}
class ADDON_OT_create(bpy.types.Operator):
bl_idname = f"{PREFIX_NAME}.create"
bl_label = "作成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
try:
setup_base_objects(context)
update_meshes_and_props(context)
self.report({'INFO'}, "地球儀を生成しました。")
except Exception as e:
self.report({'ERROR'}, f"生成エラー: {str(e)}")
print(f"Globe Addon Error: {e}")
return {'FINISHED'}
class ADDON_OT_detach(bpy.types.Operator):
bl_idname = f"{PREFIX_NAME}.detach"
bl_label = "切り離し (独立)"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj_name in [NAME_SPHERE, NAME_CUTTERS, NAME_PLANES, NAME_CAPS, NAME_CENTER_SPHERES, NAME_CONE]:
obj = bpy.data.objects.get(obj_name)
if obj:
obj.name = f"{obj_name}_独立"
if obj.data.materials:
for i, mat in enumerate(obj.data.materials):
if mat:
new_mat = mat.copy()
new_mat.name = f"{mat.name}_独立"
obj.data.materials[i] = new_mat
for c_name in [COLLECTION_MAIN, COLLECTION_SUB]:
coll = bpy.data.collections.get(c_name)
if coll:
coll.name = f"{c_name}_独立"
self.report({'INFO'}, "オブジェクトをアドオンから切り離しました。")
return {'FINISHED'}
class ADDON_OT_reset(bpy.types.Operator):
bl_idname = f"{PREFIX_NAME}.reset"
bl_label = "初期値へ戻す"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
global is_updating
props = getattr(context.scene, PROP_ID)
is_updating = True
try:
# メッシュ品質の初期化
props.use_smooth_shade = True
props.segments_main_u = SEG_U_MAIN
props.segments_main_v = SEG_V_MAIN
props.segments_circle = SEG_CIRCLE
props.segments_sub_u = SEG_U_SUB
props.segments_sub_v = SEG_V_SUB
# その他の初期化
props.show_sphere = True
props.sphere_radius = SPHERE_RADIUS
props.sphere_thickness = SPHERE_THICKNESS
props.sphere_color = SPHERE_COLOR
props.sphere_inner_color = SPHERE_INNER_COLOR
props.sphere_location = (0, 0, 0)
props.sphere_rotation = (0, 0, 0)
props.hole_latitude = LATITUDE_COUNT
props.hole_longitude = LONGITUDE_COUNT
props.hole_radius = HOLE_RADIUS
props.show_plane = True
props.plane_color = PLANE_COLOR
props.show_cap = True
props.cap_color = CAP_COLOR
props.show_center_sphere = True
props.center_sphere_mode = 'FULL'
props.center_sphere_radius = CENTER_SPHERE_RADIUS
props.center_sphere_color = CENTER_SPHERE_COLOR
props.cone_radius = CONE_RADIUS
props.cone_depth = CONE_DEPTH
props.cone_color = CONE_COLOR
props.cone_location = (0, 0, 0)
props.cone_rotation = (0, 0, 0)
finally:
is_updating = False
try:
update_meshes_and_props(context)
except Exception as e:
self.report({'ERROR'}, f"エラー: {e}")
return {'FINISHED'}
class ADDON_OT_remove_addon(bpy.types.Operator):
bl_idname = f"{PREFIX_NAME}.remove_addon"
bl_label = "アドオン削除"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
def delay_unregister():
unregister()
return None
bpy.app.timers.register(delay_unregister, first_interval=0.1)
self.report({'INFO'}, "アドオンを終了しました")
return {'FINISHED'}
# ==========================================
# 6. UIパネル群
# ==========================================
class ADDON_PT_main(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME}_pt_main"
bl_label = PANEL_TITLE
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
props = getattr(context.scene, PROP_ID)
layout.operator("view3d.view_selected", text="選択オブジェクトを画面中央に表示", icon='VIEW_CAMERA')
layout.separator()
row = layout.row(align=True)
row.scale_y = 1.2
row.operator(f"{PREFIX_NAME}.create", text="作成", icon='MESH_UVSPHERE')
row.operator(f"{PREFIX_NAME}.detach", text="切り離し (独立)", icon='UNLINKED')
layout.operator(f"{PREFIX_NAME}.reset", text="初期値へ戻す", icon='FILE_REFRESH')
layout.separator()
# ② メッシュ品質設定
box = layout.box()
box.label(text="② メッシュ品質設定", icon='MODIFIER')
box.prop(props, "use_smooth_shade")
row = box.row()
row.prop(props, "segments_main_u")
row.prop(props, "segments_main_v")
row = box.row()
row.prop(props, "segments_circle")
row = box.row()
row.prop(props, "segments_sub_u")
row.prop(props, "segments_sub_v")
# ③ 球体設定
box = layout.box()
box.label(text="③ 球体設定", icon='MESH_UVSPHERE')
row = box.row()
row.prop(props, "show_sphere", text="表示する")
if props.show_sphere:
box.prop(props, "sphere_radius")
box.prop(props, "sphere_thickness")
box.prop(props, "sphere_color")
box.prop(props, "sphere_inner_color")
box.prop(props, "sphere_location")
box.prop(props, "sphere_rotation")
# ④ 穴窓設定
box = layout.box()
box.label(text="④ 穴窓設定 (配置)", icon='OUTLINER_OB_MESH')
box.prop(props, "hole_latitude")
box.prop(props, "hole_longitude")
box.prop(props, "hole_radius")
# ⑤ 穴平面設定
box = layout.box()
box.label(text="⑤ 穴平面設定", icon='MESH_CIRCLE')
row = box.row()
row.prop(props, "show_plane", text="表示する")
if props.show_plane:
row.prop(props, "plane_color", text="色")
# ⑥ 球冠設定
box = layout.box()
box.label(text="⑥ 球冠(フタ)設定", icon='MESH_ICOSPHERE')
row = box.row()
row.prop(props, "show_cap", text="表示する")
if props.show_cap:
row.prop(props, "cap_color", text="色")
# ⑦ 穴球体設定
box = layout.box()
box.label(text="⑦ 穴球体 設定", icon='LIGHT_POINT')
row = box.row()
row.prop(props, "show_center_sphere", text="表示する")
if props.show_center_sphere:
box.prop(props, "center_sphere_mode")
row = box.row(align=True)
row.prop(props, "center_sphere_radius")
# 半径同期ボタン
row.operator(f"{PREFIX_NAME}.sync_center_radius", text="", icon='LINKED')
box.prop(props, "center_sphere_color")
# ⑧ 円錐設定
box = layout.box()
box.label(text="⑧ 中央の円錐設定", icon='MESH_CONE')
box.prop(props, "cone_radius")
box.prop(props, "cone_depth")
box.prop(props, "cone_color")
box.prop(props, "cone_location")
box.prop(props, "cone_rotation")
class ADDON_PT_link(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME}_pt_link"
bl_label = "⑨ リンク"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = TAB_NAME
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
op = self.layout.operator("wm.url_open", text="地球儀 穴窓面 20260708", icon='URL')
op.url = LINK_URL
class ADDON_PT_remove(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME}_pt_remove"
bl_label = "⑩ アドオン削除"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = TAB_NAME
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
self.layout.operator(f"{PREFIX_NAME}.remove_addon", text="アドオン削除", icon='CANCEL')
# ==========================================
# 7. 登録処理
# ==========================================
classes = [
ADDON_PG_props,
ADDON_OT_sync_center_radius,
ADDON_OT_create,
ADDON_OT_detach,
ADDON_OT_reset,
ADDON_OT_remove_addon,
ADDON_PT_main,
ADDON_PT_link,
ADDON_PT_remove,
]
def register():
for cls in classes:
bpy.utils.register_class(cls)
setattr(bpy.types.Scene, PROP_ID, bpy.props.PointerProperty(type=ADDON_PG_props))
def unregister():
for cls in reversed(classes):
try:
bpy.utils.unregister_class(cls)
except RuntimeError:
pass
if hasattr(bpy.types.Scene, PROP_ID):
delattr(bpy.types.Scene, PROP_ID)
if __name__ == "__main__":
register()