Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5ddbbcd
chore: update footprinter to 0.0.445
imrishabh18 Oct 27, 2026
31e5415
Apply suggestion from @imrishabh18
imrishabh18 Oct 27, 2026
ab4f0cd
chore: consume footprinter 0.0.447
imrishabh18 Oct 27, 2026
040194b
Merge main and preserve capacitor polarity opt-in
imrishabh18 Oct 27, 2026
52f5e8e
fix: avoid duplicate LED polarity artwork
imrishabh18 Oct 27, 2026
07ae922
fix: use explicit polarity metadata instead of guessing from fabricat…
imrishabh18 Oct 27, 2026
5c8c343
refactor: generate automatic polarity fabrication notes only in core
imrishabh18 Oct 27, 2026
fe203e9
test: verify indirect footprint consumers under core-owned polarity n…
imrishabh18 Oct 27, 2026
ccd2f1b
refactor: resolve component-specific Footprinter definitions in core
imrishabh18 Oct 27, 2026
4ad1922
fix: normalize footprints in typed components without config recursion
imrishabh18 Oct 27, 2026
9d4f84d
test: expect the resolved neutral connector CAD definition
imrishabh18 Oct 27, 2026
63e9e7b
fix: use Footprinter 0.0.449 and verify emitted polarity artwork
imrishabh18 Oct 27, 2026
c2929b7
test: refresh neutral package fabrication snapshots
imrishabh18 Oct 27, 2026
c41ddeb
refactor: keep footprint policy in owning component classes
imrishabh18 Oct 27, 2026
c17b301
refactor: keep diode footprint helper specific to string definitions
imrishabh18 Oct 27, 2026
52e42b1
Merge main and regenerate conflicted PCB snapshots
imrishabh18 Oct 27, 2026
3a416e5
refactor: type Footprinter inputs and terminal mappings
imrishabh18 Oct 27, 2026
645777e
Use Footprinter 0.0.450 to resolve default passive families
imrishabh18 Oct 27, 2026
f8df046
Share diode and LED port initialization
imrishabh18 Oct 27, 2026
3d005b2
fix: address footprint resolution review feedback
imrishabh18 Oct 29, 2026
9083f1d
test: align chip footprint expectations with review fixes
imrishabh18 Oct 29, 2026
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
17 changes: 4 additions & 13 deletions lib/components/base-components/NormalComponent/NormalComponent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { resolveFootprinterPolarity } from "./utils/resolve-footprinter-polarity"
import { NormalComponent_doInitialSourcePinSpecificationChecks } from "./NormalComponent_doInitialSourcePinSpecificationChecks"
import {
getNumericPinLabels,
Expand Down Expand Up @@ -683,8 +682,9 @@ export class NormalComponent<

// Override to choose the effective footprint without rewriting user props.
resolveFootprint() {
// Footprint-driven config can discover ports before props have been parsed.
return (
this._parsedProps.footprint ??
this._parsedProps?.footprint ??
this.props.footprint ??
this._getImpliedFootprintString?.()
)
Expand All @@ -703,16 +703,7 @@ export class NormalComponent<
if (parseLibraryFootprintRef(footprint)) return
let fpCircuitJson: AnyCircuitElement[]
try {
const resolved = resolveFootprinterPolarity({
footprint,
componentName: this.componentName,
name: this.name ?? this.componentName,
pinLabels: this._resolvePinLabels(),
polarized: this._parsedProps.polarized,
})
fpCircuitJson = fp
.string(resolved.footprint)
.soup() as AnyCircuitElement[]
fpCircuitJson = fp.string(footprint).soup() as AnyCircuitElement[]
} catch (error) {
this._queueInvalidFootprintPropMessage(footprint, error)
return
Expand Down Expand Up @@ -1657,7 +1648,7 @@ export class NormalComponent<
inferredInternallyConnectedPinNames,
implicitPinNumberByHint,
}
let { footprint } = this.props
let footprint = this.resolveFootprint()
if (
typeof footprint === "string" &&
parseLibraryFootprintRef(footprint) &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { fp } from "@tscircuit/footprinter"
import type { PinLabelsProp } from "@tscircuit/props"

const footprinterFunctions = new Set(fp.getFootprintNames())

const positiveLabels = new Set(["a", "anode", "pos", "positive"])
const negativeLabels = new Set(["k", "c", "cathode", "neg", "negative"])

/** Resolve electrical terminal identities before generating footprint artwork.
* Numeric pad identities and footprint-local geometry remain unchanged.
/** Select the Footprinter definition from component intent without changing props.
* Footprinter owns the resulting geometry and fabrication artwork. Resolved
* terminal identities remain available to core's electrical port initialization.
*/
export function resolveFootprinterPolarity({
footprint,
Expand All @@ -20,11 +23,35 @@ export function resolveFootprinterPolarity({
pinLabels?: PinLabelsProp
polarized?: boolean
}) {
if (!["Diode", "Led", "Capacitor"].includes(componentName))
if (!["Diode", "Led", "Capacitor", "Chip"].includes(componentName))
return { footprint, positivePin: 1, negativePin: 2 }

const capacitor = componentName === "Capacitor"
const parameters = fp.string(footprint).params()
const definition = footprint.trim()
const parameters = fp.string(definition).params()
// Invalid definitions must retain their authored spelling for normal diagnostics.
if (!footprinterFunctions.has(parameters.fn)) {
return { footprint, positivePin: 1, negativePin: 2 }
}
const hasPolarityModifier =
parameters.polarized !== undefined || parameters.nonpolarized !== undefined

// A generic chip does not acquire electrical polarity from its package name.
// Preserve an explicitly authored modifier; otherwise request neutral artwork.
if (componentName === "Chip") {
Comment thread
imrishabh18 marked this conversation as resolved.
Resolved
return {
footprint: hasPolarityModifier
? definition
: `${definition}_nonpolarized`,
positivePin: 1,
negativePin: 2,
}
}

// Match Footprinter's bare-size syntax, including dimensions and _metric.
let resolvedFootprint = /^\d{4,5}(?=$|_|x)/.test(definition)
? `${capacitor ? "cap" : "diode"}${definition}`
: definition
Comment thread
imrishabh18 marked this conversation as resolved.
Resolved
let positivePin = 1
let negativePin = 2
const positiveOption = capacitor ? "positivepin" : "anodepin"
Expand All @@ -38,6 +65,11 @@ export function resolveFootprinterPolarity({
)
const positive = aliases.some((label) => positiveLabels.has(label))
const negative = aliases.some((label) => negativeLabels.has(label))
if (positive && negative) {
throw new Error(
`${name} pin${pin} has conflicting positive and negative polarity labels.`,
)
}
return positive === negative
? undefined
: positive
Expand Down Expand Up @@ -90,36 +122,31 @@ export function resolveFootprinterPolarity({
}

if (capacitor) {
if (polarized) {
if (
parameters.nonpolarized ||
if (
polarized &&
(parameters.nonpolarized ||
parameters.polarized === false ||
parameters.polarized === "false"
) {
throw new Error(
`${name} is polarized but its footprint explicitly disables polarity.`,
)
}
// Bare chip sizes normally select resistor geometry. Keep capacitor
// geometry while resolving its electrical terminal identities.
const capacitorFootprint = /^\d{4,5}(?=_|$)/.test(footprint)
? `cap${footprint}`
: footprint
// Core already emits pin-aware fabrication polarity symbols. Leave the
// additional footprinter silkscreen mark opt-in: its current capacitor
// implementation always marks pin 1, even when positivepin is 2.
return {
footprint: `${capacitorFootprint}_positivepin${positivePin}_negativepin${negativePin}`,
positivePin,
negativePin,
}
parameters.polarized === "false")
) {
throw new Error(
`${name} is polarized but its footprint explicitly disables polarity.`,
)
}
} else if (hasPolarityLabels) {
return {
footprint: `${footprint}_anodepin${positivePin}_cathodepin${negativePin}`,
positivePin,
negativePin,
if (!hasPolarityModifier) {
resolvedFootprint += polarized ? "_polarized" : "_nonpolarized"
}
}

// Add only missing modifiers so every consumer can resolve idempotently.
const genericDiodePads =
!capacitor && parameters.fn === "smdpads" && parameters.num_pins === 2
if (hasPolarityLabels || genericDiodePads) {
if (explicitPositive === undefined) {
resolvedFootprint += `_${positiveOption}${positivePin}`
}
if (explicitNegative === undefined) {
resolvedFootprint += `_${negativeOption}${negativePin}`
}
}
return { footprint, positivePin, negativePin }
return { footprint: resolvedFootprint, positivePin, negativePin }
}
39 changes: 13 additions & 26 deletions lib/components/normal-components/Capacitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,25 @@ import { NormalComponent } from "../base-components/NormalComponent/NormalCompon
import { isFootprinterString } from "../base-components/NormalComponent/utils/isFootprinterString"
import { resolveFootprinterPolarity } from "../base-components/NormalComponent/utils/resolve-footprinter-polarity"
import { Trace } from "../primitive-components/Trace/Trace"
import { Capacitor_addPolarityFabricationSymbols } from "./Capacitor_addPolarityFabricationSymbols"
import { Capacitor_getAutomaticMaxDecouplingTraceLength } from "./Capacitor_getAutomaticMaxDecouplingTraceLength"

const CAPACITOR_CHIP_FOOTPRINTS = new Set([
"01005",
"0201",
"0402",
"0603",
"0805",
"1206",
"1210",
"2010",
"2512",
])

export class Capacitor extends NormalComponent<
typeof capacitorProps,
PolarizedPassivePorts
> {
_adjustSilkscreenTextAutomatically = true
resolveFootprint() {
const footprint = super.resolveFootprint()
if (!isFootprinterString(footprint)) return footprint
return resolveFootprinterPolarity({
footprint,
componentName: "Capacitor",
name: this.name ?? "Capacitor",
pinLabels: this._resolvePinLabels(),
polarized: this._parsedProps.polarized,
}).footprint
}

get config() {
const baseSymbolName = this.props.symbolName ?? "capacitor"
const compactSize =
Expand Down Expand Up @@ -66,6 +65,7 @@ export class Capacitor extends NormalComponent<
footprint: this.props.footprint,
componentName: this.componentName,
name: this.name,
pinLabels: this._resolvePinLabels(),
polarized: this._parsedProps.polarized,
})
: { positivePin: 1, negativePin: 2 }
Expand Down Expand Up @@ -112,14 +112,6 @@ export class Capacitor extends NormalComponent<
return capacitanceDisplay
}

getFootprinterString(): string | null {
const baseFootprint = super.getFootprinterString()
if (baseFootprint && CAPACITOR_CHIP_FOOTPRINTS.has(baseFootprint)) {
return `cap${baseFootprint}`
}
return baseFootprint
}
Comment thread
imrishabh18 marked this conversation as resolved.
Resolved

doInitialCreateNetsFromProps() {
this._createNetsFromProps([
this.props.decouplingFor,
Expand All @@ -146,11 +138,6 @@ export class Capacitor extends NormalComponent<
this._createTracesFromConnectionsProp()
}

doInitialPcbComponentSizeCalculation(): void {
super.doInitialPcbComponentSizeCalculation()
Capacitor_addPolarityFabricationSymbols(this)
}

doInitialSourceRender() {
const { db } = this.root!
const { _parsedProps: props } = this
Expand Down

This file was deleted.

13 changes: 13 additions & 0 deletions lib/components/normal-components/Chip.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isFootprinterString } from "../base-components/NormalComponent/utils/isFootprinterString"
import { resolveFootprinterPolarity } from "../base-components/NormalComponent/utils/resolve-footprinter-polarity"
import { normalizeDegrees } from "@tscircuit/math-utils"
import { chipProps, resolveManufacturerPartNumber } from "@tscircuit/props"
import { pcb_component_invalid_layer_error } from "circuit-json"
Expand All @@ -10,6 +12,17 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
typeof chipProps,
PinLabels
> {
resolveFootprint() {
const footprint = super.resolveFootprint()
if (!isFootprinterString(footprint)) return footprint
return resolveFootprinterPolarity({
footprint,
componentName: "Chip",
name: this.name ?? "Chip",
pinLabels: this._resolvePinLabels(),
}).footprint
}

get config() {
return {
componentName: "Chip",
Expand Down
11 changes: 11 additions & 0 deletions lib/components/normal-components/Diode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ export class Diode extends NormalComponent<
typeof diodeProps,
PolarizedPassivePorts
> {
resolveFootprint() {
const footprint = super.resolveFootprint()
if (!isFootprinterString(footprint)) return footprint
return resolveFootprinterPolarity({
Comment thread
imrishabh18 marked this conversation as resolved.
Resolved
footprint,
componentName: "Diode",
name: this.name ?? "Diode",
pinLabels: this._resolvePinLabels(),
}).footprint
}

get config() {
const symbolMap: Record<string, BaseSymbolName> = {
schottky: "schottky_diode",
Expand Down
Loading
Loading