-
Notifications
You must be signed in to change notification settings - Fork 50
Implement PDIP-8 footprint #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
3f82090
2703095
7e92d2a
272d0de
e22d2dd
bae0360
867ffd8
0665c4c
32bb13b
3e37450
ab7032f
2720891
34ad3e7
035db3c
85c87c9
80261e3
6b92de9
bb26140
697ecd9
03e9a7c
e390832
bd2d1aa
fac6c06
09a0860
523b24a
751f62a
f3758c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { fp } from "./src/footprinter" | ||
| console.log("fp keys:", Object.keys(fp)) | ||
| console.log("fp.led type:", typeof fp.led) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { fp } from "./src/footprinter" | ||
| const circuitJson = fp.pdip8().circuitJson() | ||
| console.log(JSON.stringify(circuitJson, null, 2)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { fp } from "./src/footprinter" | ||
| const p14 = fp.pdip(14).params() | ||
| console.log("pdip(14) params:", JSON.stringify(p14, null, 2)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { dip, dip_def, extendDipDef } from "./dip" | ||
|
|
||
| export const pdip_def = extendDipDef({ w: "300mil", p: "2.54mm" }) | ||
|
|
||
| export const pdip = (raw_params: any) => { | ||
| const parameters = pdip_def.parse(raw_params) | ||
| return dip({ | ||
| ...parameters, | ||
| num_pins: raw_params.num_pins ?? 8, | ||
| dip: true, | ||
| } as any) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { dip, dip_def, extendDipDef } from "./dip" | ||
|
|
||
| export const pdip8_def = extendDipDef({ w: "300mil", p: "2.54mm" }) | ||
|
|
||
| export const pdip8 = (raw_params: any) => { | ||
| const parameters = pdip8_def.parse({ ...raw_params, num_pins: 8 }) | ||
| return dip({ | ||
| ...parameters, | ||
| dip: true, | ||
| } as any) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,6 +39,12 @@ | |||||||||||||
| dip: ( | ||||||||||||||
| num_pins?: number, | ||||||||||||||
| ) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow"> | ||||||||||||||
| pdip: ( | ||||||||||||||
| num_pins?: number, | ||||||||||||||
| ) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow"> | ||||||||||||||
| pdip8: () => FootprinterParamsBuilder< | ||||||||||||||
| "w" | "p" | "id" | "od" | "wide" | "narrow" | ||||||||||||||
| > | ||||||||||||||
| cap: () => FootprinterParamsBuilder<CommonPassiveOptionKey> | ||||||||||||||
| res: () => FootprinterParamsBuilder<CommonPassiveOptionKey> | ||||||||||||||
| diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey> | ||||||||||||||
|
|
@@ -266,14 +272,12 @@ | |||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export const string = (def: string): Footprinter => { | ||||||||||||||
| let fp = footprinter() | ||||||||||||||
| let fp_instance = footprinter() | ||||||||||||||
|
|
||||||||||||||
| // The regex below automatically inserts a "res" prefix so forms like | ||||||||||||||
| // "0603_pw1.0_ph1.1" are understood without typing "res0603". | ||||||||||||||
| const modifiedDef = def.replace(/^((?:\d{4}|\d{5}))(?=$|_|x)/, "res$1") | ||||||||||||||
| const modifiedDef = def.replace(/^((?:\d{4}|\d{5}))(?=$|_|x)/, "res") | ||||||||||||||
|
|
||||||||||||||
| const def_parts = modifiedDef | ||||||||||||||
| .split(/_(?!metric)/) // split on '_' not followed by 'metric' | ||||||||||||||
| .split(/_(?!metric)/) | ||||||||||||||
| .map((s) => { | ||||||||||||||
| const m = s.match(/([a-zA-Z]+)([\(\d\.\+\?].*)?/) | ||||||||||||||
| if (!m) return null | ||||||||||||||
|
|
@@ -286,12 +290,14 @@ | |||||||||||||
| .filter(isNotNull) | ||||||||||||||
|
|
||||||||||||||
| for (const { fn, v } of def_parts) { | ||||||||||||||
| fp = fp[fn](v) | ||||||||||||||
| if (typeof (fp_instance as any)[fn] === "function") { | ||||||||||||||
| fp_instance = (fp_instance as any)[fn](v) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| fp.setString(def) | ||||||||||||||
| fp_instance.setString(def) | ||||||||||||||
|
|
||||||||||||||
| return fp | ||||||||||||||
| return fp_instance | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export const getFootprintNames = (): string[] => { | ||||||||||||||
|
|
@@ -309,8 +315,7 @@ | |||||||||||||
| const allFootprintNames = Object.keys(FOOTPRINT_FN) | ||||||||||||||
|
|
||||||||||||||
| const passiveFootprintNames = allFootprintNames.filter((name) => { | ||||||||||||||
| const fn = FOOTPRINT_FN[name] | ||||||||||||||
|
|
||||||||||||||
| const fn = (FOOTPRINT_FN as any)[name] | ||||||||||||||
| return fn.toString().includes("passive(") | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -324,17 +329,17 @@ | |||||||||||||
|
|
||||||||||||||
| export const footprinter = (): Footprinter & { | ||||||||||||||
| string: typeof string | ||||||||||||||
| getFootprintNames: string[] | ||||||||||||||
| setString: (string) => void | ||||||||||||||
| getFootprintNames: () => string[] | ||||||||||||||
| setString: (v: string) => void | ||||||||||||||
| } => { | ||||||||||||||
| const proxy = new Proxy( | ||||||||||||||
| {}, | ||||||||||||||
| { | ||||||||||||||
| get: (target: any, prop: string) => { | ||||||||||||||
| if (prop === "soup" || prop === "circuitJson") { | ||||||||||||||
| if ("fn" in target && FOOTPRINT_FN[target.fn]) { | ||||||||||||||
| if ("fn" in target && (FOOTPRINT_FN as any)[target.fn]) { | ||||||||||||||
| return () => { | ||||||||||||||
| const { circuitJson } = FOOTPRINT_FN[target.fn](target) | ||||||||||||||
| const { circuitJson } = (FOOTPRINT_FN as any)[target.fn](target) | ||||||||||||||
| const circuitWithoutSilkscreen = applyNoSilkscreen( | ||||||||||||||
| circuitJson, | ||||||||||||||
| target, | ||||||||||||||
|
|
@@ -347,36 +352,34 @@ | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (!FOOTPRINT_FN[target.fn]) { | ||||||||||||||
| if (!target.fn || !(FOOTPRINT_FN as any)[target.fn]) { | ||||||||||||||
| throw new Error( | ||||||||||||||
| `Invalid footprint function, got "${target.fn}"${ | ||||||||||||||
| target.string ? `, from string "${target.string}"` : "" | ||||||||||||||
| }`, | ||||||||||||||
| ) | ||||||||||||||
|
Check failure on line 360 in src/footprinter.ts
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return () => { | ||||||||||||||
| // TODO improve error | ||||||||||||||
| throw new Error( | ||||||||||||||
| `No function found for footprinter, make sure to specify .dip, .lr, .p, etc. Got "${prop}"`, | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (prop === "json") { | ||||||||||||||
| if (!FOOTPRINT_FN[target.fn]) { | ||||||||||||||
| if (!target.fn || !(FOOTPRINT_FN as any)[target.fn]) { | ||||||||||||||
| throw new Error( | ||||||||||||||
| `Invalid footprint function, got "${target.fn}"${ | ||||||||||||||
| target.string ? `, from string "${target.string}"` : "" | ||||||||||||||
| }`, | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| return () => FOOTPRINT_FN[target.fn](target).parameters | ||||||||||||||
| return () => (FOOTPRINT_FN as any)[target.fn](target).parameters | ||||||||||||||
| } | ||||||||||||||
| if (prop === "getFootprintNames") { | ||||||||||||||
| return () => Object.keys(FOOTPRINT_FN) | ||||||||||||||
| } | ||||||||||||||
| if (prop === "params") { | ||||||||||||||
| // TODO | ||||||||||||||
| return () => target | ||||||||||||||
| } | ||||||||||||||
| if (prop === "setString") { | ||||||||||||||
|
|
@@ -385,8 +388,15 @@ | |||||||||||||
| return proxy | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (prop === "string") { | ||||||||||||||
| return string | ||||||||||||||
| } | ||||||||||||||
| return (v: any) => { | ||||||||||||||
| if (Object.keys(target).length === 0) { | ||||||||||||||
| if ( | ||||||||||||||
| Object.keys(target).length === 0 || | ||||||||||||||
| prop === "pdip" || | ||||||||||||||
| prop === "pdip8" | ||||||||||||||
| ) { | ||||||||||||||
|
Comment on lines
+403
to
+407
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The special case for For example, Fix: if (Object.keys(target).length === 0) {
if (`${prop}${v}` in FOOTPRINT_FN) {
target[`${prop}${v}`] = true
target.fn = `${prop}${v}`
} else {
target[prop] = true
target.fn = prop
if (prop === "res" || prop === "cap") {
// ... existing logic
} else {
target.num_pins = Number.isNaN(Number.parseFloat(v))
? undefined
: Number.parseFloat(v)
}
}
}The special case should be removed since
Suggested change
Spotted by Graphite Agent |
||||||||||||||
| if (`${prop}${v}` in FOOTPRINT_FN) { | ||||||||||||||
| target[`${prop}${v}`] = true | ||||||||||||||
| target.fn = `${prop}${v}` | ||||||||||||||
|
|
@@ -398,7 +408,7 @@ | |||||||||||||
| if (typeof v === "string" && v.includes("_metric")) { | ||||||||||||||
| target.metric = v.split("_metric")[0] | ||||||||||||||
| } else { | ||||||||||||||
| target.imperial = v // e.g., res0402, cap0603 etc. | ||||||||||||||
| target.imperial = v | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
|
|
@@ -408,7 +418,6 @@ | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| // handle dip_w or other invalid booleans | ||||||||||||||
| if (!v && ["w", "h", "p"].includes(prop as string)) { | ||||||||||||||
| // ignore | ||||||||||||||
| } else { | ||||||||||||||
|
|
@@ -422,7 +431,8 @@ | |||||||||||||
| ) | ||||||||||||||
| return proxy as any | ||||||||||||||
| } | ||||||||||||||
| footprinter.string = string | ||||||||||||||
| footprinter.getFootprintNames = getFootprintNames | ||||||||||||||
|
|
||||||||||||||
| export const fp = footprinter | ||||||||||||||
| ;(footprinter as any).string = string | ||||||||||||||
| ;(footprinter as any).getFootprintNames = getFootprintNames | ||||||||||||||
|
|
||||||||||||||
| export const fp = footprinter() | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import { fp } from './src/footprinter' | ||
| console.log('pdip8 type:', typeof fp.pdip8) | ||
| console.log('pdip type:', typeof fp.pdip) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { describe, expect, it } from "bun:test" | ||
| import { fp } from "../src/footprinter" | ||
| describe("dip-check", () => { | ||
| it("dip should work", () => { | ||
| const circuitJson = fp.dip(8).circuitJson() | ||
| expect(circuitJson.length).toBeGreaterThan(0) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { describe, expect, it } from "bun:test" | ||
| import { fp } from "../src/footprinter" | ||
| describe("led-check", () => { | ||
| it("led should work", () => { | ||
| const circuitJson = fp.led().circuitJson() | ||
| expect(circuitJson.length).toBeGreaterThan(0) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { describe, expect, it } from "bun:test" | ||
| import { fp } from "../src/footprinter" | ||
|
|
||
| describe("pdip", () => { | ||
| it("pdip8 should have 8 pins and correct dimensions", () => { | ||
| const circuitJson = fp.pdip8().circuitJson() | ||
| const pins = circuitJson.filter((e) => e.type === "pcb_plated_hole") | ||
| expect(pins.length).toBe(8) | ||
|
|
||
| // Find pins by port_hints | ||
| const pin1 = pins.find((p: any) => p.port_hints?.includes("1")) as any | ||
| const pin2 = pins.find((p: any) => p.port_hints?.includes("2")) as any | ||
| const pin8 = pins.find((p: any) => p.port_hints?.includes("8")) as any | ||
|
|
||
| expect(pin1).toBeDefined() | ||
| expect(Math.abs(pin1.x - -3.81)).toBeLessThan(0.01) | ||
| expect(Math.abs(pin1.y - 3.81)).toBeLessThan(0.01) | ||
| expect(Math.abs(pin2.y - 1.27)).toBeLessThan(0.01) | ||
| expect(Math.abs(pin8.x - 3.81)).toBeLessThan(0.01) | ||
| }) | ||
|
|
||
| it("pdip should work with custom pin count", () => { | ||
| const circuitJson = fp.pdip(14).circuitJson() | ||
| const pins = circuitJson.filter((e) => e.type === "pcb_plated_hole") | ||
| expect(pins.length).toBe(14) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical regex bug - the captured group reference
$1was removed. This breaks footprint string parsing for numeric patterns.The original regex captures digits (e.g., "0603") and replaces with "res" + those digits ("res0603"). The new code replaces with just "res", dropping the captured digits entirely.
Impact: Strings like "0603" will become "res" instead of "res0603", breaking all numeric footprint string parsing.
Fix:
Spotted by Graphite Agent

Is this helpful? React 👍 or 👎 to let us know.