import bpy import bmesh import math def add_boolean(target, cutter, operation='DIFFERENCE', solver='EXACT'): mod = target.modifiers.new(name="Bool", type='BOOLEAN') mod.object = cutter mod.operation = operation mod.solver = solver bpy.context.view_layer.objects.active = target bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(cutter, do_unlink=True) def generate_blocks_v2(): # Clear existing mesh objects bpy.ops.object.select_all(action='DESELECT') for obj in list(bpy.context.scene.objects): if obj.type == 'MESH': bpy.data.objects.remove(obj, do_unlink=True) block_size = 20.0 tol = 0.22 # 0.22mm clearance for smooth, rattle-free FDM fit # Dimensions for the Horizontal-Flex Planar Snap Fork (Prints with continuous XY perimeter loops!) prong_thick_z = 8.0 # 8mm thick in Z (approx 40 layers of solid strength, centered at Z=10) stem_l = 6.5 # 6.5mm total projection out of the block (+X direction) neck_half_y = 3.2 # 6.4mm total neck width in Y bump_ext = 0.55 # 0.55mm bump overhang on each side (total bump width = 7.5mm) - gentle snap! bump_x = 4.2 # Distance from block face where the locking ridge sits slot_w_y = 2.0 # 2.0mm wide center flex slot relief_r = 1.3 # 2.6mm diameter keyhole relief embedded inside the block for long beam length # ========================================== # BLOCK 1: PLUG (At X = -18, pointing +X) # ========================================== bpy.ops.mesh.primitive_cube_add(size=block_size, location=(-18, 0, block_size/2)) b1 = bpy.context.active_object b1.name = "Block_Plug_v2" mod_bev1 = b1.modifiers.new(name="Bevel", type='BEVEL') mod_bev1.width = 0.6 mod_bev1.segments = 2 bpy.context.view_layer.objects.active = b1 bpy.ops.object.modifier_apply(modifier=mod_bev1.name) # Build the 2D profile of the outer plug fork in XY plane, extruded in Z (from Z = 6 to 14) x0 = -8.0 profile_verts = [ (x0 - 3.0, -(neck_half_y + 1.5)), (x0, -(neck_half_y + 1.2)), (x0 + 1.2, -neck_half_y), (x0 + 2.4, -neck_half_y), (x0 + bump_x, -(neck_half_y + bump_ext)), (x0 + stem_l, -(neck_half_y - 0.6)), (x0 + stem_l, (neck_half_y - 0.6)), (x0 + bump_x, (neck_half_y + bump_ext)), (x0 + 2.4, neck_half_y), (x0 + 1.2, neck_half_y), (x0, (neck_half_y + 1.2)), (x0 - 3.0, (neck_half_y + 1.5)), ] mesh_fork = bpy.data.meshes.new("ForkProfile") bm = bmesh.new() z_bot = (block_size - prong_thick_z) / 2.0 z_top = z_bot + prong_thick_z bottom_v = [bm.verts.new((x, y, z_bot)) for (x, y) in profile_verts] top_v = [bm.verts.new((x, y, z_top)) for (x, y) in profile_verts] bm.faces.new(bottom_v[::-1]) bm.faces.new(top_v) n = len(profile_verts) for i in range(n): bm.faces.new([bottom_v[i], bottom_v[(i+1)%n], top_v[(i+1)%n], top_v[i]]) bm.normal_update() bm.to_mesh(mesh_fork) bm.free() fork_obj = bpy.data.objects.new("ForkObj", mesh_fork) bpy.context.collection.objects.link(fork_obj) bpy.ops.object.select_all(action='DESELECT') fork_obj.select_set(True) bpy.context.view_layer.objects.active = fork_obj mod_fbev = fork_obj.modifiers.new(name="Bevel", type='BEVEL') mod_fbev.width = 0.8 mod_fbev.segments = 4 mod_fbev.affect = 'EDGES' mod_fbev.limit_method = 'ANGLE' mod_fbev.angle_limit = math.radians(20) bpy.ops.object.modifier_apply(modifier=mod_fbev.name) add_boolean(b1, fork_obj, operation='UNION') # Cut the center flex slot slot_len = stem_l + 4.0 bpy.ops.mesh.primitive_cube_add( size=1.0, location=(x0 - 1.5 + slot_len/2, 0, block_size/2) ) slot_cut = bpy.context.active_object slot_cut.scale = (slot_len, slot_w_y, prong_thick_z + 2.0) bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) add_boolean(b1, slot_cut, operation='DIFFERENCE') # Cut vertical stress-relief cylinder at root of slot (embedded inside the block) bpy.ops.mesh.primitive_cylinder_add( vertices=32, radius=relief_r, depth=prong_thick_z + 2.0, location=(x0 - 1.5, 0, block_size/2) ) relief_cut = bpy.context.active_object add_boolean(b1, relief_cut, operation='DIFFERENCE') # ========================================== # BLOCK 2: SOCKET (At X = +15, opening at -X face X = +5.0) # ========================================== bpy.ops.mesh.primitive_cube_add(size=block_size, location=(15, 0, block_size/2)) b2 = bpy.context.active_object b2.name = "Block_Socket_v2" mod_bev2 = b2.modifiers.new(name="Bevel", type='BEVEL') mod_bev2.width = 0.6 mod_bev2.segments = 2 bpy.context.view_layer.objects.active = b2 bpy.ops.object.modifier_apply(modifier=mod_bev2.name) sx0 = 5.0 c_neck_y = neck_half_y + tol c_bump_y = neck_half_y + bump_ext + tol c_depth = stem_l + 0.8 socket_verts = [ (sx0 - 1.0, -(c_neck_y + 1.4)), (sx0 + 1.0, -c_neck_y), (sx0 + 2.3, -c_neck_y), (sx0 + bump_x, -c_bump_y), (sx0 + c_depth, -(c_neck_y - 0.3)), (sx0 + c_depth, (c_neck_y - 0.3)), (sx0 + bump_x, c_bump_y), (sx0 + 2.3, c_neck_y), (sx0 + 1.0, c_neck_y), (sx0 - 1.0, (c_neck_y + 1.4)), ] mesh_soc = bpy.data.meshes.new("SocketProfile") bm2 = bmesh.new() sz_bot = (block_size - prong_thick_z)/2.0 - tol sz_top = (block_size + prong_thick_z)/2.0 + tol s_bot_v = [bm2.verts.new((x, y, sz_bot)) for (x, y) in socket_verts] s_top_v = [bm2.verts.new((x, y, sz_top)) for (x, y) in socket_verts] bm2.faces.new(s_bot_v[::-1]) bm2.faces.new(s_top_v) sn = len(socket_verts) for i in range(sn): bm2.faces.new([s_bot_v[i], s_bot_v[(i+1)%sn], s_top_v[(i+1)%sn], s_top_v[i]]) bm2.normal_update() bm2.to_mesh(mesh_soc) bm2.free() soc_obj = bpy.data.objects.new("SocCutter", mesh_soc) bpy.context.collection.objects.link(soc_obj) bpy.ops.object.select_all(action='DESELECT') soc_obj.select_set(True) bpy.context.view_layer.objects.active = soc_obj mod_sbev = soc_obj.modifiers.new(name="Bevel", type='BEVEL') mod_sbev.width = 0.8 mod_sbev.segments = 4 mod_sbev.affect = 'EDGES' mod_sbev.limit_method = 'ANGLE' mod_sbev.angle_limit = math.radians(20) bpy.ops.object.modifier_apply(modifier=mod_sbev.name) add_boolean(b2, soc_obj, operation='DIFFERENCE') for obj in [b1, b2]: bm_check = bmesh.new() bm_check.from_mesh(obj.data) bmesh.ops.remove_doubles(bm_check, verts=bm_check.verts, dist=0.0005) bm_check.to_mesh(obj.data) bm_check.free() if __name__ == "__main__": generate_blocks_v2()