Fusion lane: bottom copper on layer 304 (new Fusion board format) is dropped by the converter, pours lost

Zoltán Abonyi · ·closed by Colby Knox

What happens

On a Fusion board saved in Fusion's newer board format, route (and every step that pulls the live board through fusion_board_source) loses all bottom-layer copper that sits on the new layer number. With a GND pour on the bottom, route reports the plane as missing, and GND is then routed as tracks. On a dense 2-layer board (124 parts, 60 x 56 mm, 86 SMDs on the bottom) that left 55 connections open after route --variants 4 --passes 8.

Why

In the live pull (board-live.brd), the bottom copper is layer 304, named "Bottom". Layer 16 is still declared, but as an empty, invisible "Route16":

<layer number="1"   name="Top"     ... visible="yes" active="yes" />
<layer number="16"  name="Route16" ... visible="no"  active="no"  />
<layer number="304" name="Bottom"  ... visible="yes" active="yes" />

Via extents are extent="1-64", and layerSetup is (1*64). Pours come as <polygonpour layer="304"> with <polygonoutlineobjects>.

crates/aiflow-board/src/eagle.rs fn layer() maps only "1" to F.Cu and "16" to B.Cu. "304" is not in the inner-layer list (2..=15), so it maps to None, and the copper is dropped.

Fix (local, one line)

// Fusion's newer board format numbers the bottom copper 304 (named "Bottom"; layer 16 stays as an
// unused "Route16"), with layerSetup "(1*64)" and via extents "1-64"
Some("16") | Some("304") => Some("B.Cu".into()),

I am building and testing this locally on the board. If it routes cleanly, I can send it as a PR. A more robust fix may be to take B.Cu from the layer named "Bottom", or from the last layer in layerSetup, rather than from a fixed number.

  • fusion_layer_stack and other bridge replies label layer 304 as "In1.Cu". That belongs to the Fusion bridge, but the cause is the same.
  • KiCad's own EAGLE importer (kicad-cli pcb import --format eagle) also drops <polygonpour>, and puts 304 copper on the wrong layer. My workaround was to rewrite the .brd before import: polygonpour to polygon, 304 to 16, 1-64 to 1-16.

Versions

  • adom-aiflow 0.2.76
  • Fusion bridge 1.13.86, Adom Bridge 2.1.194
  • Fusion 360 Electronics, Windows, Hungarian UI
  • KiCad 10.0.8

3 Replies

Zoltán Abonyi ·

Update: the same layer-304 format also breaks land route, and the Fusion bridge too

With the converter fixed locally, route now sees the bottom GND pour. Landing the plan then failed in three places, all from the same new Fusion board format.

1. adom-aiflow sends B.Cu as Fusion layer 16

crates/aiflow-bridge/src/fusion.rs layer_arg() maps "B.Cu" to 16. fusion_route_net refuses that layer:

"error": "Layer 16 is not a copper layer of this board (F.Cu, In1.Cu)", "errorCode": "invalid_layer"

The same file reads the bottom fill as by["16"], and layer_label() names only 16 as B.Cu. On this board both should use 304.

Local fix: a bottom() helper. It reads AIFLOW_FUSION_BOTTOM, takes a number or a layer name, and defaults to 16. It maps "B.Cu" | "16" | "304" to bottom(), layer_label maps 16 | 304 to B.Cu, and the B.Cu fill falls back to by["304"]. This is a stopgap; the real fix is to read the board's own bottom layer.

2. Fusion bridge 1.13.86: bottom pads on 16, tracks on 304

  • fusion_routing_state / fusion_board_source list bottom pads with layerId 16, layer "B.Cu", but report the board's copper layers as F.Cu, In1.Cu. Layer 304 is labelled "In1.Cu".

  • With AIFLOW_FUSION_BOTTOM=304 or In1.Cu, fusion_route_net refuses the first bottom trace:

    "error": "Pad TP8.1 has no copper on F.Cu", "errorCode": "pad_layer_mismatch"
    

    (TP8 is a bottom test point; the trace starts on its pad on the bottom.)

So no layer value lets land route put a trace on a bottom pad on this board.

3. Fusion bridge 1.13.86: any via with extent "1-64" breaks fusion_routing_state

On this board Fusion writes every via as extent="1-64". In rust/bridge/src/board_model.py, span() does:

order = self.order if a in self.order and b in self.order else sorted(COPPER)
i, j = sorted((order.index(a), order.index(b)))

