Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ The hash is a list of `key=value` pairs. Keys may repeat.
| `zoom` | `board`, `selected` or `highlight`, same as W, E and Z. Used when there is no view. `selected` (and E) fits the highlighted nets as drawn, on layers that are on or that show them with Net, and also fits the markers. |
| `copper` | Shown copper layer, as named in the CAD tool, e.g. `copper=F`. Layers not listed are off; `copper=` alone hides them all. Add `:` and letters to show only some kinds: `t` tracks, `z` zones, `v` vias, `s` silk, `f` fab. `copper=In1.Cu:tv` shows tracks and vias on In1.Cu. |
| `netcopper` | Copper layer that is off but still shows the highlighted nets on it, e.g. `netcopper=In1.Cu`. Takes the same `:LETTERS` as `copper`, e.g. `netcopper=In1.Cu:t` for tracks only. May repeat. Read only together with `copper`; layers not listed show nothing while off. |
| `netlayers` | `1` turns on every layer the linked nets are routed on. |
| `netlayers` | `1` turns on every layer the linked nets have copper on: tracks, zones or pads. |

Example: `board.html#net=/ADC1/CS&net=GND&netlayers=1&zoom=selected`.

Expand Down
2 changes: 1 addition & 1 deletion mcp/src/tools/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const linkInput = {
)
.optional()
.describe('Copper layers to show; the rest are hidden. [] hides them all. Leave out to keep the user\'s layers.'),
netlayers: z.boolean().optional().describe('Also show every layer the selected nets are routed on.'),
netlayers: z.boolean().optional().describe('Also show every layer the selected nets have tracks, zones or pads on.'),
base_url: z
.string()
.optional()
Expand Down
13 changes: 13 additions & 0 deletions tests/browser/interaction.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,19 @@ test('T ticks Net on the layers the selected net is on, then unticks it', async
for (const l of others) expect(s[l].net, l).toBe(false);
});

test('T ticks Net on a layer where the selected net has only pads', async ({ page }) => {
await load(page);
const onB = (s) => (fixture.pcbdata[s].B || []).map((t) => t.net);
const routedOnB = new Set([...onB('tracks'), ...onB('zones')]);
const pad = footprints.flatMap((fp) => fp.pads)
.find((p) => p.net && p.layers.includes('B') && !routedOnB.has(p.net));
expect(pad).toBeDefined();
await selectNetFromList(page, pad.net);
await page.evaluate(() => document.activeElement.blur());
await page.keyboard.press('t');
expect((await state(page)).layers.B.net).toBe(true);
});

// On a row that is off with Net ticked, tracks, zones and vias pick what is
// drawn, so they are not dimmed. Silk stays dimmed.
test('Net undims tracks, zones and vias on a row that is off', async ({ page }) => {
Expand Down
11 changes: 11 additions & 0 deletions tests/browser/links.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ test('netlayers=1 turns on the layers of the linked nets', async ({ page }) => {
expect(s.layers[innerLayer].all).toBe(true);
});

test('netlayers=1 turns on a layer where a linked net has only pads', async ({ page }) => {
const onB = (s) => (fixture.pcbdata[s].B || []).map((t) => t.net);
const routedOnB = new Set([...onB('tracks'), ...onB('zones')]);
const pad = footprints.flatMap((fp) => fp.pads)
.find((p) => p.net && p.layers.includes('B') && !routedOnB.has(p.net));
expect(pad).toBeDefined();
await load(page, hashOf([['net', pad.net], ['copper', 'F'], ['netlayers', '1']]));
const s = await state(page);
expect(s.layers.B.all).toBe(true);
});

test('a shared link with layers restores the layer table', async ({ page, browser }) => {
await load(page);
await page.locator('#inner-layers-menu .menu-btn').hover();
Expand Down
12 changes: 12 additions & 0 deletions tests/browser/smoke.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,15 @@ test('a net routed on an inner layer lists that layer', async ({ page }) => {
);
expect(layers).toContain(layer);
});

// A net with pads on B and no tracks or zones there still touches B.
test('a net with only pads on a layer lists that layer', async ({ page }) => {
await load(page);
const onB = (s) => (fixture.pcbdata[s].B || []).map((t) => t.net);
const routedOnB = new Set([...onB('tracks'), ...onB('zones')]);
const pad = fixture.pcbdata.footprints.flatMap((fp) => fp.pads)
.find((p) => p.net && p.layers.includes('B') && !routedOnB.has(p.net));
expect(pad).toBeDefined();
const layers = await page.evaluate((n) => window.__pcbaTest.netLayers(n), pad.net);
expect(layers).toContain('B');
});
6 changes: 5 additions & 1 deletion web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,18 @@ function buildIndexes() {
var net = fp.pads[j].net;
if (!net) continue;
componentToNets[i].add(net);
// A net touches every layer it has a pad on, even with no track there
if (!netToLayers[net]) netToLayers[net] = new Set();
var padLayers = fp.pads[j].layers || [];
for (var p = 0; p < padLayers.length; p++) netToLayers[net].add(padLayers[p]);
if (!netToComponents[net]) netToComponents[net] = [];
if (!seenNets.has(net)) {
seenNets.add(net);
netToComponents[net].push({ fpIdx: i, padIdx: j });
}
}
}
// Build netToLayers from tracks (skip vias) and zones
// Add the tracks (skip vias) and zones to netToLayers
var _tl0 = performance.now();
if (pcbdata.tracks) {
for (var layer in pcbdata.tracks) {
Expand Down
Loading