app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
main
John Lauer
Publish 0.2.19
81ffca7
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
"""Issue #89 regressions, offline (synthetic step_meta; no service).
KiCad generator models carry no printed marking: the body's top edge fillets, its corner blends and the
generator's 3-face pin-1 mark (a cylinder wall in the body, a disc and a cylinder in the `mark` part) are
not glyphs, so an LQFP-48 with ef = 0.05 (STM32G431CBT6) is not refused.
A real vendor marking is still found: coplanar letter islands on the top face, and raised letters (the
AMPLH5030S-150MT vendor model: letters 0.02 mm proud of the body top), and the error names the faces.
Run: python3 -m unittest -v test_vendor_mark (from tools/laser-etch)"""
import os, sys, unittest
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import mark_plan as MP
from test_mark_plan_rows import PlanOffline, kicad_mod
import tempfile
def face(fid, st, bbox, area, normal=None, product=1):
return dict(face_id=fid, product_id=product, surface_type=st, bbox_mm=bbox, area_mm2=area, normal=normal)
def lqfp48_meta():
"""The faces at the top of the STM32G431CBT6 LQFP-48 from kicad-packages3D-generator (ef = 0.05), as
/step-meta reports them (stood up, marks removed). Product 0 is the assembly and lists the small top
faces again, as the service does."""
f = [
face("1_0", "BSPLINE", [-3.2875, 3.2916, 0.8725, 3.2875, 3.425, 1.5], 3.9142),
face("1_1", "BSPLINE", [3.2208, 3.2208, 0.8725, 3.425, 3.425, 1.5], 0.0906),
face("1_2", "BSPLINE", [-3.419, 3.2511, 1.4604, 3.419, 3.3, 1.5], 0.4359),
face("1_28", "BSPLINE", [-3.425, 3.2208, 0.8725, -3.2208, 3.425, 1.5], 0.0906),
face("1_29", "BSPLINE", [3.2916, -3.2875, 0.8725, 3.425, 3.2875, 1.5], 3.9142),
face("1_30", "BSPLINE", [3.1214, 3.1214, 1.461, 3.369, 3.369, 1.5], 0.006),
face("1_31", "BSPLINE", [-3.369, 3.1214, 1.461, -3.1214, 3.369, 1.5], 0.006),
face("1_32", "PLANE", [-3.2511, -3.2511, 1.5, 3.2511, 3.2511, 1.5], 42.0788, [0.0, 0.0, 1.0]),
face("1_36", "BSPLINE", [-3.425, -3.2875, 0.8725, -3.2916, 3.2875, 1.5], 3.9142),
face("1_89", "BSPLINE", [3.2208, -3.425, 0.8725, 3.425, -3.2208, 1.5], 0.0906),
face("1_90", "BSPLINE", [3.2511, -3.419, 1.4604, 3.3, 3.419, 1.5], 0.4359),
face("1_115", "BSPLINE", [-3.3, -3.419, 1.4604, -3.2511, 3.419, 1.5], 0.4359),
face("1_116", "BSPLINE", [-3.369, -3.369, 1.461, -3.1214, -3.1214, 1.5], 0.006),
face("1_117", "BSPLINE", [3.1214, -3.369, 1.461, 3.369, -3.1214, 1.5], 0.006),
face("1_118", "BSPLINE", [-3.419, -3.3, 1.4604, 3.419, -3.2511, 1.5], 0.4359),
face("1_119", "CYLINDER", [-2.8416, 2.3416, 1.4, -2.3416, 2.8416, 1.5], 0.1571),
face("1_148", "BSPLINE", [-3.425, -3.425, 0.8725, -3.2208, -3.2208, 1.5], 0.0906),
face("1_166", "BSPLINE", [-3.2875, -3.425, 0.8725, 3.2875, -3.2916, 1.5], 3.9142),
face("3_0", "PLANE", [-2.8416, 2.3416, 1.5, -2.3416, 2.8416, 1.5], 0.1963, [0.0, 0.0, -1.0], 3),
face("3_1", "CYLINDER", [-2.8416, 2.3416, 1.4, -2.3416, 2.8416, 1.5], 0.1571, None, 3),
face("bottom", "PLANE", [-3.5, -3.5, 0.1, 3.5, 3.5, 0.1], 49.0, [0.0, 0.0, -1.0]),
face("pins", "BSPLINE", [-4.5, -4.5, 0.0, 4.5, 4.5, 0.8725], 5.0, None, 2),
]
dup = [dict(x, face_id="0_" + x["face_id"].split("_")[1], product_id=0) for x in f if x["area_mm2"] < 0.21]
return dict(faces=f + dup, up_axis_hint="z",
products=[dict(id=0, name="STM32G431CBT6_LQFP-48_7x7mm_P0.5mm"), dict(id=1, name="body"), dict(id=2, name="pins"), dict(id=3, name="mark")])
def with_letters(meta, raised=0.0, n=12):
"""Letter islands in a row across the top face's middle: coplanar faces (raised=0), or raised letters
(a top `raised` mm proud, a floor on the body top, and four walls each)."""
m = dict(meta, faces=list(meta["faces"]))
z = 1.5
for i in range(n):
x = -2.4 + 0.4 * i
b = [x, -0.25, z + raised, x + 0.25, 0.25, z + raised]
m["faces"].append(face(f"L{i}", "PLANE", b, 0.06, [0.0, 0.0, 1.0], 4))
if raised:
m["faces"].append(face(f"L{i}f", "PLANE", [x, -0.25, z, x + 0.25, 0.25, z], 0.06, [0.0, 0.0, 1.0], 4))
for k, wb in enumerate(([x, -0.25, z, x, 0.25, z + raised], [x + 0.25, -0.25, z, x + 0.25, 0.25, z + raised],
[x, -0.25, z, x + 0.25, -0.25, z + raised], [x, 0.25, z, x + 0.25, 0.25, z + raised])):
m["faces"].append(face(f"L{i}w{k}", "PLANE", wb, 0.005, None, 4))
return m
def lqfp48_pads():
pads, n = [], 1
for side in range(4):
for k in range(12):
t = -2.75 + 0.5 * k
x, y, w, h = {0: (-4.1, -t, 1.5, 0.3), 1: (t, -4.1, 0.3, 1.5), 2: (4.1, t, 1.5, 0.3), 3: (-t, 4.1, 0.3, 1.5)}[side]
pads.append(dict(num=str(n), x=x, y=y, w=w, h=h)); n += 1
return pads
def plan_of(meta, pads):
with tempfile.TemporaryDirectory(prefix="vendormark-") as d:
fp = os.path.join(d, "synth.kicad_mod")
with open(fp, "w") as fh:
fh.write(kicad_mod(pads))
with PlanOffline(meta):
return MP.plan(os.path.join(HERE, "proxy_box.step"), fp, "STM32G431CBT6")
class KiCadGeneratorModel(unittest.TestCase):
def test_fillets_corners_and_pin1_mark_are_not_glyphs(self):
b = MP.top_band(lqfp48_meta(), [], [], None, False)
self.assertEqual(b["faceId"], "1_32")
self.assertEqual(b["vendorGlyphFaces"], 0, b["vendorGlyphFaceList"])
def test_plan_is_not_refused(self):
p = plan_of(lqfp48_meta(), lqfp48_pads())
self.assertFalse(p["vendorMarking"]["present"], p["vendorMarking"])
self.assertFalse(any("printed marking" in w for w in p["warnings"]), p["warnings"])
def test_disc_shape(self):
self.assertTrue(MP._is_disc(face("d", "PLANE", [0, 0, 1, 0.5, 0.5, 1], 0.19635)))
self.assertFalse(MP._is_disc(face("r", "PLANE", [0, 0, 1, 0.5, 0.5, 1], 0.25))) # a square letter block
self.assertFalse(MP._is_disc(face("o", "PLANE", [0, 0, 1, 0.5, 0.5, 1], 0.08))) # an 'O' ring
class VendorMarking(unittest.TestCase):
def test_coplanar_letter_islands_are_found_and_named(self):
p = plan_of(with_letters(lqfp48_meta()), lqfp48_pads())
self.assertTrue(p["vendorMarking"]["present"])
self.assertEqual(p["vendorMarking"]["glyphFaces"], 12)
w = next(w for w in p["warnings"] if "printed marking" in w)
self.assertIn("L0 (PLANE", w)
self.assertNotIn("1_30", w)
def test_raised_letters_are_found_on_the_body_top(self):
b = MP.top_band(with_letters(lqfp48_meta(), raised=0.02), [], [], None, False)
self.assertEqual(b["faceId"], "1_32") # the body top, not a letter's own top
self.assertEqual(b["vendorGlyphFaces"], 12, b["vendorGlyphFaceList"]) # each letter once, no walls
def test_too_few_islands_are_not_a_marking(self):
p = plan_of(with_letters(lqfp48_meta(), n=3), lqfp48_pads())
self.assertFalse(p["vendorMarking"]["present"])
if __name__ == "__main__":
unittest.main()