@tscircuit/core 0.0.27 → 0.0.29

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
@@ -36,7 +36,7 @@ declare class Circuit {
36
36
  }
37
37
  declare const Project: typeof Circuit;
38
38
 
39
- declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPortRender", "PcbPrimitiveRender", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "CadModelRender", "PcbAnalysis"];
39
+ declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPortRender", "PcbPrimitiveRender", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "PcbComponentSizeCalculation", "CadModelRender"];
40
40
  type RenderPhase = (typeof orderedRenderPhases)[number];
41
41
  type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
42
42
  type RenderPhaseStates = Record<RenderPhase, {
@@ -119,7 +119,10 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
119
119
  computePcbPropsTransform(): Matrix;
120
120
  /**
121
121
  * Compute a transformation matrix combining all parent transforms for PCB
122
- * components
122
+ * components, including this component's translation and rotation.
123
+ *
124
+ * This is used to compute this component's position as well as all children
125
+ * components positions
123
126
  */
124
127
  computePcbGlobalTransform(): Matrix;
125
128
  /**
@@ -167,6 +170,10 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
167
170
  getNameAndAliases(): string[];
168
171
  isMatchingNameOrAlias(name: string): boolean;
169
172
  isMatchingAnyOf(aliases: Array<string | number>): boolean;
173
+ getPcbSize(): {
174
+ width: number;
175
+ height: number;
176
+ };
170
177
  doesSelectorMatch(selector: string): boolean;
171
178
  getSubcircuit(): PrimitiveComponent;
172
179
  selectAll(selector: string): PrimitiveComponent[];
@@ -545,7 +552,11 @@ declare class NormalComponent<ZodProps extends ZodType = any, PortNames extends
545
552
  */
546
553
  doInitialSchematicComponentRender(): void;
547
554
  doInitialPcbComponentRender(): void;
548
- doInitialPcbAnalysis(): void;
555
+ /**
556
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
557
+ * the width/height of the component
558
+ */
559
+ doInitialPcbComponentSizeCalculation(): void;
549
560
  _renderReactSubtree(element: ReactElement): ReactSubtree;
550
561
  doInitialReactSubtreesRender(): void;
551
562
  add(componentOrElm: PrimitiveComponent | ReactElement): void;
@@ -702,6 +713,10 @@ declare class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
702
713
  height?: string | number | undefined;
703
714
  }>]>;
704
715
  };
716
+ getPcbSize(): {
717
+ width: number;
718
+ height: number;
719
+ };
705
720
  doInitialPortMatching(): void;
706
721
  doInitialPcbPrimitiveRender(): void;
707
722
  }
@@ -3326,6 +3341,10 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3326
3341
  portHints?: (string | number)[] | undefined;
3327
3342
  }>]>;
3328
3343
  };
3344
+ getPcbSize(): {
3345
+ width: number;
3346
+ height: number;
3347
+ };
3329
3348
  doInitialPortMatching(): void;
3330
3349
  doInitialPcbPrimitiveRender(): void;
3331
3350
  }
package/dist/index.js CHANGED
@@ -56,8 +56,8 @@ var orderedRenderPhases = [
56
56
  "PcbLayout",
57
57
  "PcbTraceRender",
58
58
  "PcbRouteNetIslands",
59
- "CadModelRender",
60
- "PcbAnalysis"
59
+ "PcbComponentSizeCalculation",
60
+ "CadModelRender"
61
61
  ];
62
62
  var globalRenderCounter = 0;
