Files
Flow_Controller/scripts/fixture_postprocess.py
2026-05-21 00:45:39 +02:00

232 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import math
import pcbnew
from shapely.affinity import translate
# ============================================================
# Fixture geometry
# ============================================================
OUTER_W_MM = 200.0
OUTER_H_MM = 130.0
INNER_W_MM = 153.4
INNER_H_MM = 87.0
INNER_X_MM = (OUTER_W_MM - INNER_W_MM) / 2 # 23.3
INNER_Y_MM = (OUTER_H_MM - INNER_H_MM) / 2 # 21.5
# ============================================================
# Alignment pins
# Absolute fixture-space coordinates in millimetres
# ============================================================
PIN_DIAMETER_MM = 3.172
# Suggested defaults:
# top = centered in top rail
# right = centered in right rail
# bottom = centered in bottom rail
# left = centered in left rail
PIN1_X_MM = 100.0
PIN1_Y_MM = 10.75
PIN2_X_MM = 188.35
PIN2_Y_MM = 65.0
PIN3_X_MM = 100.0
PIN3_Y_MM = 119.25
PIN4_X_MM = 11.65
PIN4_Y_MM = 65.0
# KiCad internal unit: 1 mm = 1 000 000 nm
MM = 1_000_000
def mm(v: float) -> int:
"""Convert mm float to KiCad internal units (nm)."""
return int(round(v * MM))
def _move_all(panel, dx_nm: int, dy_nm: int) -> None:
"""Translate every object on the board by (dx_nm, dy_nm)."""
vec = pcbnew.VECTOR2I(dx_nm, dy_nm)
for fp in panel.board.GetFootprints():
fp.Move(vec)
for track in panel.board.GetTracks():
track.Move(vec)
for drawing in panel.board.GetDrawings():
drawing.Move(vec)
for zone in panel.board.Zones():
zone.Move(vec)
# Keep the Shapely substrate in sync
panel.boardSubstrate.substrates = translate(
panel.boardSubstrate.substrates,
xoff=dx_nm,
yoff=dy_nm,
)
def _edge_cuts_bbox(panel):
"""Return (minx, miny, maxx, maxy) in nm from the Shapely substrate."""
return panel.boardSubstrate.substrates.bounds # already in nm
def _draw_rect(board, layer, x0_nm, y0_nm, x1_nm, y1_nm, width_nm=None):
"""Draw an axis-aligned rectangle on *layer* using four PCB_SHAPE lines."""
if width_nm is None:
width_nm = mm(0.1) # 0.1 mm line width thin reference line
corners = [
(x0_nm, y0_nm), (x1_nm, y0_nm),
(x1_nm, y1_nm), (x0_nm, y1_nm),
]
for i in range(4):
seg = pcbnew.PCB_SHAPE(board)
seg.SetShape(pcbnew.SHAPE_T_SEGMENT)
seg.SetLayer(layer)
seg.SetWidth(width_nm)
x_a, y_a = corners[i]
x_b, y_b = corners[(i + 1) % 4]
seg.SetStart(pcbnew.VECTOR2I(x_a, y_a))
seg.SetEnd(pcbnew.VECTOR2I(x_b, y_b))
board.Add(seg)
def _draw_circle(board, layer, cx_nm, cy_nm, r_nm, width_nm=None):
"""Draw a circle on *layer*."""
if width_nm is None:
width_nm = mm(0.1)
circ = pcbnew.PCB_SHAPE(board)
circ.SetShape(pcbnew.SHAPE_T_CIRCLE)
circ.SetLayer(layer)
circ.SetWidth(width_nm)
circ.SetCenter(pcbnew.VECTOR2I(cx_nm, cy_nm))
circ.SetEnd(pcbnew.VECTOR2I(cx_nm + r_nm, cy_nm))
board.Add(circ)
def _add_pin_footprint(board, x_nm, y_nm, diameter_mm, index):
"""
Add a non-plated through-hole (NPTH) mounting hole footprint at (x_nm, y_nm).
We create a minimal footprint with a single NPTH pad so that:
• The drill appears in the Excellon output → used for physical alignment pins.
• No copper ring is added.
"""
fp = pcbnew.FOOTPRINT(board)
fp.SetReference(f"PIN{index}")
fp.Reference().SetVisible(False)
fp.SetValue(f"Ø{diameter_mm:.3f}mm NPTH")
fp.Value().SetVisible(False)
fp.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm))
pad = pcbnew.PAD(fp)
pad.SetAttribute(pcbnew.PAD_ATTRIB_NPTH)
pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE)
pad.SetDrillShape(pcbnew.PAD_DRILL_SHAPE_CIRCLE)
d_nm = mm(diameter_mm)
pad.SetSize(pcbnew.VECTOR2I(d_nm, d_nm))
pad.SetDrillSize(pcbnew.VECTOR2I(d_nm, d_nm))
pad.SetLayerSet(pcbnew.LSET.AllCuMask()) # NPTH pads span all Cu layers
pad.SetNumber("")
fp.Add(pad)
board.Add(fp)
# Also draw a circle on Dwgs_User so the pin is visible even without
# the fab/courtyard layers being turned on.
_draw_circle(
board,
pcbnew.Dwgs_User,
x_nm, y_nm,
mm(diameter_mm / 2),
mm(0.1),
)
return fp
# ──────────────────────────────────────────────────────────────
# MAIN ENTRY POINT
# ──────────────────────────────────────────────────────────────
def kikitPostprocess(panel, args):
board = panel.board
# ── Step 1: get current Edge.Cuts bounding box ────────────────────────────
bounds = _edge_cuts_bbox(panel) # (minx, miny, maxx, maxy) in nm
panel_w_nm = bounds[2] - bounds[0]
panel_h_nm = bounds[3] - bounds[1]
panel_w_mm = panel_w_nm / MM
panel_h_mm = panel_h_nm / MM
print(f"[fixture] Panel size: {panel_w_mm:.3f} x {panel_h_mm:.3f} mm")
# ── Step 2: compute where panel top-left should land ─────────────────────
# We want the panel centred inside the inner opening of the fixture.
# Inner opening: top-left is at (INNER_X_MM, INNER_Y_MM) relative to
# the fixture outer top-left.
panel_target_x_mm = INNER_X_MM + (INNER_W_MM - panel_w_mm) / 2
panel_target_y_mm = INNER_Y_MM + (INNER_H_MM - panel_h_mm) / 2
print(f"[fixture] Panel will be placed at ({panel_target_x_mm:.3f}, {panel_target_y_mm:.3f}) mm")
# Warn if the panel is larger than the inner opening (it will still be
# centred but will overlap the frame rails).
if panel_w_mm > INNER_W_MM or panel_h_mm > INNER_H_MM:
print(
f"[fixture] WARNING: panel ({panel_w_mm:.1f}×{panel_h_mm:.1f} mm) "
f"is larger than the inner opening ({INNER_W_MM}×{INNER_H_MM} mm)!"
)
# ── Step 3: move everything so that the panel lands at the target ─────────
# Current panel TL is at (bounds[0], bounds[1]).
# We need to shift by (target - current).
dx_nm = mm(panel_target_x_mm) - bounds[0]
dy_nm = mm(panel_target_y_mm) - bounds[1]
_move_all(panel, int(dx_nm), int(dy_nm))
# ── Step 4: verify final position ────────────────────────────────────────
new_bounds = _edge_cuts_bbox(panel)
print(
f"[fixture] Panel TL after move: "
f"({new_bounds[0]/MM:.3f}, {new_bounds[1]/MM:.3f}) mm"
)
# ── Step 5: draw fixture frame on Dwgs_User ───────────────────────────
# Outer rectangle: (0,0) → (200, 130) mm
_draw_rect(
board, pcbnew.Dwgs_User,
mm(0), mm(0),
mm(OUTER_W_MM), mm(OUTER_H_MM),
mm(0.15),
)
# Inner rectangle: (23.3, 21.5) → (176.7, 108.5) mm
_draw_rect(
board, pcbnew.Dwgs_User,
mm(INNER_X_MM), mm(INNER_Y_MM),
mm(INNER_X_MM + INNER_W_MM), mm(INNER_Y_MM + INNER_H_MM),
mm(0.15),
)
print("[fixture] Fixture frame drawn on Dwgs_User layer.")
# ── Step 6: add alignment pin footprints ──────────────────────────────────
# Place pins at the absolute coordinates defined in the configuration block above
pin_positions_mm = [
(PIN1_X_MM, PIN1_Y_MM, "PIN1 (Top)"),
(PIN2_X_MM, PIN2_Y_MM, "PIN2 (Right)"),
(PIN3_X_MM, PIN3_Y_MM, "PIN3 (Bottom)"),
(PIN4_X_MM, PIN4_Y_MM, "PIN4 (Left)"),
]
for idx, (px_mm, py_mm, label) in enumerate(pin_positions_mm, start=1):
_add_pin_footprint(board, mm(px_mm), mm(py_mm), PIN_DIAMETER_MM, idx)
print(
f"[fixture] {label} placed at "
f"({px_mm:.3f}, {py_mm:.3f}) mm"
)
print("[fixture] Post-processing complete.")