From 647c0b4a0281ba319d5308dd24201a9091533867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 17:03:35 +0530 Subject: [PATCH 1/8] Implement automatic decoupling capacitor placement --- .../NormalComponent/NormalComponent.ts | 3 +- lib/components/normal-components/Capacitor.ts | 12 +- .../Capacitor_getPcbComponentLayer.ts | 55 +++++ .../primitive-components/CadModel.ts | 2 +- .../Group_doInitialPcbLayoutPack.ts | 127 +++++++++-- .../apply-decoupling-capacitor-packing.ts | 146 +++++++++++++ .../applyPackOutput.ts | 2 + .../get-decoupling-capacitor-relationships.ts | 198 +++++++++++++++++ ...ecoupling-capacitor-placement-pcb.snap.svg | 1 + ...ic-decoupling-capacitor-placement.test.tsx | 204 ++++++++++++++++++ 10 files changed, 729 insertions(+), 21 deletions(-) create mode 100644 lib/components/normal-components/Capacitor_getPcbComponentLayer.ts create mode 100644 lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts create mode 100644 lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts create mode 100644 tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg create mode 100644 tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx diff --git a/lib/components/base-components/NormalComponent/NormalComponent.ts b/lib/components/base-components/NormalComponent/NormalComponent.ts index 0a363b854..0e280ae94 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent.ts @@ -1815,9 +1815,8 @@ export class NormalComponent< : 0 : 0 - const computedLayer = this.props.layer === "bottom" ? "bottom" : "top" - const pcbComponent = db.pcb_component.get(this.pcb_component_id) + const computedLayer = pcbComponent?.layer === "bottom" ? "bottom" : "top" const globalTransform = this._computePcbGlobalTransformBeforeLayout() const decomposedTransform = decomposeTSR(globalTransform) const preLayoutRotation = diff --git a/lib/components/normal-components/Capacitor.ts b/lib/components/normal-components/Capacitor.ts index 0f2b7713a..352624306 100644 --- a/lib/components/normal-components/Capacitor.ts +++ b/lib/components/normal-components/Capacitor.ts @@ -1,13 +1,14 @@ import { capacitorProps } from "@tscircuit/props" import type { SourceSimpleCapacitorInput } from "circuit-json" +import { formatSiUnit } from "format-si-unit" import { - FTYPE, type BaseSymbolName, + FTYPE, type PolarizedPassivePorts, } from "lib/utils/constants" import { NormalComponent } from "../base-components/NormalComponent/NormalComponent" import { Trace } from "../primitive-components/Trace/Trace" -import { formatSiUnit } from "format-si-unit" +import { Capacitor_getPcbComponentLayer } from "./Capacitor_getPcbComponentLayer" export class Capacitor extends NormalComponent< typeof capacitorProps, @@ -91,6 +92,13 @@ export class Capacitor extends NormalComponent< this._createTracesFromConnectionsProp() } + protected override _getPcbComponentLayer() { + if (this._getFootprintOriginalLayer() !== undefined) { + return super._getPcbComponentLayer() + } + return Capacitor_getPcbComponentLayer(this) ?? super._getPcbComponentLayer() + } + doInitialSourceRender() { const { db } = this.root! const { _parsedProps: props } = this diff --git a/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts b/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts new file mode 100644 index 000000000..cf0f5bb93 --- /dev/null +++ b/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts @@ -0,0 +1,55 @@ +import type { LayerRef, SourceComponentBase } from "circuit-json" +import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" +import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" +import type { Capacitor } from "./Capacitor" + +type SourceComponentId = SourceComponentBase["source_component_id"] + +interface PcbLayerResolver { + _getPcbComponentLayer(): LayerRef | undefined +} + +const findComponentBySourceComponentId = ( + rootComponent: PrimitiveComponent, + sourceComponentId: SourceComponentId, +): PrimitiveComponent | undefined => + [rootComponent, ...rootComponent.getDescendants()].find( + (component) => + "source_component_id" in component && + component.source_component_id === sourceComponentId, + ) + +export const Capacitor_getPcbComponentLayer = ( + capacitor: Capacitor, +): LayerRef | undefined => { + if (capacitor._parsedProps.layer !== undefined) return undefined + if (capacitor.isRelativelyPositioned()) return undefined + if ( + capacitor.getSubcircuit()._getPcbManualPlacementForComponent(capacitor) !== + null + ) { + return undefined + } + + const root = capacitor.root + if (!root?.firstChild || !capacitor.source_component_id) return undefined + + const matchingRelationships = getDecouplingCapacitorRelationships( + root.db, + ).filter( + (relationship) => + relationship.capacitorSourceComponent.source_component_id === + capacitor.source_component_id, + ) + if (matchingRelationships.length !== 1) return undefined + + const chipComponent = findComponentBySourceComponentId( + root.firstChild, + matchingRelationships[0].chipSourceComponent.source_component_id, + ) + if (!chipComponent || !("_getPcbComponentLayer" in chipComponent)) { + return undefined + } + + return (chipComponent as unknown as PcbLayerResolver)._getPcbComponentLayer() +} diff --git a/lib/components/primitive-components/CadModel.ts b/lib/components/primitive-components/CadModel.ts index 1f7def325..d0e6aad67 100644 --- a/lib/components/primitive-components/CadModel.ts +++ b/lib/components/primitive-components/CadModel.ts @@ -69,7 +69,7 @@ export class CadModel extends PrimitiveComponent { ? distance.parse(props.zOffsetFromSurface) : 0 - const layer = parent.props.layer === "bottom" ? "bottom" : "top" + const layer = pcb_component?.layer === "bottom" ? "bottom" : "top" const ext = props.modelUrl ? getFileExtension(props.modelUrl) : undefined const modelUrlWithoutExtFragment = props.modelUrl?.replace(/#ext=\w+$/, "") diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts index 2c6a1b8f0..8c3e9adcc 100644 --- a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts +++ b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts @@ -1,17 +1,20 @@ -import type { Group } from "../Group" import { + type PackInput, + type PackOutput, PackSolver2, convertCircuitJsonToPackOutput, convertPackOutputToPackInput, getGraphicsFromPackOutput, - type PackInput, - type PackOutput, } from "calculate-packing" import { type PcbComponent, length } from "circuit-json" import Debug from "debug" +import type { NormalComponent } from "lib/components/base-components/NormalComponent" +import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" +import type { Constraint } from "../../Constraint" +import type { Group } from "../Group" +import { applyDecouplingCapacitorPacking } from "./apply-decoupling-capacitor-packing" import { applyComponentConstraintClusters } from "./applyComponentConstraintClusters" import { applyPackOutput } from "./applyPackOutput" -import type { NormalComponent } from "lib/components/base-components/NormalComponent" const DEFAULT_MIN_GAP = "1mm" const debug = Debug("Group_doInitialPcbLayoutPack") @@ -44,7 +47,7 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { // Collect pcb_component_ids that should be treated as static by the packer // Only collect from DIRECT children, not all descendants - const staticPcbComponentIds = new Set() + const staticPcbComponentIds = new Set() // Recursively collect margins from all descendants const collectMargins = (comp: any) => { @@ -112,8 +115,51 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { } } - // Keep all circuit elements; static components will remain fixed during packing - const filteredCircuitJson = db.toArray() + const constrainedPcbComponentIds = new Set() + for (const constraint of group.children.filter( + (child): child is Constraint => + child.componentName === "Constraint" && + "_parsedProps" in child && + (child._parsedProps as { pcb?: boolean }).pcb === true, + )) { + const referencedPcbComponentIds = new Set( + constraint + ._getAllReferencedComponents() + .componentsWithSelectors.map( + ({ component }) => component.pcb_component_id, + ) + .filter( + ( + pcbComponentId, + ): pcbComponentId is PcbComponent["pcb_component_id"] => + pcbComponentId !== null, + ), + ) + if (referencedPcbComponentIds.size < 2) continue + for (const pcbComponentId of referencedPcbComponentIds) { + constrainedPcbComponentIds.add(pcbComponentId) + } + } + const hasConstrainedStaticPcbComponent = [...staticPcbComponentIds].some( + (pcbComponentId) => constrainedPcbComponentIds.has(pcbComponentId), + ) + const currentPcbGroupId = group.pcb_group_id ?? undefined + const decouplingCapacitorRelationships = getDecouplingCapacitorRelationships( + db, + ).filter((relationship) => { + const chipPcbComponent = db.pcb_component.getWhere({ + source_component_id: relationship.chipSourceComponent.source_component_id, + }) + const capacitorPcbComponent = db.pcb_component.getWhere({ + source_component_id: + relationship.capacitorSourceComponent.source_component_id, + }) + if (!chipPcbComponent || !capacitorPcbComponent) return false + return ( + chipPcbComponent.pcb_group_id === currentPcbGroupId && + capacitorPcbComponent.pcb_group_id === currentPcbGroupId + ) + }) // Calculate bounds if width and height are specified let bounds: @@ -133,15 +179,58 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { } } - const initialPackOutput = convertCircuitJsonToPackOutput( - filteredCircuitJson, - { - source_group_id: group.source_group_id!, - // shouldAddInnerObstacles: true, - chipMarginsMap, - staticPcbComponentIds: Array.from(staticPcbComponentIds), - }, - ) + const circuitJson = db.toArray() + const packConversionOptions = { + source_group_id: group.source_group_id!, + // shouldAddInnerObstacles: true, + chipMarginsMap, + staticPcbComponentIds: Array.from(staticPcbComponentIds), + } + const initialPackOutputWithoutStaticPromotion = + convertCircuitJsonToPackOutput(circuitJson, packConversionOptions) + const representedPcbComponentIds = new Set([ + ...initialPackOutputWithoutStaticPromotion.components.map( + (packComponent) => packComponent.componentId, + ), + ...(initialPackOutputWithoutStaticPromotion.obstacles ?? []).map( + (packObstacle) => packObstacle.obstacleId, + ), + ]) + const hasFixedChipWithDynamicDecouplingCapacitor = + !hasConstrainedStaticPcbComponent && + decouplingCapacitorRelationships.some((relationship) => { + const chipPcbComponent = db.pcb_component.getWhere({ + source_component_id: + relationship.chipSourceComponent.source_component_id, + }) + const capacitorPcbComponent = db.pcb_component.getWhere({ + source_component_id: + relationship.capacitorSourceComponent.source_component_id, + }) + if (!chipPcbComponent || !capacitorPcbComponent) return false + return ( + staticPcbComponentIds.has(chipPcbComponent.pcb_component_id) && + !staticPcbComponentIds.has(capacitorPcbComponent.pcb_component_id) && + representedPcbComponentIds.has(chipPcbComponent.pcb_component_id) && + representedPcbComponentIds.has(capacitorPcbComponent.pcb_component_id) + ) + }) + + // The pack converter normally turns relatively positioned components into + // padless obstacles. On this local copy, expose known static components as + // full pack components so their real pads can attract connected components. + const initialPackOutput = hasFixedChipWithDynamicDecouplingCapacitor + ? convertCircuitJsonToPackOutput( + circuitJson.map((element) => + element.type === "pcb_component" && + element.position_mode === "relative_to_group_anchor" && + staticPcbComponentIds.has(element.pcb_component_id) + ? { ...element, position_mode: "packed" as const } + : element, + ), + packConversionOptions, + ) + : initialPackOutputWithoutStaticPromotion const packInput: PackInput = { ...convertPackOutputToPackInput(initialPackOutput), @@ -155,6 +244,12 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { const clusterMap = applyComponentConstraintClusters(group, packInput) + applyDecouplingCapacitorPacking( + db, + packInput, + decouplingCapacitorRelationships, + ) + if (debug.enabled) { group.root?.emit("debug:logOutput", { type: "debug:logOutput", diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts new file mode 100644 index 000000000..60f4fcfa3 --- /dev/null +++ b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts @@ -0,0 +1,146 @@ +import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" +import type { InputComponent, PackInput, PadId } from "calculate-packing" +import type { + PcbPlatedHole, + PcbPort, + PcbSmtPad, + SourcePort, +} from "circuit-json" +import type { DecouplingCapacitorRelationship } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" + +type SourcePortId = SourcePort["source_port_id"] +type PcbPortId = PcbPort["pcb_port_id"] +type PcbSmtPadId = PcbSmtPad["pcb_smtpad_id"] +type PcbPlatedHoleId = PcbPlatedHole["pcb_plated_hole_id"] +type PhysicalPadId = PcbSmtPadId | PcbPlatedHoleId +type PhysicalPadConnectionKey = string & { + readonly __brand: "PhysicalPadConnectionKey" +} + +const getPhysicalPadConnectionKey = ( + firstPhysicalPadId: PadId, + secondPhysicalPadId: PadId, +): PhysicalPadConnectionKey => + (firstPhysicalPadId < secondPhysicalPadId + ? `${firstPhysicalPadId}\0${secondPhysicalPadId}` + : `${secondPhysicalPadId}\0${firstPhysicalPadId}`) as PhysicalPadConnectionKey + +const getPhysicalPadIdsBySourcePortId = ( + db: CircuitJsonUtilObjects, +): Map => { + const sourcePortIdByPcbPortId = new Map( + db.pcb_port + .list() + .map((pcbPort) => [pcbPort.pcb_port_id, pcbPort.source_port_id]), + ) + const physicalPadIdsBySourcePortId = new Map() + const addPhysicalPad = ( + pcbPortId: PcbPortId | undefined, + physicalPadId: PhysicalPadId, + ) => { + if (!pcbPortId) return + const sourcePortId = sourcePortIdByPcbPortId.get(pcbPortId) + if (!sourcePortId) return + const physicalPadIds = physicalPadIdsBySourcePortId.get(sourcePortId) ?? [] + physicalPadIds.push(physicalPadId) + physicalPadIdsBySourcePortId.set(sourcePortId, physicalPadIds) + } + + for (const pcbSmtPad of db.pcb_smtpad.list()) { + addPhysicalPad(pcbSmtPad.pcb_port_id, pcbSmtPad.pcb_smtpad_id) + } + for (const pcbPlatedHole of db.pcb_plated_hole.list()) { + addPhysicalPad(pcbPlatedHole.pcb_port_id, pcbPlatedHole.pcb_plated_hole_id) + } + + return physicalPadIdsBySourcePortId +} + +export const applyDecouplingCapacitorPacking = ( + db: CircuitJsonUtilObjects, + packInput: PackInput, + decouplingCapacitorRelationships: DecouplingCapacitorRelationship[], +): void => { + const packComponentByPadId = new Map() + for (const packComponent of packInput.components) { + for (const packPad of packComponent.pads) { + packComponentByPadId.set(packPad.padId, packComponent) + } + } + const physicalPadIdsBySourcePortId = getPhysicalPadIdsBySourcePortId(db) + const weightedConnections = [...(packInput.weightedConnections ?? [])] + const weightedConnectionIndexByPhysicalPadConnectionKey = new Map< + PhysicalPadConnectionKey, + number + >() + for (const [ + connectionIndex, + weightedConnection, + ] of weightedConnections.entries()) { + if (weightedConnection.padIds.length !== 2) continue + weightedConnectionIndexByPhysicalPadConnectionKey.set( + getPhysicalPadConnectionKey( + weightedConnection.padIds[0], + weightedConnection.padIds[1], + ), + connectionIndex, + ) + } + for (const relationship of decouplingCapacitorRelationships) { + const chipPowerPadIds = + physicalPadIdsBySourcePortId.get( + relationship.chipPowerSourcePort.source_port_id, + ) ?? [] + const capacitorPowerPadIds = + physicalPadIdsBySourcePortId.get( + relationship.capacitorPowerSourcePort.source_port_id, + ) ?? [] + + for (const chipPowerPadId of chipPowerPadIds) { + for (const capacitorPowerPadId of capacitorPowerPadIds) { + const capacitorPackComponent = + packComponentByPadId.get(capacitorPowerPadId) + if (!capacitorPackComponent) continue + + const chipPackComponent = packComponentByPadId.get(chipPowerPadId) + if (!chipPackComponent) continue + if ( + capacitorPackComponent.componentId === chipPackComponent.componentId + ) { + continue + } + + const physicalPadConnectionKey = getPhysicalPadConnectionKey( + chipPowerPadId, + capacitorPowerPadId, + ) + const weightedConnectionIndex = + weightedConnectionIndexByPhysicalPadConnectionKey.get( + physicalPadConnectionKey, + ) + if (weightedConnectionIndex === undefined) { + weightedConnections.push({ + padIds: [chipPowerPadId, capacitorPowerPadId], + weight: 1, + ignoreWeakConnections: true, + }) + weightedConnectionIndexByPhysicalPadConnectionKey.set( + physicalPadConnectionKey, + weightedConnections.length - 1, + ) + } else if ( + !weightedConnections[weightedConnectionIndex].ignoreWeakConnections + ) { + weightedConnections[weightedConnectionIndex] = { + ...weightedConnections[weightedConnectionIndex], + ignoreWeakConnections: true, + } + } + } + } + } + + if (weightedConnections.length > 0) { + packInput.weightedConnections = weightedConnections + } +} diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts index c610dff62..5e5dc9d4d 100644 --- a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts +++ b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts @@ -63,6 +63,8 @@ export const applyPackOutput = ( const { db } = group.root! for (const packedComponent of packOutput.components) { + if (packedComponent.isStatic) continue + const { center, componentId, ccwRotationOffset, ccwRotationDegrees } = packedComponent diff --git a/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts b/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts new file mode 100644 index 000000000..ebb96f2be --- /dev/null +++ b/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts @@ -0,0 +1,198 @@ +import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" +import type { + SourceComponentBase, + SourceNet, + SourcePort, + SourceSimpleCapacitor, + SourceSimpleChip, + SourceTrace, +} from "circuit-json" +import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" +import { GROUND_NET_REGEX } from "lib/utils/gnd-power-net-regex" + +type SourceComponentId = SourceComponentBase["source_component_id"] +type SourceNetId = SourceNet["source_net_id"] +type SourcePortId = SourcePort["source_port_id"] +type SourceConnectivityId = SourcePortId | SourceNetId + +const COMMON_POWER_INPUT_PIN_REGEX = + /^(?:VCC|VDD|AVCC|AVDD|DVCC|DVDD|PVCC|PVDD|IOVCC|IOVDD)[A-Z0-9_]*$/i + +export interface SourceComponentPort extends SourcePort { + source_component_id: SourceComponentId +} + +export interface DecouplingCapacitorRelationship { + chipSourceComponent: SourceSimpleChip + capacitorSourceComponent: SourceSimpleCapacitor + chipPowerSourcePort: SourceComponentPort + capacitorPowerSourcePort: SourceComponentPort + capacitorGroundSourcePort: SourceComponentPort +} + +const getSourcePortLabels = (sourcePort: SourcePort): string[] => [ + sourcePort.name, + ...(sourcePort.port_hints ?? []), +] + +const sourcePortShouldHaveDecouplingCapacitor = ( + sourcePort: SourcePort, +): boolean => { + if (sourcePort.should_have_decoupling_capacitor !== undefined) { + return sourcePort.should_have_decoupling_capacitor + } + if (sourcePort.requires_power !== undefined) { + return sourcePort.requires_power + } + if (sourcePort.provides_power === true) return false + + return getSourcePortLabels(sourcePort).some((label) => + COMMON_POWER_INPUT_PIN_REGEX.test(label), + ) +} + +const sourcePortLooksLikeGround = (sourcePort: SourcePort): boolean => + sourcePort.requires_ground === true || + sourcePort.provides_ground === true || + getSourcePortLabels(sourcePort).some((label) => GROUND_NET_REGEX.test(label)) + +const isSourceSimpleChip = ( + sourceComponent: SourceComponentBase | undefined, +): sourceComponent is SourceSimpleChip => + sourceComponent?.ftype === "simple_chip" + +const traceDirectlyConnectsSourcePorts = ( + sourceTrace: SourceTrace, + firstSourcePortId: SourcePortId, + secondSourcePortId: SourcePortId, +): boolean => + sourceTrace.connected_source_port_ids.includes(firstSourcePortId) && + sourceTrace.connected_source_port_ids.includes(secondSourcePortId) + +export const getDecouplingCapacitorRelationships = ( + db: CircuitJsonUtilObjects, +): DecouplingCapacitorRelationship[] => { + const sourceComponents = db.source_component.list() + const sourcePorts = db.source_port.list() + const sourceTraces = db.source_trace.list() + const sourceConnectivityMap = getSourcePortConnectivityMapFromCircuitJson( + db.toArray(), + ) + const sourceComponentsById = new Map( + sourceComponents.map((sourceComponent) => [ + sourceComponent.source_component_id, + sourceComponent, + ]), + ) + const sourcePortsById = new Map( + sourcePorts.map((sourcePort) => [sourcePort.source_port_id, sourcePort]), + ) + const sourceNetsById = new Map( + db.source_net + .list() + .map((sourceNet) => [sourceNet.source_net_id, sourceNet]), + ) + + const getIdsConnectedToSourcePort = ( + sourcePortId: SourcePortId, + ): SourceConnectivityId[] => { + const connectivityNetId = + sourceConnectivityMap.getNetConnectedToId(sourcePortId) + if (!connectivityNetId) return [] + return sourceConnectivityMap.getIdsConnectedToNet( + connectivityNetId, + ) as SourceConnectivityId[] + } + + const sourcePortIsConnectedToGround = (sourcePort: SourcePort): boolean => { + const connectedIds = getIdsConnectedToSourcePort(sourcePort.source_port_id) + const connectedSourceNets = connectedIds + .map((connectedId) => sourceNetsById.get(connectedId)) + .filter((sourceNet): sourceNet is SourceNet => sourceNet !== undefined) + + if (connectedSourceNets.some((sourceNet) => sourceNet.is_ground)) { + return true + } + + return connectedIds + .map((connectedId) => sourcePortsById.get(connectedId)) + .filter( + (connectedSourcePort): connectedSourcePort is SourcePort => + connectedSourcePort !== undefined, + ) + .some(sourcePortLooksLikeGround) + } + + const relationships: DecouplingCapacitorRelationship[] = [] + + for (const capacitorSourceComponent of sourceComponents) { + if (capacitorSourceComponent.ftype !== "simple_capacitor") continue + + const capacitorSourcePorts = sourcePorts.filter( + (sourcePort): sourcePort is SourceComponentPort => + sourcePort.source_component_id === + capacitorSourceComponent.source_component_id, + ) + if (capacitorSourcePorts.length !== 2) continue + + const capacitorGroundSourcePorts = capacitorSourcePorts.filter( + sourcePortIsConnectedToGround, + ) + if (capacitorGroundSourcePorts.length !== 1) continue + + const capacitorGroundSourcePort = capacitorGroundSourcePorts[0] + const capacitorPowerSourcePort = capacitorSourcePorts.find( + (sourcePort) => + sourcePort.source_port_id !== capacitorGroundSourcePort.source_port_id, + ) + if (!capacitorPowerSourcePort) continue + + const connectedPowerRailIds = new Set( + getIdsConnectedToSourcePort(capacitorPowerSourcePort.source_port_id), + ) + const eligibleChipPowerSourcePorts = sourcePorts.filter( + (sourcePort): sourcePort is SourceComponentPort => { + if (!connectedPowerRailIds.has(sourcePort.source_port_id)) return false + if (!sourcePortShouldHaveDecouplingCapacitor(sourcePort)) return false + if (!sourcePort.source_component_id) return false + return ( + sourceComponentsById.get(sourcePort.source_component_id)?.ftype === + "simple_chip" + ) + }, + ) + const directlyConnectedChipPowerSourcePorts = + eligibleChipPowerSourcePorts.filter((chipPowerSourcePort) => + sourceTraces.some((sourceTrace) => + traceDirectlyConnectsSourcePorts( + sourceTrace, + capacitorPowerSourcePort.source_port_id, + chipPowerSourcePort.source_port_id, + ), + ), + ) + const chipPowerSourcePort = + directlyConnectedChipPowerSourcePorts.length === 1 + ? directlyConnectedChipPowerSourcePorts[0] + : directlyConnectedChipPowerSourcePorts.length === 0 && + eligibleChipPowerSourcePorts.length === 1 + ? eligibleChipPowerSourcePorts[0] + : undefined + if (!chipPowerSourcePort) continue + + const chipSourceComponent = sourceComponentsById.get( + chipPowerSourcePort.source_component_id, + ) + if (!isSourceSimpleChip(chipSourceComponent)) continue + + relationships.push({ + chipSourceComponent, + capacitorSourceComponent, + chipPowerSourcePort, + capacitorPowerSourcePort, + capacitorGroundSourcePort, + }) + } + + return relationships +} diff --git a/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg b/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg new file mode 100644 index 000000000..e50e3991e --- /dev/null +++ b/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg @@ -0,0 +1 @@ +U_AUTOC_AUTOU_HOLDC_HOLDC_AUTO: inferred VCC decoupling on bottom; C_HOLD: VBAT opt-out on top \ No newline at end of file diff --git a/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx b/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx new file mode 100644 index 000000000..b48f2f06e --- /dev/null +++ b/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx @@ -0,0 +1,204 @@ +import { expect, test } from "bun:test" +import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("automatic decoupling placement compared with an explicit opt-out", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + + + + + + , + ) + + await circuit.renderUntilSettled() + + const relationships = getDecouplingCapacitorRelationships(circuit.db) + expect(relationships).toHaveLength(1) + const autoSourceComponent = circuit.db.source_component.getWhere({ + name: "C_AUTO", + })! + const autoChipSourceComponent = circuit.db.source_component.getWhere({ + name: "U_AUTO", + })! + const holdSourceComponent = circuit.db.source_component.getWhere({ + name: "C_HOLD", + })! + const holdChipSourceComponent = circuit.db.source_component.getWhere({ + name: "U_HOLD", + })! + const autoPcbComponent = circuit.db.pcb_component.getWhere({ + source_component_id: autoSourceComponent.source_component_id, + })! + const autoChipPcbComponent = circuit.db.pcb_component.getWhere({ + source_component_id: autoChipSourceComponent.source_component_id, + })! + const holdChipPcbComponent = circuit.db.pcb_component.getWhere({ + source_component_id: holdChipSourceComponent.source_component_id, + })! + const holdPcbComponent = circuit.db.pcb_component.getWhere({ + source_component_id: holdSourceComponent.source_component_id, + })! + const autoChipPowerPcbPort = circuit.db.pcb_port.getWhere({ + source_port_id: relationships[0].chipPowerSourcePort.source_port_id, + })! + const autoChipPowerSmtPad = circuit.db.pcb_smtpad + .list() + .find( + (pcbSmtPad) => pcbSmtPad.pcb_port_id === autoChipPowerPcbPort.pcb_port_id, + )! + const autoChipPowerPadCenter = + "x" in autoChipPowerSmtPad + ? { x: autoChipPowerSmtPad.x, y: autoChipPowerSmtPad.y } + : { + x: + autoChipPowerSmtPad.points.reduce( + (sum, point) => sum + point.x, + 0, + ) / autoChipPowerSmtPad.points.length, + y: + autoChipPowerSmtPad.points.reduce( + (sum, point) => sum + point.y, + 0, + ) / autoChipPowerSmtPad.points.length, + } + const autoCadComponent = circuit.db.cad_component.getWhere({ + pcb_component_id: autoPcbComponent.pcb_component_id, + })! + const holdCadComponent = circuit.db.cad_component.getWhere({ + pcb_component_id: holdPcbComponent.pcb_component_id, + })! + + expect(circuit.db.pcb_packing_error.list()).toHaveLength(0) + expect( + [autoChipPcbComponent, holdChipPcbComponent].map( + ({ center, position_mode }) => ({ center, position_mode }), + ), + ).toEqual([ + { + center: { x: -6, y: 0 }, + position_mode: "relative_to_group_anchor", + }, + { + center: { x: 6, y: 0 }, + position_mode: "relative_to_group_anchor", + }, + ]) + expect(relationships[0].capacitorSourceComponent.source_component_id).toBe( + autoSourceComponent.source_component_id, + ) + expect( + Math.hypot( + autoPcbComponent.center.x - autoChipPowerPadCenter.x, + autoPcbComponent.center.y - autoChipPowerPadCenter.y, + ), + ).toBeLessThan( + Math.hypot( + holdPcbComponent.center.x - autoChipPowerPadCenter.x, + holdPcbComponent.center.y - autoChipPowerPadCenter.y, + ), + ) + + const autoSmtPads = circuit.db.pcb_smtpad + .list() + .filter( + (pcbSmtPad) => + pcbSmtPad.pcb_component_id === autoPcbComponent.pcb_component_id, + ) + const autoPcbPorts = circuit.db.pcb_port + .list() + .filter( + (pcbPort) => + pcbPort.pcb_component_id === autoPcbComponent.pcb_component_id, + ) + const holdSmtPads = circuit.db.pcb_smtpad + .list() + .filter( + (pcbSmtPad) => + pcbSmtPad.pcb_component_id === holdPcbComponent.pcb_component_id, + ) + const holdPcbPorts = circuit.db.pcb_port + .list() + .filter( + (pcbPort) => + pcbPort.pcb_component_id === holdPcbComponent.pcb_component_id, + ) + const autoSilkscreenElements = circuit.db + .toArray() + .filter( + (element) => + element.type.startsWith("pcb_silkscreen_") && + "pcb_component_id" in element && + element.pcb_component_id === autoPcbComponent.pcb_component_id, + ) + + expect( + [ + autoSmtPads.length, + autoPcbPorts.length, + autoSilkscreenElements.length, + holdSmtPads.length, + holdPcbPorts.length, + ].every((primitiveCount) => primitiveCount > 0), + ).toBe(true) + expect(autoPcbComponent.layer).toBe("bottom") + expect(autoSmtPads.every((pcbSmtPad) => pcbSmtPad.layer === "bottom")).toBe( + true, + ) + expect( + autoPcbPorts.every((pcbPort) => pcbPort.layers.includes("bottom")), + ).toBe(true) + expect( + autoSilkscreenElements.every( + (element) => "layer" in element && element.layer === "bottom", + ), + ).toBe(true) + expect(autoCadComponent.pcb_component_id).toBe( + autoPcbComponent.pcb_component_id, + ) + expect(autoCadComponent.position.z).toBeLessThan(0) + expect(autoCadComponent.rotation?.y).toBe(180) + expect(holdPcbComponent.layer).toBe("top") + expect(holdSmtPads.every((pcbSmtPad) => pcbSmtPad.layer === "top")).toBe(true) + expect(holdPcbPorts.every((pcbPort) => pcbPort.layers.includes("top"))).toBe( + true, + ) + expect(holdCadComponent.position.z).toBeGreaterThan(0) + expect(holdCadComponent.rotation?.y).toBe(0) + + expect(circuit).toMatchPcbSnapshot(import.meta.path) +}) From 6132c9714ef3a97527b643a0643f40a1f365cf7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 21:18:40 +0530 Subject: [PATCH 2/8] Warn about missing decoupling capacitors --- .../NormalComponent/NormalComponent.ts | 3 +- ...mponent_doInitialSourceDesignRuleChecks.ts | 27 +++ lib/components/normal-components/Capacitor.ts | 12 +- .../Capacitor_getPcbComponentLayer.ts | 55 ----- .../primitive-components/CadModel.ts | 2 +- .../Group_doInitialPcbLayoutPack.ts | 127 ++--------- .../apply-decoupling-capacitor-packing.ts | 146 ------------- .../applyPackOutput.ts | 2 - .../get-decoupling-capacitor-relationships.ts | 198 ----------------- ...urce-ports-missing-decoupling-capacitor.ts | 128 +++++++++++ ...sing-decoupling-capacitor-warning.test.tsx | 73 +++++++ ...ecoupling-capacitor-placement-pcb.snap.svg | 1 - ...ic-decoupling-capacitor-placement.test.tsx | 204 ------------------ 13 files changed, 249 insertions(+), 729 deletions(-) delete mode 100644 lib/components/normal-components/Capacitor_getPcbComponentLayer.ts delete mode 100644 lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts delete mode 100644 lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts create mode 100644 lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts create mode 100644 tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx delete mode 100644 tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg delete mode 100644 tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx diff --git a/lib/components/base-components/NormalComponent/NormalComponent.ts b/lib/components/base-components/NormalComponent/NormalComponent.ts index 0e280ae94..0a363b854 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent.ts @@ -1815,8 +1815,9 @@ export class NormalComponent< : 0 : 0 + const computedLayer = this.props.layer === "bottom" ? "bottom" : "top" + const pcbComponent = db.pcb_component.get(this.pcb_component_id) - const computedLayer = pcbComponent?.layer === "bottom" ? "bottom" : "top" const globalTransform = this._computePcbGlobalTransformBeforeLayout() const decomposedTransform = decomposeTSR(globalTransform) const preLayoutRotation = diff --git a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts index f484332df..58b94d454 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts @@ -1,5 +1,6 @@ import type { NormalComponent } from "./NormalComponent" import type { Port } from "../../primitive-components/Port" +import { getChipSourcePortsMissingDecouplingCapacitor } from "lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor" export const NormalComponent_doInitialSourceDesignRuleChecks = ( component: NormalComponent, @@ -42,6 +43,32 @@ export const NormalComponent_doInitialSourceDesignRuleChecks = ( warning_type: "source_pin_missing_trace_warning", }) } + + if (component.config.componentName !== "Chip") return + + const sourcePortsMissingDecouplingCapacitor = + getChipSourcePortsMissingDecouplingCapacitor( + db, + component.source_component_id, + ) + for (const sourcePort of sourcePortsMissingDecouplingCapacitor) { + const sourcePortLabel = + sourcePort.port_hints?.find((sourcePortHint) => + /[A-Za-z]/.test(sourcePortHint), + ) ?? sourcePort.name + const recommendedCapacitance = + sourcePort.recommended_decoupling_capacitor_capacitance + const capacitanceDescription = + recommendedCapacitance === undefined ? "" : ` ${recommendedCapacitance}` + + db.source_pin_missing_trace_warning.insert({ + message: `Power pin ${sourcePortLabel} on ${component.props.name} should have a${capacitanceDescription} decoupling capacitor connected to ground`, + source_component_id: component.source_component_id, + source_port_id: sourcePort.source_port_id, + subcircuit_id: component.getSubcircuit().subcircuit_id ?? undefined, + warning_type: "source_pin_missing_trace_warning", + }) + } } export const shouldCheckPortForMissingTrace = ( diff --git a/lib/components/normal-components/Capacitor.ts b/lib/components/normal-components/Capacitor.ts index 352624306..0f2b7713a 100644 --- a/lib/components/normal-components/Capacitor.ts +++ b/lib/components/normal-components/Capacitor.ts @@ -1,14 +1,13 @@ import { capacitorProps } from "@tscircuit/props" import type { SourceSimpleCapacitorInput } from "circuit-json" -import { formatSiUnit } from "format-si-unit" import { - type BaseSymbolName, FTYPE, + type BaseSymbolName, type PolarizedPassivePorts, } from "lib/utils/constants" import { NormalComponent } from "../base-components/NormalComponent/NormalComponent" import { Trace } from "../primitive-components/Trace/Trace" -import { Capacitor_getPcbComponentLayer } from "./Capacitor_getPcbComponentLayer" +import { formatSiUnit } from "format-si-unit" export class Capacitor extends NormalComponent< typeof capacitorProps, @@ -92,13 +91,6 @@ export class Capacitor extends NormalComponent< this._createTracesFromConnectionsProp() } - protected override _getPcbComponentLayer() { - if (this._getFootprintOriginalLayer() !== undefined) { - return super._getPcbComponentLayer() - } - return Capacitor_getPcbComponentLayer(this) ?? super._getPcbComponentLayer() - } - doInitialSourceRender() { const { db } = this.root! const { _parsedProps: props } = this diff --git a/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts b/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts deleted file mode 100644 index cf0f5bb93..000000000 --- a/lib/components/normal-components/Capacitor_getPcbComponentLayer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { LayerRef, SourceComponentBase } from "circuit-json" -import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" -import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" -import type { Capacitor } from "./Capacitor" - -type SourceComponentId = SourceComponentBase["source_component_id"] - -interface PcbLayerResolver { - _getPcbComponentLayer(): LayerRef | undefined -} - -const findComponentBySourceComponentId = ( - rootComponent: PrimitiveComponent, - sourceComponentId: SourceComponentId, -): PrimitiveComponent | undefined => - [rootComponent, ...rootComponent.getDescendants()].find( - (component) => - "source_component_id" in component && - component.source_component_id === sourceComponentId, - ) - -export const Capacitor_getPcbComponentLayer = ( - capacitor: Capacitor, -): LayerRef | undefined => { - if (capacitor._parsedProps.layer !== undefined) return undefined - if (capacitor.isRelativelyPositioned()) return undefined - if ( - capacitor.getSubcircuit()._getPcbManualPlacementForComponent(capacitor) !== - null - ) { - return undefined - } - - const root = capacitor.root - if (!root?.firstChild || !capacitor.source_component_id) return undefined - - const matchingRelationships = getDecouplingCapacitorRelationships( - root.db, - ).filter( - (relationship) => - relationship.capacitorSourceComponent.source_component_id === - capacitor.source_component_id, - ) - if (matchingRelationships.length !== 1) return undefined - - const chipComponent = findComponentBySourceComponentId( - root.firstChild, - matchingRelationships[0].chipSourceComponent.source_component_id, - ) - if (!chipComponent || !("_getPcbComponentLayer" in chipComponent)) { - return undefined - } - - return (chipComponent as unknown as PcbLayerResolver)._getPcbComponentLayer() -} diff --git a/lib/components/primitive-components/CadModel.ts b/lib/components/primitive-components/CadModel.ts index d0e6aad67..1f7def325 100644 --- a/lib/components/primitive-components/CadModel.ts +++ b/lib/components/primitive-components/CadModel.ts @@ -69,7 +69,7 @@ export class CadModel extends PrimitiveComponent { ? distance.parse(props.zOffsetFromSurface) : 0 - const layer = pcb_component?.layer === "bottom" ? "bottom" : "top" + const layer = parent.props.layer === "bottom" ? "bottom" : "top" const ext = props.modelUrl ? getFileExtension(props.modelUrl) : undefined const modelUrlWithoutExtFragment = props.modelUrl?.replace(/#ext=\w+$/, "") diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts index 8c3e9adcc..2c6a1b8f0 100644 --- a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts +++ b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/Group_doInitialPcbLayoutPack.ts @@ -1,20 +1,17 @@ +import type { Group } from "../Group" import { - type PackInput, - type PackOutput, PackSolver2, convertCircuitJsonToPackOutput, convertPackOutputToPackInput, getGraphicsFromPackOutput, + type PackInput, + type PackOutput, } from "calculate-packing" import { type PcbComponent, length } from "circuit-json" import Debug from "debug" -import type { NormalComponent } from "lib/components/base-components/NormalComponent" -import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" -import type { Constraint } from "../../Constraint" -import type { Group } from "../Group" -import { applyDecouplingCapacitorPacking } from "./apply-decoupling-capacitor-packing" import { applyComponentConstraintClusters } from "./applyComponentConstraintClusters" import { applyPackOutput } from "./applyPackOutput" +import type { NormalComponent } from "lib/components/base-components/NormalComponent" const DEFAULT_MIN_GAP = "1mm" const debug = Debug("Group_doInitialPcbLayoutPack") @@ -47,7 +44,7 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { // Collect pcb_component_ids that should be treated as static by the packer // Only collect from DIRECT children, not all descendants - const staticPcbComponentIds = new Set() + const staticPcbComponentIds = new Set() // Recursively collect margins from all descendants const collectMargins = (comp: any) => { @@ -115,51 +112,8 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { } } - const constrainedPcbComponentIds = new Set() - for (const constraint of group.children.filter( - (child): child is Constraint => - child.componentName === "Constraint" && - "_parsedProps" in child && - (child._parsedProps as { pcb?: boolean }).pcb === true, - )) { - const referencedPcbComponentIds = new Set( - constraint - ._getAllReferencedComponents() - .componentsWithSelectors.map( - ({ component }) => component.pcb_component_id, - ) - .filter( - ( - pcbComponentId, - ): pcbComponentId is PcbComponent["pcb_component_id"] => - pcbComponentId !== null, - ), - ) - if (referencedPcbComponentIds.size < 2) continue - for (const pcbComponentId of referencedPcbComponentIds) { - constrainedPcbComponentIds.add(pcbComponentId) - } - } - const hasConstrainedStaticPcbComponent = [...staticPcbComponentIds].some( - (pcbComponentId) => constrainedPcbComponentIds.has(pcbComponentId), - ) - const currentPcbGroupId = group.pcb_group_id ?? undefined - const decouplingCapacitorRelationships = getDecouplingCapacitorRelationships( - db, - ).filter((relationship) => { - const chipPcbComponent = db.pcb_component.getWhere({ - source_component_id: relationship.chipSourceComponent.source_component_id, - }) - const capacitorPcbComponent = db.pcb_component.getWhere({ - source_component_id: - relationship.capacitorSourceComponent.source_component_id, - }) - if (!chipPcbComponent || !capacitorPcbComponent) return false - return ( - chipPcbComponent.pcb_group_id === currentPcbGroupId && - capacitorPcbComponent.pcb_group_id === currentPcbGroupId - ) - }) + // Keep all circuit elements; static components will remain fixed during packing + const filteredCircuitJson = db.toArray() // Calculate bounds if width and height are specified let bounds: @@ -179,58 +133,15 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { } } - const circuitJson = db.toArray() - const packConversionOptions = { - source_group_id: group.source_group_id!, - // shouldAddInnerObstacles: true, - chipMarginsMap, - staticPcbComponentIds: Array.from(staticPcbComponentIds), - } - const initialPackOutputWithoutStaticPromotion = - convertCircuitJsonToPackOutput(circuitJson, packConversionOptions) - const representedPcbComponentIds = new Set([ - ...initialPackOutputWithoutStaticPromotion.components.map( - (packComponent) => packComponent.componentId, - ), - ...(initialPackOutputWithoutStaticPromotion.obstacles ?? []).map( - (packObstacle) => packObstacle.obstacleId, - ), - ]) - const hasFixedChipWithDynamicDecouplingCapacitor = - !hasConstrainedStaticPcbComponent && - decouplingCapacitorRelationships.some((relationship) => { - const chipPcbComponent = db.pcb_component.getWhere({ - source_component_id: - relationship.chipSourceComponent.source_component_id, - }) - const capacitorPcbComponent = db.pcb_component.getWhere({ - source_component_id: - relationship.capacitorSourceComponent.source_component_id, - }) - if (!chipPcbComponent || !capacitorPcbComponent) return false - return ( - staticPcbComponentIds.has(chipPcbComponent.pcb_component_id) && - !staticPcbComponentIds.has(capacitorPcbComponent.pcb_component_id) && - representedPcbComponentIds.has(chipPcbComponent.pcb_component_id) && - representedPcbComponentIds.has(capacitorPcbComponent.pcb_component_id) - ) - }) - - // The pack converter normally turns relatively positioned components into - // padless obstacles. On this local copy, expose known static components as - // full pack components so their real pads can attract connected components. - const initialPackOutput = hasFixedChipWithDynamicDecouplingCapacitor - ? convertCircuitJsonToPackOutput( - circuitJson.map((element) => - element.type === "pcb_component" && - element.position_mode === "relative_to_group_anchor" && - staticPcbComponentIds.has(element.pcb_component_id) - ? { ...element, position_mode: "packed" as const } - : element, - ), - packConversionOptions, - ) - : initialPackOutputWithoutStaticPromotion + const initialPackOutput = convertCircuitJsonToPackOutput( + filteredCircuitJson, + { + source_group_id: group.source_group_id!, + // shouldAddInnerObstacles: true, + chipMarginsMap, + staticPcbComponentIds: Array.from(staticPcbComponentIds), + }, + ) const packInput: PackInput = { ...convertPackOutputToPackInput(initialPackOutput), @@ -244,12 +155,6 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => { const clusterMap = applyComponentConstraintClusters(group, packInput) - applyDecouplingCapacitorPacking( - db, - packInput, - decouplingCapacitorRelationships, - ) - if (debug.enabled) { group.root?.emit("debug:logOutput", { type: "debug:logOutput", diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts deleted file mode 100644 index 60f4fcfa3..000000000 --- a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/apply-decoupling-capacitor-packing.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" -import type { InputComponent, PackInput, PadId } from "calculate-packing" -import type { - PcbPlatedHole, - PcbPort, - PcbSmtPad, - SourcePort, -} from "circuit-json" -import type { DecouplingCapacitorRelationship } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" - -type SourcePortId = SourcePort["source_port_id"] -type PcbPortId = PcbPort["pcb_port_id"] -type PcbSmtPadId = PcbSmtPad["pcb_smtpad_id"] -type PcbPlatedHoleId = PcbPlatedHole["pcb_plated_hole_id"] -type PhysicalPadId = PcbSmtPadId | PcbPlatedHoleId -type PhysicalPadConnectionKey = string & { - readonly __brand: "PhysicalPadConnectionKey" -} - -const getPhysicalPadConnectionKey = ( - firstPhysicalPadId: PadId, - secondPhysicalPadId: PadId, -): PhysicalPadConnectionKey => - (firstPhysicalPadId < secondPhysicalPadId - ? `${firstPhysicalPadId}\0${secondPhysicalPadId}` - : `${secondPhysicalPadId}\0${firstPhysicalPadId}`) as PhysicalPadConnectionKey - -const getPhysicalPadIdsBySourcePortId = ( - db: CircuitJsonUtilObjects, -): Map => { - const sourcePortIdByPcbPortId = new Map( - db.pcb_port - .list() - .map((pcbPort) => [pcbPort.pcb_port_id, pcbPort.source_port_id]), - ) - const physicalPadIdsBySourcePortId = new Map() - const addPhysicalPad = ( - pcbPortId: PcbPortId | undefined, - physicalPadId: PhysicalPadId, - ) => { - if (!pcbPortId) return - const sourcePortId = sourcePortIdByPcbPortId.get(pcbPortId) - if (!sourcePortId) return - const physicalPadIds = physicalPadIdsBySourcePortId.get(sourcePortId) ?? [] - physicalPadIds.push(physicalPadId) - physicalPadIdsBySourcePortId.set(sourcePortId, physicalPadIds) - } - - for (const pcbSmtPad of db.pcb_smtpad.list()) { - addPhysicalPad(pcbSmtPad.pcb_port_id, pcbSmtPad.pcb_smtpad_id) - } - for (const pcbPlatedHole of db.pcb_plated_hole.list()) { - addPhysicalPad(pcbPlatedHole.pcb_port_id, pcbPlatedHole.pcb_plated_hole_id) - } - - return physicalPadIdsBySourcePortId -} - -export const applyDecouplingCapacitorPacking = ( - db: CircuitJsonUtilObjects, - packInput: PackInput, - decouplingCapacitorRelationships: DecouplingCapacitorRelationship[], -): void => { - const packComponentByPadId = new Map() - for (const packComponent of packInput.components) { - for (const packPad of packComponent.pads) { - packComponentByPadId.set(packPad.padId, packComponent) - } - } - const physicalPadIdsBySourcePortId = getPhysicalPadIdsBySourcePortId(db) - const weightedConnections = [...(packInput.weightedConnections ?? [])] - const weightedConnectionIndexByPhysicalPadConnectionKey = new Map< - PhysicalPadConnectionKey, - number - >() - for (const [ - connectionIndex, - weightedConnection, - ] of weightedConnections.entries()) { - if (weightedConnection.padIds.length !== 2) continue - weightedConnectionIndexByPhysicalPadConnectionKey.set( - getPhysicalPadConnectionKey( - weightedConnection.padIds[0], - weightedConnection.padIds[1], - ), - connectionIndex, - ) - } - for (const relationship of decouplingCapacitorRelationships) { - const chipPowerPadIds = - physicalPadIdsBySourcePortId.get( - relationship.chipPowerSourcePort.source_port_id, - ) ?? [] - const capacitorPowerPadIds = - physicalPadIdsBySourcePortId.get( - relationship.capacitorPowerSourcePort.source_port_id, - ) ?? [] - - for (const chipPowerPadId of chipPowerPadIds) { - for (const capacitorPowerPadId of capacitorPowerPadIds) { - const capacitorPackComponent = - packComponentByPadId.get(capacitorPowerPadId) - if (!capacitorPackComponent) continue - - const chipPackComponent = packComponentByPadId.get(chipPowerPadId) - if (!chipPackComponent) continue - if ( - capacitorPackComponent.componentId === chipPackComponent.componentId - ) { - continue - } - - const physicalPadConnectionKey = getPhysicalPadConnectionKey( - chipPowerPadId, - capacitorPowerPadId, - ) - const weightedConnectionIndex = - weightedConnectionIndexByPhysicalPadConnectionKey.get( - physicalPadConnectionKey, - ) - if (weightedConnectionIndex === undefined) { - weightedConnections.push({ - padIds: [chipPowerPadId, capacitorPowerPadId], - weight: 1, - ignoreWeakConnections: true, - }) - weightedConnectionIndexByPhysicalPadConnectionKey.set( - physicalPadConnectionKey, - weightedConnections.length - 1, - ) - } else if ( - !weightedConnections[weightedConnectionIndex].ignoreWeakConnections - ) { - weightedConnections[weightedConnectionIndex] = { - ...weightedConnections[weightedConnectionIndex], - ignoreWeakConnections: true, - } - } - } - } - } - - if (weightedConnections.length > 0) { - packInput.weightedConnections = weightedConnections - } -} diff --git a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts index 5e5dc9d4d..c610dff62 100644 --- a/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts +++ b/lib/components/primitive-components/Group/Group_doInitialPcbLayoutPack/applyPackOutput.ts @@ -63,8 +63,6 @@ export const applyPackOutput = ( const { db } = group.root! for (const packedComponent of packOutput.components) { - if (packedComponent.isStatic) continue - const { center, componentId, ccwRotationOffset, ccwRotationDegrees } = packedComponent diff --git a/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts b/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts deleted file mode 100644 index ebb96f2be..000000000 --- a/lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships.ts +++ /dev/null @@ -1,198 +0,0 @@ -import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" -import type { - SourceComponentBase, - SourceNet, - SourcePort, - SourceSimpleCapacitor, - SourceSimpleChip, - SourceTrace, -} from "circuit-json" -import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" -import { GROUND_NET_REGEX } from "lib/utils/gnd-power-net-regex" - -type SourceComponentId = SourceComponentBase["source_component_id"] -type SourceNetId = SourceNet["source_net_id"] -type SourcePortId = SourcePort["source_port_id"] -type SourceConnectivityId = SourcePortId | SourceNetId - -const COMMON_POWER_INPUT_PIN_REGEX = - /^(?:VCC|VDD|AVCC|AVDD|DVCC|DVDD|PVCC|PVDD|IOVCC|IOVDD)[A-Z0-9_]*$/i - -export interface SourceComponentPort extends SourcePort { - source_component_id: SourceComponentId -} - -export interface DecouplingCapacitorRelationship { - chipSourceComponent: SourceSimpleChip - capacitorSourceComponent: SourceSimpleCapacitor - chipPowerSourcePort: SourceComponentPort - capacitorPowerSourcePort: SourceComponentPort - capacitorGroundSourcePort: SourceComponentPort -} - -const getSourcePortLabels = (sourcePort: SourcePort): string[] => [ - sourcePort.name, - ...(sourcePort.port_hints ?? []), -] - -const sourcePortShouldHaveDecouplingCapacitor = ( - sourcePort: SourcePort, -): boolean => { - if (sourcePort.should_have_decoupling_capacitor !== undefined) { - return sourcePort.should_have_decoupling_capacitor - } - if (sourcePort.requires_power !== undefined) { - return sourcePort.requires_power - } - if (sourcePort.provides_power === true) return false - - return getSourcePortLabels(sourcePort).some((label) => - COMMON_POWER_INPUT_PIN_REGEX.test(label), - ) -} - -const sourcePortLooksLikeGround = (sourcePort: SourcePort): boolean => - sourcePort.requires_ground === true || - sourcePort.provides_ground === true || - getSourcePortLabels(sourcePort).some((label) => GROUND_NET_REGEX.test(label)) - -const isSourceSimpleChip = ( - sourceComponent: SourceComponentBase | undefined, -): sourceComponent is SourceSimpleChip => - sourceComponent?.ftype === "simple_chip" - -const traceDirectlyConnectsSourcePorts = ( - sourceTrace: SourceTrace, - firstSourcePortId: SourcePortId, - secondSourcePortId: SourcePortId, -): boolean => - sourceTrace.connected_source_port_ids.includes(firstSourcePortId) && - sourceTrace.connected_source_port_ids.includes(secondSourcePortId) - -export const getDecouplingCapacitorRelationships = ( - db: CircuitJsonUtilObjects, -): DecouplingCapacitorRelationship[] => { - const sourceComponents = db.source_component.list() - const sourcePorts = db.source_port.list() - const sourceTraces = db.source_trace.list() - const sourceConnectivityMap = getSourcePortConnectivityMapFromCircuitJson( - db.toArray(), - ) - const sourceComponentsById = new Map( - sourceComponents.map((sourceComponent) => [ - sourceComponent.source_component_id, - sourceComponent, - ]), - ) - const sourcePortsById = new Map( - sourcePorts.map((sourcePort) => [sourcePort.source_port_id, sourcePort]), - ) - const sourceNetsById = new Map( - db.source_net - .list() - .map((sourceNet) => [sourceNet.source_net_id, sourceNet]), - ) - - const getIdsConnectedToSourcePort = ( - sourcePortId: SourcePortId, - ): SourceConnectivityId[] => { - const connectivityNetId = - sourceConnectivityMap.getNetConnectedToId(sourcePortId) - if (!connectivityNetId) return [] - return sourceConnectivityMap.getIdsConnectedToNet( - connectivityNetId, - ) as SourceConnectivityId[] - } - - const sourcePortIsConnectedToGround = (sourcePort: SourcePort): boolean => { - const connectedIds = getIdsConnectedToSourcePort(sourcePort.source_port_id) - const connectedSourceNets = connectedIds - .map((connectedId) => sourceNetsById.get(connectedId)) - .filter((sourceNet): sourceNet is SourceNet => sourceNet !== undefined) - - if (connectedSourceNets.some((sourceNet) => sourceNet.is_ground)) { - return true - } - - return connectedIds - .map((connectedId) => sourcePortsById.get(connectedId)) - .filter( - (connectedSourcePort): connectedSourcePort is SourcePort => - connectedSourcePort !== undefined, - ) - .some(sourcePortLooksLikeGround) - } - - const relationships: DecouplingCapacitorRelationship[] = [] - - for (const capacitorSourceComponent of sourceComponents) { - if (capacitorSourceComponent.ftype !== "simple_capacitor") continue - - const capacitorSourcePorts = sourcePorts.filter( - (sourcePort): sourcePort is SourceComponentPort => - sourcePort.source_component_id === - capacitorSourceComponent.source_component_id, - ) - if (capacitorSourcePorts.length !== 2) continue - - const capacitorGroundSourcePorts = capacitorSourcePorts.filter( - sourcePortIsConnectedToGround, - ) - if (capacitorGroundSourcePorts.length !== 1) continue - - const capacitorGroundSourcePort = capacitorGroundSourcePorts[0] - const capacitorPowerSourcePort = capacitorSourcePorts.find( - (sourcePort) => - sourcePort.source_port_id !== capacitorGroundSourcePort.source_port_id, - ) - if (!capacitorPowerSourcePort) continue - - const connectedPowerRailIds = new Set( - getIdsConnectedToSourcePort(capacitorPowerSourcePort.source_port_id), - ) - const eligibleChipPowerSourcePorts = sourcePorts.filter( - (sourcePort): sourcePort is SourceComponentPort => { - if (!connectedPowerRailIds.has(sourcePort.source_port_id)) return false - if (!sourcePortShouldHaveDecouplingCapacitor(sourcePort)) return false - if (!sourcePort.source_component_id) return false - return ( - sourceComponentsById.get(sourcePort.source_component_id)?.ftype === - "simple_chip" - ) - }, - ) - const directlyConnectedChipPowerSourcePorts = - eligibleChipPowerSourcePorts.filter((chipPowerSourcePort) => - sourceTraces.some((sourceTrace) => - traceDirectlyConnectsSourcePorts( - sourceTrace, - capacitorPowerSourcePort.source_port_id, - chipPowerSourcePort.source_port_id, - ), - ), - ) - const chipPowerSourcePort = - directlyConnectedChipPowerSourcePorts.length === 1 - ? directlyConnectedChipPowerSourcePorts[0] - : directlyConnectedChipPowerSourcePorts.length === 0 && - eligibleChipPowerSourcePorts.length === 1 - ? eligibleChipPowerSourcePorts[0] - : undefined - if (!chipPowerSourcePort) continue - - const chipSourceComponent = sourceComponentsById.get( - chipPowerSourcePort.source_component_id, - ) - if (!isSourceSimpleChip(chipSourceComponent)) continue - - relationships.push({ - chipSourceComponent, - capacitorSourceComponent, - chipPowerSourcePort, - capacitorPowerSourcePort, - capacitorGroundSourcePort, - }) - } - - return relationships -} diff --git a/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts b/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts new file mode 100644 index 000000000..8d572ea0a --- /dev/null +++ b/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts @@ -0,0 +1,128 @@ +import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" +import type { + SourceComponentBase, + SourcePort, + SourceSimpleCapacitor, +} from "circuit-json" +import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" +import { GROUND_NET_REGEX } from "lib/utils/gnd-power-net-regex" + +type SourceComponentId = SourceComponentBase["source_component_id"] + +const COMMON_POWER_INPUT_PIN_REGEX = + /^(?:VCC|VDD|AVCC|AVDD|DVCC|DVDD|PVCC|PVDD|IOVCC|IOVDD)[A-Z0-9_]*$/i + +const getSourcePortLabels = (sourcePort: SourcePort): string[] => [ + sourcePort.name, + ...(sourcePort.port_hints ?? []), +] + +const sourcePortShouldHaveDecouplingCapacitor = ( + sourcePort: SourcePort, +): boolean => { + if (sourcePort.should_have_decoupling_capacitor !== undefined) { + return sourcePort.should_have_decoupling_capacitor + } + if (sourcePort.provides_power === true) return false + if (sourcePort.requires_power !== undefined) return sourcePort.requires_power + + return getSourcePortLabels(sourcePort).some((sourcePortLabel) => + COMMON_POWER_INPUT_PIN_REGEX.test(sourcePortLabel), + ) +} + +const sourcePortLooksLikeGround = (sourcePort: SourcePort): boolean => + sourcePort.requires_ground === true || + sourcePort.provides_ground === true || + getSourcePortLabels(sourcePort).some((sourcePortLabel) => + GROUND_NET_REGEX.test(sourcePortLabel), + ) + +export const getChipSourcePortsMissingDecouplingCapacitor = ( + db: CircuitJsonUtilObjects, + chipSourceComponentId: SourceComponentId, +): SourcePort[] => { + const chipSourceComponent = db.source_component.get(chipSourceComponentId) + if (chipSourceComponent?.ftype !== "simple_chip") return [] + + const sourcePorts = db.source_port.list() + const sourcePortsBySourceComponentId = new Map< + SourceComponentId, + SourcePort[] + >() + for (const sourcePort of sourcePorts) { + if (!sourcePort.source_component_id) continue + const componentSourcePorts = + sourcePortsBySourceComponentId.get(sourcePort.source_component_id) ?? [] + componentSourcePorts.push(sourcePort) + sourcePortsBySourceComponentId.set( + sourcePort.source_component_id, + componentSourcePorts, + ) + } + + const capacitorSourceComponents = db.source_component + .list() + .filter( + (sourceComponent): sourceComponent is SourceSimpleCapacitor => + sourceComponent.ftype === "simple_capacitor", + ) + const sourceConnectivityMap = getSourcePortConnectivityMapFromCircuitJson( + db.toArray(), + ) + const groundSourcePorts = sourcePorts.filter(sourcePortLooksLikeGround) + const groundSourceNets = db.source_net + .list() + .filter((sourceNet) => sourceNet.is_ground) + + const sourcePortIsConnectedToGround = (sourcePort: SourcePort): boolean => + groundSourcePorts.some((groundSourcePort) => + sourceConnectivityMap.areIdsConnected( + sourcePort.source_port_id, + groundSourcePort.source_port_id, + ), + ) || + groundSourceNets.some((groundSourceNet) => + sourceConnectivityMap.areIdsConnected( + sourcePort.source_port_id, + groundSourceNet.source_net_id, + ), + ) + + const chipPowerSourcePorts = ( + sourcePortsBySourceComponentId.get(chipSourceComponentId) ?? [] + ).filter((sourcePort) => { + if (!sourcePortShouldHaveDecouplingCapacitor(sourcePort)) return false + const connectedNetId = sourceConnectivityMap.getNetConnectedToId( + sourcePort.source_port_id, + ) + if (!connectedNetId) return false + return sourceConnectivityMap + .getIdsConnectedToNet(connectedNetId) + .some((connectedId) => connectedId !== sourcePort.source_port_id) + }) + + return chipPowerSourcePorts.filter( + (chipPowerSourcePort) => + !capacitorSourceComponents.some((capacitorSourceComponent) => { + const capacitorSourcePorts = + sourcePortsBySourceComponentId.get( + capacitorSourceComponent.source_component_id, + ) ?? [] + if (capacitorSourcePorts.length !== 2) return false + + return capacitorSourcePorts.some( + (capacitorPowerSourcePort, capacitorPowerSourcePortIndex) => { + const capacitorGroundSourcePort = + capacitorSourcePorts[1 - capacitorPowerSourcePortIndex] + return ( + sourceConnectivityMap.areIdsConnected( + chipPowerSourcePort.source_port_id, + capacitorPowerSourcePort.source_port_id, + ) && sourcePortIsConnectedToGround(capacitorGroundSourcePort) + ) + }, + ) + }), + ) +} diff --git a/tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx b/tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx new file mode 100644 index 000000000..d0a61aa67 --- /dev/null +++ b/tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx @@ -0,0 +1,73 @@ +import { expect, test } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("warns when a chip power pin does not have a decoupling capacitor", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + + + + + + + + + + , + ) + + await circuit.renderUntilSettled() + + const decouplingCapacitorWarnings = + circuit.db.source_pin_missing_trace_warning + .list() + .filter((warning) => warning.message.includes("decoupling capacitor")) + + expect(decouplingCapacitorWarnings).toHaveLength(1) + expect(decouplingCapacitorWarnings[0]).toMatchObject({ + warning_type: "source_pin_missing_trace_warning", + message: + "Power pin VCC on U_MISSING should have a 100nF decoupling capacitor connected to ground", + }) +}) diff --git a/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg b/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg deleted file mode 100644 index e50e3991e..000000000 --- a/tests/features/pcb-pack-layout/__snapshots__/automatic-decoupling-capacitor-placement-pcb.snap.svg +++ /dev/null @@ -1 +0,0 @@ -U_AUTOC_AUTOU_HOLDC_HOLDC_AUTO: inferred VCC decoupling on bottom; C_HOLD: VBAT opt-out on top \ No newline at end of file diff --git a/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx b/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx deleted file mode 100644 index b48f2f06e..000000000 --- a/tests/features/pcb-pack-layout/automatic-decoupling-capacitor-placement.test.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { expect, test } from "bun:test" -import { getDecouplingCapacitorRelationships } from "lib/utils/decoupling-capacitors/get-decoupling-capacitor-relationships" -import { getTestFixture } from "tests/fixtures/get-test-fixture" - -test("automatic decoupling placement compared with an explicit opt-out", async () => { - const { circuit } = getTestFixture() - - circuit.add( - - - - - - - - - - - - - - - , - ) - - await circuit.renderUntilSettled() - - const relationships = getDecouplingCapacitorRelationships(circuit.db) - expect(relationships).toHaveLength(1) - const autoSourceComponent = circuit.db.source_component.getWhere({ - name: "C_AUTO", - })! - const autoChipSourceComponent = circuit.db.source_component.getWhere({ - name: "U_AUTO", - })! - const holdSourceComponent = circuit.db.source_component.getWhere({ - name: "C_HOLD", - })! - const holdChipSourceComponent = circuit.db.source_component.getWhere({ - name: "U_HOLD", - })! - const autoPcbComponent = circuit.db.pcb_component.getWhere({ - source_component_id: autoSourceComponent.source_component_id, - })! - const autoChipPcbComponent = circuit.db.pcb_component.getWhere({ - source_component_id: autoChipSourceComponent.source_component_id, - })! - const holdChipPcbComponent = circuit.db.pcb_component.getWhere({ - source_component_id: holdChipSourceComponent.source_component_id, - })! - const holdPcbComponent = circuit.db.pcb_component.getWhere({ - source_component_id: holdSourceComponent.source_component_id, - })! - const autoChipPowerPcbPort = circuit.db.pcb_port.getWhere({ - source_port_id: relationships[0].chipPowerSourcePort.source_port_id, - })! - const autoChipPowerSmtPad = circuit.db.pcb_smtpad - .list() - .find( - (pcbSmtPad) => pcbSmtPad.pcb_port_id === autoChipPowerPcbPort.pcb_port_id, - )! - const autoChipPowerPadCenter = - "x" in autoChipPowerSmtPad - ? { x: autoChipPowerSmtPad.x, y: autoChipPowerSmtPad.y } - : { - x: - autoChipPowerSmtPad.points.reduce( - (sum, point) => sum + point.x, - 0, - ) / autoChipPowerSmtPad.points.length, - y: - autoChipPowerSmtPad.points.reduce( - (sum, point) => sum + point.y, - 0, - ) / autoChipPowerSmtPad.points.length, - } - const autoCadComponent = circuit.db.cad_component.getWhere({ - pcb_component_id: autoPcbComponent.pcb_component_id, - })! - const holdCadComponent = circuit.db.cad_component.getWhere({ - pcb_component_id: holdPcbComponent.pcb_component_id, - })! - - expect(circuit.db.pcb_packing_error.list()).toHaveLength(0) - expect( - [autoChipPcbComponent, holdChipPcbComponent].map( - ({ center, position_mode }) => ({ center, position_mode }), - ), - ).toEqual([ - { - center: { x: -6, y: 0 }, - position_mode: "relative_to_group_anchor", - }, - { - center: { x: 6, y: 0 }, - position_mode: "relative_to_group_anchor", - }, - ]) - expect(relationships[0].capacitorSourceComponent.source_component_id).toBe( - autoSourceComponent.source_component_id, - ) - expect( - Math.hypot( - autoPcbComponent.center.x - autoChipPowerPadCenter.x, - autoPcbComponent.center.y - autoChipPowerPadCenter.y, - ), - ).toBeLessThan( - Math.hypot( - holdPcbComponent.center.x - autoChipPowerPadCenter.x, - holdPcbComponent.center.y - autoChipPowerPadCenter.y, - ), - ) - - const autoSmtPads = circuit.db.pcb_smtpad - .list() - .filter( - (pcbSmtPad) => - pcbSmtPad.pcb_component_id === autoPcbComponent.pcb_component_id, - ) - const autoPcbPorts = circuit.db.pcb_port - .list() - .filter( - (pcbPort) => - pcbPort.pcb_component_id === autoPcbComponent.pcb_component_id, - ) - const holdSmtPads = circuit.db.pcb_smtpad - .list() - .filter( - (pcbSmtPad) => - pcbSmtPad.pcb_component_id === holdPcbComponent.pcb_component_id, - ) - const holdPcbPorts = circuit.db.pcb_port - .list() - .filter( - (pcbPort) => - pcbPort.pcb_component_id === holdPcbComponent.pcb_component_id, - ) - const autoSilkscreenElements = circuit.db - .toArray() - .filter( - (element) => - element.type.startsWith("pcb_silkscreen_") && - "pcb_component_id" in element && - element.pcb_component_id === autoPcbComponent.pcb_component_id, - ) - - expect( - [ - autoSmtPads.length, - autoPcbPorts.length, - autoSilkscreenElements.length, - holdSmtPads.length, - holdPcbPorts.length, - ].every((primitiveCount) => primitiveCount > 0), - ).toBe(true) - expect(autoPcbComponent.layer).toBe("bottom") - expect(autoSmtPads.every((pcbSmtPad) => pcbSmtPad.layer === "bottom")).toBe( - true, - ) - expect( - autoPcbPorts.every((pcbPort) => pcbPort.layers.includes("bottom")), - ).toBe(true) - expect( - autoSilkscreenElements.every( - (element) => "layer" in element && element.layer === "bottom", - ), - ).toBe(true) - expect(autoCadComponent.pcb_component_id).toBe( - autoPcbComponent.pcb_component_id, - ) - expect(autoCadComponent.position.z).toBeLessThan(0) - expect(autoCadComponent.rotation?.y).toBe(180) - expect(holdPcbComponent.layer).toBe("top") - expect(holdSmtPads.every((pcbSmtPad) => pcbSmtPad.layer === "top")).toBe(true) - expect(holdPcbPorts.every((pcbPort) => pcbPort.layers.includes("top"))).toBe( - true, - ) - expect(holdCadComponent.position.z).toBeGreaterThan(0) - expect(holdCadComponent.rotation?.y).toBe(0) - - expect(circuit).toMatchPcbSnapshot(import.meta.path) -}) From 928d49ed5e614df20218b545e44148fc3fb0a1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 21:53:33 +0530 Subject: [PATCH 3/8] Refactor decoupling warning checks --- ...nt_doInitialDecouplingCapacitorWarnings.ts | 38 ++++ ...mponent_doInitialSourceDesignRuleChecks.ts | 28 +-- ...urce-ports-missing-decoupling-capacitor.ts | 213 ++++++++++++------ 3 files changed, 190 insertions(+), 89 deletions(-) create mode 100644 lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts diff --git a/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts b/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts new file mode 100644 index 000000000..29e7cdbc7 --- /dev/null +++ b/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts @@ -0,0 +1,38 @@ +import { getChipSourcePortsMissingDecouplingCapacitor } from "lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor" +import type { NormalComponent } from "./NormalComponent" + +const getSourcePortDisplayLabel = (sourcePort: { + name: string + port_hints?: string[] +}): string => + sourcePort.port_hints?.find((sourcePortHint) => + /[A-Za-z]/.test(sourcePortHint), + ) ?? sourcePort.name + +export const NormalComponent_doInitialDecouplingCapacitorWarnings = ( + component: NormalComponent, +): void => { + if (component.config.componentName !== "Chip") return + if (!component.source_component_id || !component.root) return + + const sourcePortsMissingDecouplingCapacitor = + getChipSourcePortsMissingDecouplingCapacitor( + component.root.db, + component.source_component_id, + ) + + for (const sourcePort of sourcePortsMissingDecouplingCapacitor) { + const recommendedCapacitance = + sourcePort.recommended_decoupling_capacitor_capacitance + const capacitanceDescription = + recommendedCapacitance === undefined ? "" : ` ${recommendedCapacitance}` + + component.root.db.source_pin_missing_trace_warning.insert({ + message: `Power pin ${getSourcePortDisplayLabel(sourcePort)} on ${component.props.name} should have a${capacitanceDescription} decoupling capacitor connected to ground`, + source_component_id: component.source_component_id, + source_port_id: sourcePort.source_port_id, + subcircuit_id: component.getSubcircuit().subcircuit_id ?? undefined, + warning_type: "source_pin_missing_trace_warning", + }) + } +} diff --git a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts index 58b94d454..6885b1f33 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts @@ -1,6 +1,6 @@ import type { NormalComponent } from "./NormalComponent" import type { Port } from "../../primitive-components/Port" -import { getChipSourcePortsMissingDecouplingCapacitor } from "lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor" +import { NormalComponent_doInitialDecouplingCapacitorWarnings } from "./NormalComponent_doInitialDecouplingCapacitorWarnings" export const NormalComponent_doInitialSourceDesignRuleChecks = ( component: NormalComponent, @@ -44,31 +44,7 @@ export const NormalComponent_doInitialSourceDesignRuleChecks = ( }) } - if (component.config.componentName !== "Chip") return - - const sourcePortsMissingDecouplingCapacitor = - getChipSourcePortsMissingDecouplingCapacitor( - db, - component.source_component_id, - ) - for (const sourcePort of sourcePortsMissingDecouplingCapacitor) { - const sourcePortLabel = - sourcePort.port_hints?.find((sourcePortHint) => - /[A-Za-z]/.test(sourcePortHint), - ) ?? sourcePort.name - const recommendedCapacitance = - sourcePort.recommended_decoupling_capacitor_capacitance - const capacitanceDescription = - recommendedCapacitance === undefined ? "" : ` ${recommendedCapacitance}` - - db.source_pin_missing_trace_warning.insert({ - message: `Power pin ${sourcePortLabel} on ${component.props.name} should have a${capacitanceDescription} decoupling capacitor connected to ground`, - source_component_id: component.source_component_id, - source_port_id: sourcePort.source_port_id, - subcircuit_id: component.getSubcircuit().subcircuit_id ?? undefined, - warning_type: "source_pin_missing_trace_warning", - }) - } + NormalComponent_doInitialDecouplingCapacitorWarnings(component) } export const shouldCheckPortForMissingTrace = ( diff --git a/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts b/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts index 8d572ea0a..653b2cd17 100644 --- a/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts +++ b/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts @@ -1,16 +1,29 @@ import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" import type { SourceComponentBase, + SourceNet, SourcePort, SourceSimpleCapacitor, } from "circuit-json" import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" -import { GROUND_NET_REGEX } from "lib/utils/gnd-power-net-regex" +import { + GROUND_NET_REGEX, + POWER_NET_REGEX, +} from "lib/utils/gnd-power-net-regex" type SourceComponentId = SourceComponentBase["source_component_id"] +type SourceNetId = SourceNet["source_net_id"] +type SourcePortId = SourcePort["source_port_id"] +type SourceConnectivityId = SourceNetId | SourcePortId -const COMMON_POWER_INPUT_PIN_REGEX = - /^(?:VCC|VDD|AVCC|AVDD|DVCC|DVDD|PVCC|PVDD|IOVCC|IOVDD)[A-Z0-9_]*$/i +interface SourceCircuitRelationships { + areConnected: ( + firstSourceConnectivityId: SourceConnectivityId, + secondSourceConnectivityId: SourceConnectivityId, + ) => boolean + sourcePortHasConnection: (sourcePortId: SourcePortId) => boolean + sourcePortIsConnectedToGround: (sourcePort: SourcePort) => boolean +} const getSourcePortLabels = (sourcePort: SourcePort): string[] => [ sourcePort.name, @@ -27,7 +40,7 @@ const sourcePortShouldHaveDecouplingCapacitor = ( if (sourcePort.requires_power !== undefined) return sourcePort.requires_power return getSourcePortLabels(sourcePort).some((sourcePortLabel) => - COMMON_POWER_INPUT_PIN_REGEX.test(sourcePortLabel), + POWER_NET_REGEX.test(sourcePortLabel), ) } @@ -38,18 +51,14 @@ const sourcePortLooksLikeGround = (sourcePort: SourcePort): boolean => GROUND_NET_REGEX.test(sourcePortLabel), ) -export const getChipSourcePortsMissingDecouplingCapacitor = ( - db: CircuitJsonUtilObjects, - chipSourceComponentId: SourceComponentId, -): SourcePort[] => { - const chipSourceComponent = db.source_component.get(chipSourceComponentId) - if (chipSourceComponent?.ftype !== "simple_chip") return [] - - const sourcePorts = db.source_port.list() +const getSourcePortsBySourceComponentId = ( + sourcePorts: SourcePort[], +): Map => { const sourcePortsBySourceComponentId = new Map< SourceComponentId, SourcePort[] >() + for (const sourcePort of sourcePorts) { if (!sourcePort.source_component_id) continue const componentSourcePorts = @@ -61,12 +70,13 @@ export const getChipSourcePortsMissingDecouplingCapacitor = ( ) } - const capacitorSourceComponents = db.source_component - .list() - .filter( - (sourceComponent): sourceComponent is SourceSimpleCapacitor => - sourceComponent.ftype === "simple_capacitor", - ) + return sourcePortsBySourceComponentId +} + +const createSourceCircuitRelationships = ( + db: CircuitJsonUtilObjects, + sourcePorts: SourcePort[], +): SourceCircuitRelationships => { const sourceConnectivityMap = getSourcePortConnectivityMapFromCircuitJson( db.toArray(), ) @@ -75,54 +85,131 @@ export const getChipSourcePortsMissingDecouplingCapacitor = ( .list() .filter((sourceNet) => sourceNet.is_ground) - const sourcePortIsConnectedToGround = (sourcePort: SourcePort): boolean => - groundSourcePorts.some((groundSourcePort) => - sourceConnectivityMap.areIdsConnected( - sourcePort.source_port_id, - groundSourcePort.source_port_id, - ), - ) || - groundSourceNets.some((groundSourceNet) => - sourceConnectivityMap.areIdsConnected( - sourcePort.source_port_id, - groundSourceNet.source_net_id, + const areConnected = ( + firstSourceConnectivityId: SourceConnectivityId, + secondSourceConnectivityId: SourceConnectivityId, + ): boolean => + sourceConnectivityMap.areIdsConnected( + firstSourceConnectivityId, + secondSourceConnectivityId, + ) + + return { + areConnected, + sourcePortHasConnection: (sourcePortId) => { + const connectedNetId = + sourceConnectivityMap.getNetConnectedToId(sourcePortId) + if (!connectedNetId) return false + return sourceConnectivityMap + .getIdsConnectedToNet(connectedNetId) + .some((connectedId) => connectedId !== sourcePortId) + }, + sourcePortIsConnectedToGround: (sourcePort) => + groundSourcePorts.some((groundSourcePort) => + areConnected( + sourcePort.source_port_id, + groundSourcePort.source_port_id, + ), + ) || + groundSourceNets.some((groundSourceNet) => + areConnected(sourcePort.source_port_id, groundSourceNet.source_net_id), ), + } +} + +const capacitorConnectsChipPowerSourcePortToGround = ({ + capacitorSourcePorts, + chipPowerSourcePort, + sourceCircuitRelationships, +}: { + capacitorSourcePorts: SourcePort[] + chipPowerSourcePort: SourcePort + sourceCircuitRelationships: SourceCircuitRelationships +}): boolean => { + if (capacitorSourcePorts.length !== 2) return false + + const [firstCapacitorSourcePort, secondCapacitorSourcePort] = + capacitorSourcePorts + const capacitorPortsBridgePowerToGround = ( + capacitorPowerSourcePort: SourcePort, + capacitorGroundSourcePort: SourcePort, + ): boolean => + sourceCircuitRelationships.areConnected( + chipPowerSourcePort.source_port_id, + capacitorPowerSourcePort.source_port_id, + ) && + sourceCircuitRelationships.sourcePortIsConnectedToGround( + capacitorGroundSourcePort, ) - const chipPowerSourcePorts = ( - sourcePortsBySourceComponentId.get(chipSourceComponentId) ?? [] - ).filter((sourcePort) => { - if (!sourcePortShouldHaveDecouplingCapacitor(sourcePort)) return false - const connectedNetId = sourceConnectivityMap.getNetConnectedToId( - sourcePort.source_port_id, + return ( + capacitorPortsBridgePowerToGround( + firstCapacitorSourcePort, + secondCapacitorSourcePort, + ) || + capacitorPortsBridgePowerToGround( + secondCapacitorSourcePort, + firstCapacitorSourcePort, ) - if (!connectedNetId) return false - return sourceConnectivityMap - .getIdsConnectedToNet(connectedNetId) - .some((connectedId) => connectedId !== sourcePort.source_port_id) - }) - - return chipPowerSourcePorts.filter( - (chipPowerSourcePort) => - !capacitorSourceComponents.some((capacitorSourceComponent) => { - const capacitorSourcePorts = - sourcePortsBySourceComponentId.get( - capacitorSourceComponent.source_component_id, - ) ?? [] - if (capacitorSourcePorts.length !== 2) return false - - return capacitorSourcePorts.some( - (capacitorPowerSourcePort, capacitorPowerSourcePortIndex) => { - const capacitorGroundSourcePort = - capacitorSourcePorts[1 - capacitorPowerSourcePortIndex] - return ( - sourceConnectivityMap.areIdsConnected( - chipPowerSourcePort.source_port_id, - capacitorPowerSourcePort.source_port_id, - ) && sourcePortIsConnectedToGround(capacitorGroundSourcePort) - ) - }, - ) - }), ) } + +export const getChipSourcePortsMissingDecouplingCapacitor = ( + db: CircuitJsonUtilObjects, + chipSourceComponentId: SourceComponentId, +): SourcePort[] => { + const chipSourceComponent = db.source_component.get(chipSourceComponentId) + if (chipSourceComponent?.ftype !== "simple_chip") return [] + + const sourcePorts = db.source_port.list() + const sourcePortsBySourceComponentId = + getSourcePortsBySourceComponentId(sourcePorts) + const sourceCircuitRelationships = createSourceCircuitRelationships( + db, + sourcePorts, + ) + const capacitorSourceComponents = db.source_component + .list() + .filter( + (sourceComponent): sourceComponent is SourceSimpleCapacitor => + sourceComponent.ftype === "simple_capacitor", + ) + const chipSourcePorts = + sourcePortsBySourceComponentId.get(chipSourceComponentId) ?? [] + const sourcePortsMissingDecouplingCapacitor: SourcePort[] = [] + + for (const chipSourcePort of chipSourcePorts) { + if (!sourcePortShouldHaveDecouplingCapacitor(chipSourcePort)) continue + if ( + !sourceCircuitRelationships.sourcePortHasConnection( + chipSourcePort.source_port_id, + ) + ) { + continue + } + + let hasDecouplingCapacitor = false + for (const capacitorSourceComponent of capacitorSourceComponents) { + const capacitorSourcePorts = + sourcePortsBySourceComponentId.get( + capacitorSourceComponent.source_component_id, + ) ?? [] + if ( + capacitorConnectsChipPowerSourcePortToGround({ + capacitorSourcePorts, + chipPowerSourcePort: chipSourcePort, + sourceCircuitRelationships, + }) + ) { + hasDecouplingCapacitor = true + break + } + } + + if (!hasDecouplingCapacitor) { + sourcePortsMissingDecouplingCapacitor.push(chipSourcePort) + } + } + + return sourcePortsMissingDecouplingCapacitor +} From 7399b6a1886fc2ae3e3c97166eceb2658ceee5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 22:19:55 +0530 Subject: [PATCH 4/8] Move decoupling warnings to checks --- ...nt_doInitialDecouplingCapacitorWarnings.ts | 38 ---- ...mponent_doInitialSourceDesignRuleChecks.ts | 3 - ...urce-ports-missing-decoupling-capacitor.ts | 215 ------------------ ...hip-decoupling-metadata-schematic.snap.svg | 12 + ....tsx => chip-decoupling-metadata.test.tsx} | 45 +++- 5 files changed, 48 insertions(+), 265 deletions(-) delete mode 100644 lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts delete mode 100644 lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts create mode 100644 tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg rename tests/components/normal-components/{chip-missing-decoupling-capacitor-warning.test.tsx => chip-decoupling-metadata.test.tsx} (58%) diff --git a/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts b/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts deleted file mode 100644 index 29e7cdbc7..000000000 --- a/lib/components/base-components/NormalComponent/NormalComponent_doInitialDecouplingCapacitorWarnings.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getChipSourcePortsMissingDecouplingCapacitor } from "lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor" -import type { NormalComponent } from "./NormalComponent" - -const getSourcePortDisplayLabel = (sourcePort: { - name: string - port_hints?: string[] -}): string => - sourcePort.port_hints?.find((sourcePortHint) => - /[A-Za-z]/.test(sourcePortHint), - ) ?? sourcePort.name - -export const NormalComponent_doInitialDecouplingCapacitorWarnings = ( - component: NormalComponent, -): void => { - if (component.config.componentName !== "Chip") return - if (!component.source_component_id || !component.root) return - - const sourcePortsMissingDecouplingCapacitor = - getChipSourcePortsMissingDecouplingCapacitor( - component.root.db, - component.source_component_id, - ) - - for (const sourcePort of sourcePortsMissingDecouplingCapacitor) { - const recommendedCapacitance = - sourcePort.recommended_decoupling_capacitor_capacitance - const capacitanceDescription = - recommendedCapacitance === undefined ? "" : ` ${recommendedCapacitance}` - - component.root.db.source_pin_missing_trace_warning.insert({ - message: `Power pin ${getSourcePortDisplayLabel(sourcePort)} on ${component.props.name} should have a${capacitanceDescription} decoupling capacitor connected to ground`, - source_component_id: component.source_component_id, - source_port_id: sourcePort.source_port_id, - subcircuit_id: component.getSubcircuit().subcircuit_id ?? undefined, - warning_type: "source_pin_missing_trace_warning", - }) - } -} diff --git a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts index 6885b1f33..f484332df 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent_doInitialSourceDesignRuleChecks.ts @@ -1,6 +1,5 @@ import type { NormalComponent } from "./NormalComponent" import type { Port } from "../../primitive-components/Port" -import { NormalComponent_doInitialDecouplingCapacitorWarnings } from "./NormalComponent_doInitialDecouplingCapacitorWarnings" export const NormalComponent_doInitialSourceDesignRuleChecks = ( component: NormalComponent, @@ -43,8 +42,6 @@ export const NormalComponent_doInitialSourceDesignRuleChecks = ( warning_type: "source_pin_missing_trace_warning", }) } - - NormalComponent_doInitialDecouplingCapacitorWarnings(component) } export const shouldCheckPortForMissingTrace = ( diff --git a/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts b/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts deleted file mode 100644 index 653b2cd17..000000000 --- a/lib/utils/source/get-chip-source-ports-missing-decoupling-capacitor.ts +++ /dev/null @@ -1,215 +0,0 @@ -import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" -import type { - SourceComponentBase, - SourceNet, - SourcePort, - SourceSimpleCapacitor, -} from "circuit-json" -import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" -import { - GROUND_NET_REGEX, - POWER_NET_REGEX, -} from "lib/utils/gnd-power-net-regex" - -type SourceComponentId = SourceComponentBase["source_component_id"] -type SourceNetId = SourceNet["source_net_id"] -type SourcePortId = SourcePort["source_port_id"] -type SourceConnectivityId = SourceNetId | SourcePortId - -interface SourceCircuitRelationships { - areConnected: ( - firstSourceConnectivityId: SourceConnectivityId, - secondSourceConnectivityId: SourceConnectivityId, - ) => boolean - sourcePortHasConnection: (sourcePortId: SourcePortId) => boolean - sourcePortIsConnectedToGround: (sourcePort: SourcePort) => boolean -} - -const getSourcePortLabels = (sourcePort: SourcePort): string[] => [ - sourcePort.name, - ...(sourcePort.port_hints ?? []), -] - -const sourcePortShouldHaveDecouplingCapacitor = ( - sourcePort: SourcePort, -): boolean => { - if (sourcePort.should_have_decoupling_capacitor !== undefined) { - return sourcePort.should_have_decoupling_capacitor - } - if (sourcePort.provides_power === true) return false - if (sourcePort.requires_power !== undefined) return sourcePort.requires_power - - return getSourcePortLabels(sourcePort).some((sourcePortLabel) => - POWER_NET_REGEX.test(sourcePortLabel), - ) -} - -const sourcePortLooksLikeGround = (sourcePort: SourcePort): boolean => - sourcePort.requires_ground === true || - sourcePort.provides_ground === true || - getSourcePortLabels(sourcePort).some((sourcePortLabel) => - GROUND_NET_REGEX.test(sourcePortLabel), - ) - -const getSourcePortsBySourceComponentId = ( - sourcePorts: SourcePort[], -): Map => { - const sourcePortsBySourceComponentId = new Map< - SourceComponentId, - SourcePort[] - >() - - for (const sourcePort of sourcePorts) { - if (!sourcePort.source_component_id) continue - const componentSourcePorts = - sourcePortsBySourceComponentId.get(sourcePort.source_component_id) ?? [] - componentSourcePorts.push(sourcePort) - sourcePortsBySourceComponentId.set( - sourcePort.source_component_id, - componentSourcePorts, - ) - } - - return sourcePortsBySourceComponentId -} - -const createSourceCircuitRelationships = ( - db: CircuitJsonUtilObjects, - sourcePorts: SourcePort[], -): SourceCircuitRelationships => { - const sourceConnectivityMap = getSourcePortConnectivityMapFromCircuitJson( - db.toArray(), - ) - const groundSourcePorts = sourcePorts.filter(sourcePortLooksLikeGround) - const groundSourceNets = db.source_net - .list() - .filter((sourceNet) => sourceNet.is_ground) - - const areConnected = ( - firstSourceConnectivityId: SourceConnectivityId, - secondSourceConnectivityId: SourceConnectivityId, - ): boolean => - sourceConnectivityMap.areIdsConnected( - firstSourceConnectivityId, - secondSourceConnectivityId, - ) - - return { - areConnected, - sourcePortHasConnection: (sourcePortId) => { - const connectedNetId = - sourceConnectivityMap.getNetConnectedToId(sourcePortId) - if (!connectedNetId) return false - return sourceConnectivityMap - .getIdsConnectedToNet(connectedNetId) - .some((connectedId) => connectedId !== sourcePortId) - }, - sourcePortIsConnectedToGround: (sourcePort) => - groundSourcePorts.some((groundSourcePort) => - areConnected( - sourcePort.source_port_id, - groundSourcePort.source_port_id, - ), - ) || - groundSourceNets.some((groundSourceNet) => - areConnected(sourcePort.source_port_id, groundSourceNet.source_net_id), - ), - } -} - -const capacitorConnectsChipPowerSourcePortToGround = ({ - capacitorSourcePorts, - chipPowerSourcePort, - sourceCircuitRelationships, -}: { - capacitorSourcePorts: SourcePort[] - chipPowerSourcePort: SourcePort - sourceCircuitRelationships: SourceCircuitRelationships -}): boolean => { - if (capacitorSourcePorts.length !== 2) return false - - const [firstCapacitorSourcePort, secondCapacitorSourcePort] = - capacitorSourcePorts - const capacitorPortsBridgePowerToGround = ( - capacitorPowerSourcePort: SourcePort, - capacitorGroundSourcePort: SourcePort, - ): boolean => - sourceCircuitRelationships.areConnected( - chipPowerSourcePort.source_port_id, - capacitorPowerSourcePort.source_port_id, - ) && - sourceCircuitRelationships.sourcePortIsConnectedToGround( - capacitorGroundSourcePort, - ) - - return ( - capacitorPortsBridgePowerToGround( - firstCapacitorSourcePort, - secondCapacitorSourcePort, - ) || - capacitorPortsBridgePowerToGround( - secondCapacitorSourcePort, - firstCapacitorSourcePort, - ) - ) -} - -export const getChipSourcePortsMissingDecouplingCapacitor = ( - db: CircuitJsonUtilObjects, - chipSourceComponentId: SourceComponentId, -): SourcePort[] => { - const chipSourceComponent = db.source_component.get(chipSourceComponentId) - if (chipSourceComponent?.ftype !== "simple_chip") return [] - - const sourcePorts = db.source_port.list() - const sourcePortsBySourceComponentId = - getSourcePortsBySourceComponentId(sourcePorts) - const sourceCircuitRelationships = createSourceCircuitRelationships( - db, - sourcePorts, - ) - const capacitorSourceComponents = db.source_component - .list() - .filter( - (sourceComponent): sourceComponent is SourceSimpleCapacitor => - sourceComponent.ftype === "simple_capacitor", - ) - const chipSourcePorts = - sourcePortsBySourceComponentId.get(chipSourceComponentId) ?? [] - const sourcePortsMissingDecouplingCapacitor: SourcePort[] = [] - - for (const chipSourcePort of chipSourcePorts) { - if (!sourcePortShouldHaveDecouplingCapacitor(chipSourcePort)) continue - if ( - !sourceCircuitRelationships.sourcePortHasConnection( - chipSourcePort.source_port_id, - ) - ) { - continue - } - - let hasDecouplingCapacitor = false - for (const capacitorSourceComponent of capacitorSourceComponents) { - const capacitorSourcePorts = - sourcePortsBySourceComponentId.get( - capacitorSourceComponent.source_component_id, - ) ?? [] - if ( - capacitorConnectsChipPowerSourcePortToGround({ - capacitorSourcePorts, - chipPowerSourcePort: chipSourcePort, - sourceCircuitRelationships, - }) - ) { - hasDecouplingCapacitor = true - break - } - } - - if (!hasDecouplingCapacitor) { - sourcePortsMissingDecouplingCapacitor.push(chipSourcePort) - } - } - - return sourcePortsMissingDecouplingCapacitor -} diff --git a/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg b/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg new file mode 100644 index 000000000..72da08947 --- /dev/null +++ b/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg @@ -0,0 +1,12 @@ +-10,-6-10,-5-10,-4-10,-3-10,-2-10,-1-10,0-10,1-10,2-10,3-10,4-9,-6-9,-5-9,-4-9,-3-9,-2-9,-1-9,0-9,1-9,2-9,3-9,4-8,-6-8,-5-8,-4-8,-3-8,-2-8,-1-8,0-8,1-8,2-8,3-8,4-7,-6-7,-5-7,-4-7,-3-7,-2-7,-1-7,0-7,1-7,2-7,3-7,4-6,-6-6,-5-6,-4-6,-3-6,-2-6,-1-6,0-6,1-6,2-6,3-6,4-5,-6-5,-5-5,-4-5,-3-5,-2-5,-1-5,0-5,1-5,2-5,3-5,4-4,-6-4,-5-4,-4-4,-3-4,-2-4,-1-4,0-4,1-4,2-4,3-4,4-3,-6-3,-5-3,-4-3,-3-3,-2-3,-1-3,0-3,1-3,2-3,3-3,4-2,-6-2,-5-2,-4-2,-3-2,-2-2,-1-2,0-2,1-2,2-2,3-2,4-1,-6-1,-5-1,-4-1,-3-1,-2-1,-1-1,0-1,1-1,2-1,3-1,40,-60,-50,-40,-30,-20,-10,00,10,20,30,41,-61,-51,-41,-31,-21,-11,01,11,21,31,42,-62,-52,-42,-32,-22,-12,02,12,22,32,43,-63,-53,-43,-33,-23,-13,03,13,23,33,44,-64,-54,-44,-34,-24,-14,04,14,24,34,45,-65,-55,-45,-35,-25,-15,05,15,25,35,46,-66,-56,-46,-36,-26,-16,06,16,26,36,47,-67,-57,-47,-37,-27,-17,07,17,27,37,48,-68,-58,-48,-38,-28,-18,08,18,28,38,49,-69,-59,-49,-39,-29,-19,09,19,29,39,410,-610,-510,-410,-310,-210,-110,010,110,210,310,4U_MISSING1VCC2GNDU_WITH_CAP1VDD2GNDC1100nFU_OPT_OUT1VBAT2GNDU_POWER_SOURCE1VCC2GNDVCC_MISSINGGNDGNDGNDVBATVCC_SOURCEDecoupling requirements are emitted as source-port metadata diff --git a/tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx b/tests/components/normal-components/chip-decoupling-metadata.test.tsx similarity index 58% rename from tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx rename to tests/components/normal-components/chip-decoupling-metadata.test.tsx index d0a61aa67..93bedc2c1 100644 --- a/tests/components/normal-components/chip-missing-decoupling-capacitor-warning.test.tsx +++ b/tests/components/normal-components/chip-decoupling-metadata.test.tsx @@ -1,7 +1,7 @@ import { expect, test } from "bun:test" import { getTestFixture } from "tests/fixtures/get-test-fixture" -test("warns when a chip power pin does not have a decoupling capacitor", async () => { +test("emits chip decoupling metadata for external checks", async () => { const { circuit } = getTestFixture() circuit.add( @@ -54,20 +54,47 @@ test("warns when a chip power pin does not have a decoupling capacitor", async ( /> + + , ) await circuit.renderUntilSettled() - const decouplingCapacitorWarnings = - circuit.db.source_pin_missing_trace_warning + const sourceComponentsByName = new Map( + circuit.db.source_component + .list() + .map((sourceComponent) => [sourceComponent.name, sourceComponent]), + ) + const getSourcePortByHint = (sourceComponentName: string, portHint: string) => + circuit.db.source_port .list() - .filter((warning) => warning.message.includes("decoupling capacitor")) + .find( + (sourcePort) => + sourcePort.source_component_id === + sourceComponentsByName.get(sourceComponentName) + ?.source_component_id && + sourcePort.port_hints?.includes(portHint), + ) - expect(decouplingCapacitorWarnings).toHaveLength(1) - expect(decouplingCapacitorWarnings[0]).toMatchObject({ - warning_type: "source_pin_missing_trace_warning", - message: - "Power pin VCC on U_MISSING should have a 100nF decoupling capacitor connected to ground", + expect(getSourcePortByHint("U_MISSING", "VCC")).toMatchObject({ + requires_power: true, + recommended_decoupling_capacitor_capacitance: "100nF", + }) + expect(getSourcePortByHint("U_MISSING", "GND")).toMatchObject({ + requires_ground: true, + }) + expect(getSourcePortByHint("U_OPT_OUT", "VBAT")).toMatchObject({ + requires_power: true, + should_have_decoupling_capacitor: false, + }) + expect(getSourcePortByHint("U_POWER_SOURCE", "VCC")).toMatchObject({ + provides_power: true, }) + expect(circuit).toMatchSchematicSnapshot(import.meta.path) }) From 05536b3a20bbf26c38724369e8f9efa4c3b6230b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 22:38:19 +0530 Subject: [PATCH 5/8] Emit default decoupling requirements on source ports --- .../primitive-components/Port/Port.ts | 14 +++++--- ...t-decoupling-requirement-to-source-port.ts | 32 +++++++++++++++++++ .../apply-pin-attributes-to-source-port.ts | 3 +- .../chip-decoupling-metadata.test.tsx | 6 ++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts diff --git a/lib/components/primitive-components/Port/Port.ts b/lib/components/primitive-components/Port/Port.ts index 85dbb54b6..3db5d8edf 100644 --- a/lib/components/primitive-components/Port/Port.ts +++ b/lib/components/primitive-components/Port/Port.ts @@ -9,12 +9,13 @@ import { applyToPoint, compose, translate } from "transformation-matrix" import { z } from "zod" import { PrimitiveComponent } from "../../base-components/PrimitiveComponent" import type { Trace } from "../Trace/Trace" -import type { LayerRef, SchematicPort } from "circuit-json" +import type { LayerRef, SchematicPort, SourcePort } from "circuit-json" import { areAllPcbPrimitivesOverlapping } from "./areAllPcbPrimitivesOverlapping" import { getCenterOfPcbPrimitives } from "./getCenterOfPcbPrimitives" import { type PinAttributeMap, portProps } from "@tscircuit/props" import type { INormalComponent } from "lib/components/base-components/NormalComponent/INormalComponent" import { applyPinAttributesToSourcePort } from "./apply-pin-attributes-to-source-port" +import { applyDefaultDecouplingRequirementToSourcePort } from "./apply-default-decoupling-requirement-to-source-port" import { Port_doInitialCreateTracesFromProps } from "./Port_doInitialCreateTracesFromProps" import { Port_tryRenderGroupPcbPort } from "./Port_tryRenderGroupPcbPort" import { getSourcePortNetLabelText } from "lib/utils/schematic/getSourcePortNetLabelText" @@ -393,11 +394,16 @@ export class Port extends PrimitiveComponent { // Get pin attributes from parent component and apply them to this port const pinAttributes = this._getMatchingPinAttributes() - const portAttributesFromParent: Record = {} + const sourcePortAttributes: Partial = {} for (const attributes of pinAttributes) { - applyPinAttributesToSourcePort(portAttributesFromParent, attributes) + applyPinAttributesToSourcePort(sourcePortAttributes, attributes) } + applyDefaultDecouplingRequirementToSourcePort({ + sourcePortAttributes, + sourcePortLabels: port_hints, + parentNormalComponentName: parentNormalComponent?.config.componentName, + }) const source_port = db.source_port.insert({ name: props.name!, @@ -405,7 +411,7 @@ export class Port extends PrimitiveComponent { port_hints, source_component_id: source_component_id!, subcircuit_id: this.getSubcircuit()?.subcircuit_id!, - ...portAttributesFromParent, + ...sourcePortAttributes, }) this.source_port_id = source_port.source_port_id diff --git a/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts b/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts new file mode 100644 index 000000000..971f795d9 --- /dev/null +++ b/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts @@ -0,0 +1,32 @@ +import type { SourcePort } from "circuit-json" +import { POWER_NET_REGEX } from "lib/utils/gnd-power-net-regex" + +export const applyDefaultDecouplingRequirementToSourcePort = ({ + sourcePortAttributes, + sourcePortLabels, + parentNormalComponentName, +}: { + sourcePortAttributes: Partial + sourcePortLabels: string[] + parentNormalComponentName: string | undefined +}): void => { + if (parentNormalComponentName !== "Chip") return + if (sourcePortAttributes.should_have_decoupling_capacitor !== undefined) { + return + } + + if (sourcePortAttributes.provides_power === true) { + sourcePortAttributes.should_have_decoupling_capacitor = false + return + } + + if (sourcePortAttributes.requires_power !== undefined) { + sourcePortAttributes.should_have_decoupling_capacitor = + sourcePortAttributes.requires_power + return + } + + sourcePortAttributes.should_have_decoupling_capacitor = sourcePortLabels.some( + (sourcePortLabel) => POWER_NET_REGEX.test(sourcePortLabel), + ) +} diff --git a/lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts b/lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts index 76234e551..c4db3efdb 100644 --- a/lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts +++ b/lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts @@ -1,7 +1,8 @@ import type { PinAttributeMap } from "@tscircuit/props" +import type { SourcePort } from "circuit-json" export const applyPinAttributesToSourcePort = ( - sourcePortProps: Record, + sourcePortProps: Partial, attributes: PinAttributeMap, ): void => { if (attributes.mustBeConnected !== undefined) { diff --git a/tests/components/normal-components/chip-decoupling-metadata.test.tsx b/tests/components/normal-components/chip-decoupling-metadata.test.tsx index 93bedc2c1..fe677d741 100644 --- a/tests/components/normal-components/chip-decoupling-metadata.test.tsx +++ b/tests/components/normal-components/chip-decoupling-metadata.test.tsx @@ -84,10 +84,15 @@ test("emits chip decoupling metadata for external checks", async () => { expect(getSourcePortByHint("U_MISSING", "VCC")).toMatchObject({ requires_power: true, + should_have_decoupling_capacitor: true, recommended_decoupling_capacitor_capacitance: "100nF", }) expect(getSourcePortByHint("U_MISSING", "GND")).toMatchObject({ requires_ground: true, + should_have_decoupling_capacitor: false, + }) + expect(getSourcePortByHint("U_WITH_CAP", "VDD")).toMatchObject({ + should_have_decoupling_capacitor: true, }) expect(getSourcePortByHint("U_OPT_OUT", "VBAT")).toMatchObject({ requires_power: true, @@ -95,6 +100,7 @@ test("emits chip decoupling metadata for external checks", async () => { }) expect(getSourcePortByHint("U_POWER_SOURCE", "VCC")).toMatchObject({ provides_power: true, + should_have_decoupling_capacitor: false, }) expect(circuit).toMatchSchematicSnapshot(import.meta.path) }) From 55af0f187fcb9a7fbce1b99ee6ce240e6772768d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 22:44:04 +0530 Subject: [PATCH 6/8] Preserve underspecified chip pin diagnostics --- ...ly-default-decoupling-requirement-to-source-port.ts | 10 +++++++--- .../chip-decoupling-metadata.test.tsx | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts b/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts index 971f795d9..19f2674e8 100644 --- a/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts +++ b/lib/components/primitive-components/Port/apply-default-decoupling-requirement-to-source-port.ts @@ -26,7 +26,11 @@ export const applyDefaultDecouplingRequirementToSourcePort = ({ return } - sourcePortAttributes.should_have_decoupling_capacitor = sourcePortLabels.some( - (sourcePortLabel) => POWER_NET_REGEX.test(sourcePortLabel), - ) + if ( + sourcePortLabels.some((sourcePortLabel) => + POWER_NET_REGEX.test(sourcePortLabel), + ) + ) { + sourcePortAttributes.should_have_decoupling_capacitor = true + } } diff --git a/tests/components/normal-components/chip-decoupling-metadata.test.tsx b/tests/components/normal-components/chip-decoupling-metadata.test.tsx index fe677d741..768f2375f 100644 --- a/tests/components/normal-components/chip-decoupling-metadata.test.tsx +++ b/tests/components/normal-components/chip-decoupling-metadata.test.tsx @@ -89,7 +89,6 @@ test("emits chip decoupling metadata for external checks", async () => { }) expect(getSourcePortByHint("U_MISSING", "GND")).toMatchObject({ requires_ground: true, - should_have_decoupling_capacitor: false, }) expect(getSourcePortByHint("U_WITH_CAP", "VDD")).toMatchObject({ should_have_decoupling_capacitor: true, From 82044ff4b9ca7ef928acc101cb5f2ba7f56affce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 31 Jul 2026 23:14:20 +0530 Subject: [PATCH 7/8] Fix decoupling metadata schematic overlap --- .../__snapshots__/chip-decoupling-metadata-schematic.snap.svg | 2 +- .../normal-components/chip-decoupling-metadata.test.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg b/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg index 72da08947..4b0dc3924 100644 --- a/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg +++ b/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg @@ -9,4 +9,4 @@ .port-label { fill: rgb(0, 100, 100); } .component-name { fill: rgb(0, 100, 100); } - -10,-6-10,-5-10,-4-10,-3-10,-2-10,-1-10,0-10,1-10,2-10,3-10,4-9,-6-9,-5-9,-4-9,-3-9,-2-9,-1-9,0-9,1-9,2-9,3-9,4-8,-6-8,-5-8,-4-8,-3-8,-2-8,-1-8,0-8,1-8,2-8,3-8,4-7,-6-7,-5-7,-4-7,-3-7,-2-7,-1-7,0-7,1-7,2-7,3-7,4-6,-6-6,-5-6,-4-6,-3-6,-2-6,-1-6,0-6,1-6,2-6,3-6,4-5,-6-5,-5-5,-4-5,-3-5,-2-5,-1-5,0-5,1-5,2-5,3-5,4-4,-6-4,-5-4,-4-4,-3-4,-2-4,-1-4,0-4,1-4,2-4,3-4,4-3,-6-3,-5-3,-4-3,-3-3,-2-3,-1-3,0-3,1-3,2-3,3-3,4-2,-6-2,-5-2,-4-2,-3-2,-2-2,-1-2,0-2,1-2,2-2,3-2,4-1,-6-1,-5-1,-4-1,-3-1,-2-1,-1-1,0-1,1-1,2-1,3-1,40,-60,-50,-40,-30,-20,-10,00,10,20,30,41,-61,-51,-41,-31,-21,-11,01,11,21,31,42,-62,-52,-42,-32,-22,-12,02,12,22,32,43,-63,-53,-43,-33,-23,-13,03,13,23,33,44,-64,-54,-44,-34,-24,-14,04,14,24,34,45,-65,-55,-45,-35,-25,-15,05,15,25,35,46,-66,-56,-46,-36,-26,-16,06,16,26,36,47,-67,-57,-47,-37,-27,-17,07,17,27,37,48,-68,-58,-48,-38,-28,-18,08,18,28,38,49,-69,-59,-49,-39,-29,-19,09,19,29,39,410,-610,-510,-410,-310,-210,-110,010,110,210,310,4U_MISSING1VCC2GNDU_WITH_CAP1VDD2GNDC1100nFU_OPT_OUT1VBAT2GNDU_POWER_SOURCE1VCC2GNDVCC_MISSINGGNDGNDGNDVBATVCC_SOURCEDecoupling requirements are emitted as source-port metadata + -10,-6-10,-5-10,-4-10,-3-10,-2-10,-1-10,0-10,1-10,2-10,3-10,4-9,-6-9,-5-9,-4-9,-3-9,-2-9,-1-9,0-9,1-9,2-9,3-9,4-8,-6-8,-5-8,-4-8,-3-8,-2-8,-1-8,0-8,1-8,2-8,3-8,4-7,-6-7,-5-7,-4-7,-3-7,-2-7,-1-7,0-7,1-7,2-7,3-7,4-6,-6-6,-5-6,-4-6,-3-6,-2-6,-1-6,0-6,1-6,2-6,3-6,4-5,-6-5,-5-5,-4-5,-3-5,-2-5,-1-5,0-5,1-5,2-5,3-5,4-4,-6-4,-5-4,-4-4,-3-4,-2-4,-1-4,0-4,1-4,2-4,3-4,4-3,-6-3,-5-3,-4-3,-3-3,-2-3,-1-3,0-3,1-3,2-3,3-3,4-2,-6-2,-5-2,-4-2,-3-2,-2-2,-1-2,0-2,1-2,2-2,3-2,4-1,-6-1,-5-1,-4-1,-3-1,-2-1,-1-1,0-1,1-1,2-1,3-1,40,-60,-50,-40,-30,-20,-10,00,10,20,30,41,-61,-51,-41,-31,-21,-11,01,11,21,31,42,-62,-52,-42,-32,-22,-12,02,12,22,32,43,-63,-53,-43,-33,-23,-13,03,13,23,33,44,-64,-54,-44,-34,-24,-14,04,14,24,34,45,-65,-55,-45,-35,-25,-15,05,15,25,35,46,-66,-56,-46,-36,-26,-16,06,16,26,36,47,-67,-57,-47,-37,-27,-17,07,17,27,37,48,-68,-58,-48,-38,-28,-18,08,18,28,38,49,-69,-59,-49,-39,-29,-19,09,19,29,39,410,-610,-510,-410,-310,-210,-110,010,110,210,310,4U_MISSING1VCC2GNDU_WITH_CAP1VDD2GNDC1100nFU_OPT_OUT1VBAT2GNDU_POWER_SOURCE1VCC2GNDVCC_MISSINGGNDGNDGNDVBATVCC_SOURCEDecoupling requirements are emitted as source-port metadata diff --git a/tests/components/normal-components/chip-decoupling-metadata.test.tsx b/tests/components/normal-components/chip-decoupling-metadata.test.tsx index 768f2375f..ba0ac8e42 100644 --- a/tests/components/normal-components/chip-decoupling-metadata.test.tsx +++ b/tests/components/normal-components/chip-decoupling-metadata.test.tsx @@ -46,6 +46,7 @@ test("emits chip decoupling metadata for external checks", async () => { Date: Fri, 31 Jul 2026 23:24:02 +0530 Subject: [PATCH 8/8] Drop decoupling metadata schematic snapshot --- .../chip-decoupling-metadata-schematic.snap.svg | 12 ------------ .../chip-decoupling-metadata.test.tsx | 9 --------- 2 files changed, 21 deletions(-) delete mode 100644 tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg diff --git a/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg b/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg deleted file mode 100644 index 4b0dc3924..000000000 --- a/tests/components/normal-components/__snapshots__/chip-decoupling-metadata-schematic.snap.svg +++ /dev/null @@ -1,12 +0,0 @@ --10,-6-10,-5-10,-4-10,-3-10,-2-10,-1-10,0-10,1-10,2-10,3-10,4-9,-6-9,-5-9,-4-9,-3-9,-2-9,-1-9,0-9,1-9,2-9,3-9,4-8,-6-8,-5-8,-4-8,-3-8,-2-8,-1-8,0-8,1-8,2-8,3-8,4-7,-6-7,-5-7,-4-7,-3-7,-2-7,-1-7,0-7,1-7,2-7,3-7,4-6,-6-6,-5-6,-4-6,-3-6,-2-6,-1-6,0-6,1-6,2-6,3-6,4-5,-6-5,-5-5,-4-5,-3-5,-2-5,-1-5,0-5,1-5,2-5,3-5,4-4,-6-4,-5-4,-4-4,-3-4,-2-4,-1-4,0-4,1-4,2-4,3-4,4-3,-6-3,-5-3,-4-3,-3-3,-2-3,-1-3,0-3,1-3,2-3,3-3,4-2,-6-2,-5-2,-4-2,-3-2,-2-2,-1-2,0-2,1-2,2-2,3-2,4-1,-6-1,-5-1,-4-1,-3-1,-2-1,-1-1,0-1,1-1,2-1,3-1,40,-60,-50,-40,-30,-20,-10,00,10,20,30,41,-61,-51,-41,-31,-21,-11,01,11,21,31,42,-62,-52,-42,-32,-22,-12,02,12,22,32,43,-63,-53,-43,-33,-23,-13,03,13,23,33,44,-64,-54,-44,-34,-24,-14,04,14,24,34,45,-65,-55,-45,-35,-25,-15,05,15,25,35,46,-66,-56,-46,-36,-26,-16,06,16,26,36,47,-67,-57,-47,-37,-27,-17,07,17,27,37,48,-68,-58,-48,-38,-28,-18,08,18,28,38,49,-69,-59,-49,-39,-29,-19,09,19,29,39,410,-610,-510,-410,-310,-210,-110,010,110,210,310,4U_MISSING1VCC2GNDU_WITH_CAP1VDD2GNDC1100nFU_OPT_OUT1VBAT2GNDU_POWER_SOURCE1VCC2GNDVCC_MISSINGGNDGNDGNDVBATVCC_SOURCEDecoupling requirements are emitted as source-port metadata diff --git a/tests/components/normal-components/chip-decoupling-metadata.test.tsx b/tests/components/normal-components/chip-decoupling-metadata.test.tsx index ba0ac8e42..3e5c8f4a2 100644 --- a/tests/components/normal-components/chip-decoupling-metadata.test.tsx +++ b/tests/components/normal-components/chip-decoupling-metadata.test.tsx @@ -46,7 +46,6 @@ test("emits chip decoupling metadata for external checks", async () => { { /> - - , ) @@ -102,5 +94,4 @@ test("emits chip decoupling metadata for external checks", async () => { provides_power: true, should_have_decoupling_capacitor: false, }) - expect(circuit).toMatchSchematicSnapshot(import.meta.path) })