@tscircuit/core 0.0.41 → 0.0.43
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 +3 -1
- package/dist/index.js +37 -4
- package/lib/components/base-components/Renderable.ts +1 -1
- package/lib/components/primitive-components/PlatedHole.ts +7 -0
- package/lib/components/primitive-components/SmtPad.ts +9 -2
- package/lib/components/primitive-components/Trace.ts +18 -3
- package/lib/utils/selector-matching/index.ts +2 -64
- package/lib/utils/selector-matching/is-matching-path-selector.ts +32 -0
- package/lib/utils/selector-matching/is-matching-selector.ts +64 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ declare class Circuit {
|
|
|
41
41
|
*/
|
|
42
42
|
declare const Project: typeof Circuit;
|
|
43
43
|
|
|
44
|
-
declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "InitializePortsFromChildren", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPrimitiveRender", "PcbFootprintLayout", "PcbPortRender", "
|
|
44
|
+
declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "InitializePortsFromChildren", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPrimitiveRender", "PcbFootprintLayout", "PcbPortRender", "PcbPortAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "PcbComponentSizeCalculation", "CadModelRender"];
|
|
45
45
|
type RenderPhase = (typeof orderedRenderPhases)[number];
|
|
46
46
|
type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
|
|
47
47
|
type RenderPhaseStates = Record<RenderPhase, {
|
|
@@ -788,6 +788,7 @@ declare class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
788
788
|
};
|
|
789
789
|
doInitialPortMatching(): void;
|
|
790
790
|
doInitialPcbPrimitiveRender(): void;
|
|
791
|
+
doInitialPcbPortAttachment(): void;
|
|
791
792
|
_getPcbCircuitJsonBounds(): {
|
|
792
793
|
center: {
|
|
793
794
|
x: number;
|
|
@@ -3857,6 +3858,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
|
|
|
3857
3858
|
}): void;
|
|
3858
3859
|
doInitialPortMatching(): void;
|
|
3859
3860
|
doInitialPcbPrimitiveRender(): void;
|
|
3861
|
+
doInitialPcbPortAttachment(): void;
|
|
3860
3862
|
}
|
|
3861
3863
|
|
|
3862
3864
|
declare const edgeSpecifiers: readonly ["leftedge", "rightedge", "topedge", "bottomedge", "center"];
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var orderedRenderPhases = [
|
|
|
56
56
|
"PcbPrimitiveRender",
|
|
57
57
|
"PcbFootprintLayout",
|
|
58
58
|
"PcbPortRender",
|
|
59
|
-
"
|
|
59
|
+
"PcbPortAttachment",
|
|
60
60
|
"PcbLayout",
|
|
61
61
|
"PcbTraceRender",
|
|
62
62
|
"PcbRouteNetIslands",
|
|
@@ -134,7 +134,7 @@ import {
|
|
|
134
134
|
translate
|
|
135
135
|
} from "transformation-matrix";
|
|
136
136
|
|
|
137
|
-
// lib/utils/selector-matching/
|
|
137
|
+
// lib/utils/selector-matching/is-matching-selector.ts
|
|
138
138
|
function isMatchingSelector(component, selector) {
|
|
139
139
|
const idMatch = selector.match(/^#(\w+)/);
|
|
140
140
|
if (idMatch) {
|
|
@@ -163,6 +163,20 @@ function isMatchingSelector(component, selector) {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// lib/utils/selector-matching/is-matching-path-selector.ts
|
|
167
|
+
function isMatchingPathSelector(component, selector) {
|
|
168
|
+
const selectorParts = selector.split(/\> /g).map((part) => part.trim());
|
|
169
|
+
let currentComponent = component;
|
|
170
|
+
for (let i = selectorParts.length - 1; i >= 0; i--) {
|
|
171
|
+
if (!currentComponent) return false;
|
|
172
|
+
if (!isMatchingSelector(currentComponent, selectorParts[i])) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
currentComponent = currentComponent.parent;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
// lib/components/base-components/PrimitiveComponent.ts
|
|
167
181
|
var PrimitiveComponent = class extends Renderable {
|
|
168
182
|
parent = null;
|
|
@@ -1128,6 +1142,7 @@ var SmtPad = class extends PrimitiveComponent {
|
|
|
1128
1142
|
pcb_smtpad = db.pcb_smtpad.insert({
|
|
1129
1143
|
pcb_component_id: this.parent?.pcb_component_id,
|
|
1130
1144
|
pcb_port_id: this.matchedPort?.pcb_port_id,
|
|
1145
|
+
// port likely isn't matched
|
|
1131
1146
|
layer: props.layer ?? "top",
|
|
1132
1147
|
shape: "circle",
|
|
1133
1148
|
// @ts-ignore: no idea why this is triggering
|
|
@@ -1140,6 +1155,7 @@ var SmtPad = class extends PrimitiveComponent {
|
|
|
1140
1155
|
pcb_smtpad = db.pcb_smtpad.insert({
|
|
1141
1156
|
pcb_component_id: this.parent?.pcb_component_id,
|
|
1142
1157
|
pcb_port_id: this.matchedPort?.pcb_port_id,
|
|
1158
|
+
// port likely isn't matched
|
|
1143
1159
|
layer: props.layer ?? "top",
|
|
1144
1160
|
shape: "rect",
|
|
1145
1161
|
...isRotated90 ? { width: props.height, height: props.width } : { width: props.width, height: props.height },
|
|
@@ -1152,6 +1168,12 @@ var SmtPad = class extends PrimitiveComponent {
|
|
|
1152
1168
|
this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
|
|
1153
1169
|
}
|
|
1154
1170
|
}
|
|
1171
|
+
doInitialPcbPortAttachment() {
|
|
1172
|
+
const { db } = this.root;
|
|
1173
|
+
db.pcb_smtpad.update(this.pcb_smtpad_id, {
|
|
1174
|
+
pcb_port_id: this.matchedPort?.pcb_port_id
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1155
1177
|
_getPcbCircuitJsonBounds() {
|
|
1156
1178
|
const { db } = this.root;
|
|
1157
1179
|
const smtpad = db.pcb_smtpad.get(this.pcb_smtpad_id);
|
|
@@ -1317,6 +1339,12 @@ var PlatedHole = class extends PrimitiveComponent {
|
|
|
1317
1339
|
this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id;
|
|
1318
1340
|
}
|
|
1319
1341
|
}
|
|
1342
|
+
doInitialPcbPortAttachment() {
|
|
1343
|
+
const { db } = this.root;
|
|
1344
|
+
db.pcb_plated_hole.update(this.pcb_plated_hole_id, {
|
|
1345
|
+
pcb_port_id: this.matchedPort?.pcb_port_id
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1320
1348
|
};
|
|
1321
1349
|
|
|
1322
1350
|
// lib/utils/createComponentsFromSoup.ts
|
|
@@ -2238,8 +2266,13 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
2238
2266
|
const hints = ports.flatMap(
|
|
2239
2267
|
(port) => port.matchedComponents.filter((c) => c.componentName === "TraceHint")
|
|
2240
2268
|
);
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2269
|
+
const manualTraceHints = this.getSubcircuit().props.layout?.manual_trace_hints ?? [];
|
|
2270
|
+
const pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(hints.flatMap((h) => h.getPcbRouteHints())).concat(
|
|
2271
|
+
manualTraceHints.filter(
|
|
2272
|
+
(hint) => ports.some(
|
|
2273
|
+
(port) => isMatchingPathSelector(port, hint.pcb_port_selector)
|
|
2274
|
+
)
|
|
2275
|
+
).flatMap((hint) => hint.offsets)
|
|
2243
2276
|
);
|
|
2244
2277
|
if (ports.length > 2) {
|
|
2245
2278
|
this.renderError(
|
|
@@ -98,4 +98,11 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
|
|
|
98
98
|
this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
doInitialPcbPortAttachment(): void {
|
|
103
|
+
const { db } = this.root!
|
|
104
|
+
db.pcb_plated_hole.update(this.pcb_plated_hole_id!, {
|
|
105
|
+
pcb_port_id: this.matchedPort?.pcb_port_id!,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
101
108
|
}
|
|
@@ -63,7 +63,7 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
63
63
|
if (props.shape === "circle") {
|
|
64
64
|
pcb_smtpad = db.pcb_smtpad.insert({
|
|
65
65
|
pcb_component_id: this.parent?.pcb_component_id!,
|
|
66
|
-
pcb_port_id: this.matchedPort?.pcb_port_id!,
|
|
66
|
+
pcb_port_id: this.matchedPort?.pcb_port_id!, // port likely isn't matched
|
|
67
67
|
layer: props.layer ?? "top",
|
|
68
68
|
shape: "circle",
|
|
69
69
|
|
|
@@ -78,7 +78,7 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
78
78
|
} else if (props.shape === "rect") {
|
|
79
79
|
pcb_smtpad = db.pcb_smtpad.insert({
|
|
80
80
|
pcb_component_id: this.parent?.pcb_component_id!,
|
|
81
|
-
pcb_port_id: this.matchedPort?.pcb_port_id!,
|
|
81
|
+
pcb_port_id: this.matchedPort?.pcb_port_id!, // port likely isn't matched
|
|
82
82
|
layer: props.layer ?? "top",
|
|
83
83
|
shape: "rect",
|
|
84
84
|
|
|
@@ -97,6 +97,13 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
doInitialPcbPortAttachment(): void {
|
|
101
|
+
const { db } = this.root!
|
|
102
|
+
db.pcb_smtpad.update(this.pcb_smtpad_id!, {
|
|
103
|
+
pcb_port_id: this.matchedPort?.pcb_port_id!,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
_getPcbCircuitJsonBounds(): {
|
|
101
108
|
center: { x: number; y: number }
|
|
102
109
|
bounds: { left: number; top: number; right: number; bottom: number }
|
|
@@ -32,6 +32,10 @@ import type { Net } from "./Net"
|
|
|
32
32
|
import { getClosest } from "lib/utils/getClosest"
|
|
33
33
|
import { z } from "zod"
|
|
34
34
|
import { createNetsFromProps } from "lib/utils/components/createNetsFromProps"
|
|
35
|
+
import {
|
|
36
|
+
isMatchingPathSelector,
|
|
37
|
+
isMatchingSelector,
|
|
38
|
+
} from "lib/utils/selector-matching"
|
|
35
39
|
|
|
36
40
|
type PcbRouteObjective =
|
|
37
41
|
| RouteHintPoint
|
|
@@ -299,9 +303,20 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
|
|
|
299
303
|
port.matchedComponents.filter((c) => c.componentName === "TraceHint"),
|
|
300
304
|
) as TraceHint[]
|
|
301
305
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
const manualTraceHints =
|
|
307
|
+
this.getSubcircuit().props.layout?.manual_trace_hints ?? []
|
|
308
|
+
|
|
309
|
+
const pcbRouteHints = (this._parsedProps.pcbRouteHints ?? [])
|
|
310
|
+
.concat(hints.flatMap((h) => h.getPcbRouteHints()))
|
|
311
|
+
.concat(
|
|
312
|
+
manualTraceHints
|
|
313
|
+
.filter((hint: { pcb_port_selector: string }) =>
|
|
314
|
+
ports.some((port) =>
|
|
315
|
+
isMatchingPathSelector(port, hint.pcb_port_selector),
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
.flatMap((hint: { offsets: RouteHintPoint[] }) => hint.offsets),
|
|
319
|
+
)
|
|
305
320
|
|
|
306
321
|
if (ports.length > 2) {
|
|
307
322
|
this.renderError(
|
|
@@ -1,64 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Determines if a component matches a given selector.
|
|
6
|
-
*
|
|
7
|
-
* This function supports the following selector types:
|
|
8
|
-
* - ID selectors (#id)
|
|
9
|
-
* - Class selectors (.className)
|
|
10
|
-
* - Type selectors (resistor)
|
|
11
|
-
* - Attribute selectors ([key='value'])
|
|
12
|
-
*
|
|
13
|
-
* It also supports combinations of these selectors.
|
|
14
|
-
*
|
|
15
|
-
* @param component - The component to check.
|
|
16
|
-
* @param selector - The selector string to match against.
|
|
17
|
-
* @returns True if the component matches the selector, false otherwise.
|
|
18
|
-
*/
|
|
19
|
-
export function isMatchingSelector(
|
|
20
|
-
component: PrimitiveComponent,
|
|
21
|
-
selector: string,
|
|
22
|
-
): boolean {
|
|
23
|
-
// Check for ID selector
|
|
24
|
-
const idMatch = selector.match(/^#(\w+)/)
|
|
25
|
-
if (idMatch) {
|
|
26
|
-
return component.props.id === idMatch[1]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check for class selector
|
|
30
|
-
const classMatch = selector.match(/^\.(\w+)/)
|
|
31
|
-
if (classMatch) {
|
|
32
|
-
return component.isMatchingNameOrAlias(classMatch[1])
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Split the selector into type and conditions
|
|
36
|
-
let [type, ...conditions] = selector.split(/(?=[#.[])/)
|
|
37
|
-
|
|
38
|
-
if (type === "pin") type = "port"
|
|
39
|
-
|
|
40
|
-
// Check if the component type matches
|
|
41
|
-
if (
|
|
42
|
-
type &&
|
|
43
|
-
type !== "*" &&
|
|
44
|
-
component.lowercaseComponentName !== type.toLowerCase()
|
|
45
|
-
) {
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check all conditions
|
|
50
|
-
return conditions.every((condition) => {
|
|
51
|
-
if (condition.startsWith("#")) {
|
|
52
|
-
return component.props.id === condition.slice(1)
|
|
53
|
-
}
|
|
54
|
-
if (condition.startsWith(".")) {
|
|
55
|
-
return component.isMatchingNameOrAlias(condition.slice(1))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check for attribute selector
|
|
59
|
-
const match = condition.match(/\[(\w+)=['"]?(.+?)['"]?\]/)
|
|
60
|
-
if (!match) return true
|
|
61
|
-
const [, prop, value] = match
|
|
62
|
-
return component.props[prop].toString() === value
|
|
63
|
-
})
|
|
64
|
-
}
|
|
1
|
+
export * from "./is-matching-selector"
|
|
2
|
+
export * from "./is-matching-path-selector"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PrimitiveComponent } from "lib/components"
|
|
2
|
+
import type { Primitive } from "schematic-symbols/drawing/types"
|
|
3
|
+
|
|
4
|
+
import { isMatchingSelector } from "./is-matching-selector"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Determines if a component matches a path selector, meaning that the last
|
|
8
|
+
* selector matches the current component, and the previous selectors match
|
|
9
|
+
* the parent components.
|
|
10
|
+
*/
|
|
11
|
+
export function isMatchingPathSelector(
|
|
12
|
+
component: PrimitiveComponent,
|
|
13
|
+
selector: string,
|
|
14
|
+
): boolean {
|
|
15
|
+
const selectorParts = selector.split(/\> /g).map((part) => part.trim())
|
|
16
|
+
let currentComponent: PrimitiveComponent | null = component
|
|
17
|
+
|
|
18
|
+
// Iterate through selector parts from right to left
|
|
19
|
+
for (let i = selectorParts.length - 1; i >= 0; i--) {
|
|
20
|
+
if (!currentComponent) return false
|
|
21
|
+
|
|
22
|
+
if (!isMatchingSelector(currentComponent, selectorParts[i])) {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Move to the parent component for the next iteration
|
|
27
|
+
currentComponent = currentComponent.parent
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If we've matched all parts of the selector, it's a match
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PrimitiveComponent } from "lib/components"
|
|
2
|
+
import type { Primitive } from "schematic-symbols/drawing/types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines if a component matches a given selector.
|
|
6
|
+
*
|
|
7
|
+
* This function supports the following selector types:
|
|
8
|
+
* - ID selectors (#id)
|
|
9
|
+
* - Class selectors (.className)
|
|
10
|
+
* - Type selectors (resistor)
|
|
11
|
+
* - Attribute selectors ([key='value'])
|
|
12
|
+
*
|
|
13
|
+
* It also supports combinations of these selectors.
|
|
14
|
+
*
|
|
15
|
+
* @param component - The component to check.
|
|
16
|
+
* @param selector - The selector string to match against.
|
|
17
|
+
* @returns True if the component matches the selector, false otherwise.
|
|
18
|
+
*/
|
|
19
|
+
export function isMatchingSelector(
|
|
20
|
+
component: PrimitiveComponent,
|
|
21
|
+
selector: string,
|
|
22
|
+
): boolean {
|
|
23
|
+
// Check for ID selector
|
|
24
|
+
const idMatch = selector.match(/^#(\w+)/)
|
|
25
|
+
if (idMatch) {
|
|
26
|
+
return component.props.id === idMatch[1]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check for class selector
|
|
30
|
+
const classMatch = selector.match(/^\.(\w+)/)
|
|
31
|
+
if (classMatch) {
|
|
32
|
+
return component.isMatchingNameOrAlias(classMatch[1])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Split the selector into type and conditions
|
|
36
|
+
let [type, ...conditions] = selector.split(/(?=[#.[])/)
|
|
37
|
+
|
|
38
|
+
if (type === "pin") type = "port"
|
|
39
|
+
|
|
40
|
+
// Check if the component type matches
|
|
41
|
+
if (
|
|
42
|
+
type &&
|
|
43
|
+
type !== "*" &&
|
|
44
|
+
component.lowercaseComponentName !== type.toLowerCase()
|
|
45
|
+
) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check all conditions
|
|
50
|
+
return conditions.every((condition) => {
|
|
51
|
+
if (condition.startsWith("#")) {
|
|
52
|
+
return component.props.id === condition.slice(1)
|
|
53
|
+
}
|
|
54
|
+
if (condition.startsWith(".")) {
|
|
55
|
+
return component.isMatchingNameOrAlias(condition.slice(1))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for attribute selector
|
|
59
|
+
const match = condition.match(/\[(\w+)=['"]?(.+?)['"]?\]/)
|
|
60
|
+
if (!match) return true
|
|
61
|
+
const [, prop, value] = match
|
|
62
|
+
return component.props[prop].toString() === value
|
|
63
|
+
})
|
|
64
|
+
}
|