Skip to content

Commit

Permalink
fix source ports not being created for footprints if the schematic sy…
Browse files Browse the repository at this point in the history
…mbol has less pins (#551)

* fix source ports not being created for footprints if the schematic symbol has less pins

* remove accidental test file

* remove PortDiscovery phase

* fix tests

* revert subcircuit3 changes

* update pushbutton footprint
  • Loading branch information
seveibar authored Jan 25, 2025
1 parent cc47ff0 commit 13cbc33
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 105 deletions.
38 changes: 18 additions & 20 deletions docs/RENDER_PHASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,40 @@ The render phases in @tscircuit/core are defined in the `Renderable` class (`Ren

6. SourceParentAttachment: Attaches the source component to its parent.

7. PortDiscovery: Discovers and initializes ports for the component.
7. PortMatching: Matches ports with their corresponding elements.

8. PortMatching: Matches ports with their corresponding elements.
8. SourceTraceRender: Renders the source traces, which are the basic representations of connections between components.

9. SourceTraceRender: Renders the source traces, which are the basic representations of connections between components.
9. SchematicComponentRender: Renders the schematic representation of the component.

10. SchematicComponentRender: Renders the schematic representation of the component.
10. SchematicLayout: Handles the layout of schematic components.

11. SchematicLayout: Handles the layout of schematic components.
11. SchematicPortRender: Renders ports in the schematic view.

12. SchematicPortRender: Renders ports in the schematic view.
12. SchematicTraceRender: Renders traces in the schematic view.

13. SchematicTraceRender: Renders traces in the schematic view.
13. PcbInsertTraceHints: Inserts trace hints into the PCB from "manual trace hints" or other props that imply trace hints

14. PcbInsertTraceHints: Inserts trace hints into the PCB from "manual trace hints" or other props that imply trace hints
14. PcbComponentRender: Renders the PCB representation of the component.

15. PcbComponentRender: Renders the PCB representation of the component.
15. PcbPrimitiveRender: Renders primitive PCB elements (e.g., pads, holes).

16. PcbPrimitiveRender: Renders primitive PCB elements (e.g., pads, holes).
16. PcbFootprintLayout: Handles the layout of PCB footprints.

17. PcbFootprintLayout: Handles the layout of PCB footprints.
17. PcbPortRender: Renders ports in the PCB view.

18. PcbPortRender: Renders ports in the PCB view.
18. PcbPortAttachment: Attaches ports to their corresponding PCB elements.

19. PcbPortAttachment: Attaches ports to their corresponding PCB elements.
19. PcbLayout: Handles the overall layout of PCB components.

20. PcbLayout: Handles the overall layout of PCB components.
20. PcbTraceRender: Renders traces in the PCB view.

21. PcbTraceRender: Renders traces in the PCB view.
21. PcbTraceHintRender: Renders trace hints in the PCB view.

22. PcbTraceHintRender: Renders trace hints in the PCB view.
22. PcbRouteNetIslands: Routes connections between isolated net islands on the PCB.

23. PcbRouteNetIslands: Routes connections between isolated net islands on the PCB.
23. PcbComponentSizeCalculation: Calculates the size of PCB components.

24. PcbComponentSizeCalculation: Calculates the size of PCB components.

25. CadModelRender: Renders 3D CAD models of components.
24. CadModelRender: Renders 3D CAD models of components.

Each of these phases is executed in order for every component in the project during the rendering process. Components can implement specific logic for each phase by defining methods like `doInitial<PhaseName>`, `update<PhaseName>`, or `remove<PhaseName>`.
110 changes: 38 additions & 72 deletions lib/components/base-components/NormalComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class NormalComponent<
initPorts(
opts: {
additionalAliases?: Record<`pin${number}`, string[]>
pinCount?: number
} = {},
) {
if (this.root?.schematicDisabled) return
Expand Down Expand Up @@ -214,11 +215,10 @@ export class NormalComponent<
}

this.addAll(portsToCreate)
return
}

if (!this._parsedProps.schPortArrangement) {
const portsFromFootprint = this.getPortsFromFootprint()
const portsFromFootprint = this.getPortsFromFootprint(opts)
for (const port of portsFromFootprint) {
if (
!portsToCreate.some((p) =>
Expand All @@ -233,7 +233,7 @@ export class NormalComponent<
// Add ports that we know must exist because we know the pin count and
// missing pin numbers, and they are inside the pins array of the
// schPortArrangement
for (let pn = 1; pn <= this._getPinCount(); pn++) {
for (let pn = 1; pn <= (opts.pinCount ?? this._getPinCount()); pn++) {
if (!this._parsedProps.schPortArrangement) continue
if (portsToCreate.find((p) => p._parsedProps.pinNumber === pn)) continue
let explicitlyListedPinNumbersInSchPortArrangement = [
Expand Down Expand Up @@ -614,7 +614,9 @@ export class NormalComponent<
super.add(component)
}

getPortsFromFootprint(): Port[] {
getPortsFromFootprint(opts?: {
additionalAliases?: Record<string, string[]>
}): Port[] {
let { footprint } = this.props

if (!footprint || isValidElement(footprint)) {
Expand All @@ -627,7 +629,7 @@ export class NormalComponent<
const newPorts: Port[] = []
for (const elm of fpSoup) {
if ("port_hints" in elm && elm.port_hints) {
const newPort = getPortFromHints(elm.port_hints)
const newPort = getPortFromHints(elm.port_hints, opts)
if (!newPort) continue
newPort.originDescription = `footprint:string:${footprint}:port_hints[0]:${elm.port_hints[0]}`
newPorts.push(newPort)
Expand Down Expand Up @@ -700,49 +702,6 @@ export class NormalComponent<
createNetsFromProps(this, propsWithConnections)
}

/**
* Use data from our props to create ports for this component.
*
* Generally, this is done by looking at the schematic and the footprint,
* reading the pins, making sure there aren't duplicates.
*
* Can probably be removed in favor of initPorts()
*
*/
doInitialPortDiscovery(): void {
const { _parsedProps: props } = this

// Only get ports from footprint and schematic if no schPortArrangement
let newPorts: Port[] = []
if (!props.schPortArrangement) {
newPorts = [
...this.getPortsFromFootprint(),
...this.getPortsFromSchematicSymbol(),
]
}

const existingPorts = this.children.filter(
(c) => c.componentName === "Port",
) as Port[]

for (const newPort of newPorts) {
const existingPort = existingPorts.find((p) =>
p.isMatchingAnyOf(newPort.getNameAndAliases()),
)
if (existingPort) {
if (
!existingPort.schematicSymbolPortDef &&
newPort.schematicSymbolPortDef
) {
existingPort.schematicSymbolPortDef = newPort.schematicSymbolPortDef
}
continue
}
existingPorts.push(newPort)
this.add(newPort)
}
}

_getPcbCircuitJsonBounds(): {
center: { x: number; y: number }
bounds: { left: number; top: number; right: number; bottom: number }
Expand All @@ -767,36 +726,43 @@ export class NormalComponent<
}
}

_getPinCountFromSchematicPortArrangement(): number {
const schPortArrangement = this._getSchematicPortArrangement()
if (!schPortArrangement) return 0

const isExplicitPinMapping =
isExplicitPinMappingArrangement(schPortArrangement)
if (!isExplicitPinMapping) {
return (
(schPortArrangement.leftSize ?? schPortArrangement.leftPinCount ?? 0) +
(schPortArrangement.rightSize ??
schPortArrangement.rightPinCount ??
0) +
(schPortArrangement.topSize ?? schPortArrangement.topPinCount ?? 0) +
(schPortArrangement.bottomSize ??
schPortArrangement.bottomPinCount ??
0)
)
}

const { leftSide, rightSide, topSide, bottomSide } = schPortArrangement
return Math.max(
...(leftSide?.pins ?? []),
...(rightSide?.pins ?? []),
...(topSide?.pins ?? []),
...(bottomSide?.pins ?? []),
)
}

_getPinCount(): number {
const schPortArrangement = this._getSchematicPortArrangement()

// If schPortArrangement exists, use only that for pin count

if (schPortArrangement) {
const isExplicitPinMapping =
isExplicitPinMappingArrangement(schPortArrangement)
if (!isExplicitPinMapping) {
return (
(schPortArrangement.leftSize ??
schPortArrangement.leftPinCount ??
0) +
(schPortArrangement.rightSize ??
schPortArrangement.rightPinCount ??
0) +
(schPortArrangement.topSize ?? schPortArrangement.topPinCount ?? 0) +
(schPortArrangement.bottomSize ??
schPortArrangement.bottomPinCount ??
0)
)
}
const pinCountFromSchematicPortArrangement =
this._getPinCountFromSchematicPortArrangement()

const { leftSide, rightSide, topSide, bottomSide } = schPortArrangement
return Math.max(
...(leftSide?.pins ?? []),
...(rightSide?.pins ?? []),
...(topSide?.pins ?? []),
...(bottomSide?.pins ?? []),
)
return pinCountFromSchematicPortArrangement
}

// If no schPortArrangement, fall back to footprint ports
Expand Down
1 change: 0 additions & 1 deletion lib/components/base-components/Renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export const orderedRenderPhases = [
"CreateTraceHintsFromProps",
"SourceRender",
"SourceParentAttachment",
"PortDiscovery",
"PortMatching",
"SourceTraceRender",
"SourceAddConnectivityMapKey",
Expand Down
2 changes: 2 additions & 0 deletions lib/components/normal-components/PushButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export class PushButton extends NormalComponent<

initPorts() {
super.initPorts({
pinCount: 4,
additionalAliases: {
pin1: ["side1"],
pin3: ["side2"],
},
})
}

doInitialSourceRender() {
const { db } = this.root!
const { _parsedProps: props } = this
Expand Down
15 changes: 14 additions & 1 deletion lib/sel/sel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
} from "./sel-utility-types"

type NonPolarizedSel = Record<
`R${Nums40}` | `SW${Nums40}`,
`R${Nums40}`,
{
pin1: string
pin2: string
Expand All @@ -16,6 +16,18 @@ type NonPolarizedSel = Record<
}
>

type SwSel = Record<
`SW${Nums40}`,
{
pin1: string
pin2: string
pos: string
neg: string
side1: string
side2: string
}
>

type PolarizedSel = Record<
| `C${Nums40}`
| `L${Nums40}`
Expand Down Expand Up @@ -46,6 +58,7 @@ export type Sel = NonPolarizedSel &
TransistorSel &
JumperSel &
ChipSel &
SwSel &
NetSel

export const sel: Sel = new Proxy(
Expand Down
18 changes: 14 additions & 4 deletions lib/utils/getPortFromHints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@ export const getPinNumberFromLabels = (labels: string[]) => {
return Number.parseInt(pinNumber.replace(/^pin/, ""))
}

export function getPortFromHints(hints: string[]): Port | null {
export function getPortFromHints(
hints: string[],
opts?: { additionalAliases?: Record<string, string[]> },
): Port | null {
const pinNumber = getPinNumberFromLabels(hints)
if (!pinNumber) return null
const aliasesFromHints = hints.filter(
(p) => p.toString() !== pinNumber.toString() && p !== `pin${pinNumber}`,
)

const aliases = [
...aliasesFromHints,
...(opts?.additionalAliases?.[`pin${pinNumber}`] ?? []),
]

return new Port({
pinNumber,
aliases: hints.filter(
(p) => p.toString() !== pinNumber.toString() && p !== `pin${pinNumber}`,
),
aliases,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ it("should be able to get ports from react footprint definition", () => {
),
})
component.runRenderPhase("ReactSubtreesRender")
component.runRenderPhase("PortDiscovery")
component.runRenderPhase("InitializePortsFromChildren")

const ports = component.children.filter(
(c) => c.componentName === "Port",
Expand Down
4 changes: 2 additions & 2 deletions tests/components/base-components/normal-component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ it("should be able to get ports from footprinter string footprint prop", () => {
footprint: "0402",
})

component.doInitialPortDiscovery()
component.doInitialInitializePortsFromChildren()

const ports = component.children.filter(
(c) => c.componentName === "Port",
Expand Down Expand Up @@ -39,7 +39,7 @@ it("should be able to get ports from Footprint class", () => {
footprint: footprint,
})

component.doInitialPortDiscovery()
component.doInitialInitializePortsFromChildren()

const ports = component.children.filter(
(c) => c.componentName === "Port",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ test("render lifecycle events are emitted", () => {
"InitializePortsFromChildren",
"CreateNetsFromProps",
"SourceRender",
"PortDiscovery",
] as RenderPhase[]

for (const phase of phases) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ test("dirty pattern and async effects", async () => {

// Previous phases should not be dirty
expect(component.renderPhaseStates.SourceRender.dirty).toBe(false)
expect(component.renderPhaseStates.PortDiscovery.dirty).toBe(false)
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 13cbc33

Please sign in to comment.