imrishabh18/pedometer
This code defines and assembles a simple radio receiver hardware circuit using specific imported capacitors, inductors, RF connectors, and oscillator components with precise footprints and schematic attributes.
- Version
- 1.1.3
- License
- unset
- Stars
- 0
docs/fixes/gerber-physical-via-spans.patch
diff --git a/src/excellon-drill/convert-soup-to-excellon-drill-commands.ts b/src/excellon-drill/convert-soup-to-excellon-drill-commands.ts
index b19b96a..278cf45 100644
--- a/src/excellon-drill/convert-soup-to-excellon-drill-commands.ts
+++ b/src/excellon-drill/convert-soup-to-excellon-drill-commands.ts
@@ -68,6 +68,22 @@ const getPlatedElementLayerSpan = (
return undefined
}
+ // `layers` describes the physical barrel. Deprecated from/to fields may
+ // only describe a trace transition within a plated through-via.
+ let layers: LayerRef[] = []
+ if ("layers" in element && Array.isArray(element.layers)) {
+ layers = element.layers as LayerRef[]
+ }
+ if (layers.length > 0) {
+ const sortedLayers = [...layers].sort(
+ (a, b) => getLayerNumber(a, layerCount) - getLayerNumber(b, layerCount),
+ )
+ return {
+ from_layer: sortedLayers[0],
+ to_layer: sortedLayers[sortedLayers.length - 1],
+ }
+ }
+
if (
"from_layer" in element &&
typeof element.from_layer === "string" &&
@@ -83,20 +99,6 @@ const getPlatedElementLayerSpan = (
)
}
- let layers: LayerRef[] = []
- if ("layers" in element && Array.isArray(element.layers)) {
- layers = element.layers as LayerRef[]
- }
- if (layers.length > 0) {
- const sortedLayers = [...layers].sort(
- (a, b) => getLayerNumber(a, layerCount) - getLayerNumber(b, layerCount),
- )
- return {
- from_layer: sortedLayers[0],
- to_layer: sortedLayers[sortedLayers.length - 1],
- }
- }
-
return {
from_layer: "top",
to_layer: "bottom",
@@ -198,6 +200,19 @@ const getTraceRouteViaElements = (
const fromLayer = point.from_layer as LayerRef
const toLayer = point.to_layer as LayerRef
+ // Prefer an explicit physical via record over its trace's transition.
+ // Otherwise the fallback can create a second, spurious blind drill set.
+ if (
+ circuitJson.some(
+ (candidate) =>
+ candidate.type === "pcb_via" &&
+ candidate.x === point.x &&
+ candidate.y === point.y &&
+ candidate.hole_diameter === point.hole_diameter,
+ )
+ )
+ continue
+
routeVias.push({
type: "pcb_via",
pcb_via_id: `${element.pcb_trace_id}_route_via_${index}`,
@@ -525,6 +540,7 @@ export const convertCircuitJsonToExcellonDrillCommands = ({
// --------------------
for (let i = 10; i < tool_counter; i++) {
builder.add("use_tool", { tool_number: i })
+ const drilledViaCenters = new Set<string>()
for (const element of drillElements) {
if (
element.type === "pcb_plated_hole" ||
@@ -557,6 +573,13 @@ export const convertCircuitJsonToExcellonDrillCommands = ({
const centerX = drillCenter.x
const centerY = drillCenter.y
const yMultiplier = getYMultiplier(flip_y_axis)
+ // Multiple routed traces can reference one physical via. Deduplicate
+ // within this tool and layer span, at the Excellon output precision.
+ if (element.type === "pcb_via") {
+ const key = `${centerX.toFixed(4)},${(centerY * yMultiplier).toFixed(4)}`
+ if (drilledViaCenters.has(key)) continue
+ drilledViaCenters.add(key)
+ }
if (
"hole_width" in element &&
diff --git a/tests/excellon-drill/physical-via-span.test.ts b/tests/excellon-drill/physical-via-span.test.ts
new file mode 100644
index 0000000..cb02df7
--- /dev/null
+++ b/tests/excellon-drill/physical-via-span.test.ts
@@ -0,0 +1,96 @@
+import { expect, test } from "bun:test"
+import type { AnyCircuitElement } from "circuit-json"
+import { convertCircuitJsonToGerberFiles } from "src/convert-circuit-json-to-gerber-files"
+import { convertSoupToExcellonDrillCommandLayers } from "src/excellon-drill"
+
+const board = {
+ type: "pcb_board",
+ pcb_board_id: "board",
+ center: { x: 0, y: 0 },
+ width: 10,
+ height: 10,
+ num_layers: 4,
+}
+const via = {
+ type: "pcb_via",
+ pcb_via_id: "via",
+ x: 1,
+ y: 2,
+ hole_diameter: 0.15,
+ outer_diameter: 0.3,
+ layers: ["bottom", "inner2", "top", "inner1"],
+ from_layer: "inner1",
+ to_layer: "top",
+}
+const drillFiles = (elements: unknown[]) =>
+ Object.fromEntries(
+ Object.entries(
+ convertCircuitJsonToGerberFiles(elements as AnyCircuitElement[]),
+ ).filter(([name]) => name.endsWith(".drl")),
+ )
+
+test("physical via layers take precedence over deprecated routing endpoints", () => {
+ const files = drillFiles([board, via])
+ expect(Object.keys(files)).toEqual(["drill-L1-L4.drl"])
+ expect(files["drill-L1-L4.drl"]).toContain("Plated,1,4,PTH")
+ expect(files["drill-L1-L4.drl"]).toContain("X1.0000Y2.0000")
+})
+
+test("a shared through-via is drilled once even with multiple trace records", () => {
+ const files = drillFiles([
+ board,
+ via,
+ { ...via, pcb_via_id: "via_copy", from_layer: "inner2" },
+ ])
+ expect(Object.keys(files)).toEqual(["drill-L1-L4.drl"])
+ expect(files["drill-L1-L4.drl"].match(/^X1\.0000Y2\.0000$/gm)).toHaveLength(1)
+})
+
+test("actual blind and buried vias retain their physical spans", () => {
+ const files = drillFiles([
+ board,
+ {
+ ...via,
+ layers: ["top", "inner1"],
+ from_layer: "top",
+ to_layer: "bottom",
+ },
+ { ...via, pcb_via_id: "buried", x: -1, layers: ["inner1", "inner2"] },
+ ])
+ expect(Object.keys(files).sort()).toEqual([
+ "drill-L1-L2.drl",
+ "drill-L2-L3.drl",
+ ])
+})
+
+test("trace via fallback cannot add a blind drill for an explicit through-via", () => {
+ const files = drillFiles([
+ board,
+ via,
+ {
+ type: "pcb_trace",
+ pcb_trace_id: "trace",
+ route: [
+ {
+ route_type: "via",
+ x: 1,
+ y: 2,
+ hole_diameter: 0.15,
+ outer_diameter: 0.3,
+ from_layer: "inner1",
+ to_layer: "top",
+ },
+ ],
+ },
+ ])
+ expect(Object.keys(files)).toEqual(["drill-L1-L4.drl"])
+ expect(files["drill-L1-L4.drl"].match(/^X1\.0000Y2\.0000$/gm)).toHaveLength(1)
+})
+
+test("legacy vias without physical layers still use their endpoints", () => {
+ const { layers, ...legacy } = via
+ const files = convertSoupToExcellonDrillCommandLayers({
+ circuitJson: [board, legacy] as AnyCircuitElement[],
+ })
+ expect(Object.keys(files)).toEqual(["drill-L1-L2.drl"])
+})