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()