@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 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", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "PcbComponentSizeCalculation", "CadModelRender"];
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
- "PcbParentAttachment",
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/index.ts
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 pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(
2242
- hints.flatMap((h) => h.getPcbRouteHints())
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(
@@ -19,7 +19,7 @@ export const orderedRenderPhases = [
19
19
  "PcbPrimitiveRender",
20
20
  "PcbFootprintLayout",
21
21
  "PcbPortRender",
22
- "PcbParentAttachment",
22
+ "PcbPortAttachment",
23
23
  "PcbLayout",
24
24
  "PcbTraceRender",
25
25
  "PcbRouteNetIslands",
@@ -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 pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(
303
- hints.flatMap((h) => h.getPcbRouteHints()),
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
- 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
- }
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
+ }
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.41",
5
+ "version": "0.0.43",
6
6
  "types": "dist/index.d.ts",
7
7
  "main": "dist/index.js",
8
8
  "files": [