Hadlock

Bearing clearance gauge generator

May 2nd, 2026
88
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.91 KB | Source Code | 0 0
  1. """
  2. Bearing clearance gauge generator.
  3.  
  4. Inputs a deep-groove ball bearing designation; emits a STEP file containing
  5. a two-level gauge plate:
  6.  - Section A (low surface): 9 cylinders rising 10 mm; ID-fit shaft test
  7.  - Section B (high surface): 9 pockets; OD-fit housing test
  8.  
  9. Run:  python3 bearing-clearance.py
  10. Output: <designation>-<unixtimestamp>.step
  11. """
  12.  
  13. import os
  14. import sys
  15. import time
  16. import pygame
  17. import cadquery as cq
  18.  
  19. # ---------------------------------------------------------------------------
  20. # Bearing catalog (deep-groove ball bearings, metric).  All dimensions in mm.
  21. # ---------------------------------------------------------------------------
  22. BEARINGS = {
  23.     "6804": {"ID": 20.0, "OD": 32.0, "W": 7.0},
  24.     "624":  {"ID":  4.0, "OD": 13.0, "W": 5.0},
  25. }
  26.  
  27. # Offsets vs nominal ID for the 9 A-cylinders.
  28. # Smallest is 0.10 under nominal (very loose), then exact, then progressively
  29. # tighter.  Increments: 0.10 up to +0.20, then 0.05.
  30. A_OFFSETS = [-0.10, 0.00, 0.10, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45]
  31.  
  32. # Offsets vs nominal OD for the 9 B-pockets.
  33. # -0.05 (tight), then 0.10 increments to +0.15, then +0.20, then 0.05 increments.
  34. B_OFFSETS = [-0.05, 0.05, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45]
  35.  
  36. # ---------------------------------------------------------------------------
  37. # Geometry constants
  38. # ---------------------------------------------------------------------------
  39. A_BASE_THICK    = 3.0    # A-section base plate thickness
  40. A_CYL_HEIGHT    = 10.0   # cylinders rise 10 mm above A-section top
  41. A_PITCH_GAP     = 2.0    # bearing-OD-to-bearing-OD clearance on A grid
  42. B_RIM           = 4.0    # min wall between B pockets
  43. B_FLOOR         = 3.0    # solid material under deepest pocket
  44. B_DEPTH_OFFSET  = 0.05   # pockets are bearing_W + 0.05 deep
  45. MAX_FOOTPRINT   = 220.0  # printer build plate constraint
  46.  
  47. # Embossed labels (raised text near each feature)
  48. LABEL_BAND      = 4.0    # extra Y at the plate bottom so bottom-row labels sit on solid plate
  49. LABEL_FONT_A    = 3.0    # A-side label height (mm) — A cells are roomy
  50. LABEL_FONT_B    = 2.0    # B-side label height (mm) — B cells are tighter
  51. LABEL_OFFSET    = 1.0    # gap from feature edge to label edge (mm)
  52. EMBOSS_HEIGHT   = 0.12   # raised height of text above surface (~2-6 print layers)
  53. LABEL_FONTS     = ("Menlo", "Monaco", "Courier New", "Courier", "Arial")
  54.  
  55. # ---------------------------------------------------------------------------
  56. # Geometry computation
  57. # ---------------------------------------------------------------------------
  58. def compute_geometry(key):
  59.     b = BEARINGS[key]
  60.     ID, OD, W = b["ID"], b["OD"], b["W"]
  61.  
  62.     # A-section pitch: when seated, adjacent bearings must clear by A_PITCH_GAP.
  63.     pitch_a = OD + A_PITCH_GAP
  64.  
  65.     # B-section pitch: governed by the largest pocket plus rim wall.
  66.     max_b_d = OD + max(B_OFFSETS)
  67.     pitch_b = max_b_d + B_RIM
  68.  
  69.     a_w = 3 * pitch_a
  70.     b_w = 3 * pitch_b
  71.     total_x = a_w + b_w
  72.     # Add LABEL_BAND of extra plate at the bottom so bottom-row labels can be
  73.     # embossed below their features without overhanging the plate edge.
  74.     grid_h = max(a_w, b_w)
  75.     total_y = grid_h + LABEL_BAND
  76.  
  77.     pocket_depth = W + B_DEPTH_OFFSET
  78.     b_thickness  = pocket_depth + B_FLOOR
  79.  
  80.     return {
  81.         "key": key, "ID": ID, "OD": OD, "W": W,
  82.         "pitch_a": pitch_a, "pitch_b": pitch_b,
  83.         "a_w": a_w, "b_w": b_w,
  84.         "total_x": total_x, "total_y": total_y,
  85.         "grid_h": grid_h, "label_band": LABEL_BAND,
  86.         "pocket_depth": pocket_depth,
  87.         "a_thickness": A_BASE_THICK,
  88.         "b_thickness": b_thickness,
  89.         "a_top_z": A_BASE_THICK,
  90.         "b_top_z": b_thickness,
  91.         "cyl_height": A_CYL_HEIGHT,
  92.         "fits": total_x <= MAX_FOOTPRINT and total_y <= MAX_FOOTPRINT,
  93.     }
  94.  
  95. def grid_centers(pitch, origin_x, origin_y_bot, grid_height):
  96.     """3x3 grid, row-major from top-left.  Row 0 is the top.
  97.  
  98.    The grid is centered vertically inside [origin_y_bot, origin_y_bot+grid_height].
  99.    """
  100.     pts = []
  101.     y_pad = (grid_height - 3 * pitch) / 2.0
  102.     for row in range(3):
  103.         for col in range(3):
  104.             cx = origin_x + (col + 0.5) * pitch
  105.             cy = origin_y_bot + grid_height - y_pad - (row + 0.5) * pitch
  106.             pts.append((cx, cy))
  107.     return pts
  108.  
  109.  
  110. def emboss_text(workplane, text_str, fontsize, distance):
  111.     """Build embossed text geometry; try a list of monospace fonts in order."""
  112.     last_err = None
  113.     for f in LABEL_FONTS:
  114.         try:
  115.             return workplane.text(
  116.                 text_str, fontsize, distance,
  117.                 font=f, kind="regular",
  118.                 halign="center", valign="center",
  119.             )
  120.         except Exception as e:
  121.             last_err = e
  122.     raise RuntimeError(f"no usable font for label embossing: {last_err}")
  123.  
  124.  
  125. def offset_label(off):
  126.     """Format a clearance offset as a fixed-width signed string, e.g. '+0.10', '-0.10'."""
  127.     return f"{off:+.2f}"
  128.  
  129. # ---------------------------------------------------------------------------
  130. # CadQuery model build
  131. # ---------------------------------------------------------------------------
  132. def build_model(key):
  133.     g = compute_geometry(key)
  134.  
  135.     # A-side base slab (low level).
  136.     plate = cq.Workplane("XY").box(
  137.         g["total_x"], g["total_y"], g["a_thickness"],
  138.         centered=(False, False, False),
  139.     )
  140.  
  141.     # B-side raised slab (high level).  Sits beside A, full slab thickness so
  142.     # the part is one continuous block with two distinct top elevations.
  143.     b_block = (
  144.         cq.Workplane("XY")
  145.         .transformed(offset=cq.Vector(g["a_w"], 0, 0))
  146.         .box(g["b_w"], g["total_y"], g["b_thickness"],
  147.              centered=(False, False, False))
  148.     )
  149.     body = plate.union(b_block)
  150.  
  151.     # A cylinders (additive) — sit on the A-section top surface.
  152.     a_pts = grid_centers(g["pitch_a"], 0.0, LABEL_BAND, g["grid_h"])
  153.     for (cx, cy), off in zip(a_pts, A_OFFSETS):
  154.         d = g["ID"] + off
  155.         cyl = (
  156.             cq.Workplane("XY")
  157.             .transformed(offset=cq.Vector(cx, cy, g["a_top_z"]))
  158.             .circle(d / 2.0)
  159.             .extrude(g["cyl_height"])
  160.         )
  161.         body = body.union(cyl)
  162.  
  163.     # B pockets (subtractive) — sink down from the B-section top surface.
  164.     b_pts = grid_centers(g["pitch_b"], g["a_w"], LABEL_BAND, g["grid_h"])
  165.     for (cx, cy), off in zip(b_pts, B_OFFSETS):
  166.         d = g["OD"] + off
  167.         pocket = (
  168.             cq.Workplane("XY")
  169.             .transformed(offset=cq.Vector(cx, cy, g["b_top_z"]))
  170.             .circle(d / 2.0)
  171.             .extrude(-g["pocket_depth"])
  172.         )
  173.         body = body.cut(pocket)
  174.  
  175.     # Embossed labels — raised text below each feature on the section's top
  176.     # surface.  Label positions use the LARGEST feature radius in the section
  177.     # so labels align horizontally per row regardless of feature size.
  178.     max_a_r = (g["ID"] + max(A_OFFSETS)) / 2.0
  179.     a_label_dy = max_a_r + LABEL_OFFSET + LABEL_FONT_A / 2.0
  180.     for (cx, cy), off in zip(a_pts, A_OFFSETS):
  181.         wp = (cq.Workplane("XY")
  182.               .workplane(offset=g["a_top_z"])
  183.               .center(cx, cy - a_label_dy))
  184.         label = emboss_text(wp, offset_label(off),
  185.                             LABEL_FONT_A, EMBOSS_HEIGHT)
  186.         body = body.union(label)
  187.  
  188.     max_b_r = (g["OD"] + max(B_OFFSETS)) / 2.0
  189.     b_label_dy = max_b_r + LABEL_OFFSET + LABEL_FONT_B / 2.0
  190.     for (cx, cy), off in zip(b_pts, B_OFFSETS):
  191.         wp = (cq.Workplane("XY")
  192.               .workplane(offset=g["b_top_z"])
  193.               .center(cx, cy - b_label_dy))
  194.         label = emboss_text(wp, offset_label(off),
  195.                             LABEL_FONT_B, EMBOSS_HEIGHT)
  196.         body = body.union(label)
  197.  
  198.     return body, g, a_pts, b_pts
  199.  
  200. def export_step(key, out_dir="."):
  201.     body, g, _, _ = build_model(key)
  202.     ts = int(time.time())
  203.     fname = f"{key}-{ts}.step"
  204.     path = os.path.join(out_dir, fname)
  205.     cq.exporters.export(body, path)
  206.     return path, g
  207.  
  208. # ---------------------------------------------------------------------------
  209. # Pygame GUI
  210. # ---------------------------------------------------------------------------
  211. def run_gui():
  212.     pygame.init()
  213.     W, H = 1180, 760
  214.     screen = pygame.display.set_mode((W, H))
  215.     pygame.display.set_caption("Bearing Clearance Gauge — CadQuery generator")
  216.     clock = pygame.time.Clock()
  217.  
  218.     font  = pygame.font.SysFont("monospace", 13)
  219.     fontB = pygame.font.SysFont("monospace", 13, bold=True)
  220.     head  = pygame.font.SysFont("monospace", 15, bold=True)
  221.     title = pygame.font.SysFont("monospace", 22, bold=True)
  222.     small = pygame.font.SysFont("monospace", 11)
  223.  
  224.     BG    = (18, 22, 30)
  225.     PANEL = (28, 34, 44)
  226.     LINE  = (60, 70, 85)
  227.     TXT   = (215, 222, 232)
  228.     DIM   = (140, 150, 165)
  229.     OK    = (110, 210, 140)
  230.     BAD   = (240, 100, 100)
  231.     AMB   = (220, 170, 60)
  232.     BLU   = (90, 160, 240)
  233.     BTN   = (45, 60, 80)
  234.     BTNH  = (60, 90, 130)
  235.     A_FILL = (110, 150, 200)
  236.     B_FILL = (90, 110, 140)
  237.     POCKET = (32, 40, 52)
  238.     SHADE  = (40, 48, 60)
  239.  
  240.     selected = "6804"
  241.     last_msg = ("ready — press [G] or click GENERATE", time.time())
  242.  
  243.     def text(s, x, y, c=TXT, f=font):
  244.         screen.blit(f.render(s, True, c), (x, y))
  245.  
  246.     def text_w(s, f=font):
  247.         return f.size(s)[0]
  248.  
  249.     keys_list = list(BEARINGS.keys())
  250.  
  251.     running = True
  252.     while running:
  253.         mx, my = pygame.mouse.get_pos()
  254.         click = False
  255.         for ev in pygame.event.get():
  256.             if ev.type == pygame.QUIT:
  257.                 running = False
  258.             elif ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
  259.                 click = True
  260.             elif ev.type == pygame.KEYDOWN:
  261.                 if ev.key == pygame.K_ESCAPE or ev.key == pygame.K_q:
  262.                     running = False
  263.                 elif ev.key in (pygame.K_1,):
  264.                     selected = "6804"
  265.                 elif ev.key in (pygame.K_2,):
  266.                     selected = "624"
  267.                 elif ev.key in (pygame.K_g, pygame.K_RETURN, pygame.K_SPACE):
  268.                     try:
  269.                         path, _ = export_step(selected)
  270.                         last_msg = (f"wrote {os.path.basename(path)}", time.time())
  271.                     except Exception as e:
  272.                         last_msg = (f"export failed: {e}", time.time())
  273.  
  274.         g = compute_geometry(selected)
  275.         b = BEARINGS[selected]
  276.  
  277.         screen.fill(BG)
  278.  
  279.         # ---- header
  280.         text(f"Bearing Clearance Gauge", 20, 14, TXT, title)
  281.         text("design rules: A pitch = OD + 2.0  |  B pitch = max_pocket_OD + 4.0  |  "
  282.              "pocket depth = W + 0.05", 20, 44, DIM, small)
  283.  
  284.         # ---- bearing selector + generate button
  285.         bx, by = 20, 70
  286.         for i, k in enumerate(keys_list):
  287.             r = pygame.Rect(bx + i*120, by, 110, 30)
  288.             hov = r.collidepoint(mx, my)
  289.             if k == selected:
  290.                 col = BLU
  291.             else:
  292.                 col = BTNH if hov else BTN
  293.             pygame.draw.rect(screen, col, r, border_radius=4)
  294.             pygame.draw.rect(screen, LINE, r, 1, border_radius=4)
  295.             tcol = (10, 10, 14) if k == selected else TXT
  296.             lab = f"[{i+1}] {k}"
  297.             text(lab, r.x + (r.w - text_w(lab))//2, r.y + 7, tcol)
  298.             if click and hov:
  299.                 selected = k
  300.  
  301.         gen = pygame.Rect(bx + len(keys_list)*120 + 20, by, 180, 30)
  302.         ghov = gen.collidepoint(mx, my)
  303.         pygame.draw.rect(screen, OK if ghov else (60, 130, 90), gen, border_radius=4)
  304.         pygame.draw.rect(screen, LINE, gen, 1, border_radius=4)
  305.         glab = "[G] GENERATE STEP"
  306.         text(glab, gen.x + (gen.w - text_w(glab, fontB))//2, gen.y + 7, (10,14,10), fontB)
  307.         if click and ghov:
  308.             try:
  309.                 path, _ = export_step(selected)
  310.                 last_msg = (f"wrote {os.path.basename(path)}", time.time())
  311.             except Exception as e:
  312.                 last_msg = (f"export failed: {e}", time.time())
  313.  
  314.         msg, mts = last_msg
  315.         text(msg, gen.right + 16, by + 9, OK if msg.startswith("wrote") else AMB)
  316.  
  317.         # =====================================================================
  318.         # LEFT PANEL — specs + geometry
  319.         # =====================================================================
  320.         L = pygame.Rect(20, 110, 360, 630)
  321.         pygame.draw.rect(screen, PANEL, L, border_radius=6)
  322.         pygame.draw.rect(screen, LINE, L, 1, border_radius=6)
  323.  
  324.         y = L.y + 12
  325.         text("BEARING SPECS", L.x + 12, y, AMB, head); y += 22
  326.         rows = [
  327.             ("designation", f"{selected}"),
  328.             ("ID  (bore)",  f"{b['ID']:.3f} mm"),
  329.             ("OD  (outer)", f"{b['OD']:.3f} mm"),
  330.             ("W   (width)", f"{b['W']:.3f} mm"),
  331.         ]
  332.         for k, v in rows:
  333.             text(f"  {k:<12}: {v}", L.x + 12, y); y += 17
  334.         y += 8
  335.  
  336.         text("DEVICE GEOMETRY", L.x + 12, y, AMB, head); y += 22
  337.         rows = [
  338.             ("footprint",    f"{g['total_x']:.2f} x {g['total_y']:.2f} mm"),
  339.             ("fits 220x220", "YES" if g['fits'] else "NO"),
  340.             ("A pitch",      f"{g['pitch_a']:.3f} mm"),
  341.             ("B pitch",      f"{g['pitch_b']:.3f} mm"),
  342.             ("A region",     f"{g['a_w']:.2f} x {g['total_y']:.2f}"),
  343.             ("B region",     f"{g['b_w']:.2f} x {g['total_y']:.2f}"),
  344.             ("label band",   f"{g['label_band']:.2f} mm (bottom)"),
  345.             ("A base thick", f"{g['a_thickness']:.2f} mm"),
  346.             ("B base thick", f"{g['b_thickness']:.2f} mm"),
  347.             ("cyl height",   f"{g['cyl_height']:.2f} mm"),
  348.             ("pocket depth", f"{g['pocket_depth']:.3f} mm  (W + 0.05)"),
  349.             ("A top  Z",     f"{g['a_top_z']:.2f} mm"),
  350.             ("B top  Z",     f"{g['b_top_z']:.2f} mm"),
  351.             ("cyl  top Z",   f"{g['a_top_z'] + g['cyl_height']:.2f} mm"),
  352.             ("emboss tall",  f"{EMBOSS_HEIGHT:.2f} mm  (~{round(EMBOSS_HEIGHT/0.02)} layers @0.02)"),
  353.             ("emboss font",  f"A={LABEL_FONT_A:.1f} mm  B={LABEL_FONT_B:.1f} mm"),
  354.         ]
  355.         for k, v in rows:
  356.             c = OK if (k == "fits 220x220" and v == "YES") else (BAD if v == "NO" else TXT)
  357.             text(f"  {k:<12}: {v}", L.x + 12, y, c); y += 17
  358.         y += 10
  359.  
  360.         text("PRINT NOTES", L.x + 12, y, AMB, head); y += 22
  361.         notes = [
  362.             "* recommended layer: 0.20 mm or finer",
  363.             "* perimeters >= 4 on A cylinders",
  364.             "* infill >= 30%, top layers >= 5",
  365.             "* no supports needed (all features +Z)",
  366.             "* orient with A/B faces UP on bed",
  367.         ]
  368.         for n in notes:
  369.             text(n, L.x + 12, y, DIM); y += 16
  370.  
  371.         # =====================================================================
  372.         # MIDDLE PANEL — A and B size tables
  373.         # =====================================================================
  374.         M = pygame.Rect(L.right + 16, 110, 360, 630)
  375.         pygame.draw.rect(screen, PANEL, M, border_radius=6)
  376.         pygame.draw.rect(screen, LINE, M, 1, border_radius=6)
  377.  
  378.         y = M.y + 12
  379.         text("A — CYLINDERS  (shaft / inner-race fit)", M.x + 12, y, AMB, head); y += 22
  380.         text("    grid order: row-major, top-left = #1", M.x + 12, y, DIM); y += 18
  381.         text(f"    {'#':<3} {'offset':>7}  {'diam':>9}  {'note':<20}",
  382.              M.x + 12, y, DIM, fontB); y += 16
  383.         a_pts = grid_centers(g["pitch_a"], 0.0, LABEL_BAND, g["grid_h"])
  384.         for i, off in enumerate(A_OFFSETS):
  385.             d = b["ID"] + off
  386.             sgn = "+" if off >= 0 else ""
  387.             note = "exact" if off == 0 else ("loose" if off < 0 else f"+{off:.2f} press")
  388.             text(f"    {i+1:<3} {sgn}{off:6.2f}  {d:7.3f}   {note}",
  389.                  M.x + 12, y); y += 16
  390.         y += 10
  391.  
  392.         text("B — POCKETS  (housing / outer-race fit)", M.x + 12, y, AMB, head); y += 22
  393.         text("    grid order: row-major, top-left = #1", M.x + 12, y, DIM); y += 18
  394.         text(f"    {'#':<3} {'offset':>7}  {'diam':>9}  {'note':<20}",
  395.              M.x + 12, y, DIM, fontB); y += 16
  396.         for i, off in enumerate(B_OFFSETS):
  397.             d = b["OD"] + off
  398.             sgn = "+" if off >= 0 else ""
  399.             note = "press" if off < 0 else ("slip" if off == 0 else f"+{off:.2f} clear")
  400.             text(f"    {i+1:<3} {sgn}{off:6.2f}  {d:7.3f}   {note}",
  401.                  M.x + 12, y); y += 16
  402.         y += 10
  403.  
  404.         text("OUTPUT", M.x + 12, y, AMB, head); y += 22
  405.         text(f"  filename : {selected}-<unixts>.step", M.x + 12, y); y += 16
  406.         text(f"  format   : ISO 10303-21 STEP AP214", M.x + 12, y); y += 16
  407.         text(f"  cad      : cadquery {cq.__version__}", M.x + 12, y); y += 16
  408.  
  409.         # =====================================================================
  410.         # RIGHT PANEL — top-down 2D preview
  411.         # =====================================================================
  412.         R = pygame.Rect(M.right + 16, 110, W - (M.right + 16) - 20, 630)
  413.         pygame.draw.rect(screen, PANEL, R, border_radius=6)
  414.         pygame.draw.rect(screen, LINE, R, 1, border_radius=6)
  415.  
  416.         text("TOP-DOWN PREVIEW (to scale)", R.x + 12, R.y + 12, AMB, head)
  417.  
  418.         pad = 30
  419.         avail_w = R.w - 2 * pad
  420.         avail_h = R.h - 70
  421.         scale = min(avail_w / g["total_x"], avail_h / g["total_y"])
  422.         view_w = g["total_x"] * scale
  423.         view_h = g["total_y"] * scale
  424.         ox = R.x + (R.w - view_w) / 2
  425.         oy = R.y + 50
  426.  
  427.         def to_px(x, y):
  428.             # plate model coords are bottom-left origin; flip Y for screen
  429.             return (ox + x * scale, oy + (g["total_y"] - y) * scale)
  430.  
  431.         # B section shaded slightly darker (it's "raised")
  432.         ax_px, ay_px = to_px(0, 0)
  433.         bx_px, by_px = to_px(g["a_w"], 0)
  434.         ex_px, ey_px = to_px(g["total_x"], g["total_y"])
  435.  
  436.         a_rect = pygame.Rect(ax_px, ey_px, g["a_w"]*scale, view_h)
  437.         b_rect = pygame.Rect(bx_px, ey_px, g["b_w"]*scale, view_h)
  438.         pygame.draw.rect(screen, SHADE, a_rect)
  439.         pygame.draw.rect(screen, (50, 60, 75), b_rect)
  440.         pygame.draw.rect(screen, LINE, pygame.Rect(ax_px, ey_px, view_w, view_h), 1)
  441.         pygame.draw.line(screen, LINE, (bx_px, ey_px), (bx_px, ey_px + view_h), 1)
  442.  
  443.         # section labels
  444.         text("A", a_rect.x + 6, a_rect.y + 4, AMB, head)
  445.         text("B", b_rect.x + 6, b_rect.y + 4, AMB, head)
  446.  
  447.         max_a_r = (b["ID"] + max(A_OFFSETS)) / 2.0
  448.         a_label_dy = max_a_r + LABEL_OFFSET + LABEL_FONT_A / 2.0
  449.         max_b_r = (b["OD"] + max(B_OFFSETS)) / 2.0
  450.         b_label_dy = max_b_r + LABEL_OFFSET + LABEL_FONT_B / 2.0
  451.  
  452.         # A cylinders + embossed label markers
  453.         for i, ((cx, cy), off) in enumerate(zip(a_pts, A_OFFSETS)):
  454.             px, py = to_px(cx, cy)
  455.             d = b["ID"] + off
  456.             r_px = (d/2.0) * scale
  457.             pygame.draw.circle(screen, A_FILL, (int(px), int(py)), int(r_px))
  458.             pygame.draw.circle(screen, TXT, (int(px), int(py)), int(r_px), 1)
  459.             text(f"#{i+1}", px - 8, py - 6, (12,16,22), small)
  460.             lpx, lpy = to_px(cx, cy - a_label_dy)
  461.             text(offset_label(off), lpx - 16, lpy - 5, AMB, small)
  462.  
  463.         # B pockets + embossed label markers
  464.         b_pts = grid_centers(g["pitch_b"], g["a_w"], LABEL_BAND, g["grid_h"])
  465.         for i, ((cx, cy), off) in enumerate(zip(b_pts, B_OFFSETS)):
  466.             px, py = to_px(cx, cy)
  467.             d = b["OD"] + off
  468.             r_px = (d/2.0) * scale
  469.             pygame.draw.circle(screen, POCKET, (int(px), int(py)), int(r_px))
  470.             pygame.draw.circle(screen, B_FILL, (int(px), int(py)), int(r_px), 1)
  471.             text(f"#{i+1}", px - 8, py - 6, TXT, small)
  472.             lpx, lpy = to_px(cx, cy - b_label_dy)
  473.             text(offset_label(off), lpx - 16, lpy - 5, AMB, small)
  474.  
  475.         # scale bar
  476.         bar_mm = 20.0
  477.         bar_px = bar_mm * scale
  478.         bar_y = R.bottom - 28
  479.         pygame.draw.line(screen, TXT, (R.x + 20, bar_y), (R.x + 20 + bar_px, bar_y), 2)
  480.         pygame.draw.line(screen, TXT, (R.x + 20, bar_y - 4), (R.x + 20, bar_y + 4), 2)
  481.         pygame.draw.line(screen, TXT, (R.x + 20 + bar_px, bar_y - 4),
  482.                           (R.x + 20 + bar_px, bar_y + 4), 2)
  483.         text(f"{bar_mm:.0f} mm  (scale 1:{1/scale:.2f})", R.x + 20, bar_y + 6, DIM, small)
  484.  
  485.         # footer help
  486.         text("[1]/[2] select bearing   [G]/[Enter]/[Space] generate STEP   [Q]/[Esc] quit",
  487.              20, H - 24, DIM, small)
  488.  
  489.         pygame.display.flip()
  490.         clock.tick(60)
  491.  
  492.     pygame.quit()
  493.  
  494.  
  495. if __name__ == "__main__":
  496.     # CLI fallback: `python3 bearing-clearance.py 6804` writes a STEP and exits.
  497.     if len(sys.argv) > 1:
  498.         key = sys.argv[1]
  499.         if key not in BEARINGS:
  500.             print(f"unknown bearing '{key}'.  known: {', '.join(BEARINGS)}")
  501.             sys.exit(1)
  502.         path, g = export_step(key)
  503.         print(f"wrote {path}")
  504.         print(f"  footprint: {g['total_x']:.2f} x {g['total_y']:.2f} mm  "
  505.               f"(fits 220^2: {'YES' if g['fits'] else 'NO'})")
  506.         sys.exit(0)
  507.     run_gui()
  508.  
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment