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"])
+})