Cleanup macro
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
*.FCStd
|
||||||
|
*.FCBak
|
||||||
|
*.3mf
|
||||||
+226
-154
@@ -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)
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user