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_v3(): # 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 per side for smooth FDM snap fit # Connector Pin Parameters (Symmetric Double-Ended Snap Connector) pin_thick_z = 6.0 # 6mm flat thickness on build plate (Z = 0 to 6mm) half_len_x = 7.0 # 7mm per side -> 14mm total pin length neck_half_y = 3.0 # 6.0mm neck width (fits into 6.44mm socket neck) bump_ext = 0.55 # 0.55mm locking bump per side (7.1mm across bumps -> gentle, durable snap!) bump_x = 4.3 # Distance from center (X=0) to locking bump peak collar_half_x = 0.9 # Center stop-collar half-width (1.8mm total) so pin never pushes too deep collar_half_y = 3.6 # 7.2mm width at center collar (sits nicely in the entrance chamfer) slot_w_y = 1.6 # 1.6mm flex slot width on each end relief_r = 1.15 # 2.3mm keyhole stress-relief diameter near center # ============================================================================== # PART 3: DOUBLE SNAP CONNECTOR PIN (At Y = 0, sitting flat on Z = 0..6) # ============================================================================== full_verts = [ (collar_half_x, -collar_half_y), (collar_half_x + 0.6, -neck_half_y), (2.6, -neck_half_y), (bump_x, -(neck_half_y + bump_ext)), (half_len_x, -(neck_half_y - 0.65)), (half_len_x, (neck_half_y - 0.65)), (bump_x, (neck_half_y + bump_ext)), (2.6, neck_half_y), (collar_half_x + 0.6, neck_half_y), (collar_half_x, collar_half_y), (-collar_half_x, collar_half_y), (-(collar_half_x + 0.6), neck_half_y), (-2.6, neck_half_y), (-bump_x, (neck_half_y + bump_ext)), (-half_len_x, (neck_half_y - 0.65)), (-half_len_x, -(neck_half_y - 0.65)), (-bump_x, -(neck_half_y + bump_ext)), (-2.6, -neck_half_y), (-(collar_half_x + 0.6), -neck_half_y), (-collar_half_x, -collar_half_y), ] mesh_pin = bpy.data.meshes.new("ConnectorPin") bm_p = bmesh.new() bot_v = [bm_p.verts.new((x, y, 0.0)) for (x, y) in full_verts] top_v = [bm_p.verts.new((x, y, pin_thick_z)) for (x, y) in full_verts] bm_p.faces.new(bot_v[::-1]) bm_p.faces.new(top_v) np = len(full_verts) for i in range(np): bm_p.faces.new([bot_v[i], bot_v[(i+1)%np], top_v[(i+1)%np], top_v[i]]) bm_p.normal_update() bm_p.to_mesh(mesh_pin) bm_p.free() pin_obj = bpy.data.objects.new("Snap_Connector_Pin_v3", mesh_pin) bpy.context.collection.objects.link(pin_obj) bpy.ops.object.select_all(action='DESELECT') pin_obj.select_set(True) bpy.context.view_layer.objects.active = pin_obj mod_pbev = pin_obj.modifiers.new(name="Bevel", type='BEVEL') mod_pbev.width = 0.7 mod_pbev.segments = 4 mod_pbev.affect = 'EDGES' mod_pbev.limit_method = 'ANGLE' mod_pbev.angle_limit = math.radians(18) bpy.ops.object.modifier_apply(modifier=mod_pbev.name) for sign in [1.0, -1.0]: keyhole_x = sign * 1.9 slot_len = half_len_x bpy.ops.mesh.primitive_cube_add( size=1.0, location=(keyhole_x + sign * (slot_len/2), 0, pin_thick_z/2) ) s_cut = bpy.context.active_object s_cut.scale = (slot_len, slot_w_y, pin_thick_z + 2.0) bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) add_boolean(pin_obj, s_cut, operation='DIFFERENCE') bpy.ops.mesh.primitive_cylinder_add( vertices=32, radius=relief_r, depth=pin_thick_z + 2.0, location=(keyhole_x, 0, pin_thick_z/2) ) r_cut = bpy.context.active_object add_boolean(pin_obj, r_cut, operation='DIFFERENCE') # ============================================================================== # HELPER: BUILD VERTICAL (+Z) SOCKET CUTTER AT TOP OF A BLOCK # ============================================================================== def cut_vertical_socket_in_block(block_obj, loc_x, loc_y): z_top_face = block_size c_collar_y = collar_half_y + tol + 0.5 c_neck_y = neck_half_y + tol c_bump_y = neck_half_y + bump_ext + tol c_depth = half_len_x + 0.6 c_thick_x = pin_thick_z + 2 * tol soc_yz = [ (-(c_collar_y + 0.6), z_top_face + 1.0), (-c_collar_y, z_top_face), (-c_neck_y, z_top_face - (collar_half_x + 0.5)), (-c_neck_y, z_top_face - 2.5), (-c_bump_y, z_top_face - bump_x), (-(c_neck_y - 0.3), z_top_face - c_depth), ( (c_neck_y - 0.3), z_top_face - c_depth), ( c_bump_y, z_top_face - bump_x), ( c_neck_y, z_top_face - 2.5), ( c_neck_y, z_top_face - (collar_half_x + 0.5)), ( c_collar_y, z_top_face), ( (c_collar_y + 0.6), z_top_face + 1.0), ] mesh_s = bpy.data.meshes.new("VertSocketCutter") bm_s = bmesh.new() x_min = loc_x - c_thick_x / 2.0 x_max = loc_x + c_thick_x / 2.0 v_left = [bm_s.verts.new((x_min, loc_y + y, z)) for (y, z) in soc_yz] v_right = [bm_s.verts.new((x_max, loc_y + y, z)) for (y, z) in soc_yz] bm_s.faces.new(v_left[::-1]) bm_s.faces.new(v_right) ns = len(soc_yz) for i in range(ns): bm_s.faces.new([v_left[i], v_left[(i+1)%ns], v_right[(i+1)%ns], v_right[i]]) bm_s.normal_update() bm_s.to_mesh(mesh_s) bm_s.free() cutter_obj = bpy.data.objects.new("VertCutter", mesh_s) bpy.context.collection.objects.link(cutter_obj) bpy.ops.object.select_all(action='DESELECT') cutter_obj.select_set(True) bpy.context.view_layer.objects.active = cutter_obj mod_sbev = cutter_obj.modifiers.new(name="Bevel", type='BEVEL') mod_sbev.width = 0.7 mod_sbev.segments = 4 mod_sbev.affect = 'EDGES' mod_sbev.limit_method = 'ANGLE' mod_sbev.angle_limit = math.radians(18) bpy.ops.object.modifier_apply(modifier=mod_sbev.name) add_boolean(block_obj, cutter_obj, operation='DIFFERENCE') bm_ch = bmesh.new() ch_pts = [ (loc_x - (c_thick_x/2 + 1.0), z_top_face + 1.0), (loc_x - (c_thick_x/2), z_top_face - 1.2), (loc_x + (c_thick_x/2), z_top_face - 1.2), (loc_x + (c_thick_x/2 + 1.0), z_top_face + 1.0), ] y_min = loc_y - (c_neck_y + 0.4) y_max = loc_y + (c_neck_y + 0.4) vl = [bm_ch.verts.new((x, y_min, z)) for (x, z) in ch_pts] vr = [bm_ch.verts.new((x, y_max, z)) for (x, z) in ch_pts] bm_ch.faces.new(vl[::-1]) bm_ch.faces.new(vr) for i in range(4): bm_ch.faces.new([vl[i], vl[(i+1)%4], vr[(i+1)%4], vr[i]]) bm_ch.normal_update() m_ch = bpy.data.meshes.new("XChamfer") bm_ch.to_mesh(m_ch) bm_ch.free() ch_obj = bpy.data.objects.new("XChamferObj", m_ch) bpy.context.collection.objects.link(ch_obj) add_boolean(block_obj, ch_obj, operation='DIFFERENCE') # ============================================================================== # BLOCK A & BLOCK B: BOTH WITH TOP (+Z) SNAP HOLES # ============================================================================== blocks = [] for name, y_pos in [("Block_Socket_A_v3", -22.0), ("Block_Socket_B_v3", 22.0)]: bpy.ops.mesh.primitive_cube_add(size=block_size, location=(0, y_pos, block_size/2)) blk = bpy.context.active_object blk.name = name mod_bev = blk.modifiers.new(name="Bevel", type='BEVEL') mod_bev.width = 0.6 mod_bev.segments = 2 bpy.context.view_layer.objects.active = blk bpy.ops.object.modifier_apply(modifier=mod_bev.name) cut_vertical_socket_in_block(blk, 0.0, y_pos) blocks.append(blk) all_objs = blocks + [pin_obj] for obj in all_objs: bm_c = bmesh.new() bm_c.from_mesh(obj.data) bmesh.ops.remove_doubles(bm_c, verts=bm_c.verts, dist=0.0005) bm_c.to_mesh(obj.data) bm_c.free() if __name__ == "__main__": generate_blocks_v3()