Sidebar(N) > HoleMaker", "description": "Generates a sphere with uniformly distributed holes using Boolean difference.", "category": "Object", } class HoleMakerProperties(bpy.types.PropertyGroup): sphere_radius: bpy.props.FloatProperty( name="球体の半径", default=1.0, min=0.1 ) cylinder_radius: bpy.props.FloatProperty( name="円柱の半径", default=0.1, min=0.01 ) cylinder_count: bpy.props.IntProperty( name="円柱の本数", default=12, min=1 ) class OBJECT_OT_hole_maker(bpy.types.Operator): bl_idname = "object.hole_maker" bl_label = "穴あき球体を生成" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): props = context.scene.hole_maker_props # 1. 選択状態をクリア bpy.ops.object.select_all(a"> Sidebar(N) > HoleMaker", "description": "Generates a sphere with uniformly distributed holes using Boolean difference.", "category": "Object", } class HoleMakerProperties(bpy.types.PropertyGroup): sphere_radius: bpy.props.FloatProperty( name="球体の半径", default=1.0, min=0.1 ) cylinder_radius: bpy.props.FloatProperty( name="円柱の半径", default=0.1, min=0.01 ) cylinder_count: bpy.props.IntProperty( name="円柱の本数", default=12, min=1 ) class OBJECT_OT_hole_maker(bpy.types.Operator): bl_idname = "object.hole_maker" bl_label = "穴あき球体を生成" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): props = context.scene.hole_maker_props # 1. 選択状態をクリア bpy.ops.object.select_all(a"> Sidebar(N) > HoleMaker", "description": "Generates a sphere with uniformly distributed holes using Boolean difference.", "category": "Object", } class HoleMakerProperties(bpy.types.PropertyGroup): sphere_radius: bpy.props.FloatProperty( name="球体の半径", default=1.0, min=0.1 ) cylinder_radius: bpy.props.FloatProperty( name="円柱の半径", default=0.1, min=0.01 ) cylinder_count: bpy.props.IntProperty( name="円柱の本数", default=12, min=1 ) class OBJECT_OT_hole_maker(bpy.types.Operator): bl_idname = "object.hole_maker" bl_label = "穴あき球体を生成" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): props = context.scene.hole_maker_props # 1. 選択状態をクリア bpy.ops.object.select_all(a">
import bpy
import math
import mathutils

bl_info = {
    "name": "HoleMaker",
    "author": "AI",
    "version": (1, 1),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar(N) > HoleMaker",
    "description": "Generates a sphere with uniformly distributed holes using Boolean difference.",
    "category": "Object",
}

class HoleMakerProperties(bpy.types.PropertyGroup):
    sphere_radius: bpy.props.FloatProperty(
        name="球体の半径",
        default=1.0,
        min=0.1
    )
    cylinder_radius: bpy.props.FloatProperty(
        name="円柱の半径",
        default=0.1,
        min=0.01
    )
    cylinder_count: bpy.props.IntProperty(
        name="円柱の本数",
        default=12,
        min=1
    )

class OBJECT_OT_hole_maker(bpy.types.Operator):
    bl_idname = "object.hole_maker"
    bl_label = "穴あき球体を生成"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.hole_maker_props
        
        # 1. 選択状態をクリア
        bpy.ops.object.select_all(action='DESELECT')
        
        # 2. ベースとなる球体を作成
        bpy.ops.mesh.primitive_uv_sphere_add(
            radius=props.sphere_radius, 
            location=(0, 0, 0)
        )
        sphere = context.active_object
        
        # 3. 円柱の生成とブーリアン処理
        n = props.cylinder_count
        # フィボナッチ球面配置のための黄金角
        phi = math.pi * (3.0 - math.sqrt(5.0)) 
        
        # 円柱の長さ(球体を確実に貫通する長さ)
        cylinder_depth = props.sphere_radius * 2.5 
        
        for i in range(n):
            # フィボナッチ配置による座標計算
            y = 1.0 - (i / float(n - 1)) * 2.0 if n > 1 else 0.0
            radius = math.sqrt(1.0 - y * y)
            theta = phi * i
            
            x = math.cos(theta) * radius
            z = math.sin(theta) * radius
            
            # 円柱の向きを計算(ベクトル方向へZ軸を向ける)
            vec = mathutils.Vector((x, y, z))
            rot = vec.to_track_quat('Z', 'Y').to_euler()
            
            # 円柱を作成
            bpy.ops.mesh.primitive_cylinder_add(
                radius=props.cylinder_radius,
                depth=cylinder_depth,
                location=(0, 0, 0),
                rotation=rot
            )
            cylinder = context.active_object
            
            # ブーリアンを適用するために球体をアクティブにする
            context.view_layer.objects.active = sphere
            
            # ブーリアンモディファイアの設定
            mod = sphere.modifiers.new(name="HoleBoolean", type='BOOLEAN')
            mod.operation = 'DIFFERENCE'
            
            # 【修正点】Blenderのバージョン(4.2以前/4.3以降など)によるソルバー名の違いに自動対応
            try:
                mod.solver = 'FAST'
            except TypeError:
                mod.solver = 'FLOAT' # 最新バージョンの場合
                
            mod.object = cylinder
            
            # モディファイアの適用 (Blender 4.x/5系互換)
            bpy.ops.object.modifier_apply(modifier=mod.name)
            
            # 使用済みの円柱(カッター)を完全に削除
            bpy.data.objects.remove(cylinder, do_unlink=True)
            
        # 処理完了後、生成した球体を選択・アクティブ状態にする
        sphere.select_set(True)
        context.view_layer.objects.active = sphere
        
        return {'FINISHED'}

class VIEW3D_PT_hole_maker(bpy.types.Panel):
    bl_label = "HoleMaker"
    bl_idname = "VIEW3D_PT_hole_maker"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'HoleMaker'

    def draw(self, context):
        layout = self.layout
        props = context.scene.hole_maker_props
        
        # UIプロパティの配置
        col = layout.column(align=True)
        col.prop(props, "sphere_radius")
        col.prop(props, "cylinder_radius")
        col.prop(props, "cylinder_count")
        
        layout.separator()
        
        # 実行ボタンの配置
        layout.operator("object.hole_maker", text="穴あき球体を生成", icon='MESH_UVSPHERE')

classes = (
    HoleMakerProperties,
    OBJECT_OT_hole_maker,
    VIEW3D_PT_hole_maker,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.hole_maker_props = bpy.props.PointerProperty(type=HoleMakerProperties)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.hole_maker_props

if __name__ == "__main__":
    register()