Cleanup macro

This commit is contained in:
2026-03-01 17:41:48 +01:00
parent 8c95ea0480
commit 2cd4d272d6
3 changed files with 229 additions and 154 deletions
+3
View File
@@ -0,0 +1,3 @@
*.FCStd
*.FCBak
*.3mf
+226 -154
View File
@@ -1,71 +1,60 @@
# IMU Pointer Enclosure — FreeCAD Macro v5 # IMU Pointer Enclosure
# ==========================================
# 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 FreeCAD as App
import FreeCADGui as Gui import FreeCADGui as Gui
import Part import Part
import Mesh
import os
from FreeCAD import Base from FreeCAD import Base
doc = App.newDocument("IMU_Pointer_v5") doc = App.newDocument("pointer")
# ---- PARAMETERS ------------------------------------------------------------ # Global dimensions
L = 115.0
W = 36.0
H = 20.0
WALL = 3.5
CR = 3.0
TOL = 0.25
L = 115.0 # Rail and lid
W = 34.0 RAIL_H = 4.5
H = 28.0 RAIL_D = 2.0
WALL = 2.5 LIP_H = 2.0
CR = 3.0 # corner fillet radius LIP_OVER = 1.5
LIP_EMBED = 0.2
TOL = 0.25 # fit clearance — lid is this much smaller than the shelf opening LID_H = RAIL_H - LIP_H - TOL - 0.55
# Shelf: step cut into inside of base walls near the top # Board dimensions
# Lid sits on this shelf, flush with the base rim PCB_T = 1.0
SHELF_DEPTH = 1.5 # how far shelf cuts inward from the inner wall face BRD_L = 21.0
SHELF_H = 2.0 # how tall the shelf step is (from top of base downward) BRD_W = 17.5
# lid height = SHELF_H so it sits exactly flush BRD_X = WALL
BRD_Y = (W - BRD_W) / 2
LID_H = SHELF_H # lid is exactly as tall as the shelf recess PLATFORM_H = 0.5
BRD_Z = WALL + PLATFORM_H
# XIAO board (front of device) # Clip arms
BRD_L = 21.0 ARM_LEN = 5.0
BRD_W = 18.0 ARM_THICK = 1.6
BRD_X = WALL + 4.0 ARM_H = BRD_Z + PCB_T + 0.8
BRD_Y = (W - BRD_W) / 2 CLIP_TOL = 0.35
BRD_Z = WALL + 4.5
# AAA battery holder # USB-C cutout
BAT_L = 50.0 USBC_W = 11.0
BAT_W = 12.0 USBC_H = 7.0
BAT_H = 12.0 USBC_Z = 4.5
BAT_X = BRD_X + BRD_L + 6.0
BAT_Y = (W - BAT_W) / 2
# L-post mount # Battery section
POST_OD = 3.5 BAT_L = 50.0
POST_ID = 1.6 BAT_W = 12.0
POST_H = BRD_Z BAT_H = 12.0
L_ARM = 2.2 BAT_X = BRD_X + BRD_L + 8.0
L_THICK = 1.2 BAT_Y = (W - BAT_W) / 2
BAT_CLIP_Y = 8.0
# 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 ---------------------------------------------------------------
# Rounded box helper
def rbox(lx, ly, lz, ox=0, oy=0, oz=0, r=CR): def rbox(lx, ly, lz, ox=0, oy=0, oz=0, r=CR):
b = Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz)) b = Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz))
try: try:
@@ -77,135 +66,218 @@ def rbox(lx, ly, lz, ox=0, oy=0, oz=0, r=CR):
pass pass
return b return b
# Simple box helper
def box(lx, ly, lz, ox=0, oy=0, oz=0): def box(lx, ly, lz, ox=0, oy=0, oz=0):
return Part.makeBox(lx, ly, lz, Base.Vector(ox, oy, oz)) 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): # Rounded slot helper
path = os.path.join(MACRO_DIR, name) def rounded_slot(depth, w, h, ox, oy, oz, r=None):
tess = shape.tessellate(0.06) if r is None:
m = Mesh.Mesh(list(zip(tess[0], tess[1]))) r = h / 2.0
m.write(path)
print(f" Saved: {path}")
# ==================================================================== r = min(r, h / 2.0, w / 2.0)
# BASE
# ====================================================================
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) 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 # Inner cavity
# This creates the step the lid rests on base = base.cut(
shelf_cut = box(L - WALL*2 + SHELF_DEPTH*2, box(L - WALL * 2, W - WALL * 2, H - WALL,
W - WALL*2 + SHELF_DEPTH*2, WALL, WALL, WALL)
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) rail_z = H - RAIL_H
base = base.cut(box(WALL*3, USBC_W, USBC_H, groove_h = RAIL_H - LIP_H
-WALL, W/2 - USBC_W/2, USBC_Z))
# ---- L-shaped mounting posts ---- # Rail grooves
brd_cx = BRD_X + BRD_L / 2 base = base.cut(
brd_cy = BRD_Y + BRD_W / 2 box(L - WALL * 2, RAIL_D, groove_h,
post_corners = [ WALL, WALL - RAIL_D, rail_z)
(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), base = base.cut(
(BRD_X + BRD_L - 2.0, BRD_Y + BRD_W - 2.0), 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 (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 ---- for cx, cy, ix, iy in clip_corners:
base = base.cut(box(BAT_L, BAT_W, 3.0, BAT_X, BAT_Y, WALL)) base = base.fuse(make_clip(cx, cy, ix, iy))
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.") # USB-C opening
base = base.cut(
rounded_slot(
WALL * 3,
USBC_W,
USBC_H,
-WALL,
W / 2 - USBC_W / 2,
USBC_Z
)
)
# ==================================================================== # Battery recess
# LID — sized to drop inside the shelf opening, built at Z=0 base = base.cut(
# ==================================================================== box(BAT_L, BAT_W, 3.0,
# Lid outer dimensions match the shelf recess minus tolerance BAT_X, BAT_Y, WALL)
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, clip_y_start = BAT_Y + BAT_W / 2 - BAT_CLIP_Y / 2
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 base = base.fuse(
# The notch is a slot cut from the front edge, aligned with USB-C position box(2.0, BAT_CLIP_Y, BAT_H * 0.55,
notch_y = W/2 - USBC_W/2 - (WALL - SHELF_DEPTH + TOL) # relative to lid origin BAT_X - 2.0, clip_y_start, WALL)
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.") base = base.fuse(
box(2.0, BAT_CLIP_Y, BAT_H * 0.55,
BAT_X + BAT_L, clip_y_start, WALL)
)
# Export STLs at Z=0 BEFORE any translation # Lid
print("\nExporting STLs...") TAB_W = RAIL_D - TOL + 0.5
export_stl(base, "pointer_base.stl") LID_L = L - WALL * 2 - TOL
export_stl(lid, "pointer_lid.stl") LID_EXTRA_TOL = 0.5
# Translate lid up to assembled position for FreeCAD viewport only LID_W = (
lid.translate(Base.Vector(0, 0, H - LID_H)) W - WALL * 2
- (TOL + LID_EXTRA_TOL) * 2
+ TAB_W * 2
)
# ==================================================================== lid_y0 = WALL + TOL + LID_EXTRA_TOL - TAB_W
# DOCUMENT
# ==================================================================== 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 = doc.addObject("Part::Feature", "Pointer_Base")
base_obj.Shape = base base_obj.Shape = base
base_obj.ViewObject.ShapeColor = (0.12, 0.12, 0.14) base_obj.ViewObject.ShapeColor = (0.12, 0.12, 0.14)
lid_obj = doc.addObject("Part::Feature", "Pointer_Lid") lid_obj = doc.addObject("Part::Feature", "Pointer_Lid")
lid_obj.Shape = lid lid_obj.Shape = lid
lid_obj.ViewObject.ShapeColor = (0.28, 0.28, 0.34) lid_obj.ViewObject.ShapeColor = (0.28, 0.28, 0.34)
lid_obj.ViewObject.Transparency = 25 lid_obj.ViewObject.Transparency = 25
doc.recompute() doc.recompute()
Gui.activeDocument().activeView().viewIsometric() Gui.activeDocument().activeView().viewIsometric()
Gui.SendMsgToActiveView("ViewFit") 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)
BIN
View File
Binary file not shown.