166 خطوط
15 KiB
Markdown
166 خطوط
15 KiB
Markdown
# Native PCB preview and structural comparison
|
|
|
|
Two new creation endpoints extend the existing FastAPI application. Existing schematic API paths, private capabilities and SVG output remain supported. No database migration, Redis, frontend framework or new runtime dependency is introduced. The original `schematics` table stores all four job kinds.
|
|
|
|
## API
|
|
|
|
| Method | Path | Purpose |
|
|
| --- | --- | --- |
|
|
| POST | `/api/v1/pcbs/render` | Upload `file` as `.PcbDoc` |
|
|
| POST | `/api/v1/pcbs/comparisons` | Upload `old_file` and `new_file` as `.PcbDoc` |
|
|
| GET | `/api/v1/pcbs/{id}/status` | Render status and completed layer inventory |
|
|
| GET | `/api/v1/pcbs/{id}/data` | Completed PCB document |
|
|
| GET | `/api/v1/pcbs/{id}/svg` | Completed layer-aware SVG |
|
|
| GET | `/api/v1/pcbs/comparisons/{id}` | Status and completed structural report |
|
|
| GET | `/api/v1/pcbs/comparisons/{id}/svg/{side}` | Annotated SVG; side is `old` or `new` |
|
|
| GET | `/view/pcb/{id}` | Fullscreen single PCB shell |
|
|
| GET | `/compare/pcb/{id}` | Fullscreen side-by-side PCB shell |
|
|
|
|
```bash
|
|
curl -X POST https://render.sadentel.net/api/v1/pcbs/render \
|
|
-H "X-API-Key: $API_KEY" \
|
|
-F 'file=@board.PcbDoc' -F 'access=private' -F 'expires_in=3600'
|
|
|
|
curl -X POST https://render.sadentel.net/api/v1/pcbs/comparisons \
|
|
-H "X-API-Key: $API_KEY" \
|
|
-F 'old_file=@original.PcbDoc' -F 'new_file=@modified.PcbDoc' \
|
|
-F 'access=private' -F 'expires_in=3600'
|
|
```
|
|
|
|
Both creation requests return HTTP **202**, `Location: status_url` and `Retry-After: 1`. `access` defaults to `private`; `unlisted` is supported. For finite links, expiry is measured from admission, including queue time, and cannot exceed `LINK_TTL_SECONDS`. Set `expires_in=-1` for unlimited retention (`expires_at: -1`); `0` and lower negative values are rejected.
|
|
|
|
Example render response (identifiers, timestamps and capabilities are illustrative):
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"id": "0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
"status": "queued",
|
|
"document_type": "pcb",
|
|
"viewer_url": "https://render.sadentel.net/view/pcb/0123456789abcdef0123456789abcdef0123456789abcdef#token=READ_TOKEN",
|
|
"status_url": "https://render.sadentel.net/api/v1/pcbs/0123456789abcdef0123456789abcdef0123456789abcdef/status",
|
|
"viewer_token": "READ_TOKEN",
|
|
"access": "private",
|
|
"expires_at": 1794818000
|
|
}
|
|
```
|
|
|
|
Comparison responses use `document_type: pcb_comparison` and add `comparison_id` and `url`, equal to the initial `viewer_url`. The viewer path is `/compare/pcb/{id}` and status path is `/api/v1/pcbs/comparisons/{id}`. Queued responses do not invent a summary. Completed reports contain `summary` (added/removed/modified/moved), `total_changed`, `changes`, `old`, `new`, `nets`, `matching` and `limitations`.
|
|
|
|
Retain the initial private URL and token. Status responses never return the capability. Reads accept `X-Viewer-Token` or the service `X-API-Key`. Unauthorized private reads return 404; authorized expired reads return 410 until cleanup. Pending artifacts return 409; failed artifacts return 422. Failed jobs retain their error in an HTTP 200 status response. PCB validation/decoding errors use `invalid_pcb`; resource-limit and infrastructure errors use the existing queue conventions. Filenames and geometry are not disclosed by unauthenticated status requests.
|
|
|
|
Viewer shells themselves are public static shells, like the schematic viewer; drawing data is fetched with the capability header. Token fragments are removed from browser history and stored in a resource-specific sessionStorage key. Browser storage restrictions in embedded contexts still require browser acceptance testing. No API key is shipped to the browser.
|
|
|
|
## Queue and storage compatibility
|
|
|
|
Immutable application-owned input names classify jobs:
|
|
|
|
| Input name | Job kind |
|
|
| --- | --- |
|
|
| `input.SchDoc` | schematic_render (legacy) |
|
|
| `comparison.json` | schematic_comparison (legacy) |
|
|
| `input.PcbDoc` | pcb_render |
|
|
| `pcb-comparison.json` | pcb_comparison |
|
|
|
|
A PCB comparison stores two sources `old.PcbDoc` and `new.PcbDoc`, plus a versioned manifest. The worker dispatches PCB work to `app.pcb_task`, inheriting the existing subprocess CPU, address-space, output, FD and wall-clock limits. Attempts write the same `attempt-{1|2}/data.json`, `drawing.svg` or `old.svg`/`new.svg` artifact names. Completion uses the existing attempt/status fence. API routes require the exact resource kind even for unlisted jobs and service-key reads. The storage regex permits only these named additions. Cleanup removes both source files and all attempts.
|
|
|
|
Each input remains bounded by `MAX_UPLOAD_BYTES`. Only the new comparison POST joins the existing two-file multipart allowance (`2 * MAX_UPLOAD_BYTES + 1 MiB`). The Caddy comparison matcher includes that exact new path with 65 MiB; other request limits remain unchanged. Adjust external CDN caps if required by your deployment.
|
|
|
|
PCB filenames are sanitized, MIME is not trusted, and the API checks the actual OLE header/Board6 identification with bounded stream reads. Full binary decoding remains asynchronous. Renaming PCB bytes to `.SchDoc` does not admit them to the schematic API; SchDoc bytes are rejected by PCB validation.
|
|
|
|
## Native format and geometry
|
|
|
|
`pcb_binary.py`, `pcb_geometry.py` and `pcb_parser.py` adapt an independently written reader from the user's local Altium viewer checkout; no other application/frontend/service is imported. The binary layouts and units were cross-checked with the [KiCad developer format reference](https://dev-docs.kicad.org/en/import-formats/altium/index.html). No GPL importer implementation is copied, and no Altium SDK is required.
|
|
|
|
Supported envelope: `PCB 6.0 Binary File` in `FileHeaderSix`, with `Board6/Data` identifying `Protel_Advanced_PCB`. Board format revisions 5.0, 5.01 and 6.0 are recognized; **only 5.01 is verified on the local native files**. These are format revisions, not supported Altium application release promises. Other envelopes/revisions fail explicitly.
|
|
|
|
Readers validate framing, block sizes, field boundaries, finite numbers, references, stream counts and cumulative stream sizes. They never resynchronize after a bad tag, execute embedded resources or fetch external URLs. Limits include 1024 streams, 64 MiB cumulative stream bytes, 16 MiB subrecords and 250,000 primitives, in addition to subprocess limits. Very large boards may exceed these limits.
|
|
|
|
Binary coordinates are integer 1/10000 mil. The shared world unit is 10 mil:
|
|
|
|
```text
|
|
world_x = raw_x / 100000
|
|
world_y = -raw_y / 100000
|
|
```
|
|
|
|
Placed child geometry is already global and is not rotated twice. PCB-specific models retain footprint metadata, object provenance, optional native UID, layer memberships and drill spans, without turning PCB pads into schematic pins. Source physical indexes remain provenance only.
|
|
|
|
| Native streams | Current treatment |
|
|
| --- | --- |
|
|
| Board6 | Real boundary vertices, arcs, dimensions and stack-link metadata |
|
|
| Components6 / Nets6 | Footprint placement, parameters and actual net references |
|
|
| Tracks6 / Arcs6 | Centerline geometry, widths and layers |
|
|
| Pads6 | Top/mid/bottom and supported full-stack shapes, rotation, plated state, round/square/slot drill and supported offsets |
|
|
| Vias6 | Recorded span, primary and supported per-layer diameters, drill |
|
|
| Fills6 | Rotated filled rectangles |
|
|
| Regions6 | Saved polygon copper with interior holes |
|
|
| ShapeBasedRegions6 | Standalone solid/curved regions and supported board cutouts; duplicate polygon definitions are not painted twice |
|
|
| Polygons6 | Retained definition boundaries; never treated as freshly poured copper |
|
|
| Texts6 / WideStrings6 | Stored strings and placement; font/style limitations are diagnosed |
|
|
| UniqueIDPrimitiveInformation | Native primitive identity where present |
|
|
|
|
Copper membership follows actual Board6 links, including plane-layer identities. Pads and vias keep multiple layer memberships; per-layer geometry is drawn separately. Drill visibility follows its recorded span. The safe SVG serializer emits layer identifiers and inert geometry, with top-side visibility initially selected. Layer controls allow Top/Bottom/All and individual selection. Outline is a separate layer. Changing the preset does not geometrically mirror or rearrange the board.
|
|
|
|
## Fidelity boundaries
|
|
|
|
Compatibility is explicitly `partial`:
|
|
|
|
- Layer colors are a viewer palette; Altium application display preferences are not claimed to be embedded or reproduced.
|
|
- Browser fonts replace native stroke/TrueType rendering; metrics, barcodes, inverted text and special styles may differ. Font licensing/availability still applies.
|
|
- Saved pours are rendered as stored, possibly stale. There is no repour engine, rule execution, negative-plane reconstruction or solder/paste-mask rule expansion.
|
|
- Supported board cutouts are rendered as visual cutouts; this is not a fabrication geometry boolean engine.
|
|
- Advanced/unknown pad shapes are omitted with diagnostics. Undecoded full-stack pad or nonuniform via dimensions on internal plane layers are reported rather than assigned an invented shape.
|
|
- Embedded STEP models, 3D bodies, rules and unsupported streams are not rendered. Diagnostics identify omitted Data streams and declared counts when present.
|
|
- Footprints without supported native geometry remain in metadata with diagnostics; substitute symbols are never generated.
|
|
- Geometry outside the board bounds remains pannable but may start outside the initial viewBox.
|
|
|
|
No proprietary sample, Altium software, SDK, font or compiled library is included in the repository or Docker image. No pixel-perfect claim is made.
|
|
|
|
## Diff interpretation
|
|
|
|
The PCB diff reuses the format-independent entity matcher, not schematic record-number semantics or schematic coordinate-key normalization. It matches unique native IDs, unique footprint designators, exact geometry/position, unique local structure, then unique same-kind position. Ambiguous objects remain additions/removals; identical duplicates are treated as multisets. Each match records its method.
|
|
|
|
Footprint-owned geometry is aggregated and compared in local coordinates, while independent tracks/vias remain independent. Layer, net name, width, rotation, drill, rendered geometry and properties participate. Net indexes and record/component/polygon indexes are excluded as cross-file identities. Reversed straight tracks and net renumbering do not create changes by themselves. The stack is a separate semantic entity. Unsupported stream bytes are not semantically compared, and this limitation is part of every report.
|
|
|
|
Primary added/removed/modified/moved counts are disjoint. A modified-and-moved object has primary `modified` plus `moved: true` and a movement delta. Rotation changes are reported in fields and `rotation_change`. An anchor delta is not proof of rigid translation when geometry also changed.
|
|
|
|
Net results report observed names added/removed; object fields reveal changed stored assignments. The result is not DRC, ERC, an electrical-equivalence proof, a manufacturing comparison or a project-wide compiled netlist.
|
|
|
|
## Viewer and framing
|
|
|
|
The dependency-free PCB shell uses the existing camera/interactions module and spinner. The default is a fullscreen board or two boards, plus a menu button. Layers, diagnostics and comparison controls appear only when the menu opens; there is no new dashboard or upload page. Comparison supports synchronized pan/zoom/pinch, fit, change filters, changes-only, highlights and previous/next focus.
|
|
|
|
Existing security middleware covers `/view/*` and `/compare/*`, including the new nested PCB pages. Those responses have one CSP with `frame-ancestors 'self' https://git.sadentel.net`; conflicting X-Frame-Options is removed. Other directives and API policies stay intact. Parent-site CSP and any deployed CDN overrides still need environment-specific checking.
|
|
|
|
## Validation and deployment
|
|
|
|
```bash
|
|
PCBDOC_SAMPLE='/path/to/private-board.PcbDoc' ./scripts/check.sh
|
|
API_KEY="$API_KEY" .venv/bin/python scripts/pcb-smoke.py original.PcbDoc modified.PcbDoc \
|
|
--url http://127.0.0.1:8000
|
|
```
|
|
|
|
Without a configured native file the real-PCB test is explicitly skipped. Synthetic native unit fixtures are test-only, never fallback viewer content. The HTTP smoke creates private resources, polls the real worker, checks SVGs, denies unauthenticated reads, and checks preview security headers without printing capabilities. Omit `modified.PcbDoc` to verify a same-file comparison has zero changes.
|
|
|
|
Rebuild/restart both API and worker:
|
|
|
|
```bash
|
|
docker compose up -d --build api worker
|
|
```
|
|
|
|
Reload/recreate Caddy if using the production overlay so its two-file upload matcher is updated. No schema migration is required. No automatic production deployment or unrelated service restart is performed by this feature.
|
|
|
|
### Local verification evidence (2026-11-09)
|
|
|
|
The current `/home/nearology/LTE 6/LTE 6.PcbDoc` (SHA-256 `2afb5b464cff6c4b780386bf14f39c810230d5625d7277ff01fe4823999ea561`) decoded 694 footprints, 479 nets, 12,916 tracks, 311 arcs, 2,329 pads, 1,059 vias, 1,822 texts, 13 fills, 45 polygon definitions and 48 records in each region stream. It produced 26,471 layer-aware primitives. A self-comparison reported zero changes. Its native SVG was rasterized locally and the top-side image inspected; this is not an Altium-reference visual regression.
|
|
|
|
The other local PCB file (SHA-256 `00b812958357b12f62f8d3cca12486f716925b2655d35258115b01c44eaeb314`) decoded 729 footprints and produced 41,285 primitives. Comparing those two real local documents completed with 8,073 added, 12,248 removed, 1,300 modified and 469 moved primary entities; 102 ambiguous signature groups remained. These counts demonstrate a completed structural comparison, not human verification of every pair or design equivalence. No claim is made about the chronological relationship of these files.
|
|
|
|
Real HTTP smoke checks passed against an isolated loopback Uvicorn API with SQLite and a separate real worker: private single rendering, self-comparison, two-file comparison, both SVG reads, unauthorized reads and preview headers. The test processes and their temporary database/storage were stopped/removed afterwards. This run is not evidence of live PostgreSQL/S3 integration or production deployment. S3 coverage uses botocore protocol stubs. Caddy configuration adaptation passed with 65 MiB on the three comparison upload paths and the original 33 MB elsewhere.
|
|
|
|
No connected browser was available, so actual pan/pinch/menu/iframe appearance remains unverified in a browser. No trusted Altium export was supplied, so geometric and font fidelity has not been certified against Altium output.
|
|
|
|
The final automated run includes native SchDoc and PCB fixtures, 154 passing Python tests and three passing frontend test files (camera, pointer interaction adapter and PCB layer policies). The standard Docker build was attempted with both normal and host networking, but dependency download stalled on `Temporary failure in name resolution` for PyPI's package host; those build attempts were stopped. A new Docker image was therefore not validated or deployed in this environment. The existing Dockerfile and dependency lock remain unchanged.
|
|
|
|
Both PCB viewers and upload results include Share → Copy iframe, preserving any private read capability in the URL fragment. Unlimited jobs are excluded from automatic cleanup; external S3 lifecycle policies must also permit retention. See [expiry and embedding](API.md#unlimited-links-and-embedding).
|