63
63
  var Renderable = class {
@@ -239,9 +239,23 @@ var PrimitiveComponent = class extends Renderable {
239
239
  }
240
240
  /**
241
241
  * Compute a transformation matrix combining all parent transforms for PCB
242
- * components
242
+ * components, including this component's translation and rotation.
243
+ *
244
+ * This is used to compute this component's position as well as all children
245
+ * components positions
243
246
  */
244
247
  computePcbGlobalTransform() {
248
+ const { _parsedProps: props } = this;
249
+ const manualPlacement = this.getSubcircuit()._getManualPlacementForComponent(this);
250
+ if (manualPlacement && this.props.pcbX === void 0 && this.props.pcbY === void 0) {
251
+ return compose(
252
+ this.parent?.computePcbGlobalTransform() ?? identity(),
253
+ compose(
254
+ translate(manualPlacement.x, manualPlacement.y),
255
+ rotate((props.pcbRotation ?? 0) * Math.PI / 180)
256
+ )
257
+ );
258
+ }
245
259
  return compose(
246
260
  this.parent?.computePcbGlobalTransform() ?? identity(),
247
261
  this.computePcbPropsTransform()
@@ -296,10 +310,6 @@ var PrimitiveComponent = class extends Renderable {
296
310
  return null;
297
311
  }
298
312
  getGlobalPcbPosition() {
299
- const manualPlacement = this.getSubcircuit()._getManualPlacementForComponent(this);
300
- if (manualPlacement) {
301
- return manualPlacement;
302
- }
303
313
  return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 });
304
314
  }
305
315
  getGlobalSchematicPosition() {
@@ -360,6 +370,9 @@ var PrimitiveComponent = class extends Renderable {
360
370
  (a) => aliases.map((a2) => a2.toString()).includes(a)
361
371
  );
362
372
  }
373
+ getPcbSize() {
374
+ throw new Error(`getPcbSize not implemented for ${this.componentName}`);
375
+ }
363
376
  doesSelectorMatch(selector) {
364
377
  const myTypeNames = [this.componentName, this.lowercaseComponentName];
365
378
  const myClassNames = [this._parsedProps.name].filter(Boolean);
@@ -830,6 +843,18 @@ var SmtPad = class extends PrimitiveComponent {
830
843
  zodProps: smtPadProps
831
844
  };
832
845
  }
846
+ getPcbSize() {
847
+ const { _parsedProps: props } = this;
848
+ if (props.shape === "circle") {
849
+ return { width: props.radius * 2, height: props.radius * 2 };
850
+ }
851
+ if (props.shape === "rect") {
852
+ return { width: props.width, height: props.height };
853
+ }
854
+ throw new Error(
855
+ `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
856
+ );
857
+ }
833
858
  doInitialPortMatching() {
834
859
  const parentPorts = (this.parent?.children ?? []).filter(
835
860
  (c) => c.componentName === "Port"
@@ -934,6 +959,18 @@ var PlatedHole = class extends PrimitiveComponent {
934
959
  zodProps: platedHoleProps
935
960
  };
936
961
  }
962
+ getPcbSize() {
963
+ const { _parsedProps: props } = this;
964
+ if (props.shape === "circle") {
965
+ return { width: props.outerDiameter, height: props.outerDiameter };
966
+ }
967
+ if (props.shape === "oval") {
968
+ return { width: props.outerWidth, height: props.outerHeight };
969
+ }
970
+ throw new Error(
971
+ `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
972
+ );
973
+ }
937
974
  doInitialPortMatching() {
938
975
  const parentPorts = (this.parent?.children ?? []).filter(
939
976
  (c) => c.componentName === "Port"
@@ -1307,8 +1344,8 @@ var NormalComponent = class extends PrimitiveComponent {
1307
1344
  const { db } = this.project;
1308
1345
  const { _parsedProps: props } = this;
1309
1346
  const pcb_component = db.pcb_component.insert({
1310
- center: { x: this.props.pcbX ?? 0, y: this.props.pcbY ?? 0 },
1311
- // width/height are computed in the PcbAnalysis phase
1347
+ center: this.getGlobalPcbPosition(),
1348
+ // width/height are computed in the PcbComponentSizeCalculation phase
1312
1349
  width: 0,
1313
1350
  height: 0,
1314
1351
  layer: props.layer ?? "top",
@@ -1317,9 +1354,34 @@ var NormalComponent = class extends PrimitiveComponent {
1317
1354
  });
1318
1355
  this.pcb_component_id = pcb_component.pcb_component_id;
1319
1356
  }
1320
- doInitialPcbAnalysis() {
1357
+ /**
1358
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
1359
+ * the width/height of the component
1360
+ */
1361
+ doInitialPcbComponentSizeCalculation() {
1362
+ if (!this.pcb_component_id) return;
1321
1363
  const { db } = this.project;
1322
1364
  const { _parsedProps: props } = this;
1365
+ let minX = Infinity;
1366
+ let minY = Infinity;
1367
+ let maxX = -Infinity;
1368
+ let maxY = -Infinity;
1369
+ for (const child of this.children) {
1370
+ if (child.isPcbPrimitive) {
1371
+ const { x, y } = child.getGlobalPcbPosition();
1372
+ const { width: width2, height: height2 } = child.getPcbSize();
1373
+ minX = Math.min(minX, x - width2 / 2);
1374
+ minY = Math.min(minY, y - height2 / 2);
1375
+ maxX = Math.max(maxX, x + width2 / 2);
1376
+ maxY = Math.max(maxY, y + height2 / 2);
1377
+ }
1378
+ }
1379
+ const width = maxX - minX;
1380
+ const height = maxY - minY;
1381
+ db.pcb_component.update(this.pcb_component_id, {
1382
+ width,
1383
+ height
1384
+ });
1323
1385
  }
1324
1386
  _renderReactSubtree(element) {
1325
1387
  return {
@@ -194,8 +194,8 @@ export class NormalComponent<
194
194
  const { db } = this.project!
195
195
  const { _parsedProps: props } = this
196
196
  const pcb_component = db.pcb_component.insert({
197
- center: { x: this.props.pcbX ?? 0, y: this.props.pcbY ?? 0 },
198
- // width/height are computed in the PcbAnalysis phase
197
+ center: this.getGlobalPcbPosition(),
198
+ // width/height are computed in the PcbComponentSizeCalculation phase
199
199
  width: 0,
200
200
  height: 0,
201
201
  layer: props.layer ?? "top",
@@ -205,11 +205,38 @@ export class NormalComponent<
205
205
  this.pcb_component_id = pcb_component.pcb_component_id
206
206
  }
207
207
 
208
- doInitialPcbAnalysis(): void {
208
+ /**
209
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
210
+ * the width/height of the component
211
+ */
212
+ doInitialPcbComponentSizeCalculation(): void {
213
+ if (!this.pcb_component_id) return
209
214
  const { db } = this.project!
210
215
  const { _parsedProps: props } = this
211
216
 
212
- // TODO Examine children to compute width/height and pcbX/pcbY
217
+ let minX = Infinity
218
+ let minY = Infinity
219
+ let maxX = -Infinity
220
+ let maxY = -Infinity
221
+
222
+ for (const child of this.children) {
223
+ if (child.isPcbPrimitive) {
224
+ const { x, y } = child.getGlobalPcbPosition()
225
+ const { width, height } = child.getPcbSize()
226
+ minX = Math.min(minX, x - width / 2)
227
+ minY = Math.min(minY, y - height / 2)
228
+ maxX = Math.max(maxX, x + width / 2)
229
+ maxY = Math.max(maxY, y + height / 2)
230
+ }
231
+ }
232
+
233
+ const width = maxX - minX
234
+ const height = maxY - minY
235
+
236
+ db.pcb_component.update(this.pcb_component_id!, {
237
+ width,
238
+ height,
239
+ })
213
240
  }
214
241
 
215
242
  _renderReactSubtree(element: ReactElement): ReactSubtree {
@@ -126,9 +126,31 @@ export abstract class PrimitiveComponent<
126
126
 
127
127
  /**
128
128
  * Compute a transformation matrix combining all parent transforms for PCB
129
- * components
129
+ * components, including this component's translation and rotation.
130
+ *
131
+ * This is used to compute this component's position as well as all children
132
+ * components positions
130
133
  */
131
134
  computePcbGlobalTransform(): Matrix {
135
+ const { _parsedProps: props } = this
136
+ const manualPlacement =
137
+ this.getSubcircuit()._getManualPlacementForComponent(this)
138
+
139
+ // pcbX or pcbY will override the manual placement
140
+ if (
141
+ manualPlacement &&
142
+ this.props.pcbX === undefined &&
143
+ this.props.pcbY === undefined
144
+ ) {
145
+ return compose(
146
+ this.parent?.computePcbGlobalTransform() ?? identity(),
147
+ compose(
148
+ translate(manualPlacement.x, manualPlacement.y),
149
+ rotate(((props.pcbRotation ?? 0) * Math.PI) / 180),
150
+ ),
151
+ )
152
+ }
153
+
132
154
  return compose(
133
155
  this.parent?.computePcbGlobalTransform() ?? identity(),
134
156
  this.computePcbPropsTransform(),
@@ -198,11 +220,6 @@ export abstract class PrimitiveComponent<
198
220
  }
199
221
 
200
222
  getGlobalPcbPosition(): { x: number; y: number } {
201
- const manualPlacement =
202
- this.getSubcircuit()._getManualPlacementForComponent(this)
203
- if (manualPlacement) {
204
- return manualPlacement
205
- }
206
223
  return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 })
207
224
  }
208
225
 
@@ -281,6 +298,9 @@ export abstract class PrimitiveComponent<
281
298
  aliases.map((a) => a.toString()).includes(a),
282
299
  )
283
300
  }
301
+ getPcbSize(): { width: number; height: number } {
302
+ throw new Error(`getPcbSize not implemented for ${this.componentName}`)
303
+ }
284
304
 
285
305
  doesSelectorMatch(selector: string): boolean {
286
306
  const myTypeNames = [this.componentName, this.lowercaseComponentName]
@@ -21,8 +21,8 @@ export const orderedRenderPhases = [
21
21
  "PcbLayout",
22
22
  "PcbTraceRender",
23
23
  "PcbRouteNetIslands",
24
+ "PcbComponentSizeCalculation",
24
25
  "CadModelRender",
25
- "PcbAnalysis",
26
26
  ] as const
27
27
 
28
28
  export type RenderPhase = (typeof orderedRenderPhases)[number]
@@ -14,6 +14,19 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
14
14
  }
15
15
  }
16
16
 
17
+ getPcbSize(): { width: number; height: number } {
18
+ const { _parsedProps: props } = this
19
+ if (props.shape === "circle") {
20
+ return { width: props.outerDiameter, height: props.outerDiameter }
21
+ }
22
+ if (props.shape === "oval") {
23
+ return { width: props.outerWidth, height: props.outerHeight }
24
+ }
25
+ throw new Error(
26
+ `getPcbSize for shape "${(props as any).shape}" not implemented for ${this.componentName}`,
27
+ )
28
+ }
29
+
17
30
  doInitialPortMatching(): void {
18
31
  const parentPorts = (this.parent?.children ?? []).filter(
19
32
  (c) => c.componentName === "Port",
@@ -18,6 +18,19 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
18
18
  }
19
19
  }
20
20
 
21
+ getPcbSize(): { width: number; height: number } {
22
+ const { _parsedProps: props } = this
23
+ if (props.shape === "circle") {
24
+ return { width: props.radius! * 2, height: props.radius! * 2 }
25
+ }
26
+ if (props.shape === "rect") {
27
+ return { width: props.width!, height: props.height! }
28
+ }
29
+ throw new Error(
30
+ `getPcbSize for shape "${(props as any).shape}" not implemented for ${this.componentName}`,
31
+ )
32
+ }
33
+
21
34
  doInitialPortMatching(): void {
22
35
  const parentPorts = (this.parent?.children ?? []).filter(
23
36
  (c) => c.componentName === "Port",
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.27",
5
+ "version": "0.0.29",
6
6
  "types": "dist/index.d.ts",
7
7
  "main": "dist/index.js",
8
8
  "files": [