@tscircuit/core 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +240 -51
- package/dist/index.js +307 -41
- package/lib/Project.ts +4 -1
- package/lib/components/base-components/NormalComponent.ts +4 -2
- package/lib/components/base-components/PrimitiveComponent.ts +58 -8
- package/lib/components/base-components/Renderable.ts +4 -3
- package/lib/components/index.ts +2 -0
- package/lib/components/normal-components/Board.ts +1 -1
- package/lib/components/primitive-components/Constraint.ts +49 -0
- package/lib/components/primitive-components/Footprint.ts +158 -1
- package/lib/components/primitive-components/Hole.ts +36 -0
- package/lib/components/primitive-components/Net.ts +2 -2
- package/lib/components/primitive-components/PlatedHole.ts +1 -1
- package/lib/components/primitive-components/Port.ts +8 -13
- package/lib/components/primitive-components/SilkscreenPath.ts +1 -1
- package/lib/components/primitive-components/SmtPad.ts +54 -4
- package/lib/components/primitive-components/Trace.ts +2 -2
- package/lib/components/primitive-components/TraceHint.ts +1 -1
- package/lib/fiber/intrinsic-jsx.ts +2 -0
- package/lib/utils/getClosest.ts +9 -3
- package/package.json +5 -2
|
@@ -63,6 +63,18 @@ export abstract class PrimitiveComponent<
|
|
|
63
63
|
)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A primitive container is a component that contains one or more ports and
|
|
68
|
+
* primitive components that are designed to interact.
|
|
69
|
+
*
|
|
70
|
+
* For example a resistor contains ports and smtpads that interact, so the
|
|
71
|
+
* resistor is a primitive container. Inside a primitive container, the ports
|
|
72
|
+
* and pads are likely to reference each other and look for eachother during
|
|
73
|
+
* the port matching phase.
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
76
|
+
isPrimitiveContainer = false
|
|
77
|
+
|
|
66
78
|
source_group_id: string | null = null
|
|
67
79
|
source_component_id: string | null = null
|
|
68
80
|
schematic_component_id: string | null = null
|
|
@@ -120,9 +132,9 @@ export abstract class PrimitiveComponent<
|
|
|
120
132
|
* components, including this component's translation and rotation.
|
|
121
133
|
*
|
|
122
134
|
* This is used to compute this component's position as well as all children
|
|
123
|
-
* components positions
|
|
135
|
+
* components positions before layout is applied
|
|
124
136
|
*/
|
|
125
|
-
|
|
137
|
+
_computePcbGlobalTransformBeforeLayout(): Matrix {
|
|
126
138
|
const { _parsedProps: props } = this
|
|
127
139
|
const manualPlacement =
|
|
128
140
|
this.getSubcircuit()._getManualPlacementForComponent(this)
|
|
@@ -134,7 +146,7 @@ export abstract class PrimitiveComponent<
|
|
|
134
146
|
this.props.pcbY === undefined
|
|
135
147
|
) {
|
|
136
148
|
return compose(
|
|
137
|
-
this.parent?.
|
|
149
|
+
this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
|
|
138
150
|
compose(
|
|
139
151
|
translate(manualPlacement.x, manualPlacement.y),
|
|
140
152
|
rotate(((props.pcbRotation ?? 0) * Math.PI) / 180),
|
|
@@ -143,11 +155,46 @@ export abstract class PrimitiveComponent<
|
|
|
143
155
|
}
|
|
144
156
|
|
|
145
157
|
return compose(
|
|
146
|
-
this.parent?.
|
|
158
|
+
this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
|
|
147
159
|
this.computePcbPropsTransform(),
|
|
148
160
|
)
|
|
149
161
|
}
|
|
150
162
|
|
|
163
|
+
getPrimitiveContainer(): PrimitiveComponent | null {
|
|
164
|
+
if (this.isPrimitiveContainer) return this
|
|
165
|
+
return this.parent?.getPrimitiveContainer?.() ?? null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Compute the bounds of this component the circuit json elements associated
|
|
170
|
+
* with it.
|
|
171
|
+
*/
|
|
172
|
+
_getCircuitJsonBounds(): {
|
|
173
|
+
center: { x: number; y: number }
|
|
174
|
+
bounds: { left: number; top: number; right: number; bottom: number }
|
|
175
|
+
width: number
|
|
176
|
+
height: number
|
|
177
|
+
} {
|
|
178
|
+
return {
|
|
179
|
+
center: { x: 0, y: 0 },
|
|
180
|
+
bounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
181
|
+
width: 0,
|
|
182
|
+
height: 0,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Set the position of this component from the layout solver. This method
|
|
188
|
+
* should operate using CircuitJson associated with this component, like
|
|
189
|
+
* _getCircuitJsonBounds it can be called multiple times as different
|
|
190
|
+
* parents apply layout to their children.
|
|
191
|
+
*/
|
|
192
|
+
_setPositionFromLayout(newCenter: { x: number; y: number }) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`_setPositionFromLayout not implemented for ${this.componentName}`,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
151
198
|
/**
|
|
152
199
|
* Computes a transformation matrix from the props of this component for
|
|
153
200
|
* schematic components
|
|
@@ -200,7 +247,7 @@ export abstract class PrimitiveComponent<
|
|
|
200
247
|
for (const position of placementConfig.positions) {
|
|
201
248
|
if (isMatchingSelector(component, position.selector)) {
|
|
202
249
|
const center = applyToPoint(
|
|
203
|
-
this.
|
|
250
|
+
this._computePcbGlobalTransformBeforeLayout(),
|
|
204
251
|
position.center,
|
|
205
252
|
)
|
|
206
253
|
return center
|
|
@@ -210,11 +257,14 @@ export abstract class PrimitiveComponent<
|
|
|
210
257
|
return null
|
|
211
258
|
}
|
|
212
259
|
|
|
213
|
-
|
|
214
|
-
return applyToPoint(this.
|
|
260
|
+
_getGlobalPcbPositionBeforeLayout(): { x: number; y: number } {
|
|
261
|
+
return applyToPoint(this._computePcbGlobalTransformBeforeLayout(), {
|
|
262
|
+
x: 0,
|
|
263
|
+
y: 0,
|
|
264
|
+
})
|
|
215
265
|
}
|
|
216
266
|
|
|
217
|
-
|
|
267
|
+
_getGlobalSchematicPositionBeforeLayout(): { x: number; y: number } {
|
|
218
268
|
return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 })
|
|
219
269
|
}
|
|
220
270
|
|
|
@@ -2,13 +2,13 @@ import type { PCBPlacementError, PCBTraceError } from "@tscircuit/soup"
|
|
|
2
2
|
import { Component, createElement, type ReactElement } from "react"
|
|
3
3
|
|
|
4
4
|
export const orderedRenderPhases = [
|
|
5
|
-
"ReactSubtreesRender",
|
|
5
|
+
"ReactSubtreesRender",
|
|
6
6
|
"InitializePortsFromChildren",
|
|
7
7
|
"CreateNetsFromProps",
|
|
8
8
|
"CreateTracesFromProps",
|
|
9
9
|
"SourceRender",
|
|
10
10
|
"SourceParentAttachment",
|
|
11
|
-
"PortDiscovery",
|
|
11
|
+
"PortDiscovery",
|
|
12
12
|
"PortMatching",
|
|
13
13
|
"SourceTraceRender",
|
|
14
14
|
"SchematicComponentRender",
|
|
@@ -16,8 +16,9 @@ export const orderedRenderPhases = [
|
|
|
16
16
|
"SchematicPortRender",
|
|
17
17
|
"SchematicTraceRender",
|
|
18
18
|
"PcbComponentRender",
|
|
19
|
-
"PcbPortRender",
|
|
20
19
|
"PcbPrimitiveRender",
|
|
20
|
+
"PcbFootprintLayout",
|
|
21
|
+
"PcbPortRender",
|
|
21
22
|
"PcbParentAttachment",
|
|
22
23
|
"PcbLayout",
|
|
23
24
|
"PcbTraceRender",
|
package/lib/components/index.ts
CHANGED
|
@@ -16,3 +16,5 @@ export { Chip } from "./normal-components/Chip"
|
|
|
16
16
|
export { Jumper } from "./normal-components/Jumper"
|
|
17
17
|
export { SilkscreenPath } from "./primitive-components/SilkscreenPath"
|
|
18
18
|
export { PlatedHole } from "./primitive-components/PlatedHole"
|
|
19
|
+
export { Constraint } from "./primitive-components/Constraint"
|
|
20
|
+
export { Hole } from "./primitive-components/Hole"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { constraintProps } from "@tscircuit/props"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
|
|
4
|
+
|
|
5
|
+
export class Constraint extends PrimitiveComponent<typeof constraintProps> {
|
|
6
|
+
get config() {
|
|
7
|
+
return {
|
|
8
|
+
zodProps: constraintProps,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(props: z.input<typeof constraintProps>) {
|
|
13
|
+
super(props)
|
|
14
|
+
if ("xdist" in props || "ydist" in props) {
|
|
15
|
+
if (!("edgeToEdge" in props) && !("centerToCenter" in props)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"edgeToEdge, centerToCenter (or from*/to* props) must be set for xdist or ydist for <constraint />",
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_getAllReferencedComponents(): {
|
|
24
|
+
componentsWithSelectors: Array<{
|
|
25
|
+
component: PrimitiveComponent<any>
|
|
26
|
+
selector: string
|
|
27
|
+
}>
|
|
28
|
+
} {
|
|
29
|
+
const componentsWithSelectors: Array<{
|
|
30
|
+
component: PrimitiveComponent<any>
|
|
31
|
+
selector: string
|
|
32
|
+
}> = []
|
|
33
|
+
|
|
34
|
+
for (const key of ["left", "right", "top", "bottom"]) {
|
|
35
|
+
if (key in this._parsedProps) {
|
|
36
|
+
const selector = (this._parsedProps as any)[key] as string
|
|
37
|
+
const component = this.getSubcircuit().selectOne(selector)
|
|
38
|
+
if (component) {
|
|
39
|
+
componentsWithSelectors.push({
|
|
40
|
+
selector,
|
|
41
|
+
component,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { componentsWithSelectors }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,4 +1,161 @@
|
|
|
1
1
|
import type { footprintProps } from "@tscircuit/props"
|
|
2
2
|
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
|
|
3
|
+
import type { Constraint } from "./Constraint"
|
|
4
|
+
import * as kiwi from "@lume/kiwi"
|
|
5
|
+
import Debug from "debug"
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
const debug = Debug("tscircuit:core:footprint")
|
|
8
|
+
|
|
9
|
+
export class Footprint extends PrimitiveComponent<typeof footprintProps> {
|
|
10
|
+
/**
|
|
11
|
+
* A footprint is a constrainedlayout, the db elements are adjusted according
|
|
12
|
+
* to any constraints that are defined.
|
|
13
|
+
*/
|
|
14
|
+
doInitialPcbFootprintLayout() {
|
|
15
|
+
const constraints = this.children.filter(
|
|
16
|
+
(child) => child.componentName === "Constraint",
|
|
17
|
+
) as Constraint[]
|
|
18
|
+
|
|
19
|
+
if (constraints.length === 0) return
|
|
20
|
+
|
|
21
|
+
const involvedComponents = constraints
|
|
22
|
+
.flatMap(
|
|
23
|
+
(constraint) =>
|
|
24
|
+
constraint._getAllReferencedComponents().componentsWithSelectors,
|
|
25
|
+
)
|
|
26
|
+
.map(({ component, selector }) => ({
|
|
27
|
+
component,
|
|
28
|
+
selector,
|
|
29
|
+
bounds: component._getCircuitJsonBounds(),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
function getComponentDetails(selector: string) {
|
|
33
|
+
return involvedComponents.find(({ selector: s }) => s === selector)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const solver = new kiwi.Solver()
|
|
37
|
+
|
|
38
|
+
const kVars: { [varName: string]: kiwi.Variable } = {}
|
|
39
|
+
function getKVar(name: string) {
|
|
40
|
+
if (!(name in kVars)) {
|
|
41
|
+
kVars[name] = new kiwi.Variable(name)
|
|
42
|
+
}
|
|
43
|
+
return kVars[name]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1. Create kiwi variables to represent each component's center
|
|
47
|
+
for (const { selector, bounds } of involvedComponents) {
|
|
48
|
+
const kvx = getKVar(`${selector}_x`)
|
|
49
|
+
const kvy = getKVar(`${selector}_y`)
|
|
50
|
+
solver.addEditVariable(kvx, kiwi.Strength.weak)
|
|
51
|
+
solver.addEditVariable(kvy, kiwi.Strength.weak)
|
|
52
|
+
solver.suggestValue(kvx, bounds.center.x)
|
|
53
|
+
solver.suggestValue(kvy, bounds.center.y)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Add kiwi constraints using the parsed constraint properties
|
|
57
|
+
for (const constraint of constraints) {
|
|
58
|
+
const props = constraint._parsedProps
|
|
59
|
+
|
|
60
|
+
if ("xdist" in props) {
|
|
61
|
+
const { xdist, left, right, edgeToEdge, centerToCenter } = props
|
|
62
|
+
const leftVar = getKVar(`${left}_x`)
|
|
63
|
+
const rightVar = getKVar(`${right}_x`)
|
|
64
|
+
const leftBounds = getComponentDetails(left)?.bounds!
|
|
65
|
+
const rightBounds = getComponentDetails(right)?.bounds!
|
|
66
|
+
|
|
67
|
+
if (centerToCenter) {
|
|
68
|
+
// right - left = xdist
|
|
69
|
+
const expr = new kiwi.Expression(rightVar, [-1, leftVar])
|
|
70
|
+
solver.addConstraint(
|
|
71
|
+
new kiwi.Constraint(
|
|
72
|
+
expr,
|
|
73
|
+
kiwi.Operator.Eq,
|
|
74
|
+
props.xdist,
|
|
75
|
+
kiwi.Strength.required,
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
} else if (edgeToEdge) {
|
|
79
|
+
// rightEdge - leftEdge = xdist
|
|
80
|
+
// right + rightBounds.width/2 - left - leftBounds.width/2 = xdist
|
|
81
|
+
const expr = new kiwi.Expression(
|
|
82
|
+
rightVar,
|
|
83
|
+
rightBounds.width / 2,
|
|
84
|
+
[-1, leftVar],
|
|
85
|
+
-leftBounds.width / 2,
|
|
86
|
+
)
|
|
87
|
+
solver.addConstraint(
|
|
88
|
+
new kiwi.Constraint(
|
|
89
|
+
expr,
|
|
90
|
+
kiwi.Operator.Eq,
|
|
91
|
+
props.xdist,
|
|
92
|
+
kiwi.Strength.required,
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Solve the system of equations
|
|
99
|
+
solver.updateVariables()
|
|
100
|
+
if (debug.enabled) {
|
|
101
|
+
console.log("Solution to layout constraints:")
|
|
102
|
+
console.table(
|
|
103
|
+
Object.entries(kVars).map(([key, kvar]) => ({
|
|
104
|
+
var: key,
|
|
105
|
+
val: kvar.value(),
|
|
106
|
+
})),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3.1 Compute the global offset. There are different ways to do this:
|
|
111
|
+
// - If any component has a fixed position, then that can be used as the
|
|
112
|
+
// origin to determine the offset of all other components
|
|
113
|
+
// - If no component has a fixed position, then we recenter everything
|
|
114
|
+
// using the new bounds of all the involved components
|
|
115
|
+
|
|
116
|
+
// TODO determine if there's a fixed component
|
|
117
|
+
|
|
118
|
+
// Determine the new bounds all the involved components and compute the
|
|
119
|
+
// bounds of this footprint
|
|
120
|
+
const bounds = {
|
|
121
|
+
left: Infinity,
|
|
122
|
+
right: -Infinity,
|
|
123
|
+
top: -Infinity,
|
|
124
|
+
bottom: Infinity,
|
|
125
|
+
}
|
|
126
|
+
for (const {
|
|
127
|
+
selector,
|
|
128
|
+
bounds: { width, height },
|
|
129
|
+
} of involvedComponents) {
|
|
130
|
+
const kvx = getKVar(`${selector}_x`)
|
|
131
|
+
const kvy = getKVar(`${selector}_y`)
|
|
132
|
+
|
|
133
|
+
const newLeft = kvx.value() - width / 2
|
|
134
|
+
const newRight = kvx.value() + width / 2
|
|
135
|
+
const newTop = kvy.value() + height / 2
|
|
136
|
+
const newBottom = kvy.value() - height / 2
|
|
137
|
+
|
|
138
|
+
bounds.left = Math.min(bounds.left, newLeft)
|
|
139
|
+
bounds.right = Math.max(bounds.right, newRight)
|
|
140
|
+
bounds.top = Math.max(bounds.top, newTop)
|
|
141
|
+
bounds.bottom = Math.min(bounds.bottom, newBottom)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Compute the global offset, we can use this to recenter each component
|
|
145
|
+
const globalOffset = {
|
|
146
|
+
x: -(bounds.right + bounds.left) / 2,
|
|
147
|
+
y: -(bounds.top + bounds.bottom) / 2,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 4. Update the component positions
|
|
151
|
+
for (const { component, selector } of involvedComponents) {
|
|
152
|
+
const kvx = getKVar(`${selector}_x`)
|
|
153
|
+
const kvy = getKVar(`${selector}_y`)
|
|
154
|
+
component._setPositionFromLayout({
|
|
155
|
+
x: kvx.value() + globalOffset.x,
|
|
156
|
+
y: kvy.value() + globalOffset.y,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
|
|
2
|
+
import { holeProps } from "@tscircuit/props"
|
|
3
|
+
import type { PCBHole } from "@tscircuit/soup"
|
|
4
|
+
|
|
5
|
+
export class Hole extends PrimitiveComponent<typeof holeProps> {
|
|
6
|
+
pcb_hole_id: string | null = null
|
|
7
|
+
isPcbPrimitive = true
|
|
8
|
+
|
|
9
|
+
get config() {
|
|
10
|
+
return {
|
|
11
|
+
zodProps: holeProps,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getPcbSize(): { width: number; height: number } {
|
|
16
|
+
const { _parsedProps: props } = this
|
|
17
|
+
return { width: props.holeDiameter, height: props.holeDiameter }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
doInitialPcbPrimitiveRender(): void {
|
|
21
|
+
const { db } = this.root!
|
|
22
|
+
const { _parsedProps: props } = this
|
|
23
|
+
const position = this._getGlobalPcbPositionBeforeLayout()
|
|
24
|
+
|
|
25
|
+
const pcb_hole: PCBHole = {
|
|
26
|
+
type: "pcb_hole",
|
|
27
|
+
hole_shape: "round",
|
|
28
|
+
hole_diameter: props.holeDiameter,
|
|
29
|
+
x: position.x,
|
|
30
|
+
y: position.y,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const inserted_hole = db.pcb_hole.insert(pcb_hole)
|
|
34
|
+
// this.pcb_hole_id = inserted_hole.pcb_hole_id!
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -109,10 +109,10 @@ export class Net extends PrimitiveComponent<typeof netProps> {
|
|
|
109
109
|
for (const [A, B] of islandPairs) {
|
|
110
110
|
// Find two closest ports on the island
|
|
111
111
|
const Apositions: Array<{ x: number; y: number }> = A.ports.map((port) =>
|
|
112
|
-
port.
|
|
112
|
+
port._getGlobalPcbPositionBeforeLayout(),
|
|
113
113
|
)
|
|
114
114
|
const Bpositions: Array<{ x: number; y: number }> = B.ports.map((port) =>
|
|
115
|
-
port.
|
|
115
|
+
port._getGlobalPcbPositionBeforeLayout(),
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
let closestDist = Infinity
|
|
@@ -49,7 +49,7 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
|
|
|
49
49
|
const { db } = this.root!
|
|
50
50
|
const { _parsedProps: props } = this
|
|
51
51
|
if (!props.portHints) return
|
|
52
|
-
const position = this.
|
|
52
|
+
const position = this._getGlobalPcbPositionBeforeLayout()
|
|
53
53
|
if (props.shape === "circle") {
|
|
54
54
|
const plated_hole_input: PCBPlatedHoleInput = {
|
|
55
55
|
pcb_component_id: this.parent?.pcb_component_id!,
|
|
@@ -32,7 +32,7 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
32
32
|
this.matchedComponents = []
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
_getGlobalPcbPositionBeforeLayout(): { x: number; y: number } {
|
|
36
36
|
const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive)
|
|
37
37
|
|
|
38
38
|
if (!matchedPcbElm) {
|
|
@@ -41,10 +41,10 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
return matchedPcbElm?.
|
|
44
|
+
return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
_getGlobalSchematicPositionBeforeLayout(): { x: number; y: number } {
|
|
48
48
|
if (!this.schematicSymbolPortDef) {
|
|
49
49
|
return applyToPoint(this.parent!.computeSchematicGlobalTransform(), {
|
|
50
50
|
x: 0,
|
|
@@ -146,11 +146,6 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
146
146
|
this.source_component_id = this.parent?.source_component_id
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/**
|
|
150
|
-
* For PcbPorts, we use the parent attachment phase to determine where to place
|
|
151
|
-
* the pcb_port (prior to this phase, the smtpad/platedhole isn't guaranteed
|
|
152
|
-
* to exist)
|
|
153
|
-
*/
|
|
154
149
|
doInitialPcbPortRender(): void {
|
|
155
150
|
const { db } = this.root!
|
|
156
151
|
const { matchedComponents } = this
|
|
@@ -173,19 +168,19 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
173
168
|
|
|
174
169
|
const pcbMatch: any = pcbMatches[0]
|
|
175
170
|
|
|
176
|
-
if ("
|
|
171
|
+
if ("_getCircuitJsonBounds" in pcbMatch) {
|
|
177
172
|
const pcb_port = db.pcb_port.insert({
|
|
178
173
|
pcb_component_id: this.parent?.pcb_component_id!,
|
|
179
174
|
layers: ["top"],
|
|
180
175
|
|
|
181
|
-
...pcbMatch.
|
|
176
|
+
...pcbMatch._getCircuitJsonBounds().center,
|
|
182
177
|
|
|
183
178
|
source_port_id: this.source_port_id!,
|
|
184
179
|
})
|
|
185
180
|
this.pcb_port_id = pcb_port.pcb_port_id
|
|
186
181
|
} else {
|
|
187
182
|
throw new Error(
|
|
188
|
-
`${pcbMatch.getString()} does not have a
|
|
183
|
+
`${pcbMatch.getString()} does not have a _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`,
|
|
189
184
|
)
|
|
190
185
|
}
|
|
191
186
|
}
|
|
@@ -196,8 +191,8 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
196
191
|
|
|
197
192
|
if (!this.parent) return
|
|
198
193
|
|
|
199
|
-
const center = this.
|
|
200
|
-
const parentCenter = this.parent?.
|
|
194
|
+
const center = this._getGlobalSchematicPositionBeforeLayout()
|
|
195
|
+
const parentCenter = this.parent?._getGlobalSchematicPositionBeforeLayout()
|
|
201
196
|
|
|
202
197
|
this.facingDirection = getRelativeDirection(parentCenter, center)
|
|
203
198
|
|
|
@@ -24,7 +24,7 @@ export class SilkscreenPath extends PrimitiveComponent<
|
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const transform = this.
|
|
27
|
+
const transform = this._computePcbGlobalTransformBeforeLayout()
|
|
28
28
|
|
|
29
29
|
const pcb_silkscreen_path = db.pcb_silkscreen_path.insert({
|
|
30
30
|
pcb_component_id: this.parent?.pcb_component_id!,
|
|
@@ -32,8 +32,8 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
doInitialPortMatching(): void {
|
|
35
|
-
const parentPorts =
|
|
36
|
-
|
|
35
|
+
const parentPorts = this.getPrimitiveContainer()?.selectAll(
|
|
36
|
+
"port",
|
|
37
37
|
) as Port[]
|
|
38
38
|
|
|
39
39
|
if (!this.props.portHints) {
|
|
@@ -53,8 +53,10 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
53
53
|
const { db } = this.root!
|
|
54
54
|
const { _parsedProps: props } = this
|
|
55
55
|
if (!props.portHints) return
|
|
56
|
-
const position = this.
|
|
57
|
-
const decomposedMat = decomposeTSR(
|
|
56
|
+
const position = this._getGlobalPcbPositionBeforeLayout()
|
|
57
|
+
const decomposedMat = decomposeTSR(
|
|
58
|
+
this._computePcbGlobalTransformBeforeLayout(),
|
|
59
|
+
)
|
|
58
60
|
const isRotated90 =
|
|
59
61
|
Math.abs(decomposedMat.rotation.angle * (180 / Math.PI) - 90) < 0.01
|
|
60
62
|
let pcb_smtpad: PCBSMTPad | null = null
|
|
@@ -94,4 +96,52 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
94
96
|
this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id
|
|
95
97
|
}
|
|
96
98
|
}
|
|
99
|
+
|
|
100
|
+
_getCircuitJsonBounds(): {
|
|
101
|
+
center: { x: number; y: number }
|
|
102
|
+
bounds: { left: number; top: number; right: number; bottom: number }
|
|
103
|
+
width: number
|
|
104
|
+
height: number
|
|
105
|
+
} {
|
|
106
|
+
const { db } = this.root!
|
|
107
|
+
const smtpad = db.pcb_smtpad.get(this.pcb_smtpad_id!)!
|
|
108
|
+
|
|
109
|
+
if (smtpad.shape === "rect") {
|
|
110
|
+
return {
|
|
111
|
+
center: { x: smtpad.x, y: smtpad.y },
|
|
112
|
+
bounds: {
|
|
113
|
+
left: smtpad.x - smtpad.width / 2,
|
|
114
|
+
top: smtpad.y - smtpad.height / 2,
|
|
115
|
+
right: smtpad.x + smtpad.width / 2,
|
|
116
|
+
bottom: smtpad.y + smtpad.height / 2,
|
|
117
|
+
},
|
|
118
|
+
width: smtpad.width,
|
|
119
|
+
height: smtpad.height,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (smtpad.shape === "circle") {
|
|
123
|
+
return {
|
|
124
|
+
center: { x: smtpad.x, y: smtpad.y },
|
|
125
|
+
bounds: {
|
|
126
|
+
left: smtpad.x - smtpad.radius,
|
|
127
|
+
top: smtpad.y - smtpad.radius,
|
|
128
|
+
right: smtpad.x + smtpad.radius,
|
|
129
|
+
bottom: smtpad.y + smtpad.radius,
|
|
130
|
+
},
|
|
131
|
+
width: smtpad.radius * 2,
|
|
132
|
+
height: smtpad.radius * 2,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw new Error(
|
|
136
|
+
`circuitJson bounds calculation not implemented for shape "${(smtpad as any).shape}"`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_setPositionFromLayout(newCenter: { x: number; y: number }) {
|
|
141
|
+
const { db } = this.root!
|
|
142
|
+
db.pcb_smtpad.update(this.pcb_smtpad_id!, {
|
|
143
|
+
x: newCenter.x,
|
|
144
|
+
y: newCenter.y,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
97
147
|
}
|
|
@@ -38,7 +38,7 @@ type PcbRouteObjective =
|
|
|
38
38
|
| { layers: string[]; x: number; y: number; via?: boolean }
|
|
39
39
|
|
|
40
40
|
const portToObjective = (port: Port): PcbRouteObjective => {
|
|
41
|
-
const portPosition = port.
|
|
41
|
+
const portPosition = port._getGlobalPcbPositionBeforeLayout()
|
|
42
42
|
return {
|
|
43
43
|
...portPosition,
|
|
44
44
|
layers: port.getAvailablePcbLayers(),
|
|
@@ -476,7 +476,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
|
|
|
476
476
|
for (const { port } of ports) {
|
|
477
477
|
connection.pointsToConnect.push(
|
|
478
478
|
projectPointInDirection(
|
|
479
|
-
port.
|
|
479
|
+
port._getGlobalSchematicPositionBeforeLayout(),
|
|
480
480
|
port.facingDirection!,
|
|
481
481
|
0.1501,
|
|
482
482
|
),
|
|
@@ -46,7 +46,7 @@ export class TraceHint extends PrimitiveComponent<typeof traceHintProps> {
|
|
|
46
46
|
|
|
47
47
|
if (!offsets) return []
|
|
48
48
|
|
|
49
|
-
const globalTransform = this.
|
|
49
|
+
const globalTransform = this._computePcbGlobalTransformBeforeLayout()
|
|
50
50
|
|
|
51
51
|
return offsets.map(
|
|
52
52
|
(offset): RouteHintPoint => ({
|
|
@@ -39,6 +39,8 @@ declare global {
|
|
|
39
39
|
pcbtrace: Props.PcbTraceProps
|
|
40
40
|
fabricationnotetext: Props.FabricationNoteTextProps
|
|
41
41
|
fabricationnotepath: Props.FabricationNotePathProps
|
|
42
|
+
constraint: Props.ConstraintProps
|
|
43
|
+
constrainedlayout: Props.ConstrainedLayoutProps
|
|
42
44
|
jscad: any
|
|
43
45
|
}
|
|
44
46
|
}
|
package/lib/utils/getClosest.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export type PointLike =
|
|
2
|
-
| {
|
|
2
|
+
| { _getGlobalPcbPositionBeforeLayout: () => { x: number; y: number } }
|
|
3
3
|
| { x: number; y: number }
|
|
4
4
|
|
|
5
5
|
const getDistance = (a: PointLike, b: PointLike) => {
|
|
6
|
-
const aPos =
|
|
7
|
-
|
|
6
|
+
const aPos =
|
|
7
|
+
"_getGlobalPcbPositionBeforeLayout" in a
|
|
8
|
+
? a._getGlobalPcbPositionBeforeLayout()
|
|
9
|
+
: a
|
|
10
|
+
const bPos =
|
|
11
|
+
"_getGlobalPcbPositionBeforeLayout" in b
|
|
12
|
+
? b._getGlobalPcbPositionBeforeLayout()
|
|
13
|
+
: b
|
|
8
14
|
return Math.sqrt((aPos.x - bPos.x) ** 2 + (aPos.y - bPos.y) ** 2)
|
|
9
15
|
}
|
|
10
16
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tscircuit/core",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.32",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"files": [
|
|
@@ -19,10 +19,12 @@
|
|
|
19
19
|
"@tscircuit/layout": "^0.0.28",
|
|
20
20
|
"@tscircuit/log-soup": "^1.0.2",
|
|
21
21
|
"@types/bun": "latest",
|
|
22
|
+
"@types/debug": "^4.1.12",
|
|
22
23
|
"@types/react": "^18.3.3",
|
|
23
24
|
"@types/react-reconciler": "^0.28.8",
|
|
24
25
|
"bun-match-svg": "0.0.2",
|
|
25
26
|
"circuit-to-svg": "^0.0.18",
|
|
27
|
+
"debug": "^4.3.6",
|
|
26
28
|
"howfat": "^0.3.8",
|
|
27
29
|
"looks-same": "^9.0.1",
|
|
28
30
|
"tsup": "^8.2.4"
|
|
@@ -31,8 +33,9 @@
|
|
|
31
33
|
"typescript": "^5.0.0"
|
|
32
34
|
},
|
|
33
35
|
"dependencies": {
|
|
36
|
+
"@lume/kiwi": "^0.4.3",
|
|
34
37
|
"@tscircuit/infgrid-ijump-astar": "^0.0.6",
|
|
35
|
-
"@tscircuit/props": "^0.0.
|
|
38
|
+
"@tscircuit/props": "^0.0.58",
|
|
36
39
|
"@tscircuit/soup": "^0.0.58",
|
|
37
40
|
"@tscircuit/soup-util": "0.0.18",
|
|
38
41
|
"footprinter": "^0.0.44",
|