@tscircuit/core 0.0.993 → 0.0.995

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +1162 -4
  2. package/dist/index.js +193 -59
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ __export(components_exports, {
78
78
  SmtPad: () => SmtPad,
79
79
  SolderJumper: () => SolderJumper,
80
80
  Subcircuit: () => Subcircuit,
81
+ Subpanel: () => Subpanel,
81
82
  Switch: () => Switch,
82
83
  Symbol: () => SymbolComponent,
83
84
  TestPoint: () => TestPoint,
@@ -16682,6 +16683,10 @@ var Board = class extends Group6 {
16682
16683
 
16683
16684
  // lib/components/normal-components/Panel.ts
16684
16685
  import { panelProps } from "@tscircuit/props";
16686
+ import { distance as distance12 } from "circuit-json";
16687
+
16688
+ // lib/components/normal-components/Subpanel.ts
16689
+ import { subpanelProps } from "@tscircuit/props";
16685
16690
  import { distance as distance11 } from "circuit-json";
16686
16691
 
16687
16692
  // lib/utils/panels/generate-panel-tabs-and-mouse-bites.ts
@@ -17017,16 +17022,19 @@ var packBoardsIntoGrid = ({
17017
17022
  return { positions, gridWidth: totalGridWidth, gridHeight: totalGridHeight };
17018
17023
  };
17019
17024
 
17020
- // lib/components/normal-components/Panel.ts
17021
- var Panel = class extends Group6 {
17025
+ // lib/components/normal-components/Subpanel.ts
17026
+ var Subpanel = class _Subpanel extends Group6 {
17022
17027
  pcb_panel_id = null;
17023
17028
  _tabsAndMouseBitesGenerated = false;
17024
17029
  get config() {
17025
17030
  return {
17026
- componentName: "Panel",
17027
- zodProps: panelProps
17031
+ componentName: "Subpanel",
17032
+ zodProps: subpanelProps
17028
17033
  };
17029
17034
  }
17035
+ get _errorComponentName() {
17036
+ return this.componentName.toLowerCase();
17037
+ }
17030
17038
  get isGroup() {
17031
17039
  return true;
17032
17040
  }
@@ -17034,19 +17042,55 @@ var Panel = class extends Group6 {
17034
17042
  return true;
17035
17043
  }
17036
17044
  add(component) {
17037
- if (component.lowercaseComponentName !== "board") {
17038
- throw new Error("<panel> can only contain <board> elements");
17045
+ if (component.lowercaseComponentName !== "board" && component.lowercaseComponentName !== "subpanel") {
17046
+ throw new Error(
17047
+ `<${this._errorComponentName}> can only contain <board> or <subpanel> elements`
17048
+ );
17039
17049
  }
17040
17050
  super.add(component);
17041
17051
  }
17042
17052
  _cachedGridWidth = 0;
17043
17053
  _cachedGridHeight = 0;
17054
+ /**
17055
+ * Get all board instances from this subpanel and nested subpanels
17056
+ */
17057
+ _getAllBoardInstances() {
17058
+ const boards = [];
17059
+ for (const child of this.children) {
17060
+ if (child instanceof Board) {
17061
+ boards.push(child);
17062
+ } else if (child instanceof _Subpanel) {
17063
+ boards.push(...child._getAllBoardInstances());
17064
+ }
17065
+ }
17066
+ return boards;
17067
+ }
17068
+ /**
17069
+ * Check if this subpanel contains at least one board (directly or through nested subpanels)
17070
+ */
17071
+ _containsBoards() {
17072
+ for (const child of this.children) {
17073
+ if (child.componentName === "Board") {
17074
+ return true;
17075
+ }
17076
+ if (child.componentName === "Subpanel" && "_containsBoards" in child) {
17077
+ if (child._containsBoards()) {
17078
+ return true;
17079
+ }
17080
+ }
17081
+ }
17082
+ return false;
17083
+ }
17084
+ /**
17085
+ * Get direct board children only (not from nested subpanels)
17086
+ */
17087
+ _getDirectBoardChildren() {
17088
+ return this.children.filter((c) => c instanceof Board);
17089
+ }
17044
17090
  doInitialPanelBoardLayout() {
17045
17091
  if (this.root?.pcbDisabled) return;
17046
17092
  const layoutMode = this._parsedProps.layoutMode ?? "none";
17047
- const childBoardInstances = this.children.filter(
17048
- (c) => c instanceof Board
17049
- );
17093
+ const childBoardInstances = this._getDirectBoardChildren();
17050
17094
  if (layoutMode !== "none") {
17051
17095
  for (const board of childBoardInstances) {
17052
17096
  const hasPcbX = board._parsedProps.pcbX !== void 0;
@@ -17059,12 +17103,25 @@ var Panel = class extends Group6 {
17059
17103
  this.root.db.source_property_ignored_warning.insert({
17060
17104
  source_component_id: board.source_component_id,
17061
17105
  property_name: propertyNames,
17062
- message: `Board has manual positioning (${propertyNames}) but panel layout mode is "${layoutMode}". Manual positioning will be ignored.`,
17106
+ message: `Board has manual positioning (${propertyNames}) but ${this._errorComponentName} layout mode is "${layoutMode}". Manual positioning will be ignored.`,
17063
17107
  error_type: "source_property_ignored_warning"
17064
17108
  });
17065
17109
  }
17066
17110
  }
17067
17111
  }
17112
+ if (layoutMode === "none" && childBoardInstances.length > 1) {
17113
+ const boardsWithoutPosition = childBoardInstances.filter((board) => {
17114
+ const hasPcbX = board._parsedProps.pcbX !== void 0;
17115
+ const hasPcbY = board._parsedProps.pcbY !== void 0;
17116
+ return !hasPcbX && !hasPcbY;
17117
+ });
17118
+ if (boardsWithoutPosition.length > 1) {
17119
+ this.root.db.pcb_placement_error.insert({
17120
+ error_type: "pcb_placement_error",
17121
+ message: `Multiple boards in ${this._errorComponentName} without pcbX/pcbY positions. When layoutMode="none", each board must have explicit pcbX and pcbY coordinates to avoid overlapping. Either set pcbX/pcbY on each board, or use layoutMode="grid" for automatic positioning.`
17122
+ });
17123
+ }
17124
+ }
17068
17125
  if (layoutMode !== "grid") return;
17069
17126
  const tabWidth = this._parsedProps.tabWidth ?? DEFAULT_TAB_WIDTH;
17070
17127
  const boardGap = this._parsedProps.boardGap ?? tabWidth;
@@ -17085,9 +17142,7 @@ var Panel = class extends Group6 {
17085
17142
  doInitialPanelLayout() {
17086
17143
  if (this.root?.pcbDisabled) return;
17087
17144
  const { db } = this.root;
17088
- const childBoardInstances = this.children.filter(
17089
- (c) => c instanceof Board
17090
- );
17145
+ const childBoardInstances = this._getDirectBoardChildren();
17091
17146
  const layoutMode = this._parsedProps.layoutMode ?? "none";
17092
17147
  if (layoutMode === "grid") {
17093
17148
  for (const board of childBoardInstances) {
@@ -17098,39 +17153,7 @@ var Panel = class extends Group6 {
17098
17153
  display_offset_y: `${board._panelPositionOffset.y}mm`
17099
17154
  });
17100
17155
  }
17101
- const hasExplicitWidth = this._parsedProps.width !== void 0;
17102
- const hasExplicitHeight = this._parsedProps.height !== void 0;
17103
- const gridWidth = this._cachedGridWidth;
17104
- const gridHeight = this._cachedGridHeight;
17105
- if (hasExplicitWidth && hasExplicitHeight) {
17106
- db.pcb_panel.update(this.pcb_panel_id, {
17107
- width: distance11.parse(this._parsedProps.width),
17108
- height: distance11.parse(this._parsedProps.height)
17109
- });
17110
- } else if (gridWidth > 0 || gridHeight > 0) {
17111
- const {
17112
- edgePadding: edgePaddingProp,
17113
- edgePaddingLeft: edgePaddingLeftProp,
17114
- edgePaddingRight: edgePaddingRightProp,
17115
- edgePaddingTop: edgePaddingTopProp,
17116
- edgePaddingBottom: edgePaddingBottomProp
17117
- } = this._parsedProps;
17118
- const edgePadding = distance11.parse(edgePaddingProp ?? 5);
17119
- const edgePaddingLeft = distance11.parse(
17120
- edgePaddingLeftProp ?? edgePadding
17121
- );
17122
- const edgePaddingRight = distance11.parse(
17123
- edgePaddingRightProp ?? edgePadding
17124
- );
17125
- const edgePaddingTop = distance11.parse(edgePaddingTopProp ?? edgePadding);
17126
- const edgePaddingBottom = distance11.parse(
17127
- edgePaddingBottomProp ?? edgePadding
17128
- );
17129
- db.pcb_panel.update(this.pcb_panel_id, {
17130
- width: hasExplicitWidth ? distance11.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
17131
- height: hasExplicitHeight ? distance11.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
17132
- });
17133
- }
17156
+ this._updatePanelDimensions();
17134
17157
  } else {
17135
17158
  const panelGlobalPos = this._getGlobalPcbPositionBeforeLayout();
17136
17159
  for (const board of childBoardInstances) {
@@ -17145,9 +17168,55 @@ var Panel = class extends Group6 {
17145
17168
  });
17146
17169
  }
17147
17170
  }
17171
+ this._generateTabsAndMouseBites();
17172
+ }
17173
+ /**
17174
+ * Update dimensions for the subpanel. Subpanel updates pcb_group,
17175
+ */
17176
+ _updatePanelDimensions() {
17177
+ const { db } = this.root;
17178
+ const hasExplicitWidth = this._parsedProps.width !== void 0;
17179
+ const hasExplicitHeight = this._parsedProps.height !== void 0;
17180
+ const gridWidth = this._cachedGridWidth;
17181
+ const gridHeight = this._cachedGridHeight;
17182
+ if (!this.pcb_group_id) return;
17183
+ if (hasExplicitWidth && hasExplicitHeight) {
17184
+ db.pcb_group.update(this.pcb_group_id, {
17185
+ width: distance11.parse(this._parsedProps.width),
17186
+ height: distance11.parse(this._parsedProps.height)
17187
+ });
17188
+ } else if (gridWidth > 0 || gridHeight > 0) {
17189
+ const {
17190
+ edgePadding: edgePaddingProp,
17191
+ edgePaddingLeft: edgePaddingLeftProp,
17192
+ edgePaddingRight: edgePaddingRightProp,
17193
+ edgePaddingTop: edgePaddingTopProp,
17194
+ edgePaddingBottom: edgePaddingBottomProp
17195
+ } = this._parsedProps;
17196
+ const edgePadding = distance11.parse(edgePaddingProp ?? 5);
17197
+ const edgePaddingLeft = distance11.parse(edgePaddingLeftProp ?? edgePadding);
17198
+ const edgePaddingRight = distance11.parse(
17199
+ edgePaddingRightProp ?? edgePadding
17200
+ );
17201
+ const edgePaddingTop = distance11.parse(edgePaddingTopProp ?? edgePadding);
17202
+ const edgePaddingBottom = distance11.parse(
17203
+ edgePaddingBottomProp ?? edgePadding
17204
+ );
17205
+ db.pcb_group.update(this.pcb_group_id, {
17206
+ width: hasExplicitWidth ? distance11.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
17207
+ height: hasExplicitHeight ? distance11.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
17208
+ });
17209
+ }
17210
+ }
17211
+ /**
17212
+ * Generate tabs and mouse bites for panelization
17213
+ */
17214
+ _generateTabsAndMouseBites() {
17148
17215
  if (this._tabsAndMouseBitesGenerated) return;
17216
+ const { db } = this.root;
17149
17217
  const props = this._parsedProps;
17150
17218
  const panelizationMethod = props.panelizationMethod ?? "none";
17219
+ const childBoardInstances = this._getDirectBoardChildren();
17151
17220
  if (panelizationMethod !== "none") {
17152
17221
  const childBoardIds = childBoardInstances.map((c) => c.pcb_board_id).filter((id) => !!id);
17153
17222
  const boardsInPanel = db.pcb_board.list().filter((b) => childBoardIds.includes(b.pcb_board_id));
@@ -17172,24 +17241,88 @@ var Panel = class extends Group6 {
17172
17241
  }
17173
17242
  this._tabsAndMouseBitesGenerated = true;
17174
17243
  }
17175
- runRenderCycle() {
17176
- if (!this.children.some((child) => child.componentName === "Board")) {
17177
- throw new Error("<panel> must contain at least one <board>");
17244
+ /**
17245
+ * Override to validate board containment before rendering.
17246
+ * Subpanel uses parent Group's pcb_group rendering.
17247
+ */
17248
+ doInitialPcbComponentRender() {
17249
+ if (this.root?.pcbDisabled) return;
17250
+ if (!this._containsBoards()) {
17251
+ throw new Error(
17252
+ `<${this._errorComponentName}> must contain at least one <board>`
17253
+ );
17178
17254
  }
17179
- super.runRenderCycle();
17255
+ super.doInitialPcbComponentRender();
17256
+ }
17257
+ };
17258
+
17259
+ // lib/components/normal-components/Panel.ts
17260
+ var Panel = class extends Subpanel {
17261
+ get config() {
17262
+ return {
17263
+ componentName: "Panel",
17264
+ zodProps: panelProps
17265
+ };
17180
17266
  }
17267
+ /**
17268
+ * Panel creates a pcb_panel record for the physical manufacturing panel.
17269
+ * This overrides the Subpanel behavior which uses pcb_group.
17270
+ */
17181
17271
  doInitialPcbComponentRender() {
17182
17272
  if (this.root?.pcbDisabled) return;
17273
+ if (!this._containsBoards()) {
17274
+ throw new Error(
17275
+ `<${this._errorComponentName}> must contain at least one <board>`
17276
+ );
17277
+ }
17183
17278
  const { db } = this.root;
17184
17279
  const props = this._parsedProps;
17185
17280
  const inserted = db.pcb_panel.insert({
17186
- width: props.width !== void 0 ? distance11.parse(props.width) : 0,
17187
- height: props.height !== void 0 ? distance11.parse(props.height) : 0,
17281
+ width: props.width !== void 0 ? distance12.parse(props.width) : 0,
17282
+ height: props.height !== void 0 ? distance12.parse(props.height) : 0,
17188
17283
  center: this._getGlobalPcbPositionBeforeLayout(),
17189
17284
  covered_with_solder_mask: !(props.noSolderMask ?? false)
17190
17285
  });
17191
17286
  this.pcb_panel_id = inserted.pcb_panel_id;
17192
17287
  }
17288
+ /**
17289
+ * Panel updates pcb_panel dimensions instead of pcb_group
17290
+ */
17291
+ _updatePanelDimensions() {
17292
+ const { db } = this.root;
17293
+ const hasExplicitWidth = this._parsedProps.width !== void 0;
17294
+ const hasExplicitHeight = this._parsedProps.height !== void 0;
17295
+ const gridWidth = this._cachedGridWidth;
17296
+ const gridHeight = this._cachedGridHeight;
17297
+ if (!this.pcb_panel_id) return;
17298
+ if (hasExplicitWidth && hasExplicitHeight) {
17299
+ db.pcb_panel.update(this.pcb_panel_id, {
17300
+ width: distance12.parse(this._parsedProps.width),
17301
+ height: distance12.parse(this._parsedProps.height)
17302
+ });
17303
+ } else if (gridWidth > 0 || gridHeight > 0) {
17304
+ const {
17305
+ edgePadding: edgePaddingProp,
17306
+ edgePaddingLeft: edgePaddingLeftProp,
17307
+ edgePaddingRight: edgePaddingRightProp,
17308
+ edgePaddingTop: edgePaddingTopProp,
17309
+ edgePaddingBottom: edgePaddingBottomProp
17310
+ } = this._parsedProps;
17311
+ const edgePadding = distance12.parse(edgePaddingProp ?? 5);
17312
+ const edgePaddingLeft = distance12.parse(edgePaddingLeftProp ?? edgePadding);
17313
+ const edgePaddingRight = distance12.parse(
17314
+ edgePaddingRightProp ?? edgePadding
17315
+ );
17316
+ const edgePaddingTop = distance12.parse(edgePaddingTopProp ?? edgePadding);
17317
+ const edgePaddingBottom = distance12.parse(
17318
+ edgePaddingBottomProp ?? edgePadding
17319
+ );
17320
+ db.pcb_panel.update(this.pcb_panel_id, {
17321
+ width: hasExplicitWidth ? distance12.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
17322
+ height: hasExplicitHeight ? distance12.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
17323
+ });
17324
+ }
17325
+ }
17193
17326
  updatePcbComponentRender() {
17194
17327
  if (this.root?.pcbDisabled) return;
17195
17328
  if (!this.pcb_panel_id) return;
@@ -17197,8 +17330,8 @@ var Panel = class extends Group6 {
17197
17330
  const props = this._parsedProps;
17198
17331
  const currentPanel = db.pcb_panel.get(this.pcb_panel_id);
17199
17332
  db.pcb_panel.update(this.pcb_panel_id, {
17200
- width: props.width !== void 0 ? distance11.parse(props.width) : currentPanel?.width,
17201
- height: props.height !== void 0 ? distance11.parse(props.height) : currentPanel?.height,
17333
+ width: props.width !== void 0 ? distance12.parse(props.width) : currentPanel?.width,
17334
+ height: props.height !== void 0 ? distance12.parse(props.height) : currentPanel?.height,
17202
17335
  center: this._getGlobalPcbPositionBeforeLayout(),
17203
17336
  covered_with_solder_mask: !(props.noSolderMask ?? false)
17204
17337
  });
@@ -19210,7 +19343,7 @@ var SilkscreenLine = class extends PrimitiveComponent2 {
19210
19343
 
19211
19344
  // lib/components/primitive-components/Fiducial.ts
19212
19345
  import "zod";
19213
- import { distance as distance12 } from "circuit-json";
19346
+ import { distance as distance13 } from "circuit-json";
19214
19347
  import { fiducialProps } from "@tscircuit/props";
19215
19348
  var Fiducial = class extends PrimitiveComponent2 {
19216
19349
  pcb_smtpad_id = null;
@@ -19235,15 +19368,15 @@ var Fiducial = class extends PrimitiveComponent2 {
19235
19368
  shape: "circle",
19236
19369
  x: position.x,
19237
19370
  y: position.y,
19238
- radius: distance12.parse(props.padDiameter) / 2,
19239
- soldermask_margin: props.soldermaskPullback ? distance12.parse(props.soldermaskPullback) : distance12.parse(props.padDiameter) / 2,
19371
+ radius: distance13.parse(props.padDiameter) / 2,
19372
+ soldermask_margin: props.soldermaskPullback ? distance13.parse(props.soldermaskPullback) : distance13.parse(props.padDiameter) / 2,
19240
19373
  is_covered_with_solder_mask: true
19241
19374
  });
19242
19375
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
19243
19376
  }
19244
19377
  getPcbSize() {
19245
19378
  const { _parsedProps: props } = this;
19246
- const d = distance12.parse(props.padDiameter);
19379
+ const d = distance13.parse(props.padDiameter);
19247
19380
  return { width: d, height: d };
19248
19381
  }
19249
19382
  _setPositionFromLayout(newCenter) {
@@ -21184,7 +21317,7 @@ import { identity as identity5 } from "transformation-matrix";
21184
21317
  var package_default = {
21185
21318
  name: "@tscircuit/core",
21186
21319
  type: "module",
21187
- version: "0.0.992",
21320
+ version: "0.0.994",
21188
21321
  types: "dist/index.d.ts",
21189
21322
  main: "dist/index.js",
21190
21323
  module: "dist/index.js",
@@ -21834,6 +21967,7 @@ export {
21834
21967
  SmtPad,
21835
21968
  SolderJumper,
21836
21969
  Subcircuit,
21970
+ Subpanel,
21837
21971
  Switch,
21838
21972
  SymbolComponent as Symbol,
21839
21973
  TestPoint,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.993",
4
+ "version": "0.0.995",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",