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.
← Commit history
Fix translucent physical board silkscreen and substrate; preserve shared materials
8 files changed
+91−15
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "serde",@@ -34,7 +34,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.1.87"+version = "0.1.88" dependencies = [ "roxmltree", "serde",@@ -43,7 +43,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "serde",@@ -52,7 +52,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "aiflow-grid",@@ -62,7 +62,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "serde",@@ -71,7 +71,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "serde",@@ -80,7 +80,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "aiflow-copper",@@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.1.87"+version = "0.1.88" dependencies = [ "aiflow-board", "aiflow-grid",@@ -100,7 +100,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.1.87"+version = "0.1.88" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.1.87"+version = "0.1.88" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
docs/release-0.1.88.mdadded+5@@ -0,0 +1,5 @@+# 0.1.88: readable physical board silkscreen++Some STEP conversions give the substrate and physical ink translucent materials. In the board viewer this can make labels disappear beneath solder mask despite correct geometry. `board-glb copper` now clones those materials by board-layer identity and makes them opaque, preserving their colors, mesh data, shared component materials and solder-mask transparency. `board-glb check` rejects translucent substrate or physical ink.++Regression tests cover shared materials, repeated mesh instances, unchanged geometry, idempotence and rejection of transparent input. Component-hero reference overlays remain translucent; this correction applies to physical board layers. A recorded electronic-load before/after review confirms readable bottom labels. This is a rendering correction, not manufacturing or thermal qualification.
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.87",+ "version": "0.1.88", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
page.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.87",+ "version": "0.1.88", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
skills/aiflow-molecule/SKILL.md+2−1@@ -87,7 +87,8 @@ Pads and test points are always real copper. A footprint has a copper pad, a sol - **Copper where the mask is open.** Exposed copper shows as copper, or as the board's stated finish when the spec names one (`"fab": {"finish": "enig" | "hasl"}`, or `--finish`); the default is copper. KiCad's STEP gives the pads a bare 0.5 grey with no metal or roughness, which glTF renders as rough metal: grey, or pale peach once recoloured. `board-glb copper` finds the pad, via and track meshes by their KiCad layer name (`<board>_pad`, `_via`, `_copper`), never by a material index, and gives them a copper material with explicit metal and roughness. - **The mask opens where the footprint says.** The mask is rebuilt as the board outline minus every F.Mask / B.Mask aperture: each pad grown by its mask margin (pad, else footprint, else the board's `pad_to_mask_clearance`), mask graphics and zones, untented vias, drill holes, and KiCad's minimum mask web. Never a blanket sheet over the pads, and never lift the pads through the mask. - **Paste only where paste exists.** Test points, machine contacts and pins have no paste and stay bare copper. `--paste solder` shows reflowed solder on the F.Paste / B.Paste apertures of fitted parts (paste margin and ratio applied, DNP parts skipped) and nowhere else; the default shows every exposed pad as copper.-- **The gate.** `board-glb check` fails when a pad material is outside the finish's colour range or leaves metal/roughness at the glTF default, when the mask covers any exposed pad centre (or is still the compressed export, whose openings cannot be proven), when solder sits on a pad without paste, or when the GLB's pads do not line up with the board file (a stale `.kicad_pcb`). Use the `.kicad_pcb` the STEP was exported from.+- **Readable physical silkscreen.** `board-glb copper` makes the PCB substrate and physical silkscreen opaque by semantic layer, cloning materials so shared component materials and solder-mask transparency remain unchanged. STEP converters can emit translucent ink that disappears beneath the mask despite correct geometry. Inspect both faces in the actual viewer; this rule does not apply to translucent component-hero reference overlays.+- **The gate.** `board-glb check` fails when substrate or physical silkscreen materials are transparent, when a pad material is outside the finish's colour range or leaves metal/roughness at the glTF default, when the mask covers any exposed pad centre (or is still the compressed export, whose openings cannot be proven), when solder sits on a pad without paste, or when the GLB's pads do not line up with the board file (a stale `.kicad_pcb`). Use the `.kicad_pcb` the STEP was exported from. - **Look at it.** Render a close-up of the test points and of a pad beside a part (headless three.js, the board's own GLB) before publishing. Grey pads are never shipped. What the step2glb service should do itself is tracked on adom/adom-step2glb issue 24; until it does, `board-glb copper` is the step that makes it right.
tools/board-glb-copper.py+35−1@@ -29,7 +29,7 @@ Usage: [--finish copper|enig|hasl] [--paste solder|none] [--report r.json] board-glb-copper.py check --glb out.glb --board board.kicad_pcb [--finish ...] [--report r.json] """-import argparse, json, math, re, struct, sys+import argparse, copy, json, math, re, struct, sys from pathlib import Path import numpy as np@@ -603,6 +603,30 @@ def add_node_like(g, ref_node, mesh_index, name): return ni +def opaque_board_materials(g, layers, changes=None):+ """Clone transparent substrate/ink materials; never alter shared component or mask materials."""+ replacements = {}+ for role in ('pcb', 'silkscreen'):+ for mi in sorted({mi for mi, _ in layers.get(role, [])}):+ for primitive in g['meshes'][mi]['primitives']:+ old = primitive.get('material')+ material = g.get('materials', [])[old] if old is not None else {}+ rgba = material.get('pbrMetallicRoughness', {}).get('baseColorFactor', [1, 1, 1, 1])+ if material.get('alphaMode', 'OPAQUE') == 'OPAQUE' and rgba[3] == 1:+ continue+ key = (role, old)+ if key not in replacements:+ cloned = copy.deepcopy(material)+ cloned['name'] = material.get('name', 'default') + '_opaque_' + role+ cloned['alphaMode'] = 'OPAQUE'+ cloned.setdefault('pbrMetallicRoughness', {})['baseColorFactor'] = list(rgba[:3]) + [1]+ replacements[key] = len(g.setdefault('materials', []))+ g['materials'].append(cloned)+ primitive['material'] = replacements[key]+ if changes is not None and replacements:+ changes.append(f'opaque substrate/ink: {len(replacements)} cloned material(s); shared components and mask preserved')++ # ---------------------------------------------------------------- copper def copper(args): fin = finish_name(args.finish)@@ -612,6 +636,7 @@ def copper(args): fr = Frame(g, layers, L['edge']) rep = {'tool': 'adom-aiflow board-glb copper', 'version': VERSION, 'finish': fin, 'paste': args.paste, 'board': str(args.board), 'glb': str(args.glb), 'changes': []}+ opaque_board_materials(g, layers, rep['changes']) pcb_top = fr.z_in_pcb(fr.pcb_mesh, fr.pcb_node, 'max'); pcb_bot = fr.z_in_pcb(fr.pcb_mesh, fr.pcb_node, 'min') rgb, metal, rough = FINISHES[fin]@@ -807,6 +832,15 @@ def check(args): fr = Frame(g, layers, L['edge']) except SystemExit as e: errors.append(str(e)); fr = None+ # Opaque ink and substrate avoid transparent depth sorting beneath the mask.+ for role in ('pcb', 'silkscreen'):+ for mi in sorted({mi for mi, _ in layers.get(role, [])}):+ for primitive in g['meshes'][mi]['primitives']:+ idx = primitive.get('material')+ material = g.get('materials', [])[idx] if idx is not None else {}+ rgba = material.get('pbrMetallicRoughness', {}).get('baseColorFactor', [1, 1, 1, 1])+ if material.get('alphaMode', 'OPAQUE') != 'OPAQUE' or rgba[3] != 1:+ errors.append(f'{role} material is transparent: substrate and silkscreen must be opaque') # pad materials pad_meshes = layers.get('pad', []) if not pad_meshes:
tools/test-board-glb-copper.py+36@@ -116,4 +116,40 @@ with tempfile.TemporaryDirectory() as d: # 6. never overwrite the input assert run('copper', '--glb', raw, '--board', board, '--out', raw)[0] != 0+ # Transparent export materials must be fixed without changing shared components or mesh data.+ import copy+ transparent, payload = m.read_glb(fixed)+ transparent['materials'][0]['alphaMode'] = 'BLEND'+ transparent['materials'][0]['pbrMetallicRoughness']['baseColorFactor'][3] = 0.98+ ink = copy.deepcopy(transparent['meshes'][0]); ink['name'] = 'test_silkscreen'+ component = copy.deepcopy(ink); component['name'] = 'component_body'+ for mesh in (ink, component):+ transparent['meshes'].append(mesh)+ transparent['nodes'].append({'mesh': len(transparent['meshes']) - 1})+ transparent['nodes'].append({'mesh': len(transparent['meshes']) - 2}) # repeated ink instance+ original = copy.deepcopy(transparent)+ bad = d / 'transparent.glb'; m.write_glb(bad, transparent, payload)+ code, out = run('check', '--glb', bad, '--board', board)+ assert code == 1 and 'pcb material is transparent' in out and 'silkscreen material is transparent' in out, out+ layers = m.board_meshes(transparent, 'test')+ m.opaque_board_materials(transparent, layers)+ assert transparent['materials'][:len(original['materials'])] == original['materials']+ assert len(transparent['materials']) == len(original['materials']) + 2+ assert transparent['meshes'][-1] == original['meshes'][-1], 'shared component stays untouched'+ for role in ('pcb', 'silkscreen'):+ for mi, _ in layers[role]:+ before = copy.deepcopy(original['meshes'][mi]); after = copy.deepcopy(transparent['meshes'][mi])+ for a, b in zip(before['primitives'], after['primitives']):+ material = transparent['materials'][b.pop('material')]; a.pop('material')+ assert material['alphaMode'] == 'OPAQUE'+ assert material['pbrMetallicRoughness']['baseColorFactor'] == [0.42, 0.45, 0.29, 1]+ assert before == after, 'geometry unchanged'+ once = copy.deepcopy(transparent)+ m.opaque_board_materials(transparent, layers)+ assert once == transparent, 'normalization is idempotent'+ assert transparent['nodes'] == original['nodes'] and transparent['accessors'] == original['accessors']+ repaired = d / 'opaque.glb'+ code, out = run('copper', '--glb', bad, '--board', board, '--out', repaired)+ assert code == 0, out+ assert run('check', '--glb', repaired, '--board', board)[0] == 0 print('PASS: grey/masked pads refused; copper by construction from pad, mask and paste layers; bare test points; finishes; stale board refused')