# IMU Pointer Enclosure — FreeCAD Macro v5 # ========================================== # Lid drops INSIDE the base. # Base has a shelf step cut 1.5mm into the inner walls, # LID_STEP mm from the top. Lid rests on that shelf, flush with base top. # USB-C notch on front edge of lid keeps port accessible. # # Layout (top view, front = left): # [USB-C][ XIAO on posts ]<-wires->[ AAA holder ][ grip ] # ========================================== import FreeCAD as App import FreeCADGui as Gui import Part import Mesh import os from FreeCAD import Base doc = App.newDocument("IMU_Pointer_v5") # ---- PARAMETERS ------------------------------------------------------------ L = 115.0 W = 34.0 H = 28.0 WALL = 2.5 CR = 3.0 # corner fillet radius TOL = 0.25 # fit clearance — lid is this much smaller than the shelf opening # Shelf: step cut into inside of base walls near the top # Lid sits on this shelf, flush with the base rim SHELF_DEPTH = 1.5 # how far shelf cuts inward from the inner wall face SHELF_H = 2.0 # how tall the shelf step is (from top of base downward) # lid height = SHELF_H so it sits exactly flush LID_H = SHELF_H # lid is exactly as tall as the shelf recess # XIAO board (front of device) BRD_L = 21.0 BRD_W = 18.0 BRD_X = WALL + 4.0 BRD_Y = (W - BRD_W) / 2 BRD_Z = WALL + 4.5 # AAA battery holder BAT_L = 50.0 BAT_W = 12.0 BAT_H = 12.0 BAT_X = BRD_X + BRD_L + 6.0 BAT_Y = (W - BAT_W) / 2 # L-post mount POST_OD = 3.5 POST_ID = 1.6 POST_H = BRD_Z L_ARM = 2.2 L_THICK = 1.2 # USB-C cutout on front face USBC_W = 9.5 USBC_H = 3.5 USBC_Z = BRD_Z + 0.8 MACRO_DIR = os.path.dirname(os.path.abspath(__file__)) # ---- HELPERS --------------------------------------------------------------- def rbox(lx, ly, lz, ox=0, oy=0, oz=0, r=CR): b = Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz)) try: vert = [e for e in b.Edges if abs(e.Vertexes[0].Z - e.Vertexes[1].Z) > lz * 0.9] if vert: b = b.makeFillet(r, vert) except Exception: pass return b def box(lx, ly, lz, ox=0, oy=0, oz=0): return Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz)) def cyl(r, h, ox=0, oy=0, oz=0): return Part.makeCylinder(r, h, Base.Vector(ox, oy, oz)) def export_stl(shape, name): path = os.path.join(MACRO_DIR, name) tess = shape.tessellate(0.06) m = Mesh.Mesh(list(zip(tess[0], tess[1]))) m.write(path) print(f" Saved: {path}") # ==================================================================== # BASE # ==================================================================== base = rbox(L, W, H) # Main inner cavity — full height minus floor base = base.cut(box(L - WALL*2, W - WALL*2, H - WALL, WALL, WALL, WALL)) # Shelf recess: widen the top SHELF_H of the inner cavity by SHELF_DEPTH # This creates the step the lid rests on shelf_cut = box(L - WALL*2 + SHELF_DEPTH*2, W - WALL*2 + SHELF_DEPTH*2, SHELF_H + 0.1, WALL - SHELF_DEPTH, WALL - SHELF_DEPTH, H - SHELF_H) base = base.cut(shelf_cut) # USB-C slot on front face (X=0) base = base.cut(box(WALL*3, USBC_W, USBC_H, -WALL, W/2 - USBC_W/2, USBC_Z)) # ---- L-shaped mounting posts ---- brd_cx = BRD_X + BRD_L / 2 brd_cy = BRD_Y + BRD_W / 2 post_corners = [ (BRD_X + 2.0, BRD_Y + 2.0), (BRD_X + BRD_L - 2.0, BRD_Y + 2.0), (BRD_X + 2.0, BRD_Y + BRD_W - 2.0), (BRD_X + BRD_L - 2.0, BRD_Y + BRD_W - 2.0), ] for (px, py) in post_corners: post = cyl(POST_OD/2, POST_H, px, py, WALL) post = post.cut(cyl(POST_ID/2, POST_H + 1.0, px, py, WALL - 0.5)) dx = brd_cx - px; dy = brd_cy - py mag = (dx**2 + dy**2)**0.5; dx /= mag; dy /= mag perp_x = -dy; perp_y = dx; hw = L_THICK / 2 z0 = WALL + POST_H - L_THICK; z1 = WALL + POST_H p0 = Base.Vector(px - perp_x*hw, py - perp_y*hw, z0) p1 = Base.Vector(px + perp_x*hw, py + perp_y*hw, z0) p2 = Base.Vector(px + perp_x*hw, py + perp_y*hw, z1) p3 = Base.Vector(px - perp_x*hw, py - perp_y*hw, z1) arm = Part.Face(Part.Wire([ Part.makeLine(p0,p1), Part.makeLine(p1,p2), Part.makeLine(p2,p3), Part.makeLine(p3,p0) ])).extrude(Base.Vector(dx*L_ARM, dy*L_ARM, 0)) base = base.fuse(post.fuse(arm)) # ---- Battery cradle ---- base = base.cut(box(BAT_L, BAT_W, 3.0, BAT_X, BAT_Y, WALL)) base = base.fuse(box(2.0, BAT_W+3.0, BAT_H*0.55, BAT_X-2.0, BAT_Y-1.5, WALL)) base = base.fuse(box(2.0, BAT_W+3.0, BAT_H*0.55, BAT_X+BAT_L, BAT_Y-1.5, WALL)) print("[1/2] Base complete.") # ==================================================================== # LID — sized to drop inside the shelf opening, built at Z=0 # ==================================================================== # Lid outer dimensions match the shelf recess minus tolerance LID_L = L - WALL*2 + SHELF_DEPTH*2 - TOL*2 LID_W = W - WALL*2 + SHELF_DEPTH*2 - TOL*2 lid = rbox(LID_L, LID_W, LID_H, WALL - SHELF_DEPTH + TOL, WALL - SHELF_DEPTH + TOL, 0, r=1.5) # USB-C notch on front edge (X=0 face of lid) so port stays accessible # The notch is a slot cut from the front edge, aligned with USB-C position notch_y = W/2 - USBC_W/2 - (WALL - SHELF_DEPTH + TOL) # relative to lid origin lid = lid.cut(box(WALL*2, USBC_W, USBC_H + 0.5, -(WALL - SHELF_DEPTH + TOL), # punch through front face W/2 - USBC_W/2, 0)) print("[2/2] Lid complete.") # Export STLs at Z=0 BEFORE any translation print("\nExporting STLs...") export_stl(base, "pointer_base.stl") export_stl(lid, "pointer_lid.stl") # Translate lid up to assembled position for FreeCAD viewport only lid.translate(Base.Vector(0, 0, H - LID_H)) # ==================================================================== # DOCUMENT # ==================================================================== base_obj = doc.addObject("Part::Feature", "Pointer_Base") base_obj.Shape = base base_obj.ViewObject.ShapeColor = (0.12, 0.12, 0.14) lid_obj = doc.addObject("Part::Feature", "Pointer_Lid") lid_obj.Shape = lid lid_obj.ViewObject.ShapeColor = (0.28, 0.28, 0.34) lid_obj.ViewObject.Transparency = 25 doc.recompute() Gui.activeDocument().activeView().viewIsometric() Gui.SendMsgToActiveView("ViewFit") print() print("=" * 62) print("IMU Pointer v5 — lid drops INSIDE base") print(f" Outer : {L:.0f} x {W:.0f} x {H:.0f} mm") print(f" Shelf step : {SHELF_DEPTH:.1f} mm inward, {SHELF_H:.1f} mm tall") print(f" Lid : {LID_L:.1f} x {LID_W:.1f} x {LID_H:.1f} mm") print(f" Tolerance : {TOL:.2f} mm") print() print("Assembly:") print(" Drop lid flat-side-up into the top opening.") print(" It rests on the shelf, sitting flush with the base rim.") print(" USB-C notch on front edge keeps port fully accessible.") print(" To remove: push up from the USB-C hole or pry with fingernail.") print() print("Print orientation:") print(" Base: flat bottom down, no supports") print(" Lid : either face down, it's a flat plate — no supports") print("=" * 62)