builder 作成テスト
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "Hemisphere Star Simulator",
"author": "Your Name",
"version": (1, 9, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > HemiStar",
"description": "Simulation of hemisphere dome, stars, and observers with defined names.",
"category": "3D View",
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Updated : 2024_0526_1800 (Modified for UV Segments)
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import uuid
import webbrowser
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "HemiStar Sim"
ADDON_ID = "hemistar"
ADDON_CATEGORY = "HemiStar"
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "HemiStar Docs (Addon)"
LINK_URL_1 = "<https://example.com/hemistar-docs>"
LINK_NAME_2 = "Blender Manual"
LINK_URL_2 = "<https://docs.blender.org/manual/en/latest/>"
# =========================================================
# 6. 名称定義 (プレフィックスの後に付く名前)
# =========================================================
# コレクション
SUFFIX_MAIN_COLLECTION = "Collection"
SUFFIX_SUB_COLLECTION = "Objects"
# オブジェクト
NAME_DOME_SURFACE = "Dome_Surface"
NAME_DOME_WIRE = "Dome_Wire"
NAME_STAR1 = "Star1"
NAME_STAR2 = "Star2"
NAME_OBSERVER1 = "Observer1"
NAME_OBSERVER2 = "Observer2"
# =========================================================
# 7. 初期設定 (コピーしたコードはここから下にペーストして上書きしてください)
# =========================================================
PREFIX_DEFAULT = "HEMISTAR"
RADIUS_DEFAULT = 10.0
DOME_U_SEGMENTS_DEFAULT = 32
DOME_V_SEGMENTS_DEFAULT = 16
WIRE_THICKNESS_DEFAULT = 0.05
SIZE_STAR_DEFAULT = 0.3
SIZE_OBSERVER_DEFAULT = 0.5
STAR1_U_DEFAULT = 45.0
STAR1_V_DEFAULT = 30.0
STAR2_U_DEFAULT = 135.0
STAR2_V_DEFAULT = 60.0
OBSERVER1_X_DEFAULT = -2.0
OBSERVER1_Y_DEFAULT = 0.0
OBSERVER2_X_DEFAULT = 2.0
OBSERVER2_Y_DEFAULT = 0.0
COLOR_DOME_SURFACE = (0.050, 0.100, 0.200)
ALPHA_DOME_SURFACE = 0.40
COLOR_DOME_WIRE = (0.400, 0.800, 1.000)
ALPHA_DOME_WIRE = 1.00
COLOR_STAR1 = (1.000, 0.800, 0.000)
ALPHA_STAR1 = 1.00
COLOR_STAR2 = (0.000, 0.800, 1.000)
ALPHA_STAR2 = 1.00
COLOR_OBSERVER1 = (1.000, 0.200, 0.200)
ALPHA_OBSERVER1 = 1.00
COLOR_OBSERVER2 = (0.200, 1.000, 0.200)
ALPHA_OBSERVER2 = 1.00
# =========================================================
# (ペースト上書き範囲ここまで)
# =========================================================
# =========================================================
# 8. 共通クラス
# =========================================================
class HEMISTAR_OT_open_url(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.open_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url: webbrowser.open(self.url)
return {'FINISHED'}
# =========================================================
# 9. アドオン固有機能 (Update Functions)
# =========================================================
def get_obj(name, prefix):
return bpy.data.objects.get(f"{prefix}_{name}")
def update_dome(self, context):
scale = (self.radius, self.radius, self.radius)
obj_surf = get_obj(NAME_DOME_SURFACE, self.name_prefix)
obj_wire = get_obj(NAME_DOME_WIRE, self.name_prefix)
if obj_surf: obj_surf.scale = scale
if obj_wire: obj_wire.scale = scale
def update_dome_segments(self, context):
for name in (NAME_DOME_SURFACE, NAME_DOME_WIRE):
obj = get_obj(name, self.name_prefix)
if obj and obj.type == 'MESH':
mesh = obj.data
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=self.dome_u_segments, v_segments=self.dome_v_segments, radius=1.0)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.001], context='VERTS')
bm.to_mesh(mesh)
bm.free()
def update_wire_thickness(self, context):
obj = get_obj(NAME_DOME_WIRE, self.name_prefix)
if obj and "Wireframe" in obj.modifiers:
obj.modifiers["Wireframe"].thickness = self.wire_thickness
def get_star_location(r, u_deg, v_deg):
theta, phi = math.radians(u_deg), math.radians(v_deg)
return (r * math.cos(phi) * math.cos(theta), r * math.cos(phi) * math.sin(theta), r * math.sin(phi))
def update_star1(self, context):
obj = get_obj(NAME_STAR1, self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star1_u, self.star1_v)
def update_star2(self, context):
obj = get_obj(NAME_STAR2, self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star2_u, self.star2_v)
def update_star_size(self, context):
for name in (NAME_STAR1, NAME_STAR2):
obj = get_obj(name, self.name_prefix)
if obj: obj.scale = (self.star_size, self.star_size, self.star_size)
def update_obs1(self, context):
obj = get_obj(NAME_OBSERVER1, self.name_prefix)
if obj: obj.location = (self.obs1_x, self.obs1_y, 0.0)
def update_obs2(self, context):
obj = get_obj(NAME_OBSERVER2, self.name_prefix)
if obj: obj.location = (self.obs2_x, self.obs2_y, 0.0)
def update_obs_size(self, context):
for name in (NAME_OBSERVER1, NAME_OBSERVER2):
obj = get_obj(name, self.name_prefix)
if obj: obj.scale = (self.obs_size, self.obs_size, self.obs_size)
def update_radius(self, context):
update_dome(self, context)
update_star1(self, context)
update_star2(self, context)
def update_display(self, context):
items = [
(NAME_DOME_SURFACE, self.show_dome_surface),
(NAME_DOME_WIRE, self.show_dome_wire),
(NAME_STAR1, self.show_star1),
(NAME_STAR2, self.show_star2),
(NAME_OBSERVER1, self.show_obs1),
(NAME_OBSERVER2, self.show_obs2)
]
for name, is_show in items:
obj = get_obj(name, self.name_prefix)
if obj: obj.hide_viewport = obj.hide_render = not is_show
def update_material_color_alpha(self, obj_name, color_val, alpha_val):
obj = get_obj(obj_name, self.name_prefix)
if obj and obj.data.materials:
mat = obj.data.materials[0]
mat.diffuse_color = (color_val[0], color_val[1], color_val[2], alpha_val)
if mat and mat.use_nodes:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
if "Base Color" in bsdf.inputs:
bsdf.inputs["Base Color"].default_value = (color_val[0], color_val[1], color_val[2], 1.0)
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = alpha_val
def update_mat_dome_surf(self, context): update_material_color_alpha(self, NAME_DOME_SURFACE, self.color_dome_surf, self.alpha_dome_surf)
def update_mat_dome_wire(self, context): update_material_color_alpha(self, NAME_DOME_WIRE, self.color_dome_wire, self.alpha_dome_wire)
def update_mat_star1(self, context): update_material_color_alpha(self, NAME_STAR1, self.color_star1, self.alpha_star1)
def update_mat_star2(self, context): update_material_color_alpha(self, NAME_STAR2, self.color_star2, self.alpha_star2)
def update_mat_obs1(self, context): update_material_color_alpha(self, NAME_OBSERVER1, self.color_obs1, self.alpha_obs1)
def update_mat_obs2(self, context): update_material_color_alpha(self, NAME_OBSERVER2, self.color_obs2, self.alpha_obs2)
# --- Property Group ---
class HEMISTAR_PG_props(bpy.types.PropertyGroup):
name_prefix: bpy.props.StringProperty(name="Prefix", default=PREFIX_DEFAULT)
radius: bpy.props.FloatProperty(name="Radius", default=RADIUS_DEFAULT, min=0.1, update=update_radius)
dome_u_segments: bpy.props.IntProperty(name="Dome U Segments", default=DOME_U_SEGMENTS_DEFAULT, min=3, max=128, update=update_dome_segments)
dome_v_segments: bpy.props.IntProperty(name="Dome V Segments", default=DOME_V_SEGMENTS_DEFAULT, min=2, max=64, update=update_dome_segments)
wire_thickness: bpy.props.FloatProperty(name="Wire Thickness", default=WIRE_THICKNESS_DEFAULT, min=0.001, update=update_wire_thickness)
star_size: bpy.props.FloatProperty(name="Star Size", default=SIZE_STAR_DEFAULT, min=0.01, update=update_star_size)
star1_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR1_U_DEFAULT, min=0.0, max=360.0, update=update_star1)
star1_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR1_V_DEFAULT, min=0.0, max=90.0, update=update_star1)
star2_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR2_U_DEFAULT, min=0.0, max=360.0, update=update_star2)
star2_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR2_V_DEFAULT, min=0.0, max=90.0, update=update_star2)
obs_size: bpy.props.FloatProperty(name="Observer Size", default=SIZE_OBSERVER_DEFAULT, min=0.01, update=update_obs_size)
obs1_x: bpy.props.FloatProperty(name="X", default=OBSERVER1_X_DEFAULT, update=update_obs1)
obs1_y: bpy.props.FloatProperty(name="Y", default=OBSERVER1_Y_DEFAULT, update=update_obs1)
obs2_x: bpy.props.FloatProperty(name="X", default=OBSERVER2_X_DEFAULT, update=update_obs2)
obs2_y: bpy.props.FloatProperty(name="Y", default=OBSERVER2_Y_DEFAULT, update=update_obs2)
show_dome_surface: bpy.props.BoolProperty(default=True, update=update_display)
show_dome_wire: bpy.props.BoolProperty(default=True, update=update_display)
show_star1: bpy.props.BoolProperty(default=True, update=update_display)
show_star2: bpy.props.BoolProperty(default=True, update=update_display)
show_obs1: bpy.props.BoolProperty(default=True, update=update_display)
show_obs2: bpy.props.BoolProperty(default=True, update=update_display)
color_dome_surf: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_SURFACE, update=update_mat_dome_surf)
alpha_dome_surf: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_SURFACE, update=update_mat_dome_surf)
color_dome_wire: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_WIRE, update=update_mat_dome_wire)
alpha_dome_wire: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_WIRE, update=update_mat_dome_wire)
color_star1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR1, update=update_mat_star1)
alpha_star1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR1, update=update_mat_star1)
color_star2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR2, update=update_mat_star2)
alpha_star2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR2, update=update_mat_star2)
color_obs1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER1, update=update_mat_obs1)
alpha_obs1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER1, update=update_mat_obs1)
color_obs2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER2, update=update_mat_obs2)
alpha_obs2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER2, update=update_mat_obs2)
# --- Operators ---
class HEMISTAR_OT_create(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.create"
bl_label = "Create Objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.hemistar_props
prefix = props.name_prefix
main_col_name = f"{prefix}_{SUFFIX_MAIN_COLLECTION}"
sub_col_name = f"{prefix}_{SUFFIX_SUB_COLLECTION}"
main_col = bpy.data.collections.get(main_col_name)
if not main_col:
main_col = bpy.data.collections.new(main_col_name)
context.scene.collection.children.link(main_col)
sub_col = bpy.data.collections.get(sub_col_name)
if not sub_col:
sub_col = bpy.data.collections.new(sub_col_name)
main_col.children.link(sub_col)
def create_mesh_obj(name, create_func):
obj_name = f"{prefix}_{name}"
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)
sub_col.objects.link(obj)
create_func(mesh)
mat = bpy.data.materials.new(name=f"{obj_name}_Mat")
mat.use_nodes = True
if hasattr(mat, "blend_method"): mat.blend_method = 'BLEND'
if hasattr(mat, "shadow_method"): mat.shadow_method = 'NONE'
obj.data.materials.append(mat)
return obj
def make_dome(mesh):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=props.dome_u_segments, v_segments=props.dome_v_segments, radius=1.0)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.001], context='VERTS')
bm.to_mesh(mesh)
bm.free()
def make_star(mesh):
bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=1.0)
bm.to_mesh(mesh)
bm.free()
def make_observer(mesh):
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=16, radius1=1.0, radius2=0.0, depth=2.0)
bmesh.ops.translate(bm, verts=bm.verts, vec=(0.0, 0.0, 1.0))
bm.to_mesh(mesh)
bm.free()
create_mesh_obj(NAME_DOME_SURFACE, make_dome)
wire_obj = create_mesh_obj(NAME_DOME_WIRE, make_dome)
create_mesh_obj(NAME_STAR1, make_star)
create_mesh_obj(NAME_STAR2, make_star)
create_mesh_obj(NAME_OBSERVER1, make_observer)
create_mesh_obj(NAME_OBSERVER2, make_observer)
if "Wireframe" not in wire_obj.modifiers:
mod = wire_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
mod.use_replace = True
update_dome_segments(props, context)
update_radius(props, context)
update_wire_thickness(props, context)
update_star_size(props, context)
update_obs_size(props, context)
update_obs1(props, context)
update_obs2(props, context)
update_display(props, context)
update_mat_dome_surf(props, context)
update_mat_dome_wire(props, context)
update_mat_star1(props, context)
update_mat_star2(props, context)
update_mat_obs1(props, context)
update_mat_obs2(props, context)
return {'FINISHED'}
class HEMISTAR_OT_detach(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.detach"
bl_label = "Detach / 独立させる"
bl_description = "現在のPrefixを持つコレクション・オブジェクト・マテリアルをリネームし独立させます"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefix = context.scene.hemistar_props.name_prefix
main_col_name = f"{prefix}_{SUFFIX_MAIN_COLLECTION}"
col = bpy.data.collections.get(main_col_name)
if not col:
self.report({'WARNING'}, f"切り離す対象 ({main_col_name}) が見つかりません。")
return {'CANCELLED'}
uid = str(uuid.uuid4())[:5].upper()
new_prefix = f"Detached_{uid}"
col.name = f"{new_prefix}_{SUFFIX_MAIN_COLLECTION}"
sub_col = bpy.data.collections.get(f"{prefix}_{SUFFIX_SUB_COLLECTION}")
if sub_col:
sub_col.name = f"{new_prefix}_{SUFFIX_SUB_COLLECTION}"
for obj in col.all_objects:
if obj.name.startswith(f"{prefix}_"):
obj.name = obj.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
if obj.data and obj.data.name.startswith(f"{prefix}_"):
obj.data.name = obj.data.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
for slot in obj.material_slots:
mat = slot.material
if mat and mat.name.startswith(f"{prefix}_"):
mat.name = mat.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
self.report({'INFO'}, f"オブジェクトを切り離しました ({new_prefix})")
return {'FINISHED'}
class HEMISTAR_OT_copy_code(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.copy_code"
bl_label = "現在の設定をコードとしてコピー"
bl_description = "現在の数値をPythonコードとしてクリップボードにコピーします。スクリプト内の「7. 初期設定」にペーストして上書きできます"
def execute(self, context):
p = context.scene.hemistar_props
code = f"""PREFIX_DEFAULT = "{p.name_prefix}"
RADIUS_DEFAULT = {p.radius:.2f}
DOME_U_SEGMENTS_DEFAULT = {p.dome_u_segments}
DOME_V_SEGMENTS_DEFAULT = {p.dome_v_segments}
WIRE_THICKNESS_DEFAULT = {p.wire_thickness:.3f}
SIZE_STAR_DEFAULT = {p.star_size:.2f}
SIZE_OBSERVER_DEFAULT = {p.obs_size:.2f}
STAR1_U_DEFAULT = {p.star1_u:.2f}
STAR1_V_DEFAULT = {p.star1_v:.2f}
STAR2_U_DEFAULT = {p.star2_u:.2f}
STAR2_V_DEFAULT = {p.star2_v:.2f}
OBSERVER1_X_DEFAULT = {p.obs1_x:.2f}
OBSERVER1_Y_DEFAULT = {p.obs1_y:.2f}
OBSERVER2_X_DEFAULT = {p.obs2_x:.2f}
OBSERVER2_Y_DEFAULT = {p.obs2_y:.2f}
COLOR_DOME_SURFACE = ({p.color_dome_surf[0]:.3f}, {p.color_dome_surf[1]:.3f}, {p.color_dome_surf[2]:.3f})
ALPHA_DOME_SURFACE = {p.alpha_dome_surf:.2f}
COLOR_DOME_WIRE = ({p.color_dome_wire[0]:.3f}, {p.color_dome_wire[1]:.3f}, {p.color_dome_wire[2]:.3f})
ALPHA_DOME_WIRE = {p.alpha_dome_wire:.2f}
COLOR_STAR1 = ({p.color_star1[0]:.3f}, {p.color_star1[1]:.3f}, {p.color_star1[2]:.3f})
ALPHA_STAR1 = {p.alpha_star1:.2f}
COLOR_STAR2 = ({p.color_star2[0]:.3f}, {p.color_star2[1]:.3f}, {p.color_star2[2]:.3f})
ALPHA_STAR2 = {p.alpha_star2:.2f}
COLOR_OBSERVER1 = ({p.color_obs1[0]:.3f}, {p.color_obs1[1]:.3f}, {p.color_obs1[2]:.3f})
ALPHA_OBSERVER1 = {p.alpha_obs1:.2f}
COLOR_OBSERVER2 = ({p.color_obs2[0]:.3f}, {p.color_obs2[1]:.3f}, {p.color_obs2[2]:.3f})
ALPHA_OBSERVER2 = {p.alpha_obs2:.2f}
"""
context.window_manager.clipboard = code
self.report({'INFO'}, "初期値用コードをクリップボードにコピーしました")
return {'FINISHED'}
class HEMISTAR_OT_addon_remove(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.addon_remove"
bl_label = "Unregister Addon"
def execute(self, context):
bpy.app.timers.register(unregister, first_interval=0.01)
return {'FINISHED'}
# =========================================================
# 10. パネル構成
# =========================================================
class HEMISTAR_PT_main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{ADDON_ID}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 1
def draw(self, context):
layout = self.layout
props = context.scene.hemistar_props
col = layout.column(align=True)
col.prop(props, "name_prefix", text="Name Prefix", icon='SYNTAX_ON')
col.operator(f"{ADDON_ID}.create", icon='ADD')
col.operator(f"{ADDON_ID}.detach", icon='UNLINKED')
box = layout.box()
box.label(text="Transform & Shape:", icon='OUTLINER_OB_MESH')
box.prop(props, "radius")
box.prop(props, "dome_u_segments", text="Dome U (経度・縦線) 分割数")
box.prop(props, "dome_v_segments", text="Dome V (緯度・横線) 分割数")
box.prop(props, "wire_thickness")
box.prop(props, "star_size")
col2 = box.column(align=True)
col2.prop(props, "star1_u", text="Star1 U")
col2.prop(props, "star1_v", text="Star1 V")
col2.prop(props, "star2_u", text="Star2 U")
col2.prop(props, "star2_v", text="Star2 V")
box.prop(props, "obs_size")
col3 = box.column(align=True)
col3.prop(props, "obs1_x", text="Obs1 X")
col3.prop(props, "obs1_y", text="Obs1 Y")
col3.prop(props, "obs2_x", text="Obs2 X")
col3.prop(props, "obs2_y", text="Obs2 Y")
box = layout.box()
box.label(text="Display & Materials:", icon='MATERIAL')
def draw_mat_prop(label_text, show_prop, color_prop, alpha_prop):
row = box.row(align=True)
row.prop(props, show_prop, text="")
row.label(text=label_text)
row.prop(props, color_prop, text="")
row.prop(props, alpha_prop, text="", slider=True)
header = box.row(align=True)
header.label(text=" Show | Name")
header.label(text="Color")
header.label(text="Alpha")
draw_mat_prop("Dome Surf", "show_dome_surface", "color_dome_surf", "alpha_dome_surf")
draw_mat_prop("Dome Wire", "show_dome_wire", "color_dome_wire", "alpha_dome_wire")
draw_mat_prop("Star 1", "show_star1", "color_star1", "alpha_star1")
draw_mat_prop("Star 2", "show_star2", "color_star2", "alpha_star2")
draw_mat_prop("Observer 1", "show_obs1", "color_obs1", "alpha_obs1")
draw_mat_prop("Observer 2", "show_obs2", "color_obs2", "alpha_obs2")
class HEMISTAR_PT_settings(bpy.types.Panel):
bl_label = "初期値のカスタマイズ"
bl_idname = f"{ADDON_ID}_PT_settings"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator(f"{ADDON_ID}.copy_code", icon='COPYDOWN')
layout.label(text="↑ コピーしたテキストをスクリプトの", icon='INFO')
layout.label(text=" 「7. 初期設定」に上書きペースト可能")
class HEMISTAR_PT_links(bpy.types.Panel):
bl_label = "Links"
bl_idname = f"{ADDON_ID}_PT_links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 3
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
col = self.layout.column(align=True)
for name, url in [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2)]:
col.operator(f"{ADDON_ID}.open_url", text=name, icon='URL').url = url
class HEMISTAR_PT_remove(bpy.types.Panel):
bl_label = "アドオン削除"
bl_idname = f"{ADDON_ID}_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_options = {'DEFAULT_CLOSED'}
bl_order = 4
def draw(self, context):
self.layout.column().operator(f"{ADDON_ID}.addon_remove", icon='CANCEL')
# =========================================================
# 11. Register / Unregister
# =========================================================
classes = (
HEMISTAR_OT_open_url,
HEMISTAR_PG_props,
HEMISTAR_OT_create,
HEMISTAR_OT_detach,
HEMISTAR_OT_copy_code,
HEMISTAR_OT_addon_remove,
HEMISTAR_PT_main,
HEMISTAR_PT_settings,
HEMISTAR_PT_links,
HEMISTAR_PT_remove,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.hemistar_props = bpy.props.PointerProperty(type=HEMISTAR_PG_props)
def unregister():
if hasattr(bpy.types.Scene, "hemistar_props"): del bpy.types.Scene.hemistar_props
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__): bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
緯度経度の本数を 減らす設定
本数 設定 緯度経度
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "Hemisphere Star Simulator",
"author": "Your Name",
"version": (1, 8, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > HemiStar",
"description": "Simulation of hemisphere dome, stars, and observers with defined names.",
"category": "3D View",
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Updated : 2024_0526_1800
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import uuid
import webbrowser
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "HemiStar Sim"
ADDON_ID = "hemistar"
ADDON_CATEGORY = "HemiStar"
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "HemiStar Docs (Addon)"
LINK_URL_1 = "<https://example.com/hemistar-docs>"
LINK_NAME_2 = "Blender Manual"
LINK_URL_2 = "<https://docs.blender.org/manual/en/latest/>"
# =========================================================
# 6. 名称定義 (プレフィックスの後に付く名前)
# =========================================================
# コレクション
SUFFIX_MAIN_COLLECTION = "Collection"
SUFFIX_SUB_COLLECTION = "Objects"
# オブジェクト
NAME_DOME_SURFACE = "Dome_Surface"
NAME_DOME_WIRE = "Dome_Wire"
NAME_STAR1 = "Star1"
NAME_STAR2 = "Star2"
NAME_OBSERVER1 = "Observer1"
NAME_OBSERVER2 = "Observer2"
# =========================================================
# 7. 初期設定 (コピーしたコードはここから下にペーストして上書きしてください)
# =========================================================
PREFIX_DEFAULT = "HEMISTAR"
RADIUS_DEFAULT = 10.0
WIRE_THICKNESS_DEFAULT = 0.05
SIZE_STAR_DEFAULT = 0.3
SIZE_OBSERVER_DEFAULT = 0.5
STAR1_U_DEFAULT = 45.0
STAR1_V_DEFAULT = 30.0
STAR2_U_DEFAULT = 135.0
STAR2_V_DEFAULT = 60.0
OBSERVER1_X_DEFAULT = -2.0
OBSERVER1_Y_DEFAULT = 0.0
OBSERVER2_X_DEFAULT = 2.0
OBSERVER2_Y_DEFAULT = 0.0
COLOR_DOME_SURFACE = (0.050, 0.100, 0.200)
ALPHA_DOME_SURFACE = 0.40
COLOR_DOME_WIRE = (0.400, 0.800, 1.000)
ALPHA_DOME_WIRE = 1.00
COLOR_STAR1 = (1.000, 0.800, 0.000)
ALPHA_STAR1 = 1.00
COLOR_STAR2 = (0.000, 0.800, 1.000)
ALPHA_STAR2 = 1.00
COLOR_OBSERVER1 = (1.000, 0.200, 0.200)
ALPHA_OBSERVER1 = 1.00
COLOR_OBSERVER2 = (0.200, 1.000, 0.200)
ALPHA_OBSERVER2 = 1.00
# =========================================================
# (ペースト上書き範囲ここまで)
# =========================================================
# =========================================================
# 8. 共通クラス
# =========================================================
class HEMISTAR_OT_open_url(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.open_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url: webbrowser.open(self.url)
return {'FINISHED'}
# =========================================================
# 9. アドオン固有機能 (Update Functions)
# =========================================================
def get_obj(name, prefix):
return bpy.data.objects.get(f"{prefix}_{name}")
def update_dome(self, context):
scale = (self.radius, self.radius, self.radius)
obj_surf = get_obj(NAME_DOME_SURFACE, self.name_prefix)
obj_wire = get_obj(NAME_DOME_WIRE, self.name_prefix)
if obj_surf: obj_surf.scale = scale
if obj_wire: obj_wire.scale = scale
def update_wire_thickness(self, context):
obj = get_obj(NAME_DOME_WIRE, self.name_prefix)
if obj and "Wireframe" in obj.modifiers:
obj.modifiers["Wireframe"].thickness = self.wire_thickness
def get_star_location(r, u_deg, v_deg):
theta, phi = math.radians(u_deg), math.radians(v_deg)
return (r * math.cos(phi) * math.cos(theta), r * math.cos(phi) * math.sin(theta), r * math.sin(phi))
def update_star1(self, context):
obj = get_obj(NAME_STAR1, self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star1_u, self.star1_v)
def update_star2(self, context):
obj = get_obj(NAME_STAR2, self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star2_u, self.star2_v)
def update_star_size(self, context):
for name in (NAME_STAR1, NAME_STAR2):
obj = get_obj(name, self.name_prefix)
if obj: obj.scale = (self.star_size, self.star_size, self.star_size)
def update_obs1(self, context):
obj = get_obj(NAME_OBSERVER1, self.name_prefix)
if obj: obj.location = (self.obs1_x, self.obs1_y, 0.0)
def update_obs2(self, context):
obj = get_obj(NAME_OBSERVER2, self.name_prefix)
if obj: obj.location = (self.obs2_x, self.obs2_y, 0.0)
def update_obs_size(self, context):
for name in (NAME_OBSERVER1, NAME_OBSERVER2):
obj = get_obj(name, self.name_prefix)
if obj: obj.scale = (self.obs_size, self.obs_size, self.obs_size)
def update_radius(self, context):
update_dome(self, context)
update_star1(self, context)
update_star2(self, context)
def update_display(self, context):
items = [
(NAME_DOME_SURFACE, self.show_dome_surface),
(NAME_DOME_WIRE, self.show_dome_wire),
(NAME_STAR1, self.show_star1),
(NAME_STAR2, self.show_star2),
(NAME_OBSERVER1, self.show_obs1),
(NAME_OBSERVER2, self.show_obs2)
]
for name, is_show in items:
obj = get_obj(name, self.name_prefix)
if obj: obj.hide_viewport = obj.hide_render = not is_show
def update_material_color_alpha(self, obj_name, color_val, alpha_val):
obj = get_obj(obj_name, self.name_prefix)
if obj and obj.data.materials:
mat = obj.data.materials[0]
mat.diffuse_color = (color_val[0], color_val[1], color_val[2], alpha_val)
if mat and mat.use_nodes:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
if "Base Color" in bsdf.inputs:
bsdf.inputs["Base Color"].default_value = (color_val[0], color_val[1], color_val[2], 1.0)
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = alpha_val
def update_mat_dome_surf(self, context): update_material_color_alpha(self, NAME_DOME_SURFACE, self.color_dome_surf, self.alpha_dome_surf)
def update_mat_dome_wire(self, context): update_material_color_alpha(self, NAME_DOME_WIRE, self.color_dome_wire, self.alpha_dome_wire)
def update_mat_star1(self, context): update_material_color_alpha(self, NAME_STAR1, self.color_star1, self.alpha_star1)
def update_mat_star2(self, context): update_material_color_alpha(self, NAME_STAR2, self.color_star2, self.alpha_star2)
def update_mat_obs1(self, context): update_material_color_alpha(self, NAME_OBSERVER1, self.color_obs1, self.alpha_obs1)
def update_mat_obs2(self, context): update_material_color_alpha(self, NAME_OBSERVER2, self.color_obs2, self.alpha_obs2)
# --- Property Group ---
class HEMISTAR_PG_props(bpy.types.PropertyGroup):
name_prefix: bpy.props.StringProperty(name="Prefix", default=PREFIX_DEFAULT)
radius: bpy.props.FloatProperty(name="Radius", default=RADIUS_DEFAULT, min=0.1, update=update_radius)
wire_thickness: bpy.props.FloatProperty(name="Wire Thickness", default=WIRE_THICKNESS_DEFAULT, min=0.001, update=update_wire_thickness)
star_size: bpy.props.FloatProperty(name="Star Size", default=SIZE_STAR_DEFAULT, min=0.01, update=update_star_size)
star1_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR1_U_DEFAULT, min=0.0, max=360.0, update=update_star1)
star1_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR1_V_DEFAULT, min=0.0, max=90.0, update=update_star1)
star2_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR2_U_DEFAULT, min=0.0, max=360.0, update=update_star2)
star2_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR2_V_DEFAULT, min=0.0, max=90.0, update=update_star2)
obs_size: bpy.props.FloatProperty(name="Observer Size", default=SIZE_OBSERVER_DEFAULT, min=0.01, update=update_obs_size)
obs1_x: bpy.props.FloatProperty(name="X", default=OBSERVER1_X_DEFAULT, update=update_obs1)
obs1_y: bpy.props.FloatProperty(name="Y", default=OBSERVER1_Y_DEFAULT, update=update_obs1)
obs2_x: bpy.props.FloatProperty(name="X", default=OBSERVER2_X_DEFAULT, update=update_obs2)
obs2_y: bpy.props.FloatProperty(name="Y", default=OBSERVER2_Y_DEFAULT, update=update_obs2)
show_dome_surface: bpy.props.BoolProperty(default=True, update=update_display)
show_dome_wire: bpy.props.BoolProperty(default=True, update=update_display)
show_star1: bpy.props.BoolProperty(default=True, update=update_display)
show_star2: bpy.props.BoolProperty(default=True, update=update_display)
show_obs1: bpy.props.BoolProperty(default=True, update=update_display)
show_obs2: bpy.props.BoolProperty(default=True, update=update_display)
color_dome_surf: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_SURFACE, update=update_mat_dome_surf)
alpha_dome_surf: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_SURFACE, update=update_mat_dome_surf)
color_dome_wire: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_WIRE, update=update_mat_dome_wire)
alpha_dome_wire: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_WIRE, update=update_mat_dome_wire)
color_star1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR1, update=update_mat_star1)
alpha_star1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR1, update=update_mat_star1)
color_star2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR2, update=update_mat_star2)
alpha_star2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR2, update=update_mat_star2)
color_obs1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER1, update=update_mat_obs1)
alpha_obs1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER1, update=update_mat_obs1)
color_obs2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER2, update=update_mat_obs2)
alpha_obs2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER2, update=update_mat_obs2)
# --- Operators ---
class HEMISTAR_OT_create(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.create"
bl_label = "Create Objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.hemistar_props
prefix = props.name_prefix
main_col_name = f"{prefix}_{SUFFIX_MAIN_COLLECTION}"
sub_col_name = f"{prefix}_{SUFFIX_SUB_COLLECTION}"
main_col = bpy.data.collections.get(main_col_name)
if not main_col:
main_col = bpy.data.collections.new(main_col_name)
context.scene.collection.children.link(main_col)
sub_col = bpy.data.collections.get(sub_col_name)
if not sub_col:
sub_col = bpy.data.collections.new(sub_col_name)
main_col.children.link(sub_col)
def create_mesh_obj(name, create_func):
obj_name = f"{prefix}_{name}"
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)
sub_col.objects.link(obj)
create_func(mesh)
mat = bpy.data.materials.new(name=f"{obj_name}_Mat")
mat.use_nodes = True
if hasattr(mat, "blend_method"): mat.blend_method = 'BLEND'
if hasattr(mat, "shadow_method"): mat.shadow_method = 'NONE'
obj.data.materials.append(mat)
return obj
def make_dome(mesh):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.001], context='VERTS')
bm.to_mesh(mesh)
bm.free()
def make_star(mesh):
bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=1.0)
bm.to_mesh(mesh)
bm.free()
def make_observer(mesh):
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=16, radius1=1.0, radius2=0.0, depth=2.0)
bmesh.ops.translate(bm, verts=bm.verts, vec=(0.0, 0.0, 1.0))
bm.to_mesh(mesh)
bm.free()
create_mesh_obj(NAME_DOME_SURFACE, make_dome)
wire_obj = create_mesh_obj(NAME_DOME_WIRE, make_dome)
create_mesh_obj(NAME_STAR1, make_star)
create_mesh_obj(NAME_STAR2, make_star)
create_mesh_obj(NAME_OBSERVER1, make_observer)
create_mesh_obj(NAME_OBSERVER2, make_observer)
if "Wireframe" not in wire_obj.modifiers:
mod = wire_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
mod.use_replace = True
update_radius(props, context)
update_wire_thickness(props, context)
update_star_size(props, context)
update_obs_size(props, context)
update_obs1(props, context)
update_obs2(props, context)
update_display(props, context)
update_mat_dome_surf(props, context)
update_mat_dome_wire(props, context)
update_mat_star1(props, context)
update_mat_star2(props, context)
update_mat_obs1(props, context)
update_mat_obs2(props, context)
return {'FINISHED'}
class HEMISTAR_OT_detach(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.detach"
bl_label = "Detach / 独立させる"
bl_description = "現在のPrefixを持つコレクション・オブジェクト・マテリアルをリネームし独立させます"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefix = context.scene.hemistar_props.name_prefix
main_col_name = f"{prefix}_{SUFFIX_MAIN_COLLECTION}"
col = bpy.data.collections.get(main_col_name)
if not col:
self.report({'WARNING'}, f"切り離す対象 ({main_col_name}) が見つかりません。")
return {'CANCELLED'}
uid = str(uuid.uuid4())[:5].upper()
new_prefix = f"Detached_{uid}"
col.name = f"{new_prefix}_{SUFFIX_MAIN_COLLECTION}"
sub_col = bpy.data.collections.get(f"{prefix}_{SUFFIX_SUB_COLLECTION}")
if sub_col:
sub_col.name = f"{new_prefix}_{SUFFIX_SUB_COLLECTION}"
for obj in col.all_objects:
if obj.name.startswith(f"{prefix}_"):
obj.name = obj.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
if obj.data and obj.data.name.startswith(f"{prefix}_"):
obj.data.name = obj.data.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
for slot in obj.material_slots:
mat = slot.material
if mat and mat.name.startswith(f"{prefix}_"):
mat.name = mat.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
self.report({'INFO'}, f"オブジェクトを切り離しました ({new_prefix})")
return {'FINISHED'}
class HEMISTAR_OT_copy_code(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.copy_code"
bl_label = "現在の設定をコードとしてコピー"
bl_description = "現在の数値をPythonコードとしてクリップボードにコピーします。スクリプト内の「7. 初期設定」にペーストして上書きできます"
def execute(self, context):
p = context.scene.hemistar_props
code = f"""PREFIX_DEFAULT = "{p.name_prefix}"
RADIUS_DEFAULT = {p.radius:.2f}
WIRE_THICKNESS_DEFAULT = {p.wire_thickness:.3f}
SIZE_STAR_DEFAULT = {p.star_size:.2f}
SIZE_OBSERVER_DEFAULT = {p.obs_size:.2f}
STAR1_U_DEFAULT = {p.star1_u:.2f}
STAR1_V_DEFAULT = {p.star1_v:.2f}
STAR2_U_DEFAULT = {p.star2_u:.2f}
STAR2_V_DEFAULT = {p.star2_v:.2f}
OBSERVER1_X_DEFAULT = {p.obs1_x:.2f}
OBSERVER1_Y_DEFAULT = {p.obs1_y:.2f}
OBSERVER2_X_DEFAULT = {p.obs2_x:.2f}
OBSERVER2_Y_DEFAULT = {p.obs2_y:.2f}
COLOR_DOME_SURFACE = ({p.color_dome_surf[0]:.3f}, {p.color_dome_surf[1]:.3f}, {p.color_dome_surf[2]:.3f})
ALPHA_DOME_SURFACE = {p.alpha_dome_surf:.2f}
COLOR_DOME_WIRE = ({p.color_dome_wire[0]:.3f}, {p.color_dome_wire[1]:.3f}, {p.color_dome_wire[2]:.3f})
ALPHA_DOME_WIRE = {p.alpha_dome_wire:.2f}
COLOR_STAR1 = ({p.color_star1[0]:.3f}, {p.color_star1[1]:.3f}, {p.color_star1[2]:.3f})
ALPHA_STAR1 = {p.alpha_star1:.2f}
COLOR_STAR2 = ({p.color_star2[0]:.3f}, {p.color_star2[1]:.3f}, {p.color_star2[2]:.3f})
ALPHA_STAR2 = {p.alpha_star2:.2f}
COLOR_OBSERVER1 = ({p.color_obs1[0]:.3f}, {p.color_obs1[1]:.3f}, {p.color_obs1[2]:.3f})
ALPHA_OBSERVER1 = {p.alpha_obs1:.2f}
COLOR_OBSERVER2 = ({p.color_obs2[0]:.3f}, {p.color_obs2[1]:.3f}, {p.color_obs2[2]:.3f})
ALPHA_OBSERVER2 = {p.alpha_obs2:.2f}
"""
context.window_manager.clipboard = code
self.report({'INFO'}, "初期値用コードをクリップボードにコピーしました")
return {'FINISHED'}
class HEMISTAR_OT_addon_remove(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.addon_remove"
bl_label = "Unregister Addon"
def execute(self, context):
bpy.app.timers.register(unregister, first_interval=0.01)
return {'FINISHED'}
# =========================================================
# 10. パネル構成
# =========================================================
class HEMISTAR_PT_main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{ADDON_ID}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 1
def draw(self, context):
layout = self.layout
props = context.scene.hemistar_props
col = layout.column(align=True)
col.prop(props, "name_prefix", text="Name Prefix", icon='SYNTAX_ON')
col.operator(f"{ADDON_ID}.create", icon='ADD')
col.operator(f"{ADDON_ID}.detach", icon='UNLINKED')
box = layout.box()
box.label(text="Transform & Shape:", icon='OUTLINER_OB_MESH')
box.prop(props, "radius")
box.prop(props, "wire_thickness")
box.prop(props, "star_size")
col2 = box.column(align=True)
col2.prop(props, "star1_u", text="Star1 U")
col2.prop(props, "star1_v", text="Star1 V")
col2.prop(props, "star2_u", text="Star2 U")
col2.prop(props, "star2_v", text="Star2 V")
box.prop(props, "obs_size")
col3 = box.column(align=True)
col3.prop(props, "obs1_x", text="Obs1 X")
col3.prop(props, "obs1_y", text="Obs1 Y")
col3.prop(props, "obs2_x", text="Obs2 X")
col3.prop(props, "obs2_y", text="Obs2 Y")
box = layout.box()
box.label(text="Display & Materials:", icon='MATERIAL')
def draw_mat_prop(label_text, show_prop, color_prop, alpha_prop):
row = box.row(align=True)
row.prop(props, show_prop, text="")
row.label(text=label_text)
row.prop(props, color_prop, text="")
row.prop(props, alpha_prop, text="", slider=True)
header = box.row(align=True)
header.label(text=" Show | Name")
header.label(text="Color")
header.label(text="Alpha")
draw_mat_prop("Dome Surf", "show_dome_surface", "color_dome_surf", "alpha_dome_surf")
draw_mat_prop("Dome Wire", "show_dome_wire", "color_dome_wire", "alpha_dome_wire")
draw_mat_prop("Star 1", "show_star1", "color_star1", "alpha_star1")
draw_mat_prop("Star 2", "show_star2", "show_star2", "alpha_star2") # Fixed argument 3
draw_mat_prop("Star 2", "show_star2", "color_star2", "alpha_star2")
draw_mat_prop("Observer 1", "show_obs1", "color_obs1", "alpha_obs1")
draw_mat_prop("Observer 2", "show_obs2", "color_obs2", "alpha_obs2")
class HEMISTAR_PT_settings(bpy.types.Panel):
bl_label = "初期値のカスタマイズ"
bl_idname = f"{ADDON_ID}_PT_settings"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator(f"{ADDON_ID}.copy_code", icon='COPYDOWN')
layout.label(text="↑ コピーしたテキストをスクリプトの", icon='INFO')
layout.label(text=" 「7. 初期設定」に上書きペースト可能")
class HEMISTAR_PT_links(bpy.types.Panel):
bl_label = "Links"
bl_idname = f"{ADDON_ID}_PT_links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 3
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
col = self.layout.column(align=True)
for name, url in [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2)]:
col.operator(f"{ADDON_ID}.open_url", text=name, icon='URL').url = url
class HEMISTAR_PT_remove(bpy.types.Panel):
bl_label = "アドオン削除"
bl_idname = f"{ADDON_ID}_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_options = {'DEFAULT_CLOSED'}
bl_order = 4
def draw(self, context):
self.layout.column().operator(f"{ADDON_ID}.addon_remove", icon='CANCEL')
# =========================================================
# 11. Register / Unregister
# =========================================================
classes = (
HEMISTAR_OT_open_url,
HEMISTAR_PG_props,
HEMISTAR_OT_create,
HEMISTAR_OT_detach,
HEMISTAR_OT_copy_code,
HEMISTAR_OT_addon_remove,
HEMISTAR_PT_main,
HEMISTAR_PT_settings,
HEMISTAR_PT_links,
HEMISTAR_PT_remove,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.hemistar_props = bpy.props.PointerProperty(type=HEMISTAR_PG_props)
def unregister():
if hasattr(bpy.types.Scene, "hemistar_props"): del bpy.types.Scene.hemistar_props
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__): bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
リンク定義の下に
コレクション名
サブコレクション名
Object名 羅列 1行ずつ
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "Hemisphere Star Simulator",
"author": "Your Name",
"version": (1, 7, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > HemiStar",
"description": "Simulation of hemisphere dome, stars, and observers with hierarchy and custom prefix.",
"category": "3D View",
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Updated : 2024_0526_1600
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import uuid
import webbrowser
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "HemiStar Sim"
ADDON_ID = "hemistar" # 内部処理用固定ID
ADDON_CATEGORY = "HemiStar"
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "HemiStar Docs (Addon)"
LINK_URL_1 = "<https://example.com/hemistar-docs>"
LINK_NAME_2 = "Blender Manual"
LINK_URL_2 = "<https://docs.blender.org/manual/en/latest/>"
# =========================================================
# 6. 初期設定 (コピーしたコードはここから下にペーストして上書きしてください)
# =========================================================
PREFIX_DEFAULT = "HEMISTAR"
RADIUS_DEFAULT = 10.0
WIRE_THICKNESS_DEFAULT = 0.05
SIZE_STAR_DEFAULT = 0.3
SIZE_OBSERVER_DEFAULT = 0.5
STAR1_U_DEFAULT = 45.0
STAR1_V_DEFAULT = 30.0
STAR2_U_DEFAULT = 135.0
STAR2_V_DEFAULT = 60.0
OBSERVER1_X_DEFAULT = -2.0
OBSERVER1_Y_DEFAULT = 0.0
OBSERVER2_X_DEFAULT = 2.0
OBSERVER2_Y_DEFAULT = 0.0
COLOR_DOME_SURFACE = (0.050, 0.100, 0.200)
ALPHA_DOME_SURFACE = 0.40
COLOR_DOME_WIRE = (0.400, 0.800, 1.000)
ALPHA_DOME_WIRE = 1.00
COLOR_STAR1 = (1.000, 0.800, 0.000)
ALPHA_STAR1 = 1.00
COLOR_STAR2 = (0.000, 0.800, 1.000)
ALPHA_STAR2 = 1.00
COLOR_OBSERVER1 = (1.000, 0.200, 0.200)
ALPHA_OBSERVER1 = 1.00
COLOR_OBSERVER2 = (0.200, 1.000, 0.200)
ALPHA_OBSERVER2 = 1.00
# =========================================================
# (ペースト上書き範囲ここまで)
# =========================================================
# =========================================================
# 7. 共通クラス
# =========================================================
class HEMISTAR_OT_open_url(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.open_url"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url: webbrowser.open(self.url)
return {'FINISHED'}
# =========================================================
# 8. アドオン固有機能 (Update Functions)
# =========================================================
def get_obj(name, prefix):
return bpy.data.objects.get(f"{prefix}_{name}")
def update_dome(self, context):
scale = (self.radius, self.radius, self.radius)
obj_surf, obj_wire = get_obj("Dome_Surface", self.name_prefix), get_obj("Dome_Wire", self.name_prefix)
if obj_surf: obj_surf.scale = scale
if obj_wire: obj_wire.scale = scale
def update_wire_thickness(self, context):
obj = get_obj("Dome_Wire", self.name_prefix)
if obj and "Wireframe" in obj.modifiers:
obj.modifiers["Wireframe"].thickness = self.wire_thickness
def get_star_location(r, u_deg, v_deg):
theta, phi = math.radians(u_deg), math.radians(v_deg)
return (r * math.cos(phi) * math.cos(theta), r * math.cos(phi) * math.sin(theta), r * math.sin(phi))
def update_star1(self, context):
obj = get_obj("Star1", self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star1_u, self.star1_v)
def update_star2(self, context):
obj = get_obj("Star2", self.name_prefix)
if obj: obj.location = get_star_location(self.radius, self.star2_u, self.star2_v)
def update_star_size(self, context):
for i in (1, 2):
obj = get_obj(f"Star{i}", self.name_prefix)
if obj: obj.scale = (self.star_size, self.star_size, self.star_size)
def update_obs1(self, context):
obj = get_obj("Observer1", self.name_prefix)
if obj: obj.location = (self.obs1_x, self.obs1_y, 0.0)
def update_obs2(self, context):
obj = get_obj("Observer2", self.name_prefix)
if obj: obj.location = (self.obs2_x, self.obs2_y, 0.0)
def update_obs_size(self, context):
for i in (1, 2):
obj = get_obj(f"Observer{i}", self.name_prefix)
if obj: obj.scale = (self.obs_size, self.obs_size, self.obs_size)
def update_radius(self, context):
update_dome(self, context)
update_star1(self, context)
update_star2(self, context)
def update_display(self, context):
items = [
("Dome_Surface", self.show_dome_surface), ("Dome_Wire", self.show_dome_wire),
("Star1", self.show_star1), ("Star2", self.show_star2),
("Observer1", self.show_obs1), ("Observer2", self.show_obs2)
]
for name, is_show in items:
obj = get_obj(name, self.name_prefix)
if obj: obj.hide_viewport = obj.hide_render = not is_show
def update_material_color_alpha(self, obj_name, color_val, alpha_val):
obj = get_obj(obj_name, self.name_prefix)
if obj and obj.data.materials:
mat = obj.data.materials[0]
mat.diffuse_color = (color_val[0], color_val[1], color_val[2], alpha_val)
if mat and mat.use_nodes:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
if "Base Color" in bsdf.inputs:
bsdf.inputs["Base Color"].default_value = (color_val[0], color_val[1], color_val[2], 1.0)
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = alpha_val
def update_mat_dome_surf(self, context): update_material_color_alpha(self, "Dome_Surface", self.color_dome_surf, self.alpha_dome_surf)
def update_mat_dome_wire(self, context): update_material_color_alpha(self, "Dome_Wire", self.color_dome_wire, self.alpha_dome_wire)
def update_mat_star1(self, context): update_material_color_alpha(self, "Star1", self.color_star1, self.alpha_star1)
def update_mat_star2(self, context): update_material_color_alpha(self, "Star2", self.color_star2, self.alpha_star2)
def update_mat_obs1(self, context): update_material_color_alpha(self, "Observer1", self.color_obs1, self.alpha_obs1)
def update_mat_obs2(self, context): update_material_color_alpha(self, "Observer2", self.color_obs2, self.alpha_obs2)
# --- Property Group ---
class HEMISTAR_PG_props(bpy.types.PropertyGroup):
name_prefix: bpy.props.StringProperty(name="Prefix", default=PREFIX_DEFAULT)
radius: bpy.props.FloatProperty(name="Radius", default=RADIUS_DEFAULT, min=0.1, update=update_radius)
wire_thickness: bpy.props.FloatProperty(name="Wire Thickness", default=WIRE_THICKNESS_DEFAULT, min=0.001, update=update_wire_thickness)
star_size: bpy.props.FloatProperty(name="Star Size", default=SIZE_STAR_DEFAULT, min=0.01, update=update_star_size)
star1_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR1_U_DEFAULT, min=0.0, max=360.0, update=update_star1)
star1_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR1_V_DEFAULT, min=0.0, max=90.0, update=update_star1)
star2_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR2_U_DEFAULT, min=0.0, max=360.0, update=update_star2)
star2_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR2_V_DEFAULT, min=0.0, max=90.0, update=update_star2)
obs_size: bpy.props.FloatProperty(name="Observer Size", default=SIZE_OBSERVER_DEFAULT, min=0.01, update=update_obs_size)
obs1_x: bpy.props.FloatProperty(name="X", default=OBSERVER1_X_DEFAULT, update=update_obs1)
obs1_y: bpy.props.FloatProperty(name="Y", default=OBSERVER1_Y_DEFAULT, update=update_obs1)
obs2_x: bpy.props.FloatProperty(name="X", default=OBSERVER2_X_DEFAULT, update=update_obs2)
obs2_y: bpy.props.FloatProperty(name="Y", default=OBSERVER2_Y_DEFAULT, update=update_obs2)
show_dome_surface: bpy.props.BoolProperty(default=True, update=update_display)
show_dome_wire: bpy.props.BoolProperty(default=True, update=update_display)
show_star1: bpy.props.BoolProperty(default=True, update=update_display)
show_star2: bpy.props.BoolProperty(default=True, update=update_display)
show_obs1: bpy.props.BoolProperty(default=True, update=update_display)
show_obs2: bpy.props.BoolProperty(default=True, update=update_display)
color_dome_surf: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_SURFACE, update=update_mat_dome_surf)
alpha_dome_surf: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_SURFACE, update=update_mat_dome_surf)
color_dome_wire: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_DOME_WIRE, update=update_mat_dome_wire)
alpha_dome_wire: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_DOME_WIRE, update=update_mat_dome_wire)
color_star1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR1, update=update_mat_star1)
alpha_star1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR1, update=update_mat_star1)
color_star2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_STAR2, update=update_mat_star2)
alpha_star2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_STAR2, update=update_mat_star2)
color_obs1: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER1, update=update_mat_obs1)
alpha_obs1: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER1, update=update_mat_obs1)
color_obs2: bpy.props.FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=COLOR_OBSERVER2, update=update_mat_obs2)
alpha_obs2: bpy.props.FloatProperty(subtype='FACTOR', min=0.0, max=1.0, default=ALPHA_OBSERVER2, update=update_mat_obs2)
# --- Operators ---
class HEMISTAR_OT_create(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.create"
bl_label = "Create Objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.hemistar_props
prefix = props.name_prefix
# 階層コレクションの作成
main_col_name = f"{prefix}_Collection"
sub_col_name = f"{prefix}_Objects"
main_col = bpy.data.collections.get(main_col_name)
if not main_col:
main_col = bpy.data.collections.new(main_col_name)
context.scene.collection.children.link(main_col)
sub_col = bpy.data.collections.get(sub_col_name)
if not sub_col:
sub_col = bpy.data.collections.new(sub_col_name)
main_col.children.link(sub_col)
def create_mesh_obj(name, create_func):
obj_name = f"{prefix}_{name}"
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)
sub_col.objects.link(obj)
create_func(mesh)
mat = bpy.data.materials.new(name=f"{obj_name}_Mat")
mat.use_nodes = True
if hasattr(mat, "blend_method"): mat.blend_method = 'BLEND'
if hasattr(mat, "shadow_method"): mat.shadow_method = 'NONE'
obj.data.materials.append(mat)
return obj
def make_dome(mesh):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
bmesh.ops.delete(bm, geom=[v for v in bm.verts if v.co.z < -0.001], context='VERTS')
bm.to_mesh(mesh)
bm.free()
def make_star(mesh):
bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=1.0)
bm.to_mesh(mesh)
bm.free()
def make_observer(mesh):
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, segments=16, radius1=1.0, radius2=0.0, depth=2.0)
bmesh.ops.translate(bm, verts=bm.verts, vec=(0.0, 0.0, 1.0))
bm.to_mesh(mesh)
bm.free()
create_mesh_obj("Dome_Surface", make_dome)
wire_obj = create_mesh_obj("Dome_Wire", make_dome)
create_mesh_obj("Star1", make_star)
create_mesh_obj("Star2", make_star)
create_mesh_obj("Observer1", make_observer)
create_mesh_obj("Observer2", make_observer)
if "Wireframe" not in wire_obj.modifiers:
mod = wire_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
mod.use_replace = True
# プロパティ反映
update_radius(props, context)
update_wire_thickness(props, context)
update_star_size(props, context)
update_obs_size(props, context)
update_obs1(props, context)
update_obs2(props, context)
update_display(props, context)
update_mat_dome_surf(props, context)
update_mat_dome_wire(props, context)
update_mat_star1(props, context)
update_mat_star2(props, context)
update_mat_obs1(props, context)
update_mat_obs2(props, context)
return {'FINISHED'}
class HEMISTAR_OT_detach(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.detach"
bl_label = "Detach / 独立させる"
bl_description = "現在のPrefixを持つコレクション・オブジェクト・マテリアルをリネームし独立させます"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefix = context.scene.hemistar_props.name_prefix
main_col_name = f"{prefix}_Collection"
col = bpy.data.collections.get(main_col_name)
if not col:
self.report({'WARNING'}, f"切り離す対象 ({main_col_name}) が見つかりません。")
return {'CANCELLED'}
uid = str(uuid.uuid4())[:5].upper()
new_prefix = f"Detached_{uid}"
# コレクションの名前変更
col.name = f"{new_prefix}_Collection"
sub_col = bpy.data.collections.get(f"{prefix}_Objects")
if sub_col:
sub_col.name = f"{new_prefix}_Objects"
# オブジェクトとマテリアルの名前変更
for obj in col.all_objects:
if obj.name.startswith(f"{prefix}_"):
obj.name = obj.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
if obj.data and obj.data.name.startswith(f"{prefix}_"):
obj.data.name = obj.data.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
for slot in obj.material_slots:
mat = slot.material
if mat and mat.name.startswith(f"{prefix}_"):
mat.name = mat.name.replace(f"{prefix}_", f"{new_prefix}_", 1)
self.report({'INFO'}, f"オブジェクトを切り離しました ({new_prefix})")
return {'FINISHED'}
class HEMISTAR_OT_copy_code(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.copy_code"
bl_label = "現在の設定をコードとしてコピー"
bl_description = "現在の数値をPythonコードとしてクリップボードにコピーします。スクリプト内の「6. 初期設定」にペーストして上書きできます"
def execute(self, context):
p = context.scene.hemistar_props
code = f"""PREFIX_DEFAULT = "{p.name_prefix}"
RADIUS_DEFAULT = {p.radius:.2f}
WIRE_THICKNESS_DEFAULT = {p.wire_thickness:.3f}
SIZE_STAR_DEFAULT = {p.star_size:.2f}
SIZE_OBSERVER_DEFAULT = {p.obs_size:.2f}
STAR1_U_DEFAULT = {p.star1_u:.2f}
STAR1_V_DEFAULT = {p.star1_v:.2f}
STAR2_U_DEFAULT = {p.star2_u:.2f}
STAR2_V_DEFAULT = {p.star2_v:.2f}
OBSERVER1_X_DEFAULT = {p.obs1_x:.2f}
OBSERVER1_Y_DEFAULT = {p.obs1_y:.2f}
OBSERVER2_X_DEFAULT = {p.obs2_x:.2f}
OBSERVER2_Y_DEFAULT = {p.obs2_y:.2f}
COLOR_DOME_SURFACE = ({p.color_dome_surf[0]:.3f}, {p.color_dome_surf[1]:.3f}, {p.color_dome_surf[2]:.3f})
ALPHA_DOME_SURFACE = {p.alpha_dome_surf:.2f}
COLOR_DOME_WIRE = ({p.color_dome_wire[0]:.3f}, {p.color_dome_wire[1]:.3f}, {p.color_dome_wire[2]:.3f})
ALPHA_DOME_WIRE = {p.alpha_dome_wire:.2f}
COLOR_STAR1 = ({p.color_star1[0]:.3f}, {p.color_star1[1]:.3f}, {p.color_star1[2]:.3f})
ALPHA_STAR1 = {p.alpha_star1:.2f}
COLOR_STAR2 = ({p.color_star2[0]:.3f}, {p.color_star2[1]:.3f}, {p.color_star2[2]:.3f})
ALPHA_STAR2 = {p.alpha_star2:.2f}
COLOR_OBSERVER1 = ({p.color_obs1[0]:.3f}, {p.color_obs1[1]:.3f}, {p.color_obs1[2]:.3f})
ALPHA_OBSERVER1 = {p.alpha_obs1:.2f}
COLOR_OBSERVER2 = ({p.color_obs2[0]:.3f}, {p.color_obs2[1]:.3f}, {p.color_obs2[2]:.3f})
ALPHA_OBSERVER2 = {p.alpha_obs2:.2f}
"""
context.window_manager.clipboard = code
self.report({'INFO'}, "初期値用コードをクリップボードにコピーしました")
return {'FINISHED'}
class HEMISTAR_OT_addon_remove(bpy.types.Operator):
bl_idname = f"{ADDON_ID}.addon_remove"
bl_label = "Unregister Addon"
def execute(self, context):
bpy.app.timers.register(unregister, first_interval=0.01)
return {'FINISHED'}
# =========================================================
# 9. パネル構成
# =========================================================
class HEMISTAR_PT_main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{ADDON_ID}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 1
def draw(self, context):
layout = self.layout
props = context.scene.hemistar_props
# Action Buttons (1行に1ボタンずつ縦並び)
col = layout.column(align=True)
col.prop(props, "name_prefix", text="Name Prefix", icon='SYNTAX_ON')
col.operator(f"{ADDON_ID}.create", icon='ADD')
col.operator(f"{ADDON_ID}.detach", icon='UNLINKED')
# Transform & Shape
box = layout.box()
box.label(text="Transform & Shape:", icon='OUTLINER_OB_MESH')
box.prop(props, "radius")
box.prop(props, "wire_thickness")
box.prop(props, "star_size")
col2 = box.column(align=True)
col2.prop(props, "star1_u", text="Star1 U")
col2.prop(props, "star1_v", text="Star1 V")
col2.prop(props, "star2_u", text="Star2 U")
col2.prop(props, "star2_v", text="Star2 V")
box.prop(props, "obs_size")
col3 = box.column(align=True)
col3.prop(props, "obs1_x", text="Obs1 X")
col3.prop(props, "obs1_y", text="Obs1 Y")
col3.prop(props, "obs2_x", text="Obs2 X")
col3.prop(props, "obs2_y", text="Obs2 Y")
# Display & Materials
box = layout.box()
box.label(text="Display & Materials:", icon='MATERIAL')
def draw_mat_prop(label_text, show_prop, color_prop, alpha_prop):
row = box.row(align=True)
row.prop(props, show_prop, text="")
row.label(text=label_text)
row.prop(props, color_prop, text="")
row.prop(props, alpha_prop, text="", slider=True)
header = box.row(align=True)
header.label(text=" Show | Name")
header.label(text="Color")
header.label(text="Alpha")
draw_mat_prop("Dome Surf", "show_dome_surface", "color_dome_surf", "alpha_dome_surf")
draw_mat_prop("Dome Wire", "show_dome_wire", "color_dome_wire", "alpha_dome_wire")
draw_mat_prop("Star 1", "show_star1", "color_star1", "alpha_star1")
draw_mat_prop("Star 2", "show_star2", "color_star2", "alpha_star2")
draw_mat_prop("Observer 1", "show_obs1", "color_obs1", "alpha_obs1")
draw_mat_prop("Observer 2", "show_obs2", "color_obs2", "alpha_obs2")
class HEMISTAR_PT_settings(bpy.types.Panel):
bl_label = "初期値のカスタマイズ"
bl_idname = f"{ADDON_ID}_PT_settings"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator(f"{ADDON_ID}.copy_code", icon='COPYDOWN')
layout.label(text="↑ コピーしたテキストをスクリプトの", icon='INFO')
layout.label(text=" 「6. 初期設定」に上書きペースト可能")
class HEMISTAR_PT_links(bpy.types.Panel):
bl_label = "Links"
bl_idname = f"{ADDON_ID}_PT_links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 3
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
col = self.layout.column(align=True)
for name, url in [(LINK_NAME_1, LINK_URL_1), (LINK_NAME_2, LINK_URL_2)]:
col.operator(f"{ADDON_ID}.open_url", text=name, icon='URL').url = url
class HEMISTAR_PT_remove(bpy.types.Panel):
bl_label = "アドオン削除"
bl_idname = f"{ADDON_ID}_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_options = {'DEFAULT_CLOSED'}
bl_order = 4
def draw(self, context):
self.layout.column().operator(f"{ADDON_ID}.addon_remove", icon='CANCEL')
# =========================================================
# 10. Register / Unregister
# =========================================================
classes = (
HEMISTAR_OT_open_url,
HEMISTAR_PG_props,
HEMISTAR_OT_create,
HEMISTAR_OT_detach,
HEMISTAR_OT_copy_code,
HEMISTAR_OT_addon_remove,
HEMISTAR_PT_main,
HEMISTAR_PT_settings,
HEMISTAR_PT_links,
HEMISTAR_PT_remove,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.hemistar_props = bpy.props.PointerProperty(type=HEMISTAR_PG_props)
def unregister():
if hasattr(bpy.types.Scene, "hemistar_props"): del bpy.types.Scene.hemistar_props
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__): bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
こっから 色付け
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "Hemisphere Star Simulator",
"author": "Your Name",
"version": (1, 4, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > HemiStar",
"description": "Simulation of hemisphere dome, 2 stars (angle UV), and 2 observers with Size controls.",
"category": "3D View",
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Created : 2024_0525_1600
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import webbrowser
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "HemiStar Sim"
PREFIX_VAL = "HEMISTAR"
PREFIX = PREFIX_VAL.lower()
ADDON_CATEGORY = "HemiStar"
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "HemiStar Docs (Addon)"
LINK_URL_1 = "<https://example.com/hemistar-docs>"
LINK_NAME_2 = "Blender Manual"
LINK_URL_2 = "<https://docs.blender.org/manual/en/latest/>"
LINK_NAME_3 = "Blender Python API"
LINK_URL_3 = "<https://docs.blender.org/api/current/>"
LINK_NAME_4 = "Blender Artists"
LINK_URL_4 = "<https://blenderartists.org/>"
LINK_NAME_5 = "GitHub Repository"
LINK_URL_5 = "<https://github.com/>"
# =========================================================
# 6. 初期設定
# =========================================================
# 数値
RADIUS_DEFAULT = 10.0
SIZE_STAR_DEFAULT = 0.3
SIZE_OBSERVER_DEFAULT = 0.5
WIRE_THICKNESS_DEFAULT = 0.05
# XYZ
OBSERVER1_X_DEFAULT = -2.0
OBSERVER1_Y_DEFAULT = 0.0
OBSERVER1_Z_DEFAULT = 0.0
OBSERVER2_X_DEFAULT = 2.0
OBSERVER2_Y_DEFAULT = 0.0
OBSERVER2_Z_DEFAULT = 0.0
# UV (Angle: Degree)
STAR1_U_DEFAULT = 45.0
STAR1_V_DEFAULT = 30.0
STAR2_U_DEFAULT = 135.0
STAR2_V_DEFAULT = 60.0
# Alpha
ALPHA_FRONT_DEFAULT = 1.0
ALPHA_BACK_DEFAULT = 0.5
# Text
TEXT_SIZE_DEFAULT = 1.0
TEXT_OFFSET_X_DEFAULT = 0.0
TEXT_OFFSET_Y_DEFAULT = 0.0
TEXT_OFFSET_Z_DEFAULT = 1.0
# Color (R,G,B,A)
COLOR_DOME_SURFACE = (0.05, 0.1, 0.2, 0.4)
COLOR_DOME_WIRE = (0.4, 0.8, 1.0, 1.0)
COLOR_STAR1 = (1.0, 0.8, 0.0, 1.0)
COLOR_STAR2 = (0.0, 0.8, 1.0, 1.0)
COLOR_OBSERVER1 = (1.0, 0.2, 0.2, 1.0)
COLOR_OBSERVER2 = (0.2, 1.0, 0.2, 1.0)
# =========================================================
# 7. 共通クラス
# =========================================================
class HEMISTAR_OT_open_url(bpy.types.Operator):
bl_idname = f"{PREFIX}.open_url"
bl_label = "Open URL"
bl_description = "Open the web link in default browser"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url:
webbrowser.open(self.url)
return {'FINISHED'}
# =========================================================
# 8. アドオン固有機能
# =========================================================
# --- 更新関数 ---
def update_dome(self, context):
scale = (self.radius, self.radius, self.radius)
obj_surf = bpy.data.objects.get(f"{PREFIX}_Dome_Surface")
obj_wire = bpy.data.objects.get(f"{PREFIX}_Dome_Wire")
if obj_surf: obj_surf.scale = scale
if obj_wire: obj_wire.scale = scale
def update_wire_thickness(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Dome_Wire")
if obj:
mod = obj.modifiers.get("Wireframe")
if mod:
mod.thickness = self.wire_thickness
def get_star_location(r, u_deg, v_deg):
theta = math.radians(u_deg)
phi = math.radians(v_deg)
x = r * math.cos(phi) * math.cos(theta)
y = r * math.cos(phi) * math.sin(theta)
z = r * math.sin(phi)
return (x, y, z)
def update_star1(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Star1")
if obj:
obj.location = get_star_location(self.radius, self.star1_u, self.star1_v)
def update_star2(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Star2")
if obj:
obj.location = get_star_location(self.radius, self.star2_u, self.star2_v)
def update_star_size(self, context):
for i in (1, 2):
obj = bpy.data.objects.get(f"{PREFIX}_Star{i}")
if obj:
obj.scale = (self.star_size, self.star_size, self.star_size)
def update_obs1(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Observer1")
if obj:
obj.location = (self.obs1_x, self.obs1_y, OBSERVER1_Z_DEFAULT)
def update_obs2(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Observer2")
if obj:
obj.location = (self.obs2_x, self.obs2_y, OBSERVER2_Z_DEFAULT)
def update_obs_size(self, context):
for i in (1, 2):
obj = bpy.data.objects.get(f"{PREFIX}_Observer{i}")
if obj:
obj.scale = (self.obs_size, self.obs_size, self.obs_size)
def update_radius(self, context):
update_dome(self, context)
update_star1(self, context)
update_star2(self, context)
def update_display(self, context):
items = [
("Dome_Surface", self.show_dome_surface),
("Dome_Wire", self.show_dome_wire),
("Star1", self.show_star1),
("Star2", self.show_star2),
("Observer1", self.show_obs1),
("Observer2", self.show_obs2)
]
for name, is_show in items:
obj = bpy.data.objects.get(f"{PREFIX}_{name}")
if obj:
obj.hide_viewport = not is_show
obj.hide_render = not is_show
# --- Property Group ---
class HEMISTAR_PG_props(bpy.types.PropertyGroup):
radius: bpy.props.FloatProperty(name="Radius", default=RADIUS_DEFAULT, min=0.1, update=update_radius)
wire_thickness: bpy.props.FloatProperty(name="Wire Thickness", default=WIRE_THICKNESS_DEFAULT, min=0.001, update=update_wire_thickness)
star_size: bpy.props.FloatProperty(name="Star Size", default=SIZE_STAR_DEFAULT, min=0.01, update=update_star_size)
star1_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR1_U_DEFAULT, min=0.0, max=360.0, update=update_star1)
star1_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR1_V_DEFAULT, min=0.0, max=90.0, update=update_star1)
star2_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR2_U_DEFAULT, min=0.0, max=360.0, update=update_star2)
star2_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR2_V_DEFAULT, min=0.0, max=90.0, update=update_star2)
obs_size: bpy.props.FloatProperty(name="Observer Size", default=SIZE_OBSERVER_DEFAULT, min=0.01, update=update_obs_size)
obs1_x: bpy.props.FloatProperty(name="X", default=OBSERVER1_X_DEFAULT, update=update_obs1)
obs1_y: bpy.props.FloatProperty(name="Y", default=OBSERVER1_Y_DEFAULT, update=update_obs1)
obs2_x: bpy.props.FloatProperty(name="X", default=OBSERVER2_X_DEFAULT, update=update_obs2)
obs2_y: bpy.props.FloatProperty(name="Y", default=OBSERVER2_Y_DEFAULT, update=update_obs2)
show_dome_surface: bpy.props.BoolProperty(name="Dome Surface", default=True, update=update_display)
show_dome_wire: bpy.props.BoolProperty(name="Dome Wireframe", default=True, update=update_display)
show_star1: bpy.props.BoolProperty(name="Star1", default=True, update=update_display)
show_star2: bpy.props.BoolProperty(name="Star2", default=True, update=update_display)
show_obs1: bpy.props.BoolProperty(name="Observer1", default=True, update=update_display)
show_obs2: bpy.props.BoolProperty(name="Observer2", default=True, update=update_display)
# --- Operators ---
class HEMISTAR_OT_create(bpy.types.Operator):
bl_idname = f"{PREFIX}.create"
bl_label = "Create Objects"
bl_description = "Create Hemisphere, Stars, and Observers"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.hemistar_props
col_name = f"{PREFIX_VAL}_Collection"
col = bpy.data.collections.get(col_name)
if not col:
col = bpy.data.collections.new(col_name)
context.scene.collection.children.link(col)
def create_mesh_obj(name, create_func, color):
obj_name = f"{PREFIX}_{name}"
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)
col.objects.link(obj)
create_func(mesh)
# Material (Blender 5 Eevee Next対応)
mat = bpy.data.materials.new(name=f"{obj_name}_Mat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = color[3]
obj.data.materials.append(mat)
return obj
def make_dome(mesh):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
to_delete = [v for v in bm.verts if v.co.z < -0.001]
bmesh.ops.delete(bm, geom=to_delete, context='VERTS')
bm.to_mesh(mesh)
bm.free()
def make_star(mesh):
bm = bmesh.new()
# 基準サイズ 1.0 で作成し、スケールでサイズを制御する
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=1.0)
bm.to_mesh(mesh)
bm.free()
def make_observer(mesh):
bm = bmesh.new()
# 基準サイズ (半径1, 高さ2) で作成
bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=16,
radius1=1.0, radius2=0.0, depth=2.0)
# Z=0を底面にするための移動 (+1.0Z)
bmesh.ops.translate(bm, verts=bm.verts, vec=(0.0, 0.0, 1.0))
bm.to_mesh(mesh)
bm.free()
create_mesh_obj("Dome_Surface", make_dome, COLOR_DOME_SURFACE)
wire_obj = create_mesh_obj("Dome_Wire", make_dome, COLOR_DOME_WIRE)
create_mesh_obj("Star1", make_star, COLOR_STAR1)
create_mesh_obj("Star2", make_star, COLOR_STAR2)
create_mesh_obj("Observer1", make_observer, COLOR_OBSERVER1)
create_mesh_obj("Observer2", make_observer, COLOR_OBSERVER2)
if "Wireframe" not in wire_obj.modifiers:
mod = wire_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
mod.use_replace = True
mod.thickness = props.wire_thickness
# プロパティ値をオブジェクトに反映
update_radius(props, context)
update_star_size(props, context)
update_obs_size(props, context)
update_obs1(props, context)
update_obs2(props, context)
update_display(props, context)
return {'FINISHED'}
class HEMISTAR_OT_addon_remove(bpy.types.Operator):
bl_idname = f"{PREFIX}.addon_remove"
bl_label = "Unregister Addon"
bl_description = "このアドオンを無効化し、UIから消去します"
def execute(self, context):
# タイマー方式で安全に自身をアンレジスターする
bpy.app.timers.register(unregister, first_interval=0.01)
return {'FINISHED'}
# =========================================================
# 9. パネル構成 (分割構成)
# =========================================================
class HEMISTAR_PT_main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{PREFIX_VAL}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 1
def draw(self, context):
layout = self.layout
props = context.scene.hemistar_props
# Create
box = layout.box()
box.operator(f"{PREFIX}.create", icon='ADD')
# Hemisphere
box = layout.box()
box.label(text="Hemisphere:", icon='SPHERE')
box.prop(props, "radius")
box.prop(props, "wire_thickness")
# Stars
box = layout.box()
box.label(text="Stars:", icon='LIGHT')
box.prop(props, "star_size")
row = box.row()
row.prop(props, "star1_u", text="Star1 U")
row.prop(props, "star1_v", text="Star1 V")
row = box.row()
row.prop(props, "star2_u", text="Star2 U")
row.prop(props, "star2_v", text="Star2 V")
# Observers
box = layout.box()
box.label(text="Observers:", icon='USER')
box.prop(props, "obs_size")
row = box.row()
row.prop(props, "obs1_x", text="Obs1 X")
row.prop(props, "obs1_y", text="Obs1 Y")
row = box.row()
row.prop(props, "obs2_x", text="Obs2 X")
row.prop(props, "obs2_y", text="Obs2 Y")
# Display
box = layout.box()
box.label(text="Display:", icon='HIDE_OFF')
col = box.column(align=True)
col.prop(props, "show_dome_surface")
col.prop(props, "show_dome_wire")
row = col.row()
row.prop(props, "show_star1")
row.prop(props, "show_star2")
row = col.row()
row.prop(props, "show_obs1")
row.prop(props, "show_obs2")
class HEMISTAR_PT_links(bpy.types.Panel):
bl_label = "Links"
bl_idname = f"{PREFIX_VAL}_PT_links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_order = 2
def draw(self, context):
layout = self.layout
box = layout.box()
links = [
(LINK_NAME_1, LINK_URL_1),
(LINK_NAME_2, LINK_URL_2),
(LINK_NAME_3, LINK_URL_3),
(LINK_NAME_4, LINK_URL_4),
(LINK_NAME_5, LINK_URL_5),
]
for name, url in links:
if name and url:
op = box.operator(f"{PREFIX}.open_url", text=name, icon='URL')
op.url = url
class HEMISTAR_PT_remove(bpy.types.Panel):
bl_label = "アドオン削除"
bl_idname = f"{PREFIX_VAL}_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
bl_options = {'DEFAULT_CLOSED'}
bl_order = 3
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="このアドオンを無効化し消去します", icon='ERROR')
box.operator(f"{PREFIX}.addon_remove", icon='CANCEL')
class HEMISTAR_MT_menu(bpy.types.Menu):
bl_label = ADDON_TITLE
bl_idname = f"{PREFIX_VAL}_MT_menu"
def draw(self, context):
layout = self.layout
layout.operator(f"{PREFIX}.create", icon='ADD')
layout.separator()
layout.operator(f"{PREFIX}.addon_remove", icon='CANCEL')
def menu_func(self, context):
self.layout.menu(HEMISTAR_MT_menu.bl_idname, icon='SPHERE')
# =========================================================
# 10. register()
# =========================================================
classes = (
HEMISTAR_OT_open_url,
HEMISTAR_PG_props,
HEMISTAR_OT_create,
HEMISTAR_OT_addon_remove,
HEMISTAR_PT_main,
HEMISTAR_PT_links,
HEMISTAR_PT_remove,
HEMISTAR_MT_menu,
)
def register():
# 1. Class 登録
for cls in classes:
bpy.utils.register_class(cls)
# 2. Scene Property 登録
bpy.types.Scene.hemistar_props = bpy.props.PointerProperty(type=HEMISTAR_PG_props)
# 3. Menu 登録
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
# =========================================================
# 11. unregister()
# =========================================================
def unregister():
# 1. Menu 解除
if hasattr(bpy.types, "VIEW3D_MT_mesh_add"):
try:
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
except ValueError:
pass
# 2. Scene Property 解除
if hasattr(bpy.types.Scene, "hemistar_props"):
del bpy.types.Scene.hemistar_props
# 3. Class 解除
for cls in reversed(classes):
if hasattr(bpy.types, cls.__name__):
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
不具合あり
# =========================================================
# 1. bl_info
# =========================================================
bl_info = {
"name": "Hemisphere Star Simulator",
"author": "Your Name",
"version": (1, 3, 0),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > HemiStar",
"description": "Simulation of hemisphere dome, 2 stars (angle UV), and 2 observers.",
"category": "3D View",
}
# =========================================================
# 2. コード作成日時
# =========================================================
# Code Created : 2024_0525_1500
# =========================================================
# 3. import
# =========================================================
import bpy
import bmesh
import math
import webbrowser
# =========================================================
# 4. 共通定義
# =========================================================
ADDON_TITLE = "HemiStar Sim"
PREFIX_VAL = "HEMISTAR"
PREFIX = PREFIX_VAL.lower()
ADDON_CATEGORY = "HemiStar"
# =========================================================
# 5. リンク定義
# =========================================================
LINK_NAME_1 = "HemiStar Docs (Addon)"
LINK_URL_1 = "<https://example.com/hemistar-docs>"
LINK_NAME_2 = "Blender Manual"
LINK_URL_2 = "<https://docs.blender.org/manual/en/latest/>"
LINK_NAME_3 = "Blender Python API"
LINK_URL_3 = "<https://docs.blender.org/api/current/>"
LINK_NAME_4 = "Blender Artists"
LINK_URL_4 = "<https://blenderartists.org/>"
LINK_NAME_5 = "GitHub Repository"
LINK_URL_5 = "<https://github.com/>"
# =========================================================
# 6. 初期設定
# =========================================================
# 数値
RADIUS_DEFAULT = 10.0
SIZE_STAR_DEFAULT = 0.3
SIZE_OBSERVER_DEFAULT = 0.5
WIRE_THICKNESS_DEFAULT = 0.05
# XYZ
OBSERVER1_X_DEFAULT = -2.0
OBSERVER1_Y_DEFAULT = 0.0
OBSERVER1_Z_DEFAULT = 0.0
OBSERVER2_X_DEFAULT = 2.0
OBSERVER2_Y_DEFAULT = 0.0
OBSERVER2_Z_DEFAULT = 0.0
# UV (Angle: Degree)
STAR1_U_DEFAULT = 45.0
STAR1_V_DEFAULT = 30.0
STAR2_U_DEFAULT = 135.0
STAR2_V_DEFAULT = 60.0
# Alpha
ALPHA_FRONT_DEFAULT = 1.0
ALPHA_BACK_DEFAULT = 0.5
# Text
TEXT_SIZE_DEFAULT = 1.0
TEXT_OFFSET_X_DEFAULT = 0.0
TEXT_OFFSET_Y_DEFAULT = 0.0
TEXT_OFFSET_Z_DEFAULT = 1.0
# Color (R,G,B,A)
COLOR_DOME_SURFACE = (0.05, 0.1, 0.2, 0.4)
COLOR_DOME_WIRE = (0.4, 0.8, 1.0, 1.0)
COLOR_STAR1 = (1.0, 0.8, 0.0, 1.0)
COLOR_STAR2 = (0.0, 0.8, 1.0, 1.0)
COLOR_OBSERVER1 = (1.0, 0.2, 0.2, 1.0)
COLOR_OBSERVER2 = (0.2, 1.0, 0.2, 1.0)
# =========================================================
# 7. 共通クラス
# =========================================================
class HEMISTAR_OT_open_url(bpy.types.Operator):
bl_idname = f"{PREFIX}.open_url"
bl_label = "Open URL"
bl_description = "Open the web link in default browser"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url:
webbrowser.open(self.url)
return {'FINISHED'}
# =========================================================
# 8. アドオン固有機能
# =========================================================
# --- 更新関数 ---
def update_dome(self, context):
scale = (self.radius, self.radius, self.radius)
obj_surf = bpy.data.objects.get(f"{PREFIX}_Dome_Surface")
obj_wire = bpy.data.objects.get(f"{PREFIX}_Dome_Wire")
if obj_surf: obj_surf.scale = scale
if obj_wire: obj_wire.scale = scale
def update_wire_thickness(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Dome_Wire")
if obj:
mod = obj.modifiers.get("Wireframe")
if mod:
mod.thickness = self.wire_thickness
def get_star_location(r, u_deg, v_deg):
theta = math.radians(u_deg)
phi = math.radians(v_deg)
x = r * math.cos(phi) * math.cos(theta)
y = r * math.cos(phi) * math.sin(theta)
z = r * math.sin(phi)
return (x, y, z)
def update_star1(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Star1")
if obj:
obj.location = get_star_location(self.radius, self.star1_u, self.star1_v)
def update_star2(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Star2")
if obj:
obj.location = get_star_location(self.radius, self.star2_u, self.star2_v)
def update_obs1(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Observer1")
if obj:
obj.location = (self.obs1_x, self.obs1_y, OBSERVER1_Z_DEFAULT)
def update_obs2(self, context):
obj = bpy.data.objects.get(f"{PREFIX}_Observer2")
if obj:
obj.location = (self.obs2_x, self.obs2_y, OBSERVER2_Z_DEFAULT)
def update_radius(self, context):
update_dome(self, context)
update_star1(self, context)
update_star2(self, context)
def update_display(self, context):
items = [
("Dome_Surface", self.show_dome_surface),
("Dome_Wire", self.show_dome_wire),
("Star1", self.show_star1),
("Star2", self.show_star2),
("Observer1", self.show_obs1),
("Observer2", self.show_obs2)
]
for name, is_show in items:
obj = bpy.data.objects.get(f"{PREFIX}_{name}")
if obj:
obj.hide_viewport = not is_show
obj.hide_render = not is_show
# --- Property Group ---
class HEMISTAR_PG_props(bpy.types.PropertyGroup):
radius: bpy.props.FloatProperty(name="Radius", default=RADIUS_DEFAULT, min=0.1, update=update_radius)
wire_thickness: bpy.props.FloatProperty(name="Wire Thickness", default=WIRE_THICKNESS_DEFAULT, min=0.001, update=update_wire_thickness)
star1_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR1_U_DEFAULT, min=0.0, max=360.0, update=update_star1)
star1_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR1_V_DEFAULT, min=0.0, max=90.0, update=update_star1)
star2_u: bpy.props.FloatProperty(name="U (Azimuth)", default=STAR2_U_DEFAULT, min=0.0, max=360.0, update=update_star2)
star2_v: bpy.props.FloatProperty(name="V (Elevation)", default=STAR2_V_DEFAULT, min=0.0, max=90.0, update=update_star2)
obs1_x: bpy.props.FloatProperty(name="X", default=OBSERVER1_X_DEFAULT, update=update_obs1)
obs1_y: bpy.props.FloatProperty(name="Y", default=OBSERVER1_Y_DEFAULT, update=update_obs1)
obs2_x: bpy.props.FloatProperty(name="X", default=OBSERVER2_X_DEFAULT, update=update_obs2)
obs2_y: bpy.props.FloatProperty(name="Y", default=OBSERVER2_Y_DEFAULT, update=update_obs2)
show_dome_surface: bpy.props.BoolProperty(name="Dome Surface", default=True, update=update_display)
show_dome_wire: bpy.props.BoolProperty(name="Dome Wireframe", default=True, update=update_display)
show_star1: bpy.props.BoolProperty(name="Star1", default=True, update=update_display)
show_star2: bpy.props.BoolProperty(name="Star2", default=True, update=update_display)
show_obs1: bpy.props.BoolProperty(name="Observer1", default=True, update=update_display)
show_obs2: bpy.props.BoolProperty(name="Observer2", default=True, update=update_display)
# --- Operators ---
class HEMISTAR_OT_create(bpy.types.Operator):
bl_idname = f"{PREFIX}.create"
bl_label = "Create Objects"
bl_description = "Create Hemisphere, Stars, and Observers"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.hemistar_props
col_name = f"{PREFIX_VAL}_Collection"
col = bpy.data.collections.get(col_name)
if not col:
col = bpy.data.collections.new(col_name)
context.scene.collection.children.link(col)
def create_mesh_obj(name, create_func, color):
obj_name = f"{PREFIX}_{name}"
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)
col.objects.link(obj)
create_func(mesh)
# Material作成 (Blender 5 Eevee Next対応: エラー原因のプロパティを削除)
mat = bpy.data.materials.new(name=f"{obj_name}_Mat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = color
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = color[3]
obj.data.materials.append(mat)
return obj
def make_dome(mesh):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=1.0)
to_delete = [v for v in bm.verts if v.co.z < -0.001]
bmesh.ops.delete(bm, geom=to_delete, context='VERTS')
bm.to_mesh(mesh)
bm.free()
def make_star(mesh):
bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=SIZE_STAR_DEFAULT)
bm.to_mesh(mesh)
bm.free()
def make_observer(mesh):
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=16,
radius1=SIZE_OBSERVER_DEFAULT, radius2=0.0,
depth=SIZE_OBSERVER_DEFAULT*2)
# Z=0を底面にするための移動
bmesh.ops.translate(bm, verts=bm.verts, vec=(0.0, 0.0, SIZE_OBSERVER_DEFAULT))
bm.to_mesh(mesh)
bm.free()
create_mesh_obj("Dome_Surface", make_dome, COLOR_DOME_SURFACE)
wire_obj = create_mesh_obj("Dome_Wire", make_dome, COLOR_DOME_WIRE)
create_mesh_obj("Star1", make_star, COLOR_STAR1)
create_mesh_obj("Star2", make_star, COLOR_STAR2)
create_mesh_obj("Observer1", make_observer, COLOR_OBSERVER1)
create_mesh_obj("Observer2", make_observer, COLOR_OBSERVER2)
# ワイヤーフレームモディファイアの設定
if "Wireframe" not in wire_obj.modifiers:
mod = wire_obj.modifiers.new(name="Wireframe", type='WIREFRAME')
mod.use_replace = True
mod.thickness = props.wire_thickness
# 初期状態への反映
update_radius(props, context)
update_obs1(props, context)
update_obs2(props, context)
update_display(props, context)
return {'FINISHED'}
class HEMISTAR_OT_addon_remove(bpy.types.Operator):
bl_idname = f"{PREFIX}.addon_remove"
bl_label = "Remove Addon"
bl_description = "Disable and remove this addon from Blender completely"
def execute(self, context):
addon_name = __package__
if not addon_name:
addon_name = __name__
if addon_name == "__main__":
self.report({'WARNING'}, "Cannot remove script when run directly from Text Editor.")
return {'CANCELLED'}
# アドオンの無効化と削除
bpy.ops.preferences.addon_disable(module=addon_name)
bpy.ops.preferences.addon_remove(module=addon_name)
self.report({'INFO'}, "Addon successfully removed.")
return {'FINISHED'}
# =========================================================
# 9. パネル・メニュー構成
# =========================================================
class HEMISTAR_PT_main(bpy.types.Panel):
bl_label = ADDON_TITLE
bl_idname = f"{PREFIX_VAL}_PT_main"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_CATEGORY
def draw(self, context):
layout = self.layout
props = context.scene.hemistar_props
# Create
box = layout.box()
box.operator(f"{PREFIX}.create", icon='ADD')
# Hemisphere
box = layout.box()
box.label(text="Hemisphere:", icon='SPHERE')
box.prop(props, "radius")
box.prop(props, "wire_thickness")
# Star1
box = layout.box()
box.label(text="Star1 (Angle):", icon='LIGHT')
box.prop(props, "star1_u")
box.prop(props, "star1_v")
# Star2
box = layout.box()
box.label(text="Star2 (Angle):", icon='LIGHT')
box.prop(props, "star2_u")
box.prop(props, "star2_v")
# Observer1
box = layout.box()
box.label(text="Observer1:", icon='USER')
row = box.row()
row.prop(props, "obs1_x")
row.prop(props, "obs1_y")
# Observer2
box = layout.box()
box.label(text="Observer2:", icon='USER')
row = box.row()
row.prop(props, "obs2_x")
row.prop(props, "obs2_y")
# Display
box = layout.box()
box.label(text="Display:", icon='HIDE_OFF')
col = box.column(align=True)
col.prop(props, "show_dome_surface")
col.prop(props, "show_dome_wire")
row = col.row()
row.prop(props, "show_star1")
row.prop(props, "show_star2")
row = col.row()
row.prop(props, "show_obs1")
row.prop(props, "show_obs2")
# Links
box = layout.box()
box.label(text="Links:", icon='URL')
links = [
(LINK_NAME_1, LINK_URL_1),
(LINK_NAME_2, LINK_URL_2),
]
for name, url in links:
op = box.operator(f"{PREFIX}.open_url", text=name)
op.url = url
# Addon Remove (アドオン自体の削除)
box = layout.box()
box.operator(f"{PREFIX}.addon_remove", icon='TRASH')
class HEMISTAR_MT_menu(bpy.types.Menu):
bl_label = ADDON_TITLE
bl_idname = f"{PREFIX_VAL}_MT_menu"
def draw(self, context):
layout = self.layout
layout.operator(f"{PREFIX}.create", icon='ADD')
layout.separator()
layout.operator(f"{PREFIX}.addon_remove", icon='TRASH')
def menu_func(self, context):
self.layout.menu(HEMISTAR_MT_menu.bl_idname, icon='SPHERE')
# =========================================================
# 10. register()
# =========================================================
classes = (
HEMISTAR_OT_open_url,
HEMISTAR_PG_props,
HEMISTAR_OT_create,
HEMISTAR_OT_addon_remove,
HEMISTAR_PT_main,
HEMISTAR_MT_menu,
)
def register():
# 1. Class 登録
for cls in classes:
bpy.utils.register_class(cls)
# 2. Scene Property 登録
bpy.types.Scene.hemistar_props = bpy.props.PointerProperty(type=HEMISTAR_PG_props)
# 3. Menu 登録 (Shift+A の Mesh メニューに追加)
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
# =========================================================
# 11. unregister()
# =========================================================
def unregister():
# 1. Menu 解除
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
# 2. Scene Property 解除
if hasattr(bpy.types.Scene, "hemistar_props"):
del bpy.types.Scene.hemistar_props
# 3. Class 解除
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if __name__ == "_