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__ == "_