64 is not in either list. So once the board has one via, fusion_routing_state fails:

"error": "list.index(x): x not in list", "errorCode": "routing_failed"

fusion_board_source, fusion_pour_state and fusion_export_eagle_source still work.

Workaround I used

I landed the plan with Fusion's own commands through fusion_electron_run:

LAYER 304; WIRE 'NET' (x y) ...; CHANGE DRILL d; VIA 'NET' size round (x y)

Each net went separately, and I checked the result from the exported .brd. The tracks land on 304 on the right nets. During the batch run, Fusion twice showed an internal error and then closed it again:

Unexpected Condition details: File: R:\Electron\EAGLE\src\pusher.cpp, Line 492. Condition: inLocator

The first time, it came on a batch of 8 nets with 0.8 mm motor-output traces and vias; net by net, it has not stopped the landing so far.

Ask

A board in this format needs one source of truth for the bottom copper number (304 here, 16 on older boards), shared by the converter, layer_arg, the pad model and the via span(). The bridge's layer stack should report 304 as B.Cu, not In1.Cu.

Colby Knox marked this in progress ·
Colby Knox linked pull request #97, which closes this when it merges ·
Colby Knox closed this in pull request #97 ·
Colby Knox ·

Thanks Zoltán, both reports were precise enough to fix straight from. AI Flow 0.2.77 has the AI Flow side:

  • The converter keeps the bottom copper. It now takes the layer the board itself names "Bottom": 304 on your board, 16 on older ones. So tracks and pours on 304 come through as B.Cu, and route sees the bottom GND pour.
  • Landing uses the same number. When AI Flow lands work in Fusion, it sends B.Cu as the board's own bottom number, reads 304 (and 16) back as B.Cu, and reads the bottom fill from it. The number comes from the live board once per command, so your AIFLOW_FUSION_BOTTOM stopgap isn't needed anymore (it still overrides if set).

The other three problems are in the Fusion bridge, and I've filed them there: adom/fusion-bridge#148. They are the layer stack naming 304 "In1.Cu", bottom pads reported on 16 while tracks go on 304, and a via with extent="1-64" breaking fusion_routing_state. Until that ships, land route still can't put a trace on a bottom pad on this board, so your fusion_electron_run workaround is still needed for landing.

Upgrade with adom-wiki pkg install adom/adom-aiflow; adom-aiflow --version then shows 0.2.77.

Zoltán Abonyi ·

Thanks, 0.2.77/0.2.78 works here. The converter reads 304 as B.Cu, and route sees both GND pours.

One more converter gap: package holes are dropped

crates/aiflow-board/src/eagle.rs turns a package's <smd> and <pad> into footprint pads, but it skips its <hole> elements. On this board, those are the two 0.65 mm locating pegs of the USB-C connector (J5, GCT USB4105). The bridge reports them as holes: [{"drill": 0.65, "ref": "J5", ...}].

What goes wrong: the run board has no drill there, so neither the router nor gate sees the pegs.

  • route placed VBUS tracks and vias right next to them, and gate passed.
  • KiCad's importer keeps the holes, and its DRC on the same copper found 17 hole_clearance errors (VBUS and +3V3 to the J5 NPTH pads).
  • Fusion refused the same segments ("Push Violator").

Local fix: in the element loop, after the pads, add one NPTH pad per package hole. It is placed the same way as the pads, mirrored elements included:

for h in pkg.children().filter(|x| x.has_tag_name("hole")) {
    let (hx, hy) = (f(&h, "x"), f(&h, "y"));
    let (lx, ly) = if mirror { let (wx, wy) = place((hx, hy), (ex, ey), ea, true); (wx - ex, yy(wy) - fy) } else { (hx, -hy) };
    let d = f(&h, "drill");
    if d > 0.0 { let _ = writeln!(o, "    (pad \"\" np_thru_hole circle (at {} {} {}) (size {} {}) (drill {}) (layers \"*.Cu\" \"*.Mask\"))", n(lx), n(ly), n(if mirror { 0.0 } else { ea }), n(d), n(d), n(d)); }
}

With this, the live pull of this board has J5's two NPTH pads, and the next route kept VBUS clear of them: no hole_clearance errors in KiCad DRC, and Fusion took every segment. Board-level <hole> elements in <plain> may need the same treatment. I did not check those.

This is built on 0.2.78; I can send it as a PR if you prefer.

Log in to reply.