Integrate Measured ESP32 PCB Geometry
- Add measured F.Fab bounds and USB mouth markers - Add guarded preservation and regression validation - Refresh review exports and remove draft presentation labels
This commit is contained in:
@@ -8,9 +8,23 @@ Working memory, not an implementation timeline. Source is authoritative; begin w
|
||||
- README was refined from the user's already-thinned version, not restored from Git. New `docs/getting_started.md`, `docs/user_administration.md`, and `docs/firmware_update.md` provide operating procedures; `docs/web_administration.md` is now a user guide and its original contract is preserved under `docs/devel/`. Flash layout and PCB research moved into the developer archive; contextual test/hardware/provenance READMEs remain beside artifacts.
|
||||
- Documentation relocation adds no new device evidence and does not change historical acceptance limits. Firmware, generated assets, CAD and historical hash JSON are untouched. Hardware scripts have only relocated-note reference/protection adjustments; Python syntax was checked without executing generators. Documentation links/anchors and `git diff --check` were checked; no firmware build, upload, erase, or hardware test was performed.
|
||||
|
||||
## PCB presentation labels — removed and exports refreshed
|
||||
|
||||
- Removed PCB title suffix `draft`, revision suffix `DRAFT`, front-silkscreen `DRAFT - NOT FOR FAB`, and title-comment prefix `NOT FOR FABRICATION -` (also in the PDF exporter). Generator no longer emits them. Specific technical cautions, hidden metadata, footprint identifiers, filenames and schematic remain unchanged; this is not fabrication approval.
|
||||
- `remove_draft_markers.py` guards editor locks/concurrent edits, saves with `SaveBoard(..., True)`, and audits the exact parsed-content delta and unchanged project settings. Two separate cleanup runs passed; same-process repeated board access hit a KiCad SWIG binding failure before any source overwrite.
|
||||
- 1:1 placement PDF and SVG/PNG/3D previews regenerated and visually reviewed. U1 preservation audit permits only the exact label delta in addition to prior measured-U1/device-silk changes; original baseline retained. U1/silk regression, two footprint tests and fresh PCB checks pass: 565 segments, 22 vias, zero physical DRC/unrouted, expected U2 parity warning. Existing mechanical/electrical release gates and historical A0 settings-hash limitation remain; no firmware/device tests or fabrication outputs.
|
||||
|
||||
## ESP32 mechanical measurements — board and review exports integrated
|
||||
|
||||
- User supplied main PCB 56.07 × 28.09 mm excluding 6.89 mm antenna projection; antenna left/right insets 5.41/5.05 mm. Overall length 62.96 mm. Both USB shells 8.87 mm wide, outer-edge offsets 4.16 mm left / 3.66 mm right, mouth faces flush with main PCB bottom.
|
||||
- Preserve 2.54 mm pitch / 25.40 mm row spacing. User accepted approximate 1.365 mm top/bottom pin-centre offset and same left offset; derived right margin 1.325 mm. Library HW678 F.Fab now has main PCB/antenna rectangles and USB mouth spans; no guessed USB depth, RF keepout, courtyard or heights. Footprint regression updated.
|
||||
- Embedded U1 now has the four measured F.Fab shapes and two measurement-limit annotations. `update_u1_graphics.py` adds only owned graphics; pads/nets/existing UUIDs/placement/routing/project settings and existing footprint fields remain unchanged. Old hidden status/description fields are historical, not current measurement evidence. `add_device_silkscreen.py` replaces header guides with carrier/mask-clipped measured outlines and 0.30 mm width-only USB mouth markers. PDF now verifies saved U1 instead of overlaying library geometry; PDF/SVG/PNG/3D exports refreshed.
|
||||
- New `validate_u1_update.py` audits exact pre-update preservation, library geometry, silk idempotence/clipping/mouth positions and stale-geometry rejection; snapshot/report retained in hardware/PCB/validation. Fresh PCB, independent routing, footprint and DB9 CAD checks pass: 565 segments, 22 vias, no physical DRC/unrouted, one expected U2 parity warning. Historical A0 preservation script fails its old `serial-carrier.kicad_prl` hash gate; not weakened or rebaselined. New A1-specific preservation passes.
|
||||
- At unchanged A1 placement USB faces sit 2.295 mm inside carrier bottom edge; plug clearance and physical 1:1 fit remain pending. Antenna geometry is not RF guidance; no keepout, USB depth, courtyard or height was invented. No firmware/assets changes, rerouting, hardware tests or fabrication outputs.
|
||||
|
||||
## Carrier PCB A1 — revised placement and CAD integration
|
||||
|
||||
- `hardware/PCB/serial-carrier.kicad_pcb`: routed 82 × 80 mm two-layer draft; 36 × 19 mm top-left notch, ESP32 shifted down for bottom USB access, RS-232 bare PCB front flush with carrier top. Exact ESP32 body/USB/antenna envelope remains pending user measurements; RF reserve is provisional.
|
||||
- `hardware/PCB/serial-carrier.kicad_pcb`: routed 82 × 80 mm two-layer draft; 36 × 19 mm top-left notch, ESP32 shifted down for bottom USB access, RS-232 bare PCB front flush with carrier top. Measured ESP32 planar body/antenna and USB mouths are integrated; full connector/assembly envelopes and RF qualification remain pending.
|
||||
- Adafruit STEP metal geometry extracted and validated; provenance/dimensions in `docs/devel/hardware/PCB/db9-mechanical-notes.md`. Flange front sits 1.930 mm inside carrier edge, screw-lock mouths 2.870 mm outside, shell nose 3.870 mm outside. Case wall plane is not yet fixed. Seven board-local Dwgs.User projections added; rerun `add_db9_envelope.py` and `add_device_silkscreen.py` after moving modules.
|
||||
- Placement PDF and SVG/PNG/3D previews refreshed. Fresh PCB, independent routing, preservation and CAD validators pass: 38/38 connections, 565 segments, 22 vias, 0 physical DRC violations/unrouted, one expected provisional U2 footprint parity warning. Not fabrication-ready; no hardware qualification or Gerbers. Existing project/schematic/library work preserved; board saves must use `SaveBoard(..., True)` and require checking editor lock. Prior A0 preserved in validation backup.
|
||||
|
||||
|
||||
@@ -78,10 +78,38 @@ centre (pad 1), **not a PCB corner**.
|
||||
- Numbering follows the project's explicit convention, not a generic dual-row
|
||||
connector's alternating or counterclockwise numbering. Physical numbers do
|
||||
**not** establish GPIO, supply, or ground assignments.
|
||||
- Only pads and annotation text are present. **No PCB outline, antenna keepout,
|
||||
courtyard, USB envelope, mounting holes, or 3D model is guessed.** Text positions
|
||||
are annotations, not dimensional evidence. Outline registration, antenna extent,
|
||||
connector offsets/overhang, and assembly height remain unknown.
|
||||
- User-measured planar geometry is now drawn on `F.Fab`, relative to pad 1:
|
||||
|
||||
| Feature | Start (X, Y), mm | End (X, Y), mm |
|
||||
|---|---|---|
|
||||
| Main PCB rectangle | (-1.365, -1.365) | (26.725, 54.705) |
|
||||
| Projecting antenna rectangle | (4.045, -8.255) | (21.675, -1.365) |
|
||||
| Left USB-C shell mouth span | (2.795, 54.705) | (11.665, 54.705) |
|
||||
| Right USB-C shell mouth span | (14.195, 54.705) | (23.065, 54.705) |
|
||||
|
||||
Main PCB is 28.09 mm wide × 56.07 mm long; antenna projection adds 6.89 mm,
|
||||
giving 62.96 mm overall. Antenna left/right insets are 5.41/5.05 mm, giving
|
||||
17.63 mm width and a centre 0.18 mm right of the PCB centre. Both shell widths
|
||||
are 8.87 mm, with outer-edge offsets 4.16 mm left / 3.66 mm right; mouths are
|
||||
flush with the bottom PCB edge. Lines indicate mouth spans, not shell depth.
|
||||
- User accepted approximate 1.365 mm top/bottom pin-centre offsets and supplied
|
||||
approximately the same left offset. Nominal pin pitch/row spacing are retained;
|
||||
resulting right margin is 1.325 mm. This 0.04 mm asymmetry follows the selected
|
||||
modelling datum, not a precision measurement of header asymmetry.
|
||||
- **No RF keepout, courtyard, full USB envelope, mounting holes or 3D model is
|
||||
inferred.** Shell depth/height, component/underside heights and plug clearances
|
||||
remain unknown. The antenna rectangle is physical geometry, not RF guidance.
|
||||
- Routed embedded U1 now contains the four measured F.Fab shapes and the two
|
||||
antenna/USB measurement-limit annotations. Generic library captions are omitted
|
||||
to avoid overlapping existing board labels. Existing footprint fields (including
|
||||
the historical hidden status/description), reference/value positions, pads,
|
||||
nets and UUIDs are deliberately retained; this is not a full footprint replacement.
|
||||
The library is authoritative for measured geometry, not those old status strings.
|
||||
- Measured board-local silkscreen and PDF/SVG/PNG/3D previews are refreshed.
|
||||
`update_u1_graphics.py` synchronizes only owned graphics, with deterministic new
|
||||
UUIDs and lock/concurrent-change guards; `validate_u1_update.py` checks exact
|
||||
preservation against the pre-update A1 snapshot. A physical 1:1 fit check is
|
||||
still pending; USB mouths sit 2.295 mm inside the carrier bottom edge.
|
||||
|
||||
## Validation and release gates
|
||||
|
||||
@@ -89,7 +117,9 @@ Validation performed with KiCad 10.0.6 `pcbnew.FootprintLoad`: both footprints
|
||||
parsed successfully. Assertions passed for pad counts, all numbered pad positions,
|
||||
pad shapes/types/sizes/drills, OLED NPTH positions and diameters, both OLED `F.Fab`
|
||||
rectangle bounds, and absence of courtyards, `Edge.Cuts`, and keepout zones.
|
||||
HW678 was also checked to contain no graphical shapes. The local Python binding
|
||||
The HW678 regression now checks the two measured rectangles and two USB mouth
|
||||
spans, while retaining exact pad geometry checks and prohibiting guessed
|
||||
courtyards, zones and 3D models. The local Python binding
|
||||
emitted three startup `PROPERTY_ENUM` assertions (also seen on a version-only
|
||||
import); these did not prevent loading or the checks from passing.
|
||||
|
||||
|
||||
@@ -7,6 +7,26 @@ Open **`../../../../hardware/PCB/serial-carrier.kicad_pcb`** in KiCad 10. The ro
|
||||
existing `../../../../hardware/PCB/serial-carrier.kicad_pro` / `../../../../hardware/PCB/serial-carrier.kicad_sch` project. **Not for fabrication.**
|
||||
No Gerbers, production drills or manufacturing archive were generated.
|
||||
|
||||
## Measurement update — embedded U1, silkscreen and exports
|
||||
|
||||
The user has now supplied ESP32 main-PCB, antenna and USB mouth geometry; see
|
||||
`footprint-notes.md` and the hardware README. The library HW678 footprint contains
|
||||
these measured F.Fab bounds. Embedded U1 now has the same four shapes and two
|
||||
measurement-limit annotations; generic library captions are omitted to avoid
|
||||
colliding with existing board labels. Silkscreen, PDF and SVG/PNG/3D previews are
|
||||
refreshed. Existing footprint fields, all existing non-generated UUIDs, pads/nets,
|
||||
placement, routing, edges, RF reserve and project settings are unchanged. Old
|
||||
hidden U1 status/description fields are retained historical metadata, not evidence
|
||||
that planar measurements are missing. RF requirements, full USB envelopes and
|
||||
assembly heights remain pending; the antenna rectangle is not an RF keepout.
|
||||
|
||||
At unchanged A1 U1 placement, the measured main PCB occupies carrier-local
|
||||
X=6.635…34.725, Y=21.635…77.705 mm; the projecting antenna occupies
|
||||
X=12.045…29.675, Y=14.745…21.635 mm. USB mouths are at Y=77.705 mm,
|
||||
**2.295 mm inside the carrier bottom edge**, so flush-with-module USB does not
|
||||
mean flush-with-carrier access. Review mating-plug clearance before accepting
|
||||
placement; no relocation, rerouting or RF sign-off is implied by this update.
|
||||
|
||||
## Size and placement
|
||||
|
||||
- **Carrier bounding dimensions: 82 × 80 mm**, within the requested 100 × 100 mm.
|
||||
@@ -14,8 +34,8 @@ No Gerbers, production drills or manufacturing archive were generated.
|
||||
- Proposed **1.6 mm** carrier thickness; fabrication stackup/process not selected.
|
||||
- Stepped outline: a **36 × 19 mm top-left antenna cutaway** fits within a simple
|
||||
rectangular/square case. The lower edge is now straight, with ESP32 USB toward
|
||||
the bottom and RS-232 toward the top. U1 moved down by 19 mm; its exact USB/body
|
||||
registration remains provisional pending the user's measured ESP32 geometry.
|
||||
the bottom and RS-232 toward the top. A1 moved U1 down by 19 mm; this measurement
|
||||
integration retains that placement and uses the user-accepted approximate header datum.
|
||||
- Module and mating-plug overhangs are not included in the carrier dimensions.
|
||||
The final complete assembly/enclosure envelope still needs measurement.
|
||||
|
||||
@@ -80,8 +100,8 @@ allocation, **not a measured antenna envelope or a manufacturer RF keepout**.
|
||||
The module itself may span the rule area; footprint placement is not forbidden.
|
||||
No carrier copper enters the reserve, but copper runs close to its boundary.
|
||||
|
||||
U1's exact PCB/antenna/USB offsets are still unmeasured. The intended antenna
|
||||
overhang and USB access must be checked with the actual board and a printed
|
||||
U1's planar PCB/antenna/USB-mouth offsets are now recorded. Antenna clearance
|
||||
and USB access must still be checked with the actual board and a printed
|
||||
placement template. Move U1, change the edge or enlarge the reserve if required.
|
||||
Missing RF clearance cannot be signed off from the current footprint alone.
|
||||
|
||||
@@ -147,11 +167,13 @@ RS-232's visible rounded corners retain true arcs. Small gaps clear pad/mask
|
||||
openings, vias and adjacent resistor markings; the original full F.Fab geometry
|
||||
remains unchanged.
|
||||
|
||||
U1 instead has **header-row guides**, labelled `HEADER GUIDES`: guide edges are
|
||||
1.20 mm outside the outer pin centres. These are graphic alignment aids, **not
|
||||
claimed socket-body dimensions or an inferred ESP32 PCB outline**. The actual
|
||||
U1 body/antenna/USB outline still requires measured offsets. Existing button and
|
||||
resistor silkscreen remains unchanged.
|
||||
U1 now has **measured body/antenna outlines**, replacing the header-row guides
|
||||
and `HEADER GUIDES` label. Silkscreen is clipped inside the stepped carrier with
|
||||
0.22 mm additional edge/mask clearance; the off-carrier antenna projection remains
|
||||
visible only on F.Fab. Two **0.30 mm strokes** mark the 8.87 mm USB mouth spans,
|
||||
replacing coincident 0.12 mm body-edge strokes. Wider strokes receive correspondingly
|
||||
wider clearance. No USB depth or RF keepout is inferred. Existing button/resistor
|
||||
silkscreen remains unchanged.
|
||||
|
||||
These are board-local drawings, not edits to the footprint libraries. If the
|
||||
module placement or routing changes, close PCB Editor and refresh them with:
|
||||
@@ -160,7 +182,8 @@ module placement or routing changes, close PCB Editor and refresh them with:
|
||||
/usr/bin/python3 -B hardware/PCB/add_device_silkscreen.py
|
||||
```
|
||||
|
||||
The script requires pcbnew and Shapely, replaces only its own generated graphics,
|
||||
The script first verifies the saved U1 measured graphics against the library.
|
||||
It requires pcbnew and Shapely, replaces only its own generated graphics,
|
||||
and refuses an open PCB or concurrent board change. It leaves project settings,
|
||||
copper, pads and source F.Fab outlines unchanged. Run it **after** placement or
|
||||
routing regeneration; moving a footprint does not move these board-local drawings.
|
||||
@@ -174,6 +197,21 @@ warning. PDF and both previews have been refreshed.
|
||||
|
||||
## Review outputs
|
||||
|
||||
PCB presentation labels have been removed: title suffix `draft`, revision suffix
|
||||
`DRAFT`, front-silkscreen `DRAFT - NOT FOR FAB`, and the title-comment prefix
|
||||
`NOT FOR FABRICATION -` (including the PDF exporter). The generator no longer
|
||||
emits these labels. Specific technical cautions, dimensions, geometry, hidden
|
||||
metadata and footprint identifiers remain; artifact filenames are unchanged.
|
||||
This is a presentation change, **not fabrication approval**. The schematic and
|
||||
its exports are outside this PCB-only cleanup.
|
||||
|
||||
`remove_draft_markers.py` performs the guarded, idempotent cleanup and audits
|
||||
that no other parsed board content or project settings change. The U1 regression
|
||||
now permits exactly these label edits against its unchanged historical baseline,
|
||||
without relaxing geometry/pad/net/routing checks. Fresh PCB DRC, U1/silkscreen
|
||||
regressions and both footprint tests pass; the expected U2 parity warning remains.
|
||||
PDF/SVG/PNG/3D outputs were regenerated and visually reviewed after cleanup.
|
||||
|
||||
- `../../../../hardware/PCB/validation/pcb-draft.svg` / `../../../../hardware/PCB/validation/pcb-draft.png`: combined copper/outline/fab review.
|
||||
- `../../../../hardware/PCB/validation/pcb-draft-3d.png`: KiCad render of the carrier and available standard
|
||||
component models. **ESP32, OLED and RS-232 assembled module models are absent**;
|
||||
@@ -181,7 +219,35 @@ warning. PDF and both previews have been refreshed.
|
||||
- `../../../../hardware/PCB/pcb-placement-draft.pdf`: **1:1** front placement/pad/outline drawing. Print at
|
||||
100%, disable fit-to-page, and confirm the outer width is 82 mm before use.
|
||||
It includes the known module outlines and nominal STEP-derived DE-9 metal
|
||||
bounding projections; no guessed U1 body or case-wall geometry.
|
||||
bounding projections and measured U1 planar geometry; no guessed USB depth,
|
||||
RF keepout or case-wall geometry.
|
||||
|
||||
Refresh the embedded measurement graphics and PDF without replacing the footprint:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/update_u1_graphics.py
|
||||
/usr/bin/python3 -B hardware/PCB/add_device_silkscreen.py
|
||||
/usr/bin/python3 -B hardware/PCB/export_placement_pdf.py
|
||||
```
|
||||
|
||||
Both board-writing helpers refuse PCB Editor locks and concurrent board changes,
|
||||
use `SaveBoard(..., True)`, and check unchanged project settings. The PDF exporter
|
||||
verifies embedded geometry instead of overlaying the library; only review captions
|
||||
change on its temporary plotting board. Print at 100%, not fit-to-page.
|
||||
|
||||
Preview refresh commands (repository root, KiCad and Inkscape installed):
|
||||
|
||||
```sh
|
||||
kicad-cli pcb export svg --mode-single --layers F.Cu,B.Cu,F.SilkS,F.Fab,Dwgs.User,Edge.Cuts --sketch-pads-on-fab-layers --page-size-mode 2 --output hardware/PCB/validation/pcb-draft.svg hardware/PCB/serial-carrier.kicad_pcb
|
||||
inkscape hardware/PCB/validation/pcb-draft.svg --export-type=png --export-width=1800 --export-filename=hardware/PCB/validation/pcb-draft.png
|
||||
kicad-cli pcb render --width 1600 --height 1200 --side top --quality high --output hardware/PCB/validation/pcb-draft-3d.png hardware/PCB/serial-carrier.kicad_pcb
|
||||
```
|
||||
|
||||
KiCad's SVG trailing whitespace was normalized after export (`git diff --check`
|
||||
passes); the rendered geometry is unchanged. PDF and previews were visually reviewed.
|
||||
|
||||
The board-area SVG crop omits outside-board review captions/parts of the DE-9
|
||||
projection; use the full-page PDF for the complete placement drawing.
|
||||
|
||||
## Validation and limits
|
||||
|
||||
@@ -190,6 +256,9 @@ Run from the repository root with the system Python containing KiCad bindings:
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/validate_pcb_draft.py
|
||||
/usr/bin/python3 -B hardware/PCB/validation/pcb-routing-check.py
|
||||
/usr/bin/python3 -B hardware/PCB/validate_footprints.py
|
||||
/usr/bin/python3 -B hardware/PCB/validate_u1_update.py
|
||||
/usr/bin/python3 -B hardware/PCB/reference/db9-cad/validate.py
|
||||
```
|
||||
|
||||
The first script exports a **fresh saved-schematic netlist** and checks the actual
|
||||
@@ -204,7 +273,25 @@ placement/net/UUID preservation against the initial unrouted snapshot. It needs
|
||||
locally installed NumPy and Shapely in addition to pcbnew. It is not a substitute
|
||||
for current-schematic net validation and is specific to this initial layout.
|
||||
|
||||
Results with KiCad **10.0.6**:
|
||||
Measurement-integration validation with KiCad **10.0.6**:
|
||||
|
||||
- All five commands above pass. New `validate_u1_update.py` compares the complete
|
||||
parsed board against `validation/pcb-before-u1-measured.kicad_pcb`, excluding
|
||||
only owned silk and six new U1 graphics. Snapshot and project hashes are pinned;
|
||||
full pad/net/UUID/placement/routing/edge/zone/settings preservation passes.
|
||||
- Measured embedded/library geometry, silk regeneration idempotence, carrier/notch
|
||||
clipping, two exact USB mouth markers and a stale-geometry negative control pass;
|
||||
**91 generated silk items**. U1 refresh byte-idempotence also passed; an already
|
||||
current U1 now returns without writing. Evidence: `hardware/PCB/validation/pcb-u1-update.json`.
|
||||
- The historical A0 `pcb-routing-preservation.py` does **not** pass: it stops on
|
||||
its historical `serial-carrier.kicad_prl` protected hash, already stale for this
|
||||
working state. Its older exact-footprint contract also predates measured U1
|
||||
additions. Historical script/reports were not weakened or rebaselined; the new
|
||||
A1 pre-update comparison is the preservation evidence for this change.
|
||||
- PCB, project and schematic retain their existing content except the authorized
|
||||
board graphics. No firmware/assets, reroute, upload, erase or hardware test.
|
||||
|
||||
Current results:
|
||||
|
||||
- **0 physical DRC errors/warnings; 0 unconnected items.**
|
||||
- All saved-schematic pad nets match the PCB; **1 expected U2 footprint parity warning**.
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "HW-678, component/top view antenna up USB down; origin pad 1; left top-bottom 1..22, right top-bottom 23..44; only header centres verified; no GPIO mapping, outline, courtyard or antenna keepout; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(descr "HW-678, component/top view antenna up USB down; origin pad 1; left top-bottom 1..22, right top-bottom 23..44; nominal header centres with user-measured main PCB/antenna bounds and USB shell mouth spans on F.Fab; approximate 1.365 mm top/left registration; no GPIO mapping, complete USB envelope, courtyard or RF keepout; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(tags "module provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 12.7 -3) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "HW678_2x22_Provisional" (at 12.7 57) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "PROVISIONAL CONNECTOR FIT" (at 12.7 25) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "ANTENNA UP / USB DOWN" (at 12.7 28) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_rect (start -1.365 -1.365) (end 26.725 54.705) (stroke (width 0.1) (type solid)) (fill none) (layer "F.Fab"))
|
||||
(fp_rect (start 4.045 -8.255) (end 21.675 -1.365) (stroke (width 0.1) (type solid)) (fill none) (layer "F.Fab"))
|
||||
(fp_line (start 2.795 54.705) (end 11.665 54.705) (stroke (width 0.3) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start 14.195 54.705) (end 23.065 54.705) (stroke (width 0.3) (type solid)) (layer "F.Fab"))
|
||||
(fp_text user "ANTENNA BOUNDS - NOT RF KEEPOUT" (at 12.7 -9.5) (layer "F.Fab") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "USB SHELL MOUTHS ONLY" (at 12.7 59) (layer "F.Fab") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(pad "1" thru_hole rect (at 0.00 0.00) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 0.00 2.54) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 0.00 5.08) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
|
||||
+25
-6
@@ -26,8 +26,11 @@ the carrier. All 3.3 V and ground connections are explicit named nets.
|
||||
| `validation/pcb-draft.png`, `validation/pcb-draft-3d.png` | Copper/fab preview and carrier-only 3D render |
|
||||
| `validate_pcb_draft.py` | Fresh schematic-to-PCB net checks, bounds/layers and DRC/parity report |
|
||||
| `generate_pcb_draft.py`, `route_pcb_draft.py` | Optional destructive-regeneration tools with explicit overwrite guards |
|
||||
| `add_device_silkscreen.py` | Refresh board-local OLED/RS-232 outlines and ESP32 header guides after placement/routing; preserves copper and project settings |
|
||||
| `add_device_silkscreen.py` | Refresh board-local OLED/RS-232 and measured ESP32 outlines/USB mouth markers after placement/routing; preserves copper and project settings |
|
||||
| [db9-mechanical-notes.md](../../docs/devel/hardware/PCB/db9-mechanical-notes.md), `reference/db9-cad/` | STEP-derived metal dimensions, originals, source licenses and repeatable extraction/validation |
|
||||
| `update_u1_graphics.py`, `validate_u1_update.py` | Guarded measured-U1 graphics integration and exact pre-update preservation / geometry / silkscreen regressions |
|
||||
| `export_placement_pdf.py` | 1:1 PDF from saved board geometry; refuses stale embedded U1 graphics |
|
||||
| `remove_draft_markers.py` | Guarded presentation-label cleanup with exact-content preservation audit; does not qualify fabrication |
|
||||
| `add_db9_envelope.py` | Refresh nominal connector metal projections on Dwgs.User; not a case cutout or assembly clearance |
|
||||
| `Carrier.kicad_sym`, `sym-lib-table` | Project-local module, switch and resistor symbols |
|
||||
| `Carrier.pretty/`, `fp-lib-table` | OLED/ESP32/RS-232-reference provisional footprints; standard KiCad 10 switch/resistor/mounting-hole library references |
|
||||
@@ -217,9 +220,24 @@ The wiring guide warns that compatible ESP32 boards may have different layouts.
|
||||
rows fit a standard breadboard simultaneously without forcing. Use nominal
|
||||
2.54 mm pitch (53.34 mm over 21 intervals) and 25.40 mm row spacing based on
|
||||
that fit check, not the raw caliper measurements. Verify with a 1:1 print before fabrication.
|
||||
- ESP32 seller drawing states 28 mm width and annotates 63 mm length; its 20 mm
|
||||
arrow is not header row spacing. Antenna extent and header-to-outline offsets
|
||||
are not fully dimensioned.
|
||||
- ESP32 actual user measurements supersede the seller's 28 × 63 mm drawing:
|
||||
main PCB **56.07 × 28.09 mm**, excluding the antenna projection. Top/component
|
||||
view, antenna up and USB down: antenna projects **6.89 mm** above the main PCB,
|
||||
inset **5.41 mm left / 5.05 mm right**. Derived antenna width **17.63 mm**,
|
||||
overall length **62.96 mm**, antenna centre **0.18 mm right** of PCB centre.
|
||||
- Header end offsets were initially approximately 1.5 mm; user accepted **1.365 mm**
|
||||
top/bottom to preserve the breadboard-confirmed 53.34 mm span. Left-edge-to-left-row
|
||||
centre distance is also approximately **1.365 mm**. Preserve 25.40 mm row spacing:
|
||||
the resulting right margin is **1.325 mm**, not an independently measured offset.
|
||||
These modelling offsets are not a claim of micron-level measurement accuracy.
|
||||
- Both USB-C metal shells are **8.87 mm wide**, with opening faces flush with the
|
||||
main PCB bottom edge. Left shell's outside left edge is **4.16 mm** from the PCB
|
||||
left edge; right shell's outside right edge is **3.66 mm** from the PCB right edge.
|
||||
Shell depth/height and mating-plug clearance remain unmeasured.
|
||||
- The library and routed board's embedded U1 now record these bounds on F.Fab;
|
||||
USB mouths are width spans only, not complete connector outlines. Measured
|
||||
board-local silkscreen, 1:1 placement PDF and SVG/PNG/3D previews are refreshed. See
|
||||
`../../docs/devel/hardware/PCB/footprint-notes.md` for coordinates and limits.
|
||||
- OLED actual PCB outline measured by user: 26 × 26 mm, superseding the seller's
|
||||
28.7 × 27.8 mm dimensions. Seller's 4.1 mm height remains unverified.
|
||||
- OLED front view, connector at top: GND, VCC, SCL, SDA from left to right,
|
||||
@@ -258,8 +276,9 @@ The wiring guide warns that compatible ESP32 boards may have different layouts.
|
||||
1. OLED outline, mounting-hole placement, revised header position and display
|
||||
outline are user fit-verified. Select carrier header/socket pads and drills
|
||||
from the actual connector drawings.
|
||||
2. Before final module outlines/courtyards, establish their registration to header
|
||||
centres, connector overhang and display position from drawings or measurements.
|
||||
2. ESP32 planar body/antenna bounds and USB mouth positions are now recorded in
|
||||
the library and embedded board footprint. Verify a 1:1 fit; complete module heights, USB depth/height, mating-plug clearances and
|
||||
assembly courtyards. Measured antenna bounds do not establish an RF keepout.
|
||||
3. Verify power-path schematic/regulator capacity and OLED pull-up implementation.
|
||||
Photos alone do not establish those electrical properties.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import uuid
|
||||
import pcbnew as p
|
||||
from shapely.geometry import LineString, Point, box
|
||||
from shapely.ops import unary_union, polygonize
|
||||
from update_u1_graphics import verify
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
@@ -38,6 +39,7 @@ def outline_lines(shape):
|
||||
|
||||
|
||||
def add_outlines(board):
|
||||
verify(board)
|
||||
owned = {str(uuid.uuid5(NS, str(i))) for i in range(4096)}
|
||||
for drawing in list(board.GetDrawings()):
|
||||
if drawing.m_Uuid.AsString() in owned:
|
||||
@@ -82,8 +84,11 @@ def add_outlines(board):
|
||||
board.Add(shape)
|
||||
count+=1
|
||||
|
||||
def line(a,b):
|
||||
clipped=LineString([a,b]).intersection(silk_area).difference(blocked)
|
||||
def line(a,b,width=WIDTH):
|
||||
extra = max(0, (width-WIDTH)/2)
|
||||
area = silk_area.buffer(-extra) if extra else silk_area
|
||||
obstacles = blocked.buffer(extra) if extra else blocked
|
||||
clipped=LineString([a,b]).intersection(area).difference(obstacles)
|
||||
pieces=list(clipped.geoms) if hasattr(clipped,"geoms") else [clipped]
|
||||
for piece in pieces:
|
||||
if piece.geom_type!="LineString" or piece.length < 0.25:
|
||||
@@ -91,7 +96,7 @@ def add_outlines(board):
|
||||
shape=p.PCB_SHAPE(board)
|
||||
shape.SetShape(p.SHAPE_T_SEGMENT)
|
||||
shape.SetStart(vec(piece.coords[0])); shape.SetEnd(vec(piece.coords[-1]))
|
||||
shape.SetWidth(p.FromMM(WIDTH))
|
||||
shape.SetWidth(p.FromMM(width))
|
||||
add(shape)
|
||||
|
||||
for ref in ("DS1","U2"):
|
||||
@@ -137,22 +142,19 @@ def add_outlines(board):
|
||||
distance+=1.3
|
||||
else:
|
||||
line(edge.coords[0],edge.coords[-1])
|
||||
# These are pad-row guides, not inferred socket/body or ESP32 PCB dimensions.
|
||||
fp=footprints["U1"]
|
||||
assert fp.GetOrientationDegrees()==0, "Review guide construction for rotated U1"
|
||||
pads={pad.GetNumber():xy(pad.GetPosition()) for pad in fp.Pads()}
|
||||
for first,last in (("1","22"),("23","44")):
|
||||
x,ytop=pads[first]; _,ybottom=pads[last]
|
||||
corners=[(x-1.2,ytop-1.2),(x+1.2,ytop-1.2),(x+1.2,ybottom+1.2),(x-1.2,ybottom+1.2)]
|
||||
for a,b in zip(corners,corners[1:]+corners[:1]):
|
||||
line(a,b)
|
||||
label=p.PCB_TEXT(board)
|
||||
label.SetText("HEADER GUIDES")
|
||||
a,b=pads["1"],pads["23"]
|
||||
label.SetPosition(vec(((a[0]+b[0])/2,a[1]+28)))
|
||||
label.SetTextSize(p.VECTOR2I(p.FromMM(0.85),p.FromMM(0.85)))
|
||||
label.SetTextThickness(p.FromMM(0.12))
|
||||
add(label)
|
||||
# Union removes the shared body/antenna edge duplication. Mouth spans replace
|
||||
# the coincident bottom-edge strokes, with their measured library line weight;
|
||||
# this marks width only, without inventing connector depth or an RF keepout.
|
||||
sources=[g for g in footprints["U1"].GraphicalItems()
|
||||
if isinstance(g,p.PCB_SHAPE) and g.GetLayer()==p.F_Fab]
|
||||
mouths=[g for g in sources if g.GetShape()==p.SHAPE_T_SEGMENT]
|
||||
mouth_lines=unary_union([edge for g in mouths for edge in outline_lines(g)])
|
||||
perimeter=unary_union([edge for g in sources if g.GetShape()==p.SHAPE_T_RECT
|
||||
for edge in outline_lines(g)]).difference(mouth_lines)
|
||||
for edge in perimeter.geoms:
|
||||
line(edge.coords[0],edge.coords[-1])
|
||||
for mouth in mouths:
|
||||
line(xy(mouth.GetStart()),xy(mouth.GetEnd()),p.ToMM(mouth.GetWidth()))
|
||||
return count
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export a 1:1 placement PDF with measured HW678 geometry, without editing CAD.
|
||||
|
||||
Export the saved board's verified measured U1 graphics. Only review captions
|
||||
change on a temporary plotting board; no library overlay hides stale CAD.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import pcbnew as p
|
||||
from update_u1_graphics import verify
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
PDF = ROOT / "pcb-placement-draft.pdf"
|
||||
|
||||
|
||||
def export():
|
||||
original = PCB.read_bytes()
|
||||
board = p.LoadBoard(str(PCB))
|
||||
verify(board)
|
||||
for drawing in board.GetDrawings():
|
||||
if isinstance(drawing, p.PCB_TEXT):
|
||||
if drawing.GetText() == "Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending":
|
||||
drawing.SetText("Measured ESP32 outline / USB mouths; RF / plug / assembly clearance pending")
|
||||
board.GetTitleBlock().SetComment(0, "Measured planar outlines; verify RF / plug / assembly clearance")
|
||||
with tempfile.TemporaryDirectory(prefix="carrier-placement-") as temporary:
|
||||
draft = Path(temporary) / PCB.name
|
||||
result = Path(temporary) / PDF.name
|
||||
if not p.SaveBoard(str(draft), board, True):
|
||||
raise RuntimeError("Cannot save temporary plotting board")
|
||||
subprocess.run([
|
||||
"kicad-cli", "pcb", "export", "pdf", "--mode-single",
|
||||
"--layers", "F.Fab,F.SilkS,Dwgs.User,Edge.Cuts",
|
||||
"--sketch-pads-on-fab-layers", "--black-and-white", "--scale", "1",
|
||||
"--output", str(result), str(draft),
|
||||
], check=True)
|
||||
if PCB.read_bytes() != original:
|
||||
raise RuntimeError("Source PCB changed during export; PDF not replaced")
|
||||
PDF.write_bytes(result.read_bytes())
|
||||
print(f"Exported {PDF}; scale 1:1; source PCB unchanged")
|
||||
print(f"Source PCB SHA256: {hashlib.sha256(original).hexdigest()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
export()
|
||||
@@ -114,9 +114,9 @@ def main():
|
||||
settings.m_ViasMinSize=mm(0.7)
|
||||
# Proposed in-memory draft rules only; never overwrite the user's project settings.
|
||||
title=board.GetTitleBlock()
|
||||
title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier draft")
|
||||
title.SetRevision("A1 DRAFT")
|
||||
title.SetComment(0,"NOT FOR FABRICATION - USB / antenna geometry pending; verify module pinout / assembly")
|
||||
title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier")
|
||||
title.SetRevision("A1")
|
||||
title.SetComment(0,"USB / antenna geometry pending; verify module pinout / assembly")
|
||||
pad_nets={}
|
||||
for source_net in source.findall("nets/net"):
|
||||
net=p.NETINFO_ITEM(board,source_net.get("name"))
|
||||
@@ -194,7 +194,7 @@ def main():
|
||||
text(board,"26mm OLED",56,52)
|
||||
for label,x in zip(("BACK","SELECT","NEXT"),(46.25,58.25,70.25)):
|
||||
text(board,label,x,75, size=0.9)
|
||||
text(board,"DRAFT - NOT FOR FAB",56,78,size=0.9)
|
||||
|
||||
text(board,"82 x 80 mm / 2 copper layers",41,-5,p.Dwgs_User,1.5)
|
||||
text(board,"Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending",41,-2.5,p.Dwgs_User,0.9)
|
||||
board.BuildConnectivity()
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove presentation-only PCB draft labels; retain all technical cautions."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
TITLE = "Serial Swiss Army Knife - 82x80 mm carrier"
|
||||
COMMENT = "USB / antenna geometry pending; verify module pinout / assembly"
|
||||
SILK = "DRAFT - NOT FOR FAB"
|
||||
|
||||
|
||||
def remove_labels(board):
|
||||
title = board.GetTitleBlock()
|
||||
assert title.GetTitle() in (TITLE, TITLE + " draft")
|
||||
assert title.GetRevision() in ("A1", "A1 DRAFT")
|
||||
assert title.GetComment(0) in (COMMENT, "NOT FOR FABRICATION - " + COMMENT)
|
||||
title.SetTitle(TITLE)
|
||||
title.SetRevision("A1")
|
||||
title.SetComment(0, COMMENT)
|
||||
for drawing in list(board.GetDrawings()):
|
||||
if isinstance(drawing, p.PCB_TEXT) and drawing.GetText() == SILK:
|
||||
assert drawing.GetLayer() == p.F_SilkS
|
||||
board.Remove(drawing)
|
||||
|
||||
|
||||
def expected_tree(tree):
|
||||
"""Exact permitted delta for preservation audits, independent of pcbnew edits."""
|
||||
import copy
|
||||
result = copy.deepcopy(tree)
|
||||
for item in list(result):
|
||||
if not isinstance(item, list):
|
||||
continue
|
||||
if item[0] == "title_block":
|
||||
for field in item[1:]:
|
||||
replacements = {
|
||||
('title', '"' + TITLE + ' draft"'): '"' + TITLE + '"',
|
||||
('rev', '"A1 DRAFT"'): '"A1"',
|
||||
('comment', '"NOT FOR FABRICATION - ' + COMMENT + '"'): '"' + COMMENT + '"',
|
||||
}
|
||||
replacement = replacements.get((field[0], field[-1]))
|
||||
if replacement is not None:
|
||||
field[-1] = replacement
|
||||
elif item[0] == "gr_text" and item[1] == '"' + SILK + '"':
|
||||
assert ['layer', '"F.SilkS"'] in item
|
||||
result.remove(item)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
from validate_u1_update import parse, canonical
|
||||
lock = ROOT / "~serial-carrier.kicad_pcb.lck"
|
||||
if lock.exists():
|
||||
raise RuntimeError("Close PCB Editor before changing labels")
|
||||
original = PCB.read_bytes()
|
||||
settings = {path: path.read_bytes() for path in ROOT.glob("serial-carrier.kicad_pr*")}
|
||||
expected = expected_tree(parse(PCB))
|
||||
board = p.LoadBoard(str(PCB))
|
||||
remove_labels(board)
|
||||
with tempfile.TemporaryDirectory(prefix="carrier-labels-", dir=ROOT) as temporary:
|
||||
result = Path(temporary) / PCB.name
|
||||
assert p.SaveBoard(str(result), board, True)
|
||||
assert canonical(parse(result)) == canonical(expected), "Change beyond exact label edits"
|
||||
assert canonical(expected_tree(parse(result))) == canonical(expected), "Label cleanup not idempotent"
|
||||
if lock.exists() or PCB.read_bytes() != original:
|
||||
raise RuntimeError("PCB changed or reopened; refusing overwrite")
|
||||
assert all(path.read_bytes() == data for path, data in settings.items()), "Project settings changed"
|
||||
result.replace(PCB)
|
||||
print("Removed draft presentation labels; exact preservation and idempotence passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,9 +8,9 @@
|
||||
)
|
||||
(paper "A4")
|
||||
(title_block
|
||||
(title "Serial Swiss Army Knife - 82x80 mm carrier draft")
|
||||
(rev "A1 DRAFT")
|
||||
(comment 1 "NOT FOR FABRICATION - USB / antenna geometry pending; verify module pinout / assembly")
|
||||
(title "Serial Swiss Army Knife - 82x80 mm carrier")
|
||||
(rev "A1")
|
||||
(comment 1 "USB / antenna geometry pending; verify module pinout / assembly")
|
||||
)
|
||||
(layers
|
||||
(0 "F.Cu" signal)
|
||||
@@ -562,6 +562,70 @@
|
||||
(sheetfile "serial-carrier.kicad_sch")
|
||||
(attr through_hole)
|
||||
(duplicate_pad_numbers_are_jumpers no)
|
||||
(fp_line
|
||||
(start 2.795 54.705)
|
||||
(end 11.665 54.705)
|
||||
(stroke
|
||||
(width 0.3)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "6434f4c9-cb7e-5b60-902d-bd13b5c57f13")
|
||||
)
|
||||
(fp_line
|
||||
(start 14.195 54.705)
|
||||
(end 23.065 54.705)
|
||||
(stroke
|
||||
(width 0.3)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "a4082d84-0a5f-5edd-b97f-afbb68b73336")
|
||||
)
|
||||
(fp_rect
|
||||
(start -1.365 -1.365)
|
||||
(end 26.725 54.705)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.Fab")
|
||||
(uuid "53b4b62c-705f-584c-9987-8e20cbcb0eae")
|
||||
)
|
||||
(fp_rect
|
||||
(start 4.045 -8.255)
|
||||
(end 21.675 -1.365)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.Fab")
|
||||
(uuid "1a629168-d420-520b-820e-a4de5828228b")
|
||||
)
|
||||
(fp_text user "ANTENNA BOUNDS - NOT RF KEEPOUT"
|
||||
(at 12.7 -9.5 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "acabe363-725d-5486-8c68-8e06b5758ceb")
|
||||
(effects
|
||||
(font
|
||||
(size 0.8 0.8)
|
||||
(thickness 0.12)
|
||||
)
|
||||
)
|
||||
)
|
||||
(fp_text user "USB SHELL MOUTHS ONLY"
|
||||
(at 12.7 59 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "4af1d9b9-5ec1-5ef3-a72d-b72b93ecc391")
|
||||
(effects
|
||||
(font
|
||||
(size 0.8 0.8)
|
||||
(thickness 0.12)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "1" thru_hole rect
|
||||
(at 0 0)
|
||||
(size 1.7 1.7)
|
||||
@@ -3025,8 +3089,8 @@
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(gr_line
|
||||
(start 106.8 121.8)
|
||||
(end 109.2 121.8)
|
||||
(start 106.635 121.635)
|
||||
(end 112.045 121.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
@@ -3035,78 +3099,8 @@
|
||||
(uuid "cdd870c2-6daf-560d-a619-6903566202a1")
|
||||
)
|
||||
(gr_line
|
||||
(start 106.8 177.54)
|
||||
(end 106.8 121.8)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "1576658b-4eb9-5105-9817-e374c6ef271e")
|
||||
)
|
||||
(gr_line
|
||||
(start 109.2 121.8)
|
||||
(end 109.2 177.54)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "8f746121-d8e6-5a89-9328-da46f2da8ac0")
|
||||
)
|
||||
(gr_line
|
||||
(start 109.2 177.54)
|
||||
(end 106.8 177.54)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "bfbd0b33-54e8-590f-87da-4935db08a121")
|
||||
)
|
||||
(gr_line
|
||||
(start 132.2 121.8)
|
||||
(end 134.6 121.8)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "fef8b02b-8a5c-5e27-8e75-fc116ddbd526")
|
||||
)
|
||||
(gr_line
|
||||
(start 132.2 136.509273)
|
||||
(end 132.2 121.8)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "abb27fb8-ae0c-56d1-9e35-c66b92681547")
|
||||
)
|
||||
(gr_line
|
||||
(start 132.2 162.671905)
|
||||
(end 132.2 137.390726)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "03740f02-7c63-574b-ad1d-eeaa2c77d2ea")
|
||||
)
|
||||
(gr_line
|
||||
(start 132.2 170.202783)
|
||||
(end 132.2 163.828094)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "2cecac83-165e-5e49-a695-c0b09f3b631c")
|
||||
)
|
||||
(gr_line
|
||||
(start 132.2 177.54)
|
||||
(end 132.2 171.397216)
|
||||
(start 106.635 177.705)
|
||||
(end 106.635 121.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
@@ -3115,48 +3109,48 @@
|
||||
(uuid "5473bcda-98ee-5a28-9fda-f27d137d534c")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 121.8)
|
||||
(end 134.6 130.421905)
|
||||
(start 110.795 177.705)
|
||||
(end 106.635 177.705)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "0445e79c-43e4-5609-90ad-67bffdf64341")
|
||||
(uuid "2441022c-c903-5a57-a84c-86f0a5143cb5")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 131.578094)
|
||||
(end 134.6 140.571905)
|
||||
(start 110.795 177.705)
|
||||
(end 119.665 177.705)
|
||||
(stroke
|
||||
(width 0.3)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "abb27fb8-ae0c-56d1-9e35-c66b92681547")
|
||||
)
|
||||
(gr_line
|
||||
(start 112.045 121.635)
|
||||
(end 112.045 119.28)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "6405d812-d377-52d9-8013-8c19bd4cdc2b")
|
||||
(uuid "03740f02-7c63-574b-ad1d-eeaa2c77d2ea")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 141.728094)
|
||||
(end 134.6 160.952783)
|
||||
(start 112.045 121.635)
|
||||
(end 129.675 121.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "f938fe4f-5ff2-577d-9bff-2827fb32ce22")
|
||||
(uuid "8f746121-d8e6-5a89-9328-da46f2da8ac0")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 162.147216)
|
||||
(end 134.6 169.559273)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "d472d222-21c6-5a78-ab7f-18799bceab8a")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 170.440726)
|
||||
(end 134.6 177.54)
|
||||
(start 122.195 177.705)
|
||||
(end 119.665 177.705)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
@@ -3165,14 +3159,94 @@
|
||||
(uuid "bfa82c3d-e8e2-58ea-8b46-f0db41e84458")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.6 177.54)
|
||||
(end 132.2 177.54)
|
||||
(start 122.195 177.705)
|
||||
(end 131.065 177.705)
|
||||
(stroke
|
||||
(width 0.3)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "2b9b8e99-a473-51bc-8241-448388f63fcb")
|
||||
)
|
||||
(gr_line
|
||||
(start 129.675 119.28)
|
||||
(end 129.675 121.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "2441022c-c903-5a57-a84c-86f0a5143cb5")
|
||||
(uuid "2cecac83-165e-5e49-a695-c0b09f3b631c")
|
||||
)
|
||||
(gr_line
|
||||
(start 129.675 121.635)
|
||||
(end 134.725 121.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "bfbd0b33-54e8-590f-87da-4935db08a121")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 121.635)
|
||||
(end 134.725 130.382575)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "1576658b-4eb9-5105-9817-e374c6ef271e")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 131.617424)
|
||||
(end 134.725 140.532575)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "fef8b02b-8a5c-5e27-8e75-fc116ddbd526")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 141.767424)
|
||||
(end 134.725 160.924667)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "0445e79c-43e4-5609-90ad-67bffdf64341")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 162.175332)
|
||||
(end 134.725 169.460337)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "6405d812-d377-52d9-8013-8c19bd4cdc2b")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 170.539662)
|
||||
(end 134.725 177.705)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "f938fe4f-5ff2-577d-9bff-2827fb32ce22")
|
||||
)
|
||||
(gr_line
|
||||
(start 134.725 177.705)
|
||||
(end 131.065 177.705)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type default)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "d472d222-21c6-5a78-ab7f-18799bceab8a")
|
||||
)
|
||||
(gr_line
|
||||
(start 141.095 100.28)
|
||||
@@ -4050,17 +4124,6 @@
|
||||
(layer "Edge.Cuts")
|
||||
(uuid "17ad1955-d7db-47eb-a736-baff736aac39")
|
||||
)
|
||||
(gr_text "HEADER GUIDES"
|
||||
(at 120.7 151 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "2b9b8e99-a473-51bc-8241-448388f63fcb")
|
||||
(effects
|
||||
(font
|
||||
(size 0.85 0.85)
|
||||
(thickness 0.12)
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "BACK"
|
||||
(at 146.25 175 0)
|
||||
(layer "F.SilkS")
|
||||
@@ -4072,17 +4135,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "DRAFT - NOT FOR FAB"
|
||||
(at 156 178 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "875ba70f-7139-4924-a69c-d2e171f3f526")
|
||||
(effects
|
||||
(font
|
||||
(size 0.9 0.9)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "SELECT"
|
||||
(at 158.25 175 0)
|
||||
(layer "F.SilkS")
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Synchronize measured U1 F.Fab graphics only; never replace the footprint."""
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
NS = uuid.UUID("36693251-e1f8-44e9-a8a9-e201bd658ceb")
|
||||
|
||||
|
||||
def library_graphics():
|
||||
fp = p.FootprintLoad(str(ROOT / "Carrier.pretty"), "HW678_2x22_Provisional")
|
||||
if fp is None:
|
||||
raise RuntimeError("Cannot load measured HW678 footprint")
|
||||
return fp
|
||||
|
||||
|
||||
def signature(g, origin):
|
||||
def point(v):
|
||||
return v.x-origin.x, v.y-origin.y
|
||||
if isinstance(g, p.PCB_SHAPE):
|
||||
return ("shape", g.GetShape(), point(g.GetStart()), point(g.GetEnd()), g.GetWidth())
|
||||
return ("text", g.GetText(), point(g.GetPosition()),
|
||||
(g.GetTextSize().x, g.GetTextSize().y), g.GetTextThickness())
|
||||
|
||||
|
||||
def measured_items(fp):
|
||||
# Generic library captions would overlap existing board labels.
|
||||
return sorted((g for g in fp.GraphicalItems() if g.GetLayer() == p.F_Fab
|
||||
and not (isinstance(g, p.PCB_TEXT) and g.GetText() in (
|
||||
"PROVISIONAL CONNECTOR FIT", "ANTENNA UP / USB DOWN"))),
|
||||
key=lambda g: signature(g, fp.GetPosition()))
|
||||
|
||||
|
||||
def verify(board):
|
||||
u1 = next(f for f in board.GetFootprints() if f.GetReference() == "U1")
|
||||
assert u1.GetOrientationDegrees() == 0 and u1.GetLayer() == p.F_Cu
|
||||
assert len([g for g in u1.GraphicalItems() if g.GetLayer() == p.F_Fab]) == 6
|
||||
library = library_graphics()
|
||||
expected = [signature(g, library.GetPosition()) for g in measured_items(library)]
|
||||
actual = [signature(g, u1.GetPosition()) for g in measured_items(u1)]
|
||||
assert actual == expected, "Embedded U1 F.Fab differs from measured library; run update_u1_graphics.py"
|
||||
|
||||
|
||||
def main():
|
||||
lock = ROOT / "~serial-carrier.kicad_pcb.lck"
|
||||
if lock.exists():
|
||||
raise SystemExit("Close PCB Editor before updating U1 graphics")
|
||||
before = PCB.read_bytes()
|
||||
project = ROOT / "serial-carrier.kicad_pro"
|
||||
project_before = project.read_bytes()
|
||||
board = p.LoadBoard(str(PCB))
|
||||
u1 = next(f for f in board.GetFootprints() if f.GetReference() == "U1")
|
||||
assert u1.GetOrientationDegrees() == 0 and u1.GetLayer() == p.F_Cu
|
||||
library = library_graphics()
|
||||
sources = measured_items(library)
|
||||
assert len(sources) == 6, "Review changed measured library before integration"
|
||||
owned = {str(uuid.uuid5(NS, str(i))) for i in range(len(sources))}
|
||||
existing = list(u1.GraphicalItems())
|
||||
assert all(g.m_Uuid.AsString() in owned for g in existing if g.GetLayer() == p.F_Fab), "Unowned U1 fabrication graphics; review manually"
|
||||
if [signature(g, u1.GetPosition()) for g in measured_items(u1)] == [
|
||||
signature(g, library.GetPosition()) for g in sources]:
|
||||
verify(board)
|
||||
print("Embedded measured U1 graphics already current; no board write")
|
||||
return
|
||||
for g in existing:
|
||||
if g.m_Uuid.AsString() in owned:
|
||||
u1.Remove(g)
|
||||
for i, source in enumerate(sources):
|
||||
graphic = source.Duplicate()
|
||||
graphic.SetParent(u1)
|
||||
graphic.SetUuid(p.KIID(str(uuid.uuid5(NS, str(i)))))
|
||||
graphic.Move(u1.GetPosition() - library.GetPosition())
|
||||
u1.Add(graphic)
|
||||
verify(board)
|
||||
temp = ROOT / "validation/pcb-u1-tmp.kicad_pcb"
|
||||
try:
|
||||
assert p.SaveBoard(str(temp), board, True)
|
||||
verify(p.LoadBoard(str(temp)))
|
||||
if lock.exists() or PCB.read_bytes() != before:
|
||||
raise RuntimeError("PCB changed or reopened; refusing to overwrite")
|
||||
assert project.read_bytes() == project_before, "Project settings changed"
|
||||
temp.replace(PCB)
|
||||
finally:
|
||||
temp.unlink(missing_ok=True)
|
||||
print("Embedded measured U1 graphics; existing footprint fields, pads and routing retained")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -117,9 +117,23 @@ class FootprintRegression(unittest.TestCase):
|
||||
for row, x in enumerate((0, 25.4)) for index in range(22)
|
||||
}
|
||||
self.check_header(footprint, positions)
|
||||
self.assertFalse(any(isinstance(g, k.PCB_SHAPE)
|
||||
for g in footprint.GraphicalItems()),
|
||||
"HW678 must not acquire guessed outlines or envelopes")
|
||||
shapes = [g for g in footprint.GraphicalItems() if isinstance(g, k.PCB_SHAPE)]
|
||||
expected = [
|
||||
(k.SHAPE_T_RECT, mm(-1.365, -1.365), mm(26.725, 54.705), k.FromMM(0.1)),
|
||||
(k.SHAPE_T_RECT, mm(4.045, -8.255), mm(21.675, -1.365), k.FromMM(0.1)),
|
||||
(k.SHAPE_T_SEGMENT, mm(2.795, 54.705), mm(11.665, 54.705), k.FromMM(0.3)),
|
||||
(k.SHAPE_T_SEGMENT, mm(14.195, 54.705), mm(23.065, 54.705), k.FromMM(0.3)),
|
||||
]
|
||||
self.assertEqual(Counter((g.GetShape(), xy(g.GetStart()), xy(g.GetEnd()),
|
||||
g.GetWidth()) for g in shapes), Counter(expected))
|
||||
for shape in shapes:
|
||||
self.assertEqual(shape.GetLayer(), k.F_Fab)
|
||||
self.assertFalse(shape.IsAnyFill())
|
||||
# Only measured planar bounds/mouth spans: no invented shell depth or RF area.
|
||||
self.assertAlmostEqual(26.725 - (-1.365), 28.09)
|
||||
self.assertAlmostEqual(54.705 - (-1.365), 56.07)
|
||||
self.assertAlmostEqual(54.705 - (-8.255), 62.96)
|
||||
self.assertAlmostEqual(26.725 - 25.4, 1.325)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,6 +10,7 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
from update_u1_graphics import verify
|
||||
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
PCB=ROOT/"serial-carrier.kicad_pcb"
|
||||
@@ -21,6 +22,7 @@ def run(*args):
|
||||
run("kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(OUT/"pcb-source-netlist.xml"),str(ROOT/"serial-carrier.kicad_sch"))
|
||||
run("kicad-cli","pcb","drc","--format","json","--all-track-errors","--severity-all","--schematic-parity","-o",str(OUT/"pcb-parity-drc.json"),str(PCB))
|
||||
board=p.LoadBoard(str(PCB))
|
||||
verify(board)
|
||||
source=ET.parse(OUT/"pcb-source-netlist.xml").getroot()
|
||||
comps={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
footprints={f.GetReference():f for f in board.GetFootprints()}
|
||||
@@ -121,7 +123,7 @@ summary={
|
||||
"known_parity_warning":"U2 reference footprint assigned on PCB only; schematic footprint intentionally blank; nominal male external metal dimensions user-confirmed",
|
||||
"project_settings_sha256":hashlib.sha256((ROOT/'serial-carrier.kicad_pro').read_bytes()).hexdigest(),
|
||||
"ignored_DRC_checks":report["ignored_checks"],
|
||||
"not_validated":["male module pin map/header fit", "ESP32 geometry and complete assembly envelope/heights", "header/socket fit", "RF performance and antenna clearance", "power/return-path integrity", "USB power path", "OLED pull-ups", "fabrication process", "hardware operation"],
|
||||
"not_validated":["male module pin map/header fit", "ESP32 measured-planar 1:1 fit and complete assembly envelope/heights", "header/socket fit", "RF performance and antenna clearance", "power/return-path integrity", "USB power path", "OLED pull-ups", "fabrication process", "hardware operation"],
|
||||
}
|
||||
(OUT/"pcb-validation-summary.json").write_text(json.dumps(summary,indent=2)+"\n")
|
||||
print("PCB draft verified: 82 x 80 mm; 2 copper layers; 0 physical DRC violations; 0 unrouted.")
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Measured U1 integration regression and exact pre-update preservation audit."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
import uuid
|
||||
import pcbnew as p
|
||||
from shapely.geometry import Polygon
|
||||
from add_device_silkscreen import add_outlines, NS as SILK_NS, xy, outline_lines
|
||||
from update_u1_graphics import verify, NS as U1_NS
|
||||
from remove_draft_markers import expected_tree
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
BASE = ROOT / "validation/pcb-before-u1-measured.kicad_pcb"
|
||||
|
||||
|
||||
def parse(path):
|
||||
tokens = iter(re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+', path.read_text()))
|
||||
def group():
|
||||
result = []
|
||||
for token in tokens:
|
||||
if token == ')':
|
||||
return result
|
||||
result.append(group() if token == '(' else token)
|
||||
raise ValueError("Unterminated S-expression")
|
||||
assert next(tokens) == '('
|
||||
return group()
|
||||
|
||||
|
||||
def ident(item):
|
||||
return next((v[1].strip('"') for v in item if isinstance(v,list) and v[0]=='uuid'), None)
|
||||
|
||||
|
||||
def canonical(item, excluded=frozenset()):
|
||||
# KiCad can reorder independent board items when saving; compare their full
|
||||
# parsed contents, not byte order. Pad coordinates/netnames/UUIDs remain exact.
|
||||
if not isinstance(item, list):
|
||||
return item
|
||||
values = [canonical(v, excluded) for v in item
|
||||
if not isinstance(v,list) or ident(v) not in excluded]
|
||||
if item and item[0] in ("kicad_pcb", "footprint"):
|
||||
return ([v for v in values if not isinstance(v,list)] +
|
||||
sorted((v for v in values if isinstance(v,list)), key=repr))
|
||||
# Coordinates, polygon vertex order, text and other ordered values must
|
||||
# remain ordered: sorting these could conceal an X/Y swap or zone edit.
|
||||
return values
|
||||
|
||||
|
||||
def main():
|
||||
assert hashlib.sha256(BASE.read_bytes()).hexdigest() == "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045", "Pre-update snapshot changed"
|
||||
assert hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest() == "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516", "Project settings changed"
|
||||
before = PCB.read_bytes()
|
||||
board = p.LoadBoard(str(PCB))
|
||||
verify(board)
|
||||
silk_ids = {str(uuid.uuid5(SILK_NS,str(i))) for i in range(4096)}
|
||||
u1_ids = {str(uuid.uuid5(U1_NS,str(i))) for i in range(6)}
|
||||
# The original baseline retains historical draft labels. Permit only the
|
||||
# exact requested title/revision/comment edits and removal of the silk label.
|
||||
assert canonical(expected_tree(parse(BASE)), silk_ids) == canonical(parse(PCB), silk_ids | u1_ids), "Change outside owned U1 additions / device silk / exact presentation labels"
|
||||
generated = [g for g in board.GetDrawings() if g.m_Uuid.AsString() in silk_ids]
|
||||
assert not any(isinstance(g,p.PCB_TEXT) and g.GetText()=="HEADER GUIDES" for g in board.GetDrawings())
|
||||
carrier = Polygon([(136,100),(182,100),(182,180),(100,180),(100,119),(136,119)])
|
||||
mouths = []
|
||||
for g in generated:
|
||||
assert g.GetLayer() == p.F_SilkS
|
||||
for edge in outline_lines(g):
|
||||
assert carrier.covers(edge.buffer(p.ToMM(g.GetWidth())/2)), "Silk outside carrier/notch"
|
||||
if g.GetWidth() == p.FromMM(0.3):
|
||||
mouths.append(g)
|
||||
assert len(mouths) == 2
|
||||
assert sorted((xy(g.GetStart()),xy(g.GetEnd())) for g in mouths) == [
|
||||
((110.795,177.705),(119.665,177.705)),
|
||||
((122.195,177.705),(131.065,177.705))]
|
||||
# A second refresh must reproduce all drawing geometry and UUIDs.
|
||||
add_outlines(board)
|
||||
with tempfile.TemporaryDirectory(prefix="u1-regression-") as temporary:
|
||||
result = Path(temporary)/PCB.name
|
||||
assert p.SaveBoard(str(result),board,True)
|
||||
assert canonical(parse(result)) == canonical(parse(PCB)), "Silkscreen refresh not idempotent"
|
||||
u1 = next(f for f in board.GetFootprints() if f.GetReference()=="U1")
|
||||
shape = next(g for g in u1.GraphicalItems() if isinstance(g,p.PCB_SHAPE))
|
||||
shape.SetWidth(shape.GetWidth()+1)
|
||||
try:
|
||||
verify(board)
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("Stale embedded geometry was not rejected")
|
||||
assert PCB.read_bytes() == before
|
||||
assert not (ROOT/"~serial-carrier.kicad_pcb.lck").exists()
|
||||
summary = dict(measured_library_matches_embedded_u1=True,
|
||||
existing_uuids_pads_nets_placement_routing_settings_and_unrelated_graphics_preserved=True,
|
||||
presentation_label_cleanup=True,
|
||||
silk_refresh_idempotent=True, stale_geometry_negative_control=True,
|
||||
silk_clipped_to_carrier=True, usb_mouth_markers=2,
|
||||
generated_silk_items=len(generated),
|
||||
baseline_sha256=hashlib.sha256(BASE.read_bytes()).hexdigest(),
|
||||
board_sha256=hashlib.sha256(before).hexdigest(),
|
||||
project_sha256=hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest())
|
||||
(ROOT/"validation/pcb-u1-update.json").write_text(json.dumps(summary,indent=2)+"\n")
|
||||
print(json.dumps(summary,indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 231 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 345 KiB |
+10804
-670
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 368 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-21T11:39:10",
|
||||
"date": "2026-09-21T22:52:26",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"immutable_geometry_nets_pads_linkage_preserved": true,
|
||||
"board_sha256": "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045",
|
||||
"board_sha256": "94e941625b3b9fc9a08b9f2d532eb35003ea8da41d456a1c67269b9651200e05",
|
||||
"independent_min_different_net_or_npth_clearance_mm": 0.2705441169852849,
|
||||
"closest_items": [
|
||||
"U1.38:4b8eabef-8493-437d-bd02-cb9ff4afd7cd",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-21T11:39:13",
|
||||
"date": "2026-09-21T22:33:53",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<export version="E">
|
||||
<design>
|
||||
<source>/home/mscholz/Repos/ESP32_serial_swiss_army_knife/hardware/PCB/serial-carrier.kicad_sch</source>
|
||||
<date>2026-09-21T11:39:08</date>
|
||||
<date>2026-09-21T22:52:19</date>
|
||||
<tool>Eeschema 10.0.6-1.fc44</tool>
|
||||
<sheet number="1" name="/" tstamps="/">
|
||||
<title_block>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"measured_library_matches_embedded_u1": true,
|
||||
"existing_uuids_pads_nets_placement_routing_settings_and_unrelated_graphics_preserved": true,
|
||||
"presentation_label_cleanup": true,
|
||||
"silk_refresh_idempotent": true,
|
||||
"stale_geometry_negative_control": true,
|
||||
"silk_clipped_to_carrier": true,
|
||||
"usb_mouth_markers": 2,
|
||||
"generated_silk_items": 91,
|
||||
"baseline_sha256": "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045",
|
||||
"board_sha256": "b9e020939fff42b5c7a3161ed89af9dc3c1b9c016268311b3db7ab407ed67192",
|
||||
"project_sha256": "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516"
|
||||
}
|
||||
@@ -130,7 +130,7 @@
|
||||
],
|
||||
"not_validated": [
|
||||
"male module pin map/header fit",
|
||||
"ESP32 geometry and complete assembly envelope/heights",
|
||||
"ESP32 measured-planar 1:1 fit and complete assembly envelope/heights",
|
||||
"header/socket fit",
|
||||
"RF performance and antenna clearance",
|
||||
"power/return-path integrity",
|
||||
|
||||
Reference in New Issue
Block a user