# IMU Pointer Enclosure import FreeCAD as App import FreeCADGui as Gui import Part from FreeCAD import Base doc = App.newDocument("pointer") # Global dimensions L = 115.0 W = 36.0 H = 20.0 WALL = 3.5 CR = 3.0 TOL = 0.25 # Rail and lid RAIL_H = 4.5 RAIL_D = 2.0 LIP_H = 2.0 LIP_OVER = 1.5 LIP_EMBED = 0.2 LID_H = RAIL_H - LIP_H - TOL - 0.55 # Board dimensions PCB_T = 1.0 BRD_L = 21.0 BRD_W = 17.5 BRD_X = WALL BRD_Y = (W - BRD_W) / 2 PLATFORM_H = 0.5 BRD_Z = WALL + PLATFORM_H # Clip arms ARM_LEN = 5.0 ARM_THICK = 1.6 ARM_H = BRD_Z + PCB_T + 0.8 CLIP_TOL = 0.35 # USB-C cutout USBC_W = 11.0 USBC_H = 7.0 USBC_Z = 4.5 # Battery section BAT_L = 50.0 BAT_W = 12.0 BAT_H = 12.0 BAT_X = BRD_X + BRD_L + 8.0 BAT_Y = (W - BAT_W) / 2 BAT_CLIP_Y = 8.0 # Rounded box helper 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 # Simple box helper def box(lx, ly, lz, ox=0, oy=0, oz=0): return Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz)) # Rounded slot helper def rounded_slot(depth, w, h, ox, oy, oz, r=None): if r is None: r = h / 2.0 r = min(r, h / 2.0, w / 2.0) import math cy = oy + w / 2.0 cz = oz + h / 2.0 hw = w / 2.0 - r def pt(cx, cy_v, cz_v, angle_deg, radius): a = math.radians(angle_deg) return Base.Vector( cx, cy_v + radius * math.cos(a), cz_v + radius * math.sin(a) ) l_start = pt(ox, cy - hw, cz, 270, r) l_mid = pt(ox, cy - hw, cz, 180, r) l_end = pt(ox, cy - hw, cz, 90, r) arc_left = Part.Arc(l_start, l_mid, l_end).toShape() line_top = Part.makeLine( l_end, pt(ox, cy + hw, cz, 90, r) ) r_start = pt(ox, cy + hw, cz, 90, r) r_mid = pt(ox, cy + hw, cz, 0, r) r_end = pt(ox, cy + hw, cz, 270, r) arc_right = Part.Arc(r_start, r_mid, r_end).toShape() line_bot = Part.makeLine(r_end, l_start) wire = Part.Wire([arc_left, line_top, arc_right, line_bot]) face = Part.Face(wire) return face.extrude(Base.Vector(depth, 0, 0)) # Board clip helper def make_clip(corner_x, corner_y, inward_x, inward_y): plat_w = ARM_THICK + CLIP_TOL plat_x = corner_x if inward_x > 0 else corner_x - plat_w plat_y = corner_y if inward_y > 0 else corner_y - plat_w platform = box( plat_w, plat_w, PLATFORM_H + PCB_T, plat_x, plat_y, WALL ) ax_ox = corner_x if inward_x > 0 else corner_x - ARM_LEN ax_oy = corner_y - ARM_THICK - CLIP_TOL if inward_y > 0 else corner_y + CLIP_TOL arm_x = box(ARM_LEN, ARM_THICK, ARM_H, ax_ox, ax_oy, WALL) ay_oy = corner_y if inward_y > 0 else corner_y - ARM_LEN ay_ox = corner_x - ARM_THICK - CLIP_TOL if inward_x > 0 else corner_x + CLIP_TOL arm_y = box(ARM_THICK, ARM_LEN, ARM_H, ay_ox, ay_oy, WALL) corner_block_w = ARM_THICK + CLIP_TOL cb_ox = corner_x - corner_block_w if inward_x > 0 else corner_x cb_oy = corner_y - corner_block_w if inward_y > 0 else corner_y corner_block = box( corner_block_w, corner_block_w, ARM_H, cb_ox, cb_oy, WALL ) return platform.fuse(arm_x.fuse(arm_y).fuse(corner_block)) # Base outer body base = rbox(L, W, H) # Inner cavity base = base.cut( box(L - WALL * 2, W - WALL * 2, H - WALL, WALL, WALL, WALL) ) rail_z = H - RAIL_H groove_h = RAIL_H - LIP_H # Rail grooves base = base.cut( box(L - WALL * 2, RAIL_D, groove_h, WALL, WALL - RAIL_D, rail_z) ) base = base.cut( box(L - WALL * 2, RAIL_D, groove_h, WALL, W - WALL, rail_z) ) # Lid lips lip_z = H - LIP_H base = base.fuse( box(L - WALL * 2, LIP_OVER, LIP_H, WALL, WALL, lip_z) ) base = base.fuse( box(L - WALL * 2, LIP_OVER, LIP_H, WALL, W - WALL - LIP_OVER, lip_z) ) # Back slot slot_y0 = WALL - RAIL_D slot_yw = W - WALL * 2 + RAIL_D * 2 base = base.cut( box(WALL + 1.0, slot_yw, RAIL_H, L - WALL, slot_y0, rail_z) ) # Entry bump BUMP_H = 0.5 pad_raw = box(WALL, slot_yw, BUMP_H, L - WALL, slot_y0, rail_z) pad_trimmed = pad_raw.common(rbox(L, W, H)) base = base.fuse(pad_trimmed) # Board clips clip_corners = [ (BRD_X, BRD_Y, +1, +1), (BRD_X + BRD_L, BRD_Y, -1, +1), (BRD_X, BRD_Y + BRD_W, +1, -1), (BRD_X + BRD_L, BRD_Y + BRD_W, -1, -1), ] for cx, cy, ix, iy in clip_corners: base = base.fuse(make_clip(cx, cy, ix, iy)) # USB-C opening base = base.cut( rounded_slot( WALL * 3, USBC_W, USBC_H, -WALL, W / 2 - USBC_W / 2, USBC_Z ) ) # Battery recess base = base.cut( box(BAT_L, BAT_W, 3.0, BAT_X, BAT_Y, WALL) ) clip_y_start = BAT_Y + BAT_W / 2 - BAT_CLIP_Y / 2 base = base.fuse( box(2.0, BAT_CLIP_Y, BAT_H * 0.55, BAT_X - 2.0, clip_y_start, WALL) ) base = base.fuse( box(2.0, BAT_CLIP_Y, BAT_H * 0.55, BAT_X + BAT_L, clip_y_start, WALL) ) # Lid TAB_W = RAIL_D - TOL + 0.5 LID_L = L - WALL * 2 - TOL LID_EXTRA_TOL = 0.5 LID_W = ( W - WALL * 2 - (TOL + LID_EXTRA_TOL) * 2 + TAB_W * 2 ) lid_y0 = WALL + TOL + LID_EXTRA_TOL - TAB_W lid = box(LID_L, LID_W, LID_H, 0, lid_y0, 0) lid = lid.cut( box(WALL * 2, USBC_W + TOL, LID_H + 0.2, LID_L - WALL, W / 2 - (USBC_W + TOL) / 2, -0.1) ) lid.translate(Base.Vector(WALL + TOL, 0, rail_z)) # Final objects 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")