Skip to content

Commit

Permalink
Merge pull request #33 from tscircuit/footprint-solving
Browse files Browse the repository at this point in the history
initial constraint solving implementation for footprints, introduce PrimitiveContainers
  • Loading branch information
seveibar authored Sep 5, 2024
2 parents df54237 + c7f85c0 commit f2e0411
Show file tree
Hide file tree
Showing 23 changed files with 427 additions and 49 deletions.
Binary file modified bun.lockb
Binary file not shown.
5 changes: 4 additions & 1 deletion lib/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class Circuit {
return identity()
}

computePcbGlobalTransform(): Matrix {
_computePcbGlobalTransformBeforeLayout(): Matrix {
return identity()
}

Expand All @@ -124,4 +124,7 @@ export class Circuit {
}
}

/**
* @deprecated
*/
export const Project = Circuit
6 changes: 4 additions & 2 deletions lib/components/base-components/NormalComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class NormalComponent<
> extends PrimitiveComponent<ZodProps> {
reactSubtrees: Array<ReactSubtree> = []

isPrimitiveContainer = true

constructor(props: z.input<ZodProps>) {
super(props)
this._addChildrenFromStringFootprint()
Expand Down Expand Up @@ -199,7 +201,7 @@ export class NormalComponent<
const { db } = this.root!
const { _parsedProps: props } = this
const pcb_component = db.pcb_component.insert({
center: this.getGlobalPcbPosition(),
center: this._getGlobalPcbPositionBeforeLayout(),
// width/height are computed in the PcbComponentSizeCalculation phase
width: 0,
height: 0,
Expand All @@ -226,7 +228,7 @@ export class NormalComponent<

for (const child of this.children) {
if (child.isPcbPrimitive) {
const { x, y } = child.getGlobalPcbPosition()
const { x, y } = child._getGlobalPcbPositionBeforeLayout()
const { width, height } = child.getPcbSize()
minX = Math.min(minX, x - width / 2)
minY = Math.min(minY, y - height / 2)
Expand Down
66 changes: 58 additions & 8 deletions lib/components/base-components/PrimitiveComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ export abstract class PrimitiveComponent<
)
}

/**
* A primitive container is a component that contains one or more ports and
* primitive components that are designed to interact.
*
* For example a resistor contains ports and smtpads that interact, so the
* resistor is a primitive container. Inside a primitive container, the ports
* and pads are likely to reference each other and look for eachother during
* the port matching phase.
*
*/
isPrimitiveContainer = false

source_group_id: string | null = null
source_component_id: string | null = null
schematic_component_id: string | null = null
Expand Down Expand Up @@ -120,9 +132,9 @@ export abstract class PrimitiveComponent<
* components, including this component's translation and rotation.
*
* This is used to compute this component's position as well as all children
* components positions
* components positions before layout is applied
*/
computePcbGlobalTransform(): Matrix {
_computePcbGlobalTransformBeforeLayout(): Matrix {
const { _parsedProps: props } = this
const manualPlacement =
this.getSubcircuit()._getManualPlacementForComponent(this)
Expand All @@ -134,7 +146,7 @@ export abstract class PrimitiveComponent<
this.props.pcbY === undefined
) {
return compose(
this.parent?.computePcbGlobalTransform() ?? identity(),
this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
compose(
translate(manualPlacement.x, manualPlacement.y),
rotate(((props.pcbRotation ?? 0) * Math.PI) / 180),
Expand All @@ -143,11 +155,46 @@ export abstract class PrimitiveComponent<
}

return compose(
this.parent?.computePcbGlobalTransform() ?? identity(),
this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
this.computePcbPropsTransform(),
)
}

getPrimitiveContainer(): PrimitiveComponent | null {
if (this.isPrimitiveContainer) return this
return this.parent?.getPrimitiveContainer?.() ?? null
}

/**
* Compute the bounds of this component the circuit json elements associated
* with it.
*/
_getCircuitJsonBounds(): {
center: { x: number; y: number }
bounds: { left: number; top: number; right: number; bottom: number }
width: number
height: number
} {
return {
center: { x: 0, y: 0 },
bounds: { left: 0, top: 0, right: 0, bottom: 0 },
width: 0,
height: 0,
}
}

/**
* Set the position of this component from the layout solver. This method
* should operate using CircuitJson associated with this component, like
* _getCircuitJsonBounds it can be called multiple times as different
* parents apply layout to their children.
*/
_setPositionFromLayout(newCenter: { x: number; y: number }) {
throw new Error(
`_setPositionFromLayout not implemented for ${this.componentName}`,
)
}

/**
* Computes a transformation matrix from the props of this component for
* schematic components
Expand Down Expand Up @@ -200,7 +247,7 @@ export abstract class PrimitiveComponent<
for (const position of placementConfig.positions) {
if (isMatchingSelector(component, position.selector)) {
const center = applyToPoint(
this.computePcbGlobalTransform(),
this._computePcbGlobalTransformBeforeLayout(),
position.center,
)
return center
Expand All @@ -210,11 +257,14 @@ export abstract class PrimitiveComponent<
return null
}

getGlobalPcbPosition(): { x: number; y: number } {
return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 })
_getGlobalPcbPositionBeforeLayout(): { x: number; y: number } {
return applyToPoint(this._computePcbGlobalTransformBeforeLayout(), {
x: 0,
y: 0,
})
}

getGlobalSchematicPosition(): { x: number; y: number } {
_getGlobalSchematicPositionBeforeLayout(): { x: number; y: number } {
return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 })
}

Expand Down
7 changes: 4 additions & 3 deletions lib/components/base-components/Renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ import type { PCBPlacementError, PCBTraceError } from "@tscircuit/soup"
import { Component, createElement, type ReactElement } from "react"

export const orderedRenderPhases = [
"ReactSubtreesRender", // probably going to be removed b/c subtrees should render instantly
"ReactSubtreesRender",
"InitializePortsFromChildren",
"CreateNetsFromProps",
"CreateTracesFromProps",
"SourceRender",
"SourceParentAttachment",
"PortDiscovery", // probably going to be removed b/c port discovery can always be done on prop change
"PortDiscovery",
"PortMatching",
"SourceTraceRender",
"SchematicComponentRender",
"SchematicLayout",
"SchematicPortRender",
"SchematicTraceRender",
"PcbComponentRender",
"PcbPortRender",
"PcbPrimitiveRender",
"PcbFootprintLayout",
"PcbPortRender",
"PcbParentAttachment",
"PcbLayout",
"PcbTraceRender",
Expand Down
1 change: 1 addition & 0 deletions lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { Chip } from "./normal-components/Chip"
export { Jumper } from "./normal-components/Jumper"
export { SilkscreenPath } from "./primitive-components/SilkscreenPath"
export { PlatedHole } from "./primitive-components/PlatedHole"
export { Constraint } from "./primitive-components/Constraint"
2 changes: 1 addition & 1 deletion lib/components/normal-components/Board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Board extends Group<typeof boardProps> {
this.pcb_board_id = null
}

computePcbGlobalTransform(): Matrix {
_computePcbGlobalTransformBeforeLayout(): Matrix {
return identity()
}
}
49 changes: 49 additions & 0 deletions lib/components/primitive-components/Constraint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { constraintProps } from "@tscircuit/props"
import { z } from "zod"
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"

export class Constraint extends PrimitiveComponent<typeof constraintProps> {
get config() {
return {
zodProps: constraintProps,
}
}

constructor(props: z.input<typeof constraintProps>) {
super(props)
if ("xdist" in props || "ydist" in props) {
if (!("edgeToEdge" in props) && !("centerToCenter" in props)) {
throw new Error(
"edgeToEdge, centerToCenter (or from*/to* props) must be set for xdist or ydist for <constraint />",
)
}
}
}

_getAllReferencedComponents(): {
componentsWithSelectors: Array<{
component: PrimitiveComponent<any>
selector: string
}>
} {
const componentsWithSelectors: Array<{
component: PrimitiveComponent<any>
selector: string
}> = []

for (const key of ["left", "right", "top", "bottom"]) {
if (key in this._parsedProps) {
const selector = (this._parsedProps as any)[key] as string
const component = this.getSubcircuit().selectOne(selector)
if (component) {
componentsWithSelectors.push({
selector,
component,
})
}
}
}

return { componentsWithSelectors }
}
}
Loading

0 comments on commit f2e0411

Please sign in to comment.