Specctra round trip: N copper layers, copper zones as planes, back-side components, custom rules handed to Freerouting
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 12/100
Research direction
This is a design proposal, not a task to start: the author asks for agreement on the split and order before any code. Read the four places named in the body: crates/copperpour-core/src/specctra.rs (copper_layers, SupportedProfile), crates/copperpour-core/src/specctra_ses.rs (validate_manifest), and crates/copperpour-core/src/tools/pcb_export.rs. Done for a newcomer means the open questions in step 3a are answered by a maintainer and the scope is agreed, not a patch.
Written by the indexing model from the issue text.
Description
Problem
The Specctra round trip (export_specctra_dsn → route_specctra_dsn → apply_specctra_ses) only accepts a two-layer board with all components on the front, no copper zones and no custom rules. A dense 4-layer board, with ground and power planes on the inner layers, components on both sides and a project .kicad_dru, is refused at the first step:
unsupported first routing profile: expected exactly F.Cu and B.Cu, got [F.Cu, In1.Cu, In2.Cu, B.Cu]
Freerouting is the only autorouting path, and Copperpour reaches it only through Specctra. On such a board, nothing can be autorouted.
The limit is not a single check. On main (1f2f0eb0) it sits in four places that have to move together:
- export,
crates/copperpour-core/src/specctra.rs:copper_layers()requires exactly[F.Cu, B.Cu]; any zone or rule area is refused; locked vias must span[F.Cu, B.Cu]; padstacks are written forF.Cu/B.Cu; componentsideis hard-coded to"front"; - manifest, same file:
SupportedProfilestorescopper_layers: 2,component_side: "front",copper_zones: falseandcustom_rules: falseas constants, not as values read from the board (MANIFEST_SCHEMA_VERSION = 2); - import,
crates/copperpour-core/src/specctra_ses.rs:validate_manifest()re-checks the same four constants,"manifest must contain exactly two copper layer mappings", andcomponent.side != "front"; - tool,
crates/copperpour-core/src/tools/pcb_export.rs: refusal on the mere presence of<board>.kicad_dru, andcapabilities.supported_profileis the fixed stringtwo_layer_front_side_…_no_existing_routing_or_zones.
Existing issues
- #37 is this limit, imported from Konnect, with its acceptance list (ordered copper stack in DSN and manifest, SES validated on inner layers, atomic apply, real multilayer end-to-end evidence, fail closed on anything not representable). This plan follows that list. If you agree, the series closes #37.
- #75 holds the
.kicad_druhalf. Step 4 below proposes a different way to lift that refusal; if you agree, it closes #75 too. Its increment 1 (a truthful refusal message) stays independent and is not touched here. - #79 is the multilayer tracker. This series does not take it over: it only covers its point 5 (multilayer autorouting) and would be linked there.
- #72 (export check order, native bridge), assigned to mn89h: not touched. The series widens only the checks that the decision on #72 calls import-owned (layers, side, zones, rules), which run in every mode whatever order #72 settles on.
- #81 (validated two-layer rule areas) and #71 (stale zone fills after copper edits) are close to step 3a; see the question there.
- #243 (rounded outline) is out of scope.
On the export path
The Konnect review on #37 asked not to make a Rust reimplementation of KiCad's multilayer exporter the primary solution. Two facts from the code shape this plan:
- the reverse manifest that
apply_specctra_sesvalidates against is built by the Rust export in every mode;adopt_native_dsnonly correlates the native DSN against it, andcorrelate_layersrefuses a layer-count mismatch. A multilayer manifest therefore has to come from the Rust side whichever writer produces the DSN; - the native bridge is the KiCad 10 ActionPlugin, so KiCad 11 stays on the Rust path (decision on #72).
So step 2 extends the Rust side to N copper layers, bounded to what the native exporter writes for the same board, and each step proves it against KiCad's own DSN for the same board.
Plan: one PR per step
Each PR is one result, built from main, with the PR template, the reliability table, scripts/control.sh runs for every new guard ("Guard broken | Tests failing") and a note in docs/api-migrations/. Fixtures come from KiCad, never hand-written.
-
This issue. No code before the approach is agreed here.
-
N copper layers (N ≥ 2, even, inner layers included) in DSN export, manifest and SES import. One PR, because an export without the matching import is not a usable result.
copper_layers()returns the ordered list of enabled copper layers (F.Cu,In*.Cu,B.Cu) and refuses an empty, gapped or out-of-order list. The DSNstructurelists every layer assignal; through-hole padstacks and locked through vias span every layer.- The manifest stores the layer count read from the board and one mapping per layer.
MANIFEST_SCHEMA_VERSIONgoes to 3 if the shape changes; schema 1 and 2 manifests are still read (test). validate_manifest()and the layer-mapping check accept N, and refuse a SES that names a layer absent from the manifest.- KiCad 10 native bridge: the native DSN of the same 4-layer board passes the existing comparison, with a test that the Rust and native paths agree on it.
capabilities.supported_profileand the tool description state the new profile, derived from the code rather than a fixed string.- Evidence: a KiCad-produced multilayer fixture (for example
crates/copperpour-core/tests/fixtures/six_layer_power_planes_kicad10.kicad_pcb, already in the repository), and a full round trip on a public 4-layer board (KiCad'sdemos/video, unlocked tracks and vias removed): export,route_specctra_dsn,apply_specctra_ses, DRC, with routed nets, vias and tracks per inner layer in the PR. - Still refused: blind, buried and micro vias, arcs, non-rectilinear outlines (unchanged), zones, back-side components and custom rules (steps 3 and 4).
-
Two PRs after step 2, each from
main, independent of each other.- 3a, copper zones. A filled copper zone becomes a
planein the DSN, as KiCad's native exporter writes it. The manifest inventories zones per layer and per net, read from the board.apply_specctra_sesnever creates, modifies or deletes a zone, checks that the inventory has not changed, and says in its response that a zone refill is needed. Evidence: the same public board with its planes kept, DRC after refill.
Questions for you: shouldapply_specctra_sesonly report the needed refill, or runrefill_zonesitself (#71 is about the same staleness)? And do rule areas (keep-outs) belong here, or do they stay refused and remain with #81? - 3b, back-side components.
(place REF x y back rot)and the mirrored image, as KiCad writes them; the realsidein the manifest; the placement invariance check on import also compares the side. Evidence: a KiCad fixture with a component flipped byflip_component, round trip, DRC, comparison with the native DSN.
- 3a, copper zones. A filled copper zone becomes a
-
Custom rules: lift the
.kicad_drurefusal and hand the file to Freerouting. This step waits for an option in Freerouting itself that reads KiCad custom rules; it is being proposed upstream there, and I will link it here once it is public. Copperpour would then not translate any rule: no.kicad_druparser on this side.export_specctra_dsnstops refusing a board with<board>.kicad_dru; the manifest recordscustom_rules: trueand the file's sha256;apply_specctra_sesrefuses if the file changed since export.route_specctra_dsnpasses the file to Freerouting unchanged, and refuses with a clear message (minimum version, test) when the Freerouting it finds does not support the option.- Evidence: a public board with a
.kicad_druthat changes the routing (a wider clearance on one net), before and after. - A possible later direction, not part of this plan: KiCad 11's IPC API exposes custom rules (
GetCustomDesignRules, on KiCad master, empty in 10.0.x), so the file could be written for Freerouting from IPC when the board has none on disk.
Steps 2 and 3 do not depend on step 4, and step 4 does not start before the Freerouting option is released.
Does this split and order work for you, and should anything be scoped differently before I start on step 2?
- Dominant language
- Rust
- Stars
- 34
- Forks
- 9
- Avg merge
- 9h 26m
- Merged PRs (30d)
- 226
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Copperpour/Copperpour
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Copperpour/Copperpour#500 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Copperpour/Copperpour#493 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Copperpour/Copperpour#368 ·
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Copperpour/Copperpour#251 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Copperpour/Copperpour#124 · 1 comment ·
Maintainers usually reply within 1 day
All issues in Copperpour/Copperpour
Similar issues
-
priority: medium ready
Difficulty 1/5 Under an hour Newbie friendliness 88/100
TusanHomichi/PSIV#646 ·
Maintainers usually reply within 1 day
-
bug lab rust-mcp
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
area:cli good first issue help wanted needs translation priority:P2 type:documentation
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
SpecterAoD/pqkeep#226 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
haowjy/mars-agents#381 ·
Maintainers usually reply within 1 day
-
[Bug]: opencode_go chat route drops reasoning_effort (even 'none'), while the same endpoint honors it via custom_llm_provider=openaiPossibly taken @arvindkumar-ship-it claimed this today. Openllm translation
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
BerriAI/litellm#51871 · 1 comment ·
Maintainers usually reply within 1 day