diff --git a/examples/Crystal.example.tsx b/examples/Crystal.example.tsx
new file mode 100644
index 0000000..cda7a46
--- /dev/null
+++ b/examples/Crystal.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { Crystal } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/DPAK.example.tsx b/examples/DPAK.example.tsx
new file mode 100644
index 0000000..bbdd227
--- /dev/null
+++ b/examples/DPAK.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { DPAK } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/ElectrolyticCapacitor.example.tsx b/examples/ElectrolyticCapacitor.example.tsx
new file mode 100644
index 0000000..cfb802c
--- /dev/null
+++ b/examples/ElectrolyticCapacitor.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { ElectrolyticCapacitor } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/JST-PH.example.tsx b/examples/JST-PH.example.tsx
new file mode 100644
index 0000000..076160f
--- /dev/null
+++ b/examples/JST-PH.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { JST } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/JST-SH.example.tsx b/examples/JST-SH.example.tsx
new file mode 100644
index 0000000..53e6922
--- /dev/null
+++ b/examples/JST-SH.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { JST } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/JST-ZH.example.tsx b/examples/JST-ZH.example.tsx
new file mode 100644
index 0000000..4419dec
--- /dev/null
+++ b/examples/JST-ZH.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { JST } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/LED2835.example.tsx b/examples/LED2835.example.tsx
new file mode 100644
index 0000000..e09dcf4
--- /dev/null
+++ b/examples/LED2835.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { LED2835 } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/LED5050.example.tsx b/examples/LED5050.example.tsx
new file mode 100644
index 0000000..1f6e235
--- /dev/null
+++ b/examples/LED5050.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { LED5050 } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/M2Host.example.tsx b/examples/M2Host.example.tsx
new file mode 100644
index 0000000..e06af91
--- /dev/null
+++ b/examples/M2Host.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { M2Host } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/Potentiometer.example.tsx b/examples/Potentiometer.example.tsx
new file mode 100644
index 0000000..08502d7
--- /dev/null
+++ b/examples/Potentiometer.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { Potentiometer } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/SOT343.example.tsx b/examples/SOT343.example.tsx
new file mode 100644
index 0000000..d18d0a5
--- /dev/null
+++ b/examples/SOT343.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { SOT343 } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/SOT89.example.tsx b/examples/SOT89.example.tsx
new file mode 100644
index 0000000..71d5991
--- /dev/null
+++ b/examples/SOT89.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { SOT89 } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/SmdDiode.example.tsx b/examples/SmdDiode.example.tsx
new file mode 100644
index 0000000..d408890
--- /dev/null
+++ b/examples/SmdDiode.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { SmdDiode } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/SmdPushButton.example.tsx b/examples/SmdPushButton.example.tsx
new file mode 100644
index 0000000..25dab11
--- /dev/null
+++ b/examples/SmdPushButton.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { SmdPushButton } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/examples/SolderJumper.example.tsx b/examples/SolderJumper.example.tsx
new file mode 100644
index 0000000..a6edecd
--- /dev/null
+++ b/examples/SolderJumper.example.tsx
@@ -0,0 +1,10 @@
+import { JsCadView } from "jscad-fiber"
+import { SolderJumper } from "../lib"
+
+export default function Example() {
+ return (
+
+
+
+ )
+}
diff --git a/lib/Crystal.tsx b/lib/Crystal.tsx
new file mode 100644
index 0000000..141c919
--- /dev/null
+++ b/lib/Crystal.tsx
@@ -0,0 +1,60 @@
+import { Colorize, Cuboid } from "jscad-fiber"
+
+interface CrystalProps {
+ /** pad pitch X (footprinter px) */
+ padPitchX?: number
+ /** pad pitch Y (footprinter py) */
+ padPitchY?: number
+ padWidth?: number
+ padLength?: number
+ bodyWidth?: number
+ bodyLength?: number
+ bodyHeight?: number
+}
+
+/**
+ * 4-pad SMD quartz crystal: metal can over a dark base.
+ */
+export const Crystal = ({
+ padPitchX = 2.2,
+ padPitchY = 1.7,
+ padWidth = 1.4,
+ padLength = 1.2,
+ bodyWidth = 3.2,
+ bodyLength = 2.5,
+ bodyHeight = 0.8,
+}: CrystalProps) => {
+ return (
+ <>
+ {/* Corner pads */}
+ {[-1, 1].map((sx) =>
+ [-1, 1].map((sy) => (
+
+
+
+ )),
+ )}
+
+ {/* Dark base */}
+
+
+
+
+ {/* Metal can */}
+
+
+
+ >
+ )
+}
+
+export default Crystal
diff --git a/lib/DPAK.tsx b/lib/DPAK.tsx
new file mode 100644
index 0000000..c7f9ff1
--- /dev/null
+++ b/lib/DPAK.tsx
@@ -0,0 +1,92 @@
+import { ChipBody } from "./ChipBody"
+import { SmdChipLead } from "./SmdChipLead"
+
+interface DPAKProps {
+ /** number of gull-wing leads (tab side excluded), default 3 */
+ numPins?: number
+ /** package width across leads (footprint w) */
+ bodyWidth?: number
+ /** package length (footprint h) */
+ bodyLength?: number
+ bodyHeight?: number
+ /** pitch between gull-wing leads */
+ pitch?: number
+ leadWidth?: number
+ leadThickness?: number
+ leadHeight?: number
+ padContactLength?: number
+ /** heatsink tab width (footprint tabw) */
+ tabWidth?: number
+ /** heatsink tab length (footprint tabh) */
+ tabLength?: number
+}
+
+/**
+ * Parametric DPAK-family package: DPAK / TO-252, D2PAK / TO-263.
+ * Gull-wing leads on one side, wide heatsink tab on the other.
+ */
+export const DPAK = ({
+ numPins = 3,
+ bodyWidth = 6.6,
+ bodyLength = 6.5,
+ bodyHeight = 2.3,
+ pitch = 2.29,
+ leadWidth = 0.9,
+ leadThickness = 0.25,
+ leadHeight = 1.15,
+ padContactLength = 0.6,
+ tabWidth = 6.2,
+ tabLength = 2.5,
+}: DPAKProps) => {
+ const leadSpan = (numPins - 1) * pitch
+ const startY = -leadSpan / 2
+
+ return (
+ <>
+ {/* Gull-wing leads on the left side */}
+ {Array.from({ length: numPins }).map((_, i) => (
+
+ ))}
+
+ {/* Heatsink tab on the right side */}
+
+
+ {/* Package body */}
+
+ >
+ )
+}
+
+export default DPAK
diff --git a/lib/ElectrolyticCapacitor.tsx b/lib/ElectrolyticCapacitor.tsx
new file mode 100644
index 0000000..fc86156
--- /dev/null
+++ b/lib/ElectrolyticCapacitor.tsx
@@ -0,0 +1,69 @@
+import { Colorize, Cylinder, Subtract, Cuboid, Translate } from "jscad-fiber"
+
+interface ElectrolyticCapacitorProps {
+ /** can diameter in mm (footprinter `d`) */
+ diameter?: number
+ height?: number
+ pitch?: number
+ bodyColor?: string
+ topColor?: string
+}
+
+/**
+ * Radial aluminum electrolytic capacitor (vertical can).
+ */
+export const ElectrolyticCapacitor = ({
+ diameter = 10.5,
+ height = 12.5,
+ pitch = 7.5,
+ bodyColor = "#1a1a5e",
+ topColor = "#c0c0c0",
+}: ElectrolyticCapacitorProps) => {
+ const radius = diameter / 2
+ const legRadius = 0.25
+
+ return (
+ <>
+ {/* Can body */}
+
+
+
+
+ {/* Metal top with score lines */}
+
+
+
+
+
+
+
+
+ {/* Black base ring */}
+
+
+
+
+ {/* Leads */}
+
+
+
+
+ >
+ )
+}
+
+export default ElectrolyticCapacitor
diff --git a/lib/Footprinter3d.tsx b/lib/Footprinter3d.tsx
index 60bf6b6..39b419f 100644
--- a/lib/Footprinter3d.tsx
+++ b/lib/Footprinter3d.tsx
@@ -52,14 +52,42 @@ import { SOT886 } from "./SOT-886"
import { SOD323 } from "./sod-323"
import { SOD323F } from "./sod-323F"
import { SOD323FL } from "./sod-323FL"
+import { SOD123 } from "./sod-123"
import { AxialCapacitor } from "./AxialCapacitor"
import { StampBoard } from "./stampboard"
import { MountedPcbModule } from "./MountedPcbModule"
import SOD723 from "./SOD723"
import { JSTZH1_5mm } from "./JSTZH1_5mm"
+import { JST } from "./JST"
import { FPC } from "./FPC"
import { SmdPinHeader } from "./SmdPinHeader"
import { RJ45 } from "./RJ45"
+import { BGA } from "./BGA"
+import { SOT233P } from "./SOT-23-3P"
+import { SOT563 } from "./SOT-563"
+import { SOT89 } from "./SOT89"
+import { SOT343 } from "./SOT343"
+import { SmdDiode } from "./SmdDiode"
+import { DPAK } from "./DPAK"
+import { RadialCapacitor } from "./RadialCapacitor"
+import { ElectrolyticCapacitor } from "./ElectrolyticCapacitor"
+import { Potentiometer } from "./Potentiometer"
+import { Crystal } from "./Crystal"
+import { SmdPushButton } from "./SmdPushButton"
+import { LED2835 } from "./LED2835"
+import { LED5050 } from "./LED5050"
+import { SolderJumper } from "./SolderJumper"
+import { M2Host } from "./M2Host"
+import { USB_C } from "./USB-C"
+import { FootprintPad } from "./FootprintPad"
+import { FootprintPlatedHole } from "./FootprintPlatedHole"
+import type { PcbPlatedHole, PcbSmtPad } from "circuit-json"
+
+/** Parse footprinter values that may be numbers or strings like "3mm" */
+const fpNum = (v: unknown, fallback = 0): number => {
+ const n = typeof v === "number" ? v : Number.parseFloat(String(v))
+ return Number.isFinite(n) ? n : fallback
+}
/**
* Outputs a 3d model for any [footprinter string](https://github.com/tscircuit/footprinter)
@@ -84,6 +112,8 @@ export const Footprinter3d = ({ footprint }: { footprint: string }) => {
num_pins: number
fn: string
zh?: boolean
+ sh?: boolean
+ ph?: boolean
thermalpad?: { x: number; y: number }
imperial: String
male: boolean
@@ -131,6 +161,8 @@ export const Footprinter3d = ({ footprint }: { footprint: string }) => {
ledp?: number
ledy?: number
bodyy?: number
+ // Allow any additional footprinter fields (grid, tabw, epw, ...)
+ [key: string]: unknown
}
switch (fpJson.fn) {
@@ -304,8 +336,6 @@ export const Footprinter3d = ({ footprint }: { footprint: string }) => {
return
case "sod323fl":
return
- case "sot363":
- return
case "sot886":
return
case "sot963":
@@ -318,11 +348,240 @@ export const Footprinter3d = ({ footprint }: { footprint: string }) => {
innerDiameter={fpJson.id}
/>
)
- case "jst":
- if (fpJson.zh) {
- return
+ case "jst": {
+ const series = fpJson.zh ? "zh" : fpJson.sh ? "sh" : "ph"
+ return
+ }
+ case "bga":
+ case "lga":
+ return
+ case "quad":
+ case "mlp": {
+ const hasThermalPad = fpJson.fn === "mlp" || !!fpJson.thermalpad
+ return (
+
+ )
+ }
+ case "son":
+ case "vson":
+ case "wson": {
+ const epw = fpNum(fpJson.epw, 0)
+ const eph = fpNum(fpJson.eph, 0)
+ const hasThermalPad = epw > 0 && eph > 0
+ return (
+
+ )
+ }
+ case "sop8":
+ case "ssop":
+ return (
+
+ )
+ case "sot":
+ case "sot363":
+ return
+ case "sot23":
+ return
+ case "sot25":
+ return
+ case "sot563":
+ return
+ case "sot343":
+ return
+ case "sot89":
+ return
+ case "dpak":
+ case "to252":
+ return (
+
+ )
+ case "d2pak":
+ case "to263":
+ return (
+
+ )
+ case "to220f":
+ return
+ case "to92l":
+ case "to92s":
+ return
+ case "radial":
+ return
+ case "electrolytic":
+ return (
+
+ )
+ case "potentiometer":
+ return (
+
+ )
+ case "crystal":
+ return (
+
+ )
+ case "smdpushbutton":
+ return (
+
+ )
+ case "led2835":
+ return
+ case "led5050":
+ return (
+
+ )
+ case "solderjumper":
+ return
+ case "m2host":
+ return
+ case "usbcmidmount":
+ return
+ case "breakoutheaders":
+ return (
+
+ )
+ case "smbf":
+ return (
+
+ )
+ case "sod110":
+ return (
+
+ )
+ case "sod80":
+ return (
+
+ )
+ case "sod323w":
+ return (
+
+ )
+ case "sod882d":
+ return (
+
+ )
+ case "sod123":
+ return
+ case "smtpad":
+ case "pad": {
+ const pad: PcbSmtPad = {
+ type: "pcb_smtpad",
+ pcb_smtpad_id: "smtpad_0",
+ shape: "rect",
+ x: 0,
+ y: 0,
+ width: fpNum(fpJson.width ?? fpJson.pw, 1),
+ height: fpNum(fpJson.height ?? fpJson.ph, 1),
+ layer: "top",
}
- break
+ return
+ }
+ case "platedhole": {
+ const hole: PcbPlatedHole = {
+ type: "pcb_plated_hole",
+ pcb_plated_hole_id: "platedhole_0",
+ shape: "circle",
+ x: 0,
+ y: 0,
+ hole_diameter: fpNum(fpJson.d, 1),
+ outer_diameter: fpNum(fpJson.pd, 1.5),
+ layers: ["top", "bottom"],
+ }
+ return
+ }
case "fpc":
return (
= {
+ // JST ZH 1.5mm through-hole (top entry)
+ zh: {
+ pitch: 1.5,
+ bodyHeight: 6,
+ bodyDepth: 3.5,
+ sideMargin: 2,
+ pinRadius: 0.35,
+ pinLength: 6,
+ smd: false,
+ holeDiameter: 0.73,
+ padWidth: 1.03,
+ padHeight: 1.73,
+ },
+ // JST SH 1.0mm surface-mount (top entry)
+ sh: {
+ pitch: 1.0,
+ bodyHeight: 3.9,
+ bodyDepth: 4.2,
+ sideMargin: 1.9,
+ pinRadius: 0.25,
+ pinLength: 3.5,
+ smd: true,
+ holeDiameter: 0,
+ padWidth: 0.6,
+ padHeight: 1.6,
+ },
+ // JST PH 2.0mm through-hole (top entry)
+ ph: {
+ pitch: 2.0,
+ bodyHeight: 6.5,
+ bodyDepth: 4.5,
+ sideMargin: 2.25,
+ pinRadius: 0.35,
+ pinLength: 6.5,
+ smd: false,
+ holeDiameter: 0.75,
+ padWidth: 1.1,
+ padHeight: 1.8,
+ },
+}
+
+interface JSTProps {
+ numPins?: number
+ series?: JstSeries
+ showPins?: boolean
+ showFootprint?: boolean
+ bodyColor?: string
+ pinColor?: string
+}
+
+/**
+ * Parametric JST connector (ZH / SH / PH series).
+ * ZH and PH are through-hole top-entry, SH is surface-mount top-entry.
+ */
+export const JST = ({
+ numPins = 4,
+ series = "ph",
+ showPins = true,
+ showFootprint = true,
+ bodyColor = "#f5f5f5",
+ pinColor = "#635959",
+}: JSTProps) => {
+ const dims = SERIES_DIMS[series]
+ const { pitch, bodyHeight, bodyDepth, sideMargin, pinRadius, pinLength } =
+ dims
+ const wallThickness = 0.5
+ const hollowHeight = bodyHeight * 0.6
+ const bodyWidth = (numPins - 1) * pitch + sideMargin * 2
+ const startX = -((numPins - 1) * pitch) / 2
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showPins &&
+ Array.from({ length: numPins }).map((_, i) =>
+ dims.smd ? (
+ // SMD gull-wing contact: short vertical stub + horizontal foot
+
+
+
+
+ ) : (
+
+
+
+ ),
+ )}
+
+ {showFootprint &&
+ !dims.smd &&
+ Array.from({ length: numPins }).map((_, i) => {
+ const isPin1 = i === 0
+ const hole: PcbPlatedHole = isPin1
+ ? {
+ type: "pcb_plated_hole",
+ pcb_plated_hole_id: `jst${series}_${i}`,
+ shape: "circular_hole_with_rect_pad",
+ x: startX + i * pitch,
+ y: 0,
+ hole_diameter: dims.holeDiameter,
+ rect_pad_width: dims.padWidth,
+ rect_pad_height: dims.padHeight,
+ hole_shape: "circle",
+ pad_shape: "rect",
+ layers: ["top", "bottom"],
+ port_hints: [`${i + 1}`],
+ }
+ : {
+ type: "pcb_plated_hole",
+ pcb_plated_hole_id: `jst${series}_${i}`,
+ shape: "pill",
+ x: startX + i * pitch,
+ y: 0,
+ hole_height: dims.holeDiameter,
+ hole_width: dims.holeDiameter,
+ outer_height: dims.padHeight,
+ outer_width: dims.padWidth,
+ layers: ["top", "bottom"],
+ port_hints: [`${i + 1}`],
+ }
+ return (
+
+ )
+ })}
+
+ {showFootprint &&
+ dims.smd &&
+ Array.from({ length: numPins }).map((_, i) => {
+ const pad: PcbSmtPad = {
+ type: "pcb_smtpad",
+ pcb_smtpad_id: `jst${series}_${i}`,
+ shape: "rect",
+ x: startX + i * pitch,
+ y: bodyDepth / 2 + dims.padHeight / 2 - 0.2,
+ width: dims.padWidth,
+ height: dims.padHeight,
+ layer: "top",
+ port_hints: [`${i + 1}`],
+ }
+ return (
+
+ )
+ })}
+ >
+ )
+}
+
+export default JST
diff --git a/lib/LED2835.tsx b/lib/LED2835.tsx
new file mode 100644
index 0000000..fed167e
--- /dev/null
+++ b/lib/LED2835.tsx
@@ -0,0 +1,44 @@
+import { Colorize, Cuboid } from "jscad-fiber"
+
+interface LED2835Props {
+ bodyWidth?: number
+ bodyLength?: number
+ bodyHeight?: number
+}
+
+/**
+ * 2835 SMD LED (2.8 x 3.5mm): white package with yellow phosphor area.
+ */
+export const LED2835 = ({
+ bodyWidth = 3.5,
+ bodyLength = 2.8,
+ bodyHeight = 0.8,
+}: LED2835Props) => {
+ return (
+ <>
+ {/* Two pads underneath (asymmetric, anode wider) */}
+
+
+
+
+
+ {/* White body */}
+
+
+
+
+ {/* Phosphor / emitting area */}
+
+
+
+ >
+ )
+}
+
+export default LED2835
diff --git a/lib/LED5050.tsx b/lib/LED5050.tsx
new file mode 100644
index 0000000..81cba69
--- /dev/null
+++ b/lib/LED5050.tsx
@@ -0,0 +1,60 @@
+import { Colorize, Cuboid, Cylinder } from "jscad-fiber"
+
+interface LED5050Props {
+ bodyWidth?: number
+ bodyLength?: number
+ bodyHeight?: number
+ pitch?: number
+ rowSpan?: number
+}
+
+/**
+ * 5050 SMD RGB LED (5 x 5mm): white package, 6 pads (3 per side),
+ * three emitting dots for R/G/B dies.
+ */
+export const LED5050 = ({
+ bodyWidth = 5,
+ bodyLength = 5,
+ bodyHeight = 1.6,
+ pitch = 1.7,
+ rowSpan = 4.8,
+}: LED5050Props) => {
+ const startY = -pitch
+
+ return (
+ <>
+ {/* 3 pads per side */}
+ {[-1, 1].map((sx) =>
+ [0, 1, 2].map((i) => (
+
+
+
+ )),
+ )}
+
+ {/* White body */}
+
+
+
+
+ {/* Emitting dots (R/G/B) */}
+ {["#e74c3c", "#2ecc71", "#3498db"].map((color, i) => (
+
+
+
+ ))}
+ >
+ )
+}
+
+export default LED5050
diff --git a/lib/M2Host.tsx b/lib/M2Host.tsx
new file mode 100644
index 0000000..262e89e
--- /dev/null
+++ b/lib/M2Host.tsx
@@ -0,0 +1,67 @@
+import { Colorize, Cuboid, Subtract } from "jscad-fiber"
+
+interface M2HostProps {
+ bodyWidth?: number
+ bodyDepth?: number
+ bodyHeight?: number
+ pinCount?: number
+ pinPitch?: number
+}
+
+/**
+ * M.2 (NGFF) host connector: low elongated body with a card slot.
+ */
+export const M2Host = ({
+ bodyWidth = 22,
+ bodyDepth = 8.7,
+ bodyHeight = 3.2,
+ pinCount = 67,
+ pinPitch = 0.5,
+}: M2HostProps) => {
+ const slotDepth = bodyDepth * 0.55
+
+ return (
+ <>
+ {/* Body with card slot */}
+
+
+
+ {/* card slot opening on the front face */}
+
+
+
+
+ {/* Contact fingers inside the slot */}
+ {Array.from({ length: Math.min(pinCount, 32) }).map((_, i) => (
+
+
+
+ ))}
+
+ {/* Mounting posts at both ends */}
+ {[-1, 1].map((s) => (
+
+
+
+ ))}
+ >
+ )
+}
+
+export default M2Host
diff --git a/lib/Potentiometer.tsx b/lib/Potentiometer.tsx
new file mode 100644
index 0000000..97ae2a0
--- /dev/null
+++ b/lib/Potentiometer.tsx
@@ -0,0 +1,61 @@
+import { Colorize, Cuboid, Cylinder, Subtract } from "jscad-fiber"
+
+interface PotentiometerProps {
+ /** body diameter (footprinter `ca`) */
+ diameter?: number
+ /** body height (footprinter `h`) */
+ height?: number
+ /** pin pitch (footprinter `p`) */
+ pitch?: number
+ numPins?: number
+}
+
+/**
+ * Through-hole trimmer/rotary potentiometer: cylindrical body,
+ * shaft on top, pins along one edge.
+ */
+export const Potentiometer = ({
+ diameter = 14,
+ height = 4,
+ pitch = 5,
+ numPins = 3,
+}: PotentiometerProps) => {
+ const radius = diameter / 2
+ const startX = -((numPins - 1) * pitch) / 2
+
+ return (
+ <>
+ {/* Body */}
+
+
+
+
+ {/* Shaft with slot */}
+
+
+
+
+
+
+
+ {/* Pins */}
+ {Array.from({ length: numPins }).map((_, i) => (
+
+
+
+ ))}
+ >
+ )
+}
+
+export default Potentiometer
diff --git a/lib/SOT343.tsx b/lib/SOT343.tsx
new file mode 100644
index 0000000..3dd797a
--- /dev/null
+++ b/lib/SOT343.tsx
@@ -0,0 +1,69 @@
+import { ChipBody } from "./ChipBody"
+import { SmdChipLead } from "./SmdChipLead"
+
+/**
+ * SOT-343 package: 4 gull-wing leads, 2 per side.
+ */
+export const SOT343 = () => {
+ const fullWidth = 3.2
+ const bodyWidth = 1.9
+ const bodyLength = 2.1
+ const bodyHeight = 1.1
+ const leadWidth = 0.4
+ const leadThickness = 0.15
+ const leadHeight = 0.55
+ const padContactLength = 0.4
+ const padPitch = 1.3
+
+ const bodyDistance = (fullWidth - bodyWidth) / 2
+
+ return (
+ <>
+ {/* 2 leads on the left side */}
+ {[-padPitch / 2, padPitch / 2].map((y, i) => (
+
+ ))}
+
+ {/* 2 leads on the right side */}
+ {[-padPitch / 2, padPitch / 2].map((y, i) => (
+
+ ))}
+
+ {/* Chip Body */}
+
+ >
+ )
+}
+
+export default SOT343
diff --git a/lib/SOT89.tsx b/lib/SOT89.tsx
new file mode 100644
index 0000000..e7bc51a
--- /dev/null
+++ b/lib/SOT89.tsx
@@ -0,0 +1,70 @@
+import { ChipBody } from "./ChipBody"
+import { SmdChipLead } from "./SmdChipLead"
+
+/**
+ * SOT-89 package: 3 gull-wing leads on one side, wide tab on the other.
+ */
+export const SOT89 = () => {
+ const fullWidth = 4.2
+ const bodyWidth = 2.6
+ const bodyLength = 4.5
+ const bodyHeight = 1.5
+ const leadWidth = 0.5
+ const tabLeadWidth = 1.9
+ const leadThickness = 0.2
+ const leadHeight = 0.75
+ const padContactLength = 0.5
+ const padPitch = 1.5
+
+ const extendedBodyDistance = fullWidth - bodyWidth
+
+ return (
+ <>
+ {/* Tab on the right side */}
+
+
+ {/* 3 leads on the left side */}
+ {[-padPitch, 0, padPitch].map((y, i) => (
+
+ ))}
+
+ {/* Chip Body */}
+
+ >
+ )
+}
+
+export default SOT89
diff --git a/lib/SmdDiode.tsx b/lib/SmdDiode.tsx
new file mode 100644
index 0000000..12bd4a6
--- /dev/null
+++ b/lib/SmdDiode.tsx
@@ -0,0 +1,77 @@
+import { ChipBody } from "./ChipBody"
+import { SmdChipLead } from "./SmdChipLead"
+
+interface SmdDiodeProps {
+ /** total width including leads (footprint w) */
+ fullWidth?: number
+ bodyWidth?: number
+ bodyLength?: number
+ bodyHeight?: number
+ leadWidth?: number
+ leadThickness?: number
+ leadHeight?: number
+ padContactLength?: number
+}
+
+/**
+ * Generic parametric 2-pin SMD diode/small-2-terminal package
+ * (covers SOD-110, SOD-80, SOD-323W, SOD-882D, SMBF and similar).
+ */
+export const SmdDiode = ({
+ fullWidth = 3.3,
+ bodyWidth = fullWidth * 0.72,
+ bodyLength = 1.7,
+ bodyHeight = bodyLength * 0.7,
+ leadWidth = 0.8,
+ leadThickness = 0.15,
+ leadHeight = 0.5,
+ padContactLength = 0.4,
+}: SmdDiodeProps) => {
+ const padThickness = leadThickness / 2
+ const bodyDistance = (fullWidth - bodyWidth) / 2
+
+ return (
+ <>
+ {/* Lead on the left side */}
+
+
+ {/* Lead on the right side */}
+
+
+ {/* Chip Body */}
+
+ >
+ )
+}
+
+export default SmdDiode
diff --git a/lib/SmdPushButton.tsx b/lib/SmdPushButton.tsx
new file mode 100644
index 0000000..c106e7e
--- /dev/null
+++ b/lib/SmdPushButton.tsx
@@ -0,0 +1,69 @@
+import { Colorize, Cuboid, Cylinder } from "jscad-fiber"
+
+interface SmdPushButtonProps {
+ /** pad pitch X (footprinter px) */
+ padPitchX?: number
+ /** pad pitch Y (footprinter py) */
+ padPitchY?: number
+ padWidth?: number
+ padLength?: number
+ bodyWidth?: number
+ bodyLength?: number
+ bodyHeight?: number
+}
+
+/**
+ * SMD tactile push button: square base, round actuator, 4 corner pads.
+ */
+export const SmdPushButton = ({
+ padPitchX = 4.2,
+ padPitchY = 2.15,
+ padWidth = 1.05,
+ padLength = 0.7,
+ bodyWidth = 6,
+ bodyLength = 6,
+ bodyHeight = 3.5,
+}: SmdPushButtonProps) => {
+ return (
+ <>
+ {/* 4 corner gull-wing pads */}
+ {[-1, 1].map((sx) =>
+ [-1, 1].map((sy) => (
+
+
+
+ )),
+ )}
+
+ {/* Base */}
+
+
+
+
+ {/* Top cover */}
+
+
+
+
+ {/* Actuator */}
+
+
+
+ >
+ )
+}
+
+export default SmdPushButton
diff --git a/lib/SolderJumper.tsx b/lib/SolderJumper.tsx
new file mode 100644
index 0000000..a150d5b
--- /dev/null
+++ b/lib/SolderJumper.tsx
@@ -0,0 +1,45 @@
+import type { PcbSmtPad } from "circuit-json"
+import { FootprintPad } from "./FootprintPad"
+
+interface SolderJumperProps {
+ /** number of pads (2 = jumper2, 3 = jumper3) */
+ numPads?: number
+ padWidth?: number
+ padHeight?: number
+ gap?: number
+}
+
+/**
+ * Solder jumper: 2 (or 3) SMD pads separated by a small gap,
+ * meant to be bridged with a blob of solder.
+ */
+export const SolderJumper = ({
+ numPads = 2,
+ padWidth = 1,
+ padHeight = 1.5,
+ gap = 0.3,
+}: SolderJumperProps) => {
+ const totalWidth = numPads * padWidth + (numPads - 1) * gap
+ const startX = -totalWidth / 2 + padWidth / 2
+
+ return (
+ <>
+ {Array.from({ length: numPads }).map((_, i) => {
+ const pad: PcbSmtPad = {
+ type: "pcb_smtpad",
+ pcb_smtpad_id: `solderjumper_${i}`,
+ shape: "rect",
+ x: startX + i * (padWidth + gap),
+ y: 0,
+ width: padWidth,
+ height: padHeight,
+ layer: "top",
+ port_hints: [`${i + 1}`],
+ }
+ return
+ })}
+ >
+ )
+}
+
+export default SolderJumper
diff --git a/lib/index.ts b/lib/index.ts
index a30e911..8223d68 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -73,3 +73,31 @@ export * from "./Screen"
export * from "./JSTZH1_5mm"
export * from "./FPC"
export * from "./RJ45"
+export * from "./JST"
+export * from "./SmdDiode"
+export * from "./DPAK"
+export * from "./SOT89"
+export * from "./SOT343"
+export * from "./Crystal"
+export * from "./ElectrolyticCapacitor"
+export * from "./Potentiometer"
+export * from "./SmdPushButton"
+export * from "./LED2835"
+export * from "./LED5050"
+export * from "./SolderJumper"
+export * from "./M2Host"
+export * from "./AxialLed"
+export * from "./AxialResistor"
+export * from "./BGA"
+export * from "./ChipBody"
+export * from "./DualInlinePackage"
+export * from "./PushButton"
+export * from "./RadialCapacitor"
+export * from "./SOIC"
+export * from "./SOT-23-3P"
+export * from "./SOT-235"
+export * from "./SOT-563"
+export * from "./SmdChipLead"
+export * from "./USB-A"
+export * from "./USB-C"
+export * from "./smdLED"
diff --git a/lib/vanilla/primitives.ts b/lib/vanilla/primitives.ts
index 9be88e7..d9f6a2e 100644
--- a/lib/vanilla/primitives.ts
+++ b/lib/vanilla/primitives.ts
@@ -2,6 +2,7 @@ export const Cuboid = Symbol("Cuboid")
export const Cube = Symbol("Cube")
export const Cylinder = Symbol("Cylinder")
export const Sphere = Symbol("Sphere")
+export const Ellipsoid = Symbol("Ellipsoid")
export const RoundedCuboid = Symbol("RoundedCuboid")
export const Translate = Symbol("Translate")
diff --git a/lib/vanilla/render.ts b/lib/vanilla/render.ts
index 274f763..80fbe0f 100644
--- a/lib/vanilla/render.ts
+++ b/lib/vanilla/render.ts
@@ -7,6 +7,7 @@ import {
Cuboid,
Cylinder,
Sphere,
+ Ellipsoid,
Hull,
Rotate,
RoundedCuboid,
@@ -160,6 +161,7 @@ function renderNode(
type === Cube ||
type === Cylinder ||
type === Sphere ||
+ type === Ellipsoid ||
type === RoundedCuboid ||
type === RoundedCylinder
) {
@@ -187,6 +189,10 @@ function renderNode(
const radius = props?.radius ?? 1
const center = props?.center ?? [0, 0, 0]
g = primitives.sphere({ radius, center })
+ } else if (type === Ellipsoid) {
+ const radius = props?.radius ?? [1, 1, 1]
+ const center = props?.center ?? [0, 0, 0]
+ g = primitives.ellipsoid({ radius, center })
} else if (type === RoundedCylinder) {
const height = props?.height ?? 1
const radius = props?.radius ?? 1
diff --git a/tests/footprinter-coverage.test.ts b/tests/footprinter-coverage.test.ts
new file mode 100644
index 0000000..13b4730
--- /dev/null
+++ b/tests/footprinter-coverage.test.ts
@@ -0,0 +1,69 @@
+import { expect, test } from "bun:test"
+import { fp } from "@tscircuit/footprinter"
+import { Footprinter3d } from "../lib/Footprinter3d"
+
+/**
+ * Footprinter coverage test.
+ *
+ * Every footprint name exposed by @tscircuit/footprinter must produce a
+ * non-null 3D model from . If footprinter adds a new
+ * footprint function, it automatically appears here and fails until a
+ * model is wired up in lib/Footprinter3d.tsx — this is the checklist for
+ * the "missing models" backlog.
+ */
+
+// Footprint names that need parameters to parse into valid footprint
+// strings. Everything else is used as-is.
+const FOOTPRINT_STRING_OVERRIDES: Record = {
+ cap: "cap0603",
+ diode: "diode0603",
+ led: "led0603",
+ res: "res0603",
+ fpc: "fpc4",
+ jst: "jst4_sh",
+}
+
+// Footprint names where fp.string(...) itself throws inside
+// @tscircuit/footprinter (broken upstream). Tracked separately so the
+// test starts failing if footprinter fixes them and they become
+// renderable — at that point wire them into Footprinter3d and move them
+// out of this list.
+const BROKEN_IN_FOOTPRINTER = new Set(["pad", "vson"])
+
+const footprintNames = fp.getFootprintNames()
+
+test("every footprinter footprint renders a non-null 3D model", () => {
+ const missing: string[] = []
+
+ for (const name of footprintNames) {
+ if (BROKEN_IN_FOOTPRINTER.has(name)) continue
+ const footprint = FOOTPRINT_STRING_OVERRIDES[name] ?? name
+
+ let element: unknown
+ try {
+ element = Footprinter3d({ footprint })
+ } catch (error) {
+ missing.push(`${name} (${footprint}) threw: ${(error as Error).message}`)
+ continue
+ }
+ if (element === null || element === undefined) {
+ missing.push(`${name} (${footprint}) returned null`)
+ }
+ }
+
+ expect(missing).toEqual([])
+})
+
+test("JST connector series all render", () => {
+ for (const footprint of ["jst2_zh", "jst4_sh", "jst6_ph", "jst4"]) {
+ expect(Footprinter3d({ footprint })).not.toBeNull()
+ }
+ // legacy jstzh1_5mm string normalization still works
+ expect(Footprinter3d({ footprint: "jstzh1_5mm4" })).not.toBeNull()
+})
+
+test("footprints broken upstream in footprinter are still broken", () => {
+ for (const name of BROKEN_IN_FOOTPRINTER) {
+ expect(() => fp.string(name).json()).toThrow()
+ }
+})