@tscircuit/core 0.0.1305 → 0.0.1307

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 (2) hide show
  1. package/dist/index.js +1557 -1500
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9170,1590 +9170,1647 @@ var SmtPad = class extends PrimitiveComponent2 {
9170
9170
  }
9171
9171
  };
9172
9172
 
9173
- // lib/components/primitive-components/Symbol/Symbol.ts
9174
- import { symbolProps } from "@tscircuit/props";
9175
- import { compose as compose3, translate as translate2, scale } from "transformation-matrix";
9176
- var SymbolComponent = class extends PrimitiveComponent2 {
9177
- isPrimitiveContainer = true;
9178
- schematic_symbol_id;
9179
- userCoordinateToResizedSymbolTransformMat;
9180
- schematicSymbolBoundsInUserCoordinates;
9173
+ // lib/utils/get-relative-direction.ts
9174
+ function getRelativeDirection(pointA, pointB) {
9175
+ const dx = pointB.x - pointA.x;
9176
+ const dy = pointB.y - pointA.y;
9177
+ if (Math.abs(dx) > Math.abs(dy)) {
9178
+ return dx >= 0 ? "right" : "left";
9179
+ }
9180
+ return dy >= 0 ? "up" : "down";
9181
+ }
9182
+
9183
+ // lib/components/primitive-components/Port/Port.ts
9184
+ import "schematic-symbols";
9185
+ import { applyToPoint as applyToPoint22, compose as compose3, translate as translate2 } from "transformation-matrix";
9186
+ import "zod";
9187
+
9188
+ // lib/components/primitive-components/Port/getCenterOfPcbPrimitives.ts
9189
+ var getCenterOfPcbPrimitives = (pcbPrimitives) => {
9190
+ if (pcbPrimitives.length === 0) {
9191
+ throw new Error("Cannot get center of empty PCB primitives array");
9192
+ }
9193
+ const positions = pcbPrimitives.map((p) => p._getPcbCircuitJsonBounds().center).filter(Boolean);
9194
+ const sumX = positions.reduce((sum, pos) => sum + pos.x, 0);
9195
+ const sumY = positions.reduce((sum, pos) => sum + pos.y, 0);
9196
+ return {
9197
+ x: sumX / positions.length,
9198
+ y: sumY / positions.length
9199
+ };
9200
+ };
9201
+
9202
+ // lib/components/primitive-components/Port/Port.ts
9203
+ import { portProps } from "@tscircuit/props";
9204
+
9205
+ // lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts
9206
+ var applyPinAttributesToSourcePort = (sourcePortProps, attributes) => {
9207
+ if (attributes.mustBeConnected !== void 0) {
9208
+ sourcePortProps.must_be_connected = attributes.mustBeConnected;
9209
+ }
9210
+ if (attributes.providesPower !== void 0) {
9211
+ sourcePortProps.provides_power = attributes.providesPower;
9212
+ }
9213
+ if (attributes.requiresPower !== void 0) {
9214
+ sourcePortProps.requires_power = attributes.requiresPower;
9215
+ }
9216
+ if (attributes.providesGround !== void 0) {
9217
+ sourcePortProps.provides_ground = attributes.providesGround;
9218
+ }
9219
+ if (attributes.requiresGround !== void 0) {
9220
+ sourcePortProps.requires_ground = attributes.requiresGround;
9221
+ }
9222
+ if (attributes.providesVoltage !== void 0) {
9223
+ sourcePortProps.provides_voltage = attributes.providesVoltage;
9224
+ }
9225
+ if (attributes.requiresVoltage !== void 0) {
9226
+ sourcePortProps.requires_voltage = attributes.requiresVoltage;
9227
+ }
9228
+ if (attributes.doNotConnect !== void 0) {
9229
+ sourcePortProps.do_not_connect = attributes.doNotConnect;
9230
+ }
9231
+ if (attributes.includeInBoardPinout !== void 0) {
9232
+ sourcePortProps.include_in_board_pinout = attributes.includeInBoardPinout;
9233
+ }
9234
+ if (attributes.canUseInternalPullup !== void 0) {
9235
+ sourcePortProps.can_use_internal_pullup = attributes.canUseInternalPullup;
9236
+ }
9237
+ if (attributes.isUsingInternalPullup !== void 0) {
9238
+ sourcePortProps.is_using_internal_pullup = attributes.isUsingInternalPullup;
9239
+ }
9240
+ if (attributes.needsExternalPullup !== void 0) {
9241
+ sourcePortProps.needs_external_pullup = attributes.needsExternalPullup;
9242
+ }
9243
+ if (attributes.canUseInternalPulldown !== void 0) {
9244
+ sourcePortProps.can_use_internal_pulldown = attributes.canUseInternalPulldown;
9245
+ }
9246
+ if (attributes.isUsingInternalPulldown !== void 0) {
9247
+ sourcePortProps.is_using_internal_pulldown = attributes.isUsingInternalPulldown;
9248
+ }
9249
+ if (attributes.needsExternalPulldown !== void 0) {
9250
+ sourcePortProps.needs_external_pulldown = attributes.needsExternalPulldown;
9251
+ }
9252
+ if (attributes.canUseOpenDrain !== void 0) {
9253
+ sourcePortProps.can_use_open_drain = attributes.canUseOpenDrain;
9254
+ }
9255
+ if (attributes.isUsingOpenDrain !== void 0) {
9256
+ sourcePortProps.is_using_open_drain = attributes.isUsingOpenDrain;
9257
+ }
9258
+ if (attributes.canUsePushPull !== void 0) {
9259
+ sourcePortProps.can_use_push_pull = attributes.canUsePushPull;
9260
+ }
9261
+ if (attributes.isUsingPushPull !== void 0) {
9262
+ sourcePortProps.is_using_push_pull = attributes.isUsingPushPull;
9263
+ }
9264
+ if (attributes.shouldHaveDecouplingCapacitor !== void 0) {
9265
+ sourcePortProps.should_have_decoupling_capacitor = attributes.shouldHaveDecouplingCapacitor;
9266
+ }
9267
+ if (attributes.recommendedDecouplingCapacitorCapacitance !== void 0) {
9268
+ sourcePortProps.recommended_decoupling_capacitor_capacitance = attributes.recommendedDecouplingCapacitorCapacitance;
9269
+ }
9270
+ };
9271
+
9272
+ // lib/components/primitive-components/Port/Port.ts
9273
+ var Port = class extends PrimitiveComponent2 {
9274
+ source_port_id = null;
9275
+ pcb_port_id = null;
9276
+ schematic_port_id = null;
9277
+ schematic_stem_line_id = null;
9278
+ schematicSymbolPortDef = null;
9279
+ matchedComponents;
9280
+ _isPrimaryPort = true;
9281
+ _primaryPinNumber = null;
9282
+ facingDirection = null;
9283
+ originDescription = null;
9181
9284
  get config() {
9182
9285
  return {
9183
- componentName: "Symbol",
9184
- zodProps: symbolProps
9286
+ componentName: "Port",
9287
+ zodProps: portProps
9185
9288
  };
9186
9289
  }
9187
- hasExplicitSize() {
9188
- const { _parsedProps: props } = this;
9189
- return props.width !== void 0 || props.height !== void 0;
9290
+ constructor(props, opts = {}) {
9291
+ if (!props.name && props.pinNumber !== void 0)
9292
+ props.name = `pin${props.pinNumber}`;
9293
+ if (!props.name) {
9294
+ throw new Error("Port must have a name or a pinNumber");
9295
+ }
9296
+ super(props);
9297
+ if (opts.originDescription) {
9298
+ this.originDescription = opts.originDescription;
9299
+ }
9300
+ this.matchedComponents = [];
9190
9301
  }
9191
- /**
9192
- * Create the schematic_symbol element in SymbolContainerRender phase.
9193
- * This runs before SchematicPrimitiveRender, ensuring children can
9194
- * reference the schematic_symbol_id when they render.
9195
- */
9196
- doInitialSymbolContainerRender() {
9197
- if (this.root?.schematicDisabled) return;
9198
- const { db } = this.root;
9302
+ isGroupPort() {
9303
+ return this.parent?.componentName === "Group";
9304
+ }
9305
+ isComponentPort() {
9306
+ return !this.isGroupPort();
9307
+ }
9308
+ _getConnectedPortsFromConnectsTo() {
9199
9309
  const { _parsedProps: props } = this;
9200
- const parentNormal = this.getParentNormalComponent();
9201
- const kicadSymbolMetadata = parentNormal?._parsedProps?.kicadSymbolMetadata;
9202
- const schematic_symbol = db.schematic_symbol.insert({
9203
- name: props.name,
9204
- metadata: kicadSymbolMetadata ? { kicad_symbol: kicadSymbolMetadata } : void 0
9205
- });
9206
- this.schematic_symbol_id = schematic_symbol.schematic_symbol_id;
9310
+ const connectsTo = props.connectsTo;
9311
+ if (!connectsTo) return [];
9312
+ const connectedPorts = [];
9313
+ const connectsToArray = Array.isArray(connectsTo) ? connectsTo : [connectsTo];
9314
+ for (const connection of connectsToArray) {
9315
+ const port = this.getSubcircuit().selectOne(connection, {
9316
+ type: "port"
9317
+ });
9318
+ if (port) {
9319
+ connectedPorts.push(port);
9320
+ }
9321
+ }
9322
+ return connectedPorts;
9207
9323
  }
9208
- getSchematicSymbolBounds() {
9209
- if (this.schematicSymbolBoundsInUserCoordinates) {
9210
- return this.schematicSymbolBoundsInUserCoordinates;
9324
+ _isBoardPinoutFromAttributes() {
9325
+ const parent = this.parent;
9326
+ if (parent?._parsedProps?.pinAttributes) {
9327
+ const pinAttributes = parent._parsedProps.pinAttributes;
9328
+ for (const alias of this.getNameAndAliases()) {
9329
+ if (pinAttributes[alias]?.includeInBoardPinout) {
9330
+ return true;
9331
+ }
9332
+ }
9211
9333
  }
9212
- this._computeSchematicSymbolBounds();
9213
- return this.schematicSymbolBoundsInUserCoordinates ?? null;
9214
9334
  }
9215
- getUserCoordinateToResizedSymbolTransform() {
9216
- if (!this.hasExplicitSize()) {
9217
- return null;
9335
+ _getGlobalPcbPositionBeforeLayout() {
9336
+ const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive);
9337
+ const parentComponent = this.parent;
9338
+ if (parentComponent && !parentComponent.props.footprint) {
9339
+ throw new Error(
9340
+ `${parentComponent.componentName} "${parentComponent.props.name}" does not have a footprint. Add a footprint prop, e.g. <${parentComponent.componentName.toLowerCase()} footprint="..." />`
9341
+ );
9218
9342
  }
9219
- if (this.userCoordinateToResizedSymbolTransformMat) {
9220
- return this.userCoordinateToResizedSymbolTransformMat;
9343
+ if (!matchedPcbElm) {
9344
+ throw new Error(
9345
+ `Port ${this} has no matching PCB primitives. This often means the footprint's pads lack matching port hints.`
9346
+ );
9221
9347
  }
9222
- this._computeUserCoordinateToResizedSymbolTransform();
9223
- return this.userCoordinateToResizedSymbolTransformMat ?? null;
9348
+ return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 };
9224
9349
  }
9225
- _computeSchematicSymbolBounds() {
9226
- if (this.root?.schematicDisabled) return;
9350
+ _getPcbCircuitJsonBounds() {
9351
+ if (!this.pcb_port_id) {
9352
+ return super._getPcbCircuitJsonBounds();
9353
+ }
9227
9354
  const { db } = this.root;
9228
- const schematicElements = [];
9229
- for (const child of this.children) {
9230
- if (child.componentName === "Port") {
9231
- const portId = child.schematic_port_id;
9232
- if (portId) {
9233
- const port = db.schematic_port.get(portId);
9234
- if (port) schematicElements.push(port);
9235
- }
9236
- continue;
9355
+ const pcb_port = db.pcb_port.get(this.pcb_port_id);
9356
+ return {
9357
+ center: { x: pcb_port.x, y: pcb_port.y },
9358
+ bounds: { left: 0, top: 0, right: 0, bottom: 0 },
9359
+ width: 0,
9360
+ height: 0
9361
+ };
9362
+ }
9363
+ _getGlobalPcbPositionAfterLayout() {
9364
+ return this._getPcbCircuitJsonBounds().center;
9365
+ }
9366
+ _getPortsInternallyConnectedToThisPort() {
9367
+ const parent = this.parent;
9368
+ if (!parent || !parent._getInternallyConnectedPins) return [];
9369
+ const internallyConnectedPorts = parent._getInternallyConnectedPins();
9370
+ for (const ports of internallyConnectedPorts) {
9371
+ if (ports.some((port) => port === this)) {
9372
+ return ports;
9237
9373
  }
9238
- if (!child.isSchematicPrimitive) continue;
9239
- if (child.componentName === "SchematicLine") {
9240
- const line = db.schematic_line.get(child.schematic_line_id);
9241
- if (line) schematicElements.push(line);
9242
- } else if (child.componentName === "SchematicRect") {
9243
- const rect = db.schematic_rect.get(child.schematic_rect_id);
9244
- if (rect) schematicElements.push(rect);
9245
- } else if (child.componentName === "SchematicCircle") {
9246
- const circle = db.schematic_circle.get(
9247
- child.schematic_circle_id
9248
- );
9249
- if (circle) schematicElements.push(circle);
9250
- } else if (child.componentName === "SchematicArc") {
9251
- const arc = db.schematic_arc.get(child.schematic_arc_id);
9252
- if (arc) schematicElements.push(arc);
9253
- } else if (child.componentName === "SchematicText") {
9254
- const text = db.schematic_text.get(child.schematic_text_id);
9255
- if (text) schematicElements.push(text);
9256
- } else if (child.componentName === "SchematicPath") {
9257
- const pathIds = child.schematic_path_ids;
9258
- if (pathIds) {
9259
- for (const pathId of pathIds) {
9260
- const path = db.schematic_path.get(pathId);
9261
- if (path) schematicElements.push(path);
9262
- }
9374
+ }
9375
+ return [];
9376
+ }
9377
+ /**
9378
+ * Return true if this port has a schematic representation and can be rendered
9379
+ * to the schematic.
9380
+ *
9381
+ * Sometimes things like mounting holes don't have a schematic representation
9382
+ * and aren't rendered to the schematic.
9383
+ *
9384
+ * It's common for a schematic symbol to not have a representation for all of
9385
+ * the pins on a footprint, e.g. a pushbutton has 4 pins but is typically
9386
+ * represented by a two-pin symbol. In these cases, it's best to use
9387
+ * internallyConnectedPorts or externallyConnectedPorts to ensure the things
9388
+ * are rendered properly.
9389
+ */
9390
+ _hasSchematicPort() {
9391
+ const { schX, schY } = this._parsedProps;
9392
+ if (schX !== void 0 && schY !== void 0) {
9393
+ return true;
9394
+ }
9395
+ const parentNormalComponent = this.getParentNormalComponent();
9396
+ const symbol = parentNormalComponent?.getSchematicSymbol();
9397
+ if (symbol) {
9398
+ if (this.schematicSymbolPortDef) return true;
9399
+ const portsInternallyConnectedToThisPort = this._getPortsInternallyConnectedToThisPort();
9400
+ if (portsInternallyConnectedToThisPort.some((p) => p.schematicSymbolPortDef))
9401
+ return true;
9402
+ return false;
9403
+ }
9404
+ const parentBoxDim = parentNormalComponent?._getSchematicBoxDimensions();
9405
+ if (parentBoxDim && this._parsedProps.pinNumber !== void 0) {
9406
+ const localPortPosition = parentBoxDim.getPortPositionByPinNumber(
9407
+ this._parsedProps.pinNumber
9408
+ );
9409
+ if (localPortPosition) return true;
9410
+ }
9411
+ return false;
9412
+ }
9413
+ _getGlobalSchematicPositionBeforeLayout() {
9414
+ const { schX, schY } = this._parsedProps;
9415
+ if (schX !== void 0 && schY !== void 0) {
9416
+ return { x: schX, y: schY };
9417
+ }
9418
+ const parentNormalComponent = this.getParentNormalComponent();
9419
+ const symbol = parentNormalComponent?.getSchematicSymbol();
9420
+ if (symbol) {
9421
+ let schematicSymbolPortDef = this.schematicSymbolPortDef;
9422
+ if (!schematicSymbolPortDef) {
9423
+ schematicSymbolPortDef = this._getPortsInternallyConnectedToThisPort().find(
9424
+ (p) => p.schematicSymbolPortDef
9425
+ )?.schematicSymbolPortDef ?? null;
9426
+ if (!schematicSymbolPortDef) {
9427
+ throw new Error(
9428
+ `Couldn't find schematicSymbolPortDef for port ${this.getString()}, searched internally connected ports and none had a schematicSymbolPortDef. Why are we trying to get the schematic position of this port?`
9429
+ );
9263
9430
  }
9264
9431
  }
9432
+ const transform = compose3(
9433
+ parentNormalComponent.computeSchematicGlobalTransform(),
9434
+ translate2(-symbol.center.x, -symbol.center.y)
9435
+ );
9436
+ return applyToPoint22(transform, schematicSymbolPortDef);
9437
+ }
9438
+ const parentBoxDim = parentNormalComponent?._getSchematicBoxDimensions();
9439
+ if (parentBoxDim && this._parsedProps.pinNumber !== void 0) {
9440
+ const localPortPosition = parentBoxDim.getPortPositionByPinNumber(
9441
+ this._parsedProps.pinNumber
9442
+ );
9443
+ if (!localPortPosition) {
9444
+ throw new Error(
9445
+ `Couldn't find position for schematic_port for port ${this.getString()} inside of the schematic box`
9446
+ );
9447
+ }
9448
+ return applyToPoint22(
9449
+ parentNormalComponent.computeSchematicGlobalTransform(),
9450
+ localPortPosition
9451
+ );
9265
9452
  }
9266
- if (schematicElements.length === 0) return;
9267
- const bounds = getBoundsForSchematic(schematicElements);
9268
- this.schematicSymbolBoundsInUserCoordinates = bounds;
9453
+ throw new Error(
9454
+ `Couldn't find position for schematic_port for port ${this.getString()}`
9455
+ );
9269
9456
  }
9270
- _computeUserCoordinateToResizedSymbolTransform() {
9271
- const bounds = this.getSchematicSymbolBounds();
9272
- if (!bounds) return;
9457
+ _getGlobalSchematicPositionAfterLayout() {
9458
+ const { db } = this.root;
9459
+ if (!this.schematic_port_id) {
9460
+ throw new Error(
9461
+ `Can't get schematic port position after layout for "${this.getString()}", no schematic_port_id`
9462
+ );
9463
+ }
9464
+ const schematic_port = db.schematic_port.get(this.schematic_port_id);
9465
+ if (!schematic_port)
9466
+ throw new Error(
9467
+ `Schematic port not found when trying to get post-layout position: ${this.schematic_port_id}`
9468
+ );
9469
+ return schematic_port.center;
9470
+ }
9471
+ /**
9472
+ * Smtpads and platedholes call this method to register themselves as a match
9473
+ * for this port. All the matching is done by primitives other than the Port,
9474
+ * but everyone registers themselves as a match with their Port.
9475
+ */
9476
+ registerMatch(component) {
9477
+ this.matchedComponents.push(component);
9478
+ }
9479
+ getNameAndAliases() {
9273
9480
  const { _parsedProps: props } = this;
9274
- const targetWidth = props.width;
9275
- const targetHeight = props.height;
9276
- if (targetWidth === void 0 && targetHeight === void 0) return;
9277
- const currentWidth = bounds.maxX - bounds.minX;
9278
- const currentHeight = bounds.maxY - bounds.minY;
9279
- if (currentWidth === 0 && currentHeight === 0) return;
9280
- const currentCenterX = (bounds.minX + bounds.maxX) / 2;
9281
- const currentCenterY = (bounds.minY + bounds.maxY) / 2;
9282
- const scaleX = targetWidth !== void 0 && currentWidth > 0 ? targetWidth / currentWidth : 1;
9283
- const scaleY = targetHeight !== void 0 && currentHeight > 0 ? targetHeight / currentHeight : 1;
9284
- const globalPos = this._getGlobalSchematicPositionBeforeLayout();
9285
- this.userCoordinateToResizedSymbolTransformMat = compose3(
9286
- translate2(globalPos.x, globalPos.y),
9287
- scale(scaleX, scaleY),
9288
- translate2(-currentCenterX, -currentCenterY)
9481
+ return Array.from(
9482
+ /* @__PURE__ */ new Set([
9483
+ ...props.name ? [props.name] : [],
9484
+ ...props.aliases ?? [],
9485
+ ...typeof props.pinNumber === "number" ? [`pin${props.pinNumber}`, props.pinNumber.toString()] : [],
9486
+ ...this.externallyAddedAliases ?? []
9487
+ ])
9289
9488
  );
9290
9489
  }
9291
- };
9292
-
9293
- // lib/utils/createPinrowSilkscreenText.ts
9294
- var createPinrowSilkscreenText = ({
9295
- elm,
9296
- pinLabels,
9297
- layer,
9298
- readableRotation,
9299
- anchorAlignment
9300
- }) => {
9301
- const pinNum = elm.text.replace(/[{}]/g, "").toLowerCase();
9302
- let label = pinNum;
9303
- if (Array.isArray(pinLabels)) {
9304
- const index = parseInt(pinNum.replace(/[^\d]/g, ""), 10) - 1;
9305
- label = String(pinLabels[index] ?? pinNum);
9306
- } else if (typeof pinLabels === "object") {
9307
- label = String(pinLabels[pinNum] ?? pinNum);
9490
+ _getMatchingPinAttributes() {
9491
+ const parentProps = this.parent?._parsedProps;
9492
+ const pinAttributes = parentProps?.pinAttributes;
9493
+ const noConnect = parentProps?.noConnect;
9494
+ const matches = [];
9495
+ for (const alias of this.getNameAndAliases()) {
9496
+ if (pinAttributes) {
9497
+ const attributes = pinAttributes[alias];
9498
+ if (attributes) {
9499
+ matches.push(attributes);
9500
+ }
9501
+ }
9502
+ if (noConnect?.includes(alias)) {
9503
+ matches.push({ doNotConnect: true });
9504
+ }
9505
+ }
9506
+ if (matches.length === 0) {
9507
+ return [];
9508
+ }
9509
+ return matches;
9308
9510
  }
9309
- const silkscreenText = new SilkscreenText({
9310
- anchorAlignment: anchorAlignment || "center",
9311
- text: label ?? pinNum,
9312
- layer: layer || "top",
9313
- pcbX: isNaN(elm.anchor_position.x) ? 0 : elm.anchor_position.x,
9314
- pcbY: elm.anchor_position.y,
9315
- pcbRotation: readableRotation ?? 0
9316
- });
9317
- silkscreenText._footprinterFontSize = elm.font_size + 0.2;
9318
- return silkscreenText;
9319
- };
9320
-
9321
- // lib/utils/createComponentsFromCircuitJson.ts
9322
- var calculateCcwRotation = (componentRotationStr, elementCcwRotation) => {
9323
- const componentAngle = parseInt(componentRotationStr || "0", 10);
9324
- let totalRotation;
9325
- if (elementCcwRotation !== void 0 && elementCcwRotation !== null) {
9326
- totalRotation = elementCcwRotation - componentAngle;
9327
- } else {
9328
- totalRotation = componentAngle;
9511
+ _shouldIncludeInBoardPinout() {
9512
+ return this._getMatchingPinAttributes().some(
9513
+ (attributes) => attributes.includeInBoardPinout === true
9514
+ );
9329
9515
  }
9330
- const normalizedRotation = (totalRotation % 360 + 360) % 360;
9331
- return normalizedRotation;
9332
- };
9333
- var optional = (value) => value ?? void 0;
9334
- var getFacingDirectionFromSide = (side) => {
9335
- switch (side) {
9336
- case "left":
9337
- case "right":
9338
- return side;
9339
- case "top":
9340
- return "up";
9341
- case "bottom":
9342
- return "down";
9516
+ isMatchingPort(port) {
9517
+ return this.isMatchingAnyOf(port.getNameAndAliases());
9343
9518
  }
9344
- return null;
9345
- };
9346
- var createComponentsFromCircuitJson = ({
9347
- componentName,
9348
- componentRotation,
9349
- footprinterString,
9350
- pinLabels,
9351
- pcbPinLabels
9352
- }, circuitJson) => {
9353
- const components = [];
9354
- const schematicSymbolsByImportedId = /* @__PURE__ */ new Map();
9355
- const schematicComponentsByImportedId = /* @__PURE__ */ new Map();
9356
- for (const elm of circuitJson) {
9357
- if (elm.type !== "schematic_symbol") continue;
9358
- const schematicSymbol = new SymbolComponent({
9359
- name: elm.name
9360
- });
9361
- schematicSymbolsByImportedId.set(elm.schematic_symbol_id, schematicSymbol);
9362
- components.push(schematicSymbol);
9519
+ getPortSelector() {
9520
+ const parentComponent = this.getParentNormalComponent() ?? this.parent;
9521
+ return `.${parentComponent?.props.name} > port.${this.props.name}`;
9363
9522
  }
9364
- for (const elm of circuitJson) {
9365
- if (elm.type !== "schematic_component") continue;
9366
- schematicComponentsByImportedId.set(elm.schematic_component_id, elm);
9523
+ getAvailablePcbLayers() {
9524
+ const { layer, layers } = this._parsedProps;
9525
+ if (layers) return layers;
9526
+ if (layer) return [layer];
9527
+ return Array.from(
9528
+ new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
9529
+ );
9367
9530
  }
9368
- const addSchematicPrimitive = (elm, primitive) => {
9369
- const schematicSymbolId = elm.schematic_symbol_id;
9370
- const parentSymbol = typeof schematicSymbolId === "string" ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
9371
- if (parentSymbol) {
9372
- parentSymbol.add(primitive);
9373
- } else {
9374
- components.push(primitive);
9375
- }
9376
- };
9377
- for (const elm of circuitJson) {
9378
- if (elm.type === "pcb_smtpad" && elm.shape === "rect") {
9379
- components.push(
9380
- new SmtPad({
9381
- pcbX: elm.x,
9382
- pcbY: elm.y,
9383
- layer: elm.layer,
9384
- shape: "rect",
9385
- height: elm.height,
9386
- width: elm.width,
9387
- portHints: elm.port_hints,
9388
- rectBorderRadius: elm.rect_border_radius
9389
- })
9390
- );
9391
- } else if (elm.type === "pcb_smtpad" && elm.shape === "circle") {
9392
- components.push(
9393
- new SmtPad({
9394
- pcbX: elm.x,
9395
- pcbY: elm.y,
9396
- layer: elm.layer,
9397
- shape: "circle",
9398
- radius: elm.radius,
9399
- portHints: elm.port_hints
9400
- })
9401
- );
9402
- } else if (elm.type === "pcb_smtpad" && elm.shape === "pill") {
9403
- components.push(
9404
- new SmtPad({
9405
- shape: "pill",
9406
- height: elm.height,
9407
- width: elm.width,
9408
- radius: elm.radius,
9409
- portHints: elm.port_hints,
9410
- pcbX: elm.x,
9411
- pcbY: elm.y,
9412
- layer: elm.layer
9413
- })
9414
- );
9415
- } else if (elm.type === "pcb_smtpad" && elm.shape === "polygon") {
9416
- components.push(
9417
- new SmtPad({
9418
- shape: "polygon",
9419
- points: elm.points,
9420
- portHints: elm.port_hints,
9421
- layer: elm.layer
9422
- })
9423
- );
9424
- } else if (elm.type === "pcb_silkscreen_path") {
9425
- components.push(
9426
- new SilkscreenPath({
9427
- layer: elm.layer,
9428
- route: elm.route,
9429
- strokeWidth: elm.stroke_width
9430
- })
9531
+ /**
9532
+ * Return traces that are explicitly connected to this port (not via a net)
9533
+ */
9534
+ _getDirectlyConnectedTraces() {
9535
+ const allSubcircuitTraces = this.getSubcircuit().selectAll(
9536
+ "trace"
9537
+ );
9538
+ const connectedTraces = allSubcircuitTraces.filter((trace) => !trace._couldNotFindPort).filter((trace) => trace._isExplicitlyConnectedToPort(this));
9539
+ return connectedTraces;
9540
+ }
9541
+ doInitialSourceRender() {
9542
+ const { db } = this.root;
9543
+ const { _parsedProps: props } = this;
9544
+ const port_hints = this.getNameAndAliases();
9545
+ const parentNormalComponent = this.getParentNormalComponent();
9546
+ const parentWithSourceId = this.parent?.source_component_id ? this.parent : parentNormalComponent;
9547
+ const source_component_id = parentWithSourceId?.source_component_id ?? null;
9548
+ const pinAttributes = this._getMatchingPinAttributes();
9549
+ const portAttributesFromParent = {};
9550
+ for (const attributes of pinAttributes) {
9551
+ applyPinAttributesToSourcePort(portAttributesFromParent, attributes);
9552
+ }
9553
+ const source_port = db.source_port.insert({
9554
+ name: props.name,
9555
+ pin_number: props.pinNumber,
9556
+ port_hints,
9557
+ source_component_id,
9558
+ subcircuit_id: this.getSubcircuit()?.subcircuit_id,
9559
+ ...portAttributesFromParent
9560
+ });
9561
+ this.source_port_id = source_port.source_port_id;
9562
+ }
9563
+ doInitialSourceParentAttachment() {
9564
+ const { db } = this.root;
9565
+ const parentNormalComponent = this.getParentNormalComponent();
9566
+ const parentWithSourceId = this.parent?.source_component_id ? this.parent : parentNormalComponent;
9567
+ if (this.isGroupPort()) {
9568
+ db.source_port.update(this.source_port_id, {
9569
+ source_component_id: null,
9570
+ subcircuit_id: this.getSubcircuit()?.subcircuit_id
9571
+ });
9572
+ return;
9573
+ }
9574
+ if (!parentWithSourceId?.source_component_id) {
9575
+ throw new Error(
9576
+ `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`
9431
9577
  );
9432
- } else if (elm.type === "pcb_copper_text") {
9433
- components.push(
9434
- new CopperText({
9435
- text: elm.text,
9436
- pcbX: elm.anchor_position.x,
9437
- pcbY: elm.anchor_position.y,
9438
- pcbRotation: elm.ccw_rotation,
9439
- anchorAlignment: elm.anchor_alignment,
9440
- fontSize: elm.font_size,
9441
- layer: elm.layer,
9442
- mirrored: elm.is_mirrored,
9443
- knockout: elm.is_knockout
9444
- })
9578
+ }
9579
+ db.source_port.update(this.source_port_id, {
9580
+ source_component_id: parentWithSourceId.source_component_id,
9581
+ subcircuit_id: this.getSubcircuit()?.subcircuit_id
9582
+ });
9583
+ this.source_component_id = parentWithSourceId.source_component_id;
9584
+ }
9585
+ doInitialPcbPortRender() {
9586
+ if (this.root?.pcbDisabled) return;
9587
+ const { db } = this.root;
9588
+ const { matchedComponents } = this;
9589
+ if (this.isGroupPort()) {
9590
+ const connectedPorts = this._getConnectedPortsFromConnectsTo();
9591
+ if (connectedPorts.length === 0) {
9592
+ return;
9593
+ }
9594
+ const connectedPort = connectedPorts[0];
9595
+ if (!connectedPort.pcb_port_id) {
9596
+ return;
9597
+ }
9598
+ const connectedPcbPort = db.pcb_port.get(connectedPort.pcb_port_id);
9599
+ const matchCenter2 = { x: connectedPcbPort.x, y: connectedPcbPort.y };
9600
+ const subcircuit = this.getSubcircuit();
9601
+ const pcb_port = db.pcb_port.insert({
9602
+ pcb_component_id: void 0,
9603
+ layers: connectedPort.getAvailablePcbLayers(),
9604
+ subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
9605
+ pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
9606
+ ...matchCenter2,
9607
+ source_port_id: this.source_port_id,
9608
+ is_board_pinout: false
9609
+ });
9610
+ this.pcb_port_id = pcb_port.pcb_port_id;
9611
+ return;
9612
+ }
9613
+ const parentNormalComponent = this.getParentNormalComponent();
9614
+ const parentWithPcbComponentId = this.parent?.pcb_component_id ? this.parent : parentNormalComponent;
9615
+ if (!parentWithPcbComponentId?.pcb_component_id) {
9616
+ throw new Error(
9617
+ `${this.getString()} has no parent pcb component, cannot render pcb_port (parent: ${this.parent?.getString()}, parentNormalComponent: ${parentNormalComponent?.getString()})`
9445
9618
  );
9446
- } else if (elm.type === "pcb_plated_hole") {
9447
- if (elm.shape === "circle") {
9448
- components.push(
9449
- new PlatedHole({
9450
- pcbX: elm.x,
9451
- pcbY: elm.y,
9452
- shape: "circle",
9453
- holeDiameter: elm.hole_diameter,
9454
- outerDiameter: elm.outer_diameter,
9455
- portHints: elm.port_hints
9456
- })
9457
- );
9458
- } else if (elm.shape === "circular_hole_with_rect_pad") {
9459
- components.push(
9460
- new PlatedHole({
9461
- pcbX: elm.x,
9462
- pcbY: elm.y,
9463
- shape: "circular_hole_with_rect_pad",
9464
- holeDiameter: elm.hole_diameter,
9465
- rectPadHeight: elm.rect_pad_height,
9466
- rectPadWidth: elm.rect_pad_width,
9467
- portHints: elm.port_hints,
9468
- rectBorderRadius: elm.rect_border_radius,
9469
- holeOffsetX: elm.hole_offset_x,
9470
- holeOffsetY: elm.hole_offset_y
9471
- })
9472
- );
9473
- } else if (elm.shape === "pill" || elm.shape === "oval") {
9474
- components.push(
9475
- new PlatedHole({
9476
- pcbX: elm.x,
9477
- pcbY: elm.y,
9478
- shape: elm.shape,
9479
- holeWidth: elm.hole_width,
9480
- holeHeight: elm.hole_height,
9481
- outerWidth: elm.outer_width,
9482
- outerHeight: elm.outer_height,
9483
- portHints: elm.port_hints
9484
- })
9485
- );
9486
- } else if (elm.shape === "pill_hole_with_rect_pad") {
9487
- components.push(
9488
- new PlatedHole({
9489
- pcbX: elm.x,
9490
- pcbY: elm.y,
9491
- shape: "pill_hole_with_rect_pad",
9492
- holeShape: "pill",
9493
- padShape: "rect",
9494
- holeWidth: elm.hole_width,
9495
- holeHeight: elm.hole_height,
9496
- rectPadWidth: elm.rect_pad_width,
9497
- rectPadHeight: elm.rect_pad_height,
9498
- rectBorderRadius: elm.rect_border_radius,
9499
- portHints: elm.port_hints,
9500
- holeOffsetX: elm.hole_offset_x,
9501
- holeOffsetY: elm.hole_offset_y
9502
- })
9503
- );
9504
- } else if (elm.shape === "rotated_pill_hole_with_rect_pad") {
9505
- components.push(
9506
- new PlatedHole({
9507
- pcbX: elm.x,
9508
- pcbY: elm.y,
9509
- shape: "pill_hole_with_rect_pad",
9510
- holeShape: "pill",
9511
- padShape: "rect",
9512
- holeWidth: elm.hole_width,
9513
- holeHeight: elm.hole_height,
9514
- rectPadWidth: elm.rect_pad_width,
9515
- rectPadHeight: elm.rect_pad_height,
9516
- rectBorderRadius: elm.rect_border_radius,
9517
- portHints: elm.port_hints,
9518
- holeOffsetX: elm.hole_offset_x,
9519
- holeOffsetY: elm.hole_offset_y,
9520
- pcbRotation: elm.hole_ccw_rotation
9521
- })
9522
- );
9523
- } else if (elm.shape === "hole_with_polygon_pad") {
9524
- components.push(
9525
- new PlatedHole({
9526
- pcbX: elm.x,
9527
- pcbY: elm.y,
9528
- shape: "hole_with_polygon_pad",
9529
- holeShape: elm.hole_shape || "circle",
9530
- holeDiameter: elm.hole_diameter,
9531
- holeWidth: elm.hole_width,
9532
- holeHeight: elm.hole_height,
9533
- padOutline: elm.pad_outline || [],
9534
- holeOffsetX: elm.hole_offset_x,
9535
- holeOffsetY: elm.hole_offset_y,
9536
- portHints: elm.port_hints
9537
- })
9619
+ }
9620
+ const pcbMatches = matchedComponents.filter((c) => c.isPcbPrimitive);
9621
+ if (pcbMatches.length === 0) return;
9622
+ let matchCenter = null;
9623
+ if (pcbMatches.length === 1) {
9624
+ matchCenter = pcbMatches[0]._getPcbCircuitJsonBounds().center;
9625
+ }
9626
+ if (pcbMatches.length > 1) {
9627
+ if (!areAllPcbPrimitivesOverlapping(pcbMatches)) {
9628
+ const portName = this.props.name;
9629
+ const componentName = this.getParentNormalComponent()?.props.name ?? "unknown";
9630
+ const altAliases = this.getNameAndAliases().filter(
9631
+ (h) => h !== portName
9538
9632
  );
9633
+ const altMsg = altAliases.length > 0 ? ` (consider using alternate aliases: ${altAliases.join(", ")})` : "";
9634
+ db.source_ambiguous_port_reference.insert({
9635
+ error_type: "source_ambiguous_port_reference",
9636
+ message: `${componentName}.${portName} is ambiguous: ${componentName}.${portName} references multiple non-overlapping pads: ${pcbMatches.map((c) => c.getString()).join(", ")}${altMsg}`,
9637
+ source_port_id: this.source_port_id ?? void 0,
9638
+ source_component_id: this.getParentNormalComponent()?.source_component_id ?? void 0
9639
+ });
9640
+ return;
9539
9641
  }
9540
- } else if (elm.type === "pcb_keepout" && elm.shape === "circle") {
9541
- components.push(
9542
- new Keepout({
9543
- pcbX: elm.center.x,
9544
- pcbY: elm.center.y,
9545
- shape: "circle",
9546
- radius: elm.radius
9547
- })
9548
- );
9549
- } else if (elm.type === "pcb_keepout" && elm.shape === "rect") {
9550
- components.push(
9551
- new Keepout({
9552
- pcbX: elm.center.x,
9553
- pcbY: elm.center.y,
9554
- shape: "rect",
9555
- width: elm.width,
9556
- height: elm.height
9557
- })
9558
- );
9559
- } else if (elm.type === "pcb_hole" && elm.hole_shape === "circle") {
9560
- components.push(
9561
- new Hole({
9562
- pcbX: elm.x,
9563
- pcbY: elm.y,
9564
- diameter: elm.hole_diameter
9565
- })
9566
- );
9567
- } else if (elm.type === "pcb_hole" && elm.hole_shape === "rect") {
9568
- components.push(
9569
- new Hole({
9570
- pcbX: elm.x,
9571
- pcbY: elm.y,
9572
- shape: "rect",
9573
- width: elm.hole_width,
9574
- height: elm.hole_height
9575
- })
9576
- );
9577
- } else if (elm.type === "pcb_hole" && elm.hole_shape === "pill") {
9578
- components.push(
9579
- new Hole({
9580
- pcbX: elm.x,
9581
- pcbY: elm.y,
9582
- shape: "pill",
9583
- width: elm.hole_width,
9584
- height: elm.hole_height
9585
- })
9586
- );
9587
- } else if (elm.type === "pcb_hole" && elm.hole_shape === "oval") {
9588
- components.push(
9589
- new Hole({
9590
- pcbX: elm.x,
9591
- pcbY: elm.y,
9592
- shape: "oval",
9593
- width: elm.hole_width,
9594
- height: elm.hole_height
9595
- })
9596
- );
9597
- } else if (elm.type === "pcb_hole" && elm.hole_shape === "rotated_pill") {
9598
- components.push(
9599
- new Hole({
9600
- pcbX: elm.x,
9601
- pcbY: elm.y,
9602
- shape: "pill",
9603
- width: elm.hole_width,
9604
- height: elm.hole_height,
9605
- pcbRotation: elm.ccw_rotation
9606
- })
9607
- );
9608
- } else if (elm.type === "pcb_cutout") {
9609
- if (elm.shape === "rect") {
9610
- components.push(
9611
- new Cutout({
9612
- pcbX: elm.center.x,
9613
- pcbY: elm.center.y,
9614
- shape: "rect",
9615
- width: elm.width,
9616
- height: elm.height
9617
- })
9618
- );
9619
- } else if (elm.shape === "circle") {
9620
- components.push(
9621
- new Cutout({
9622
- pcbX: elm.center.x,
9623
- pcbY: elm.center.y,
9624
- shape: "circle",
9625
- radius: elm.radius
9626
- })
9627
- );
9628
- } else if (elm.shape === "polygon") {
9629
- components.push(
9630
- new Cutout({
9631
- shape: "polygon",
9632
- points: elm.points
9633
- })
9634
- );
9635
- }
9636
- } else if (elm.type === "pcb_silkscreen_text") {
9637
- const ccwRotation = calculateCcwRotation(
9638
- componentRotation,
9639
- elm.ccw_rotation
9640
- );
9641
- if (footprinterString?.includes("pinrow") && elm.text.includes("PIN")) {
9642
- components.push(
9643
- createPinrowSilkscreenText({
9644
- elm,
9645
- pinLabels: pcbPinLabels ?? pinLabels ?? {},
9646
- layer: elm.layer,
9647
- readableRotation: ccwRotation,
9648
- anchorAlignment: elm.anchor_alignment
9649
- })
9650
- );
9651
- } else {
9652
- const silkscreenText = new SilkscreenText({
9653
- anchorAlignment: elm.anchor_alignment || "center",
9654
- text: componentName || elm.text,
9655
- pcbX: Number.isNaN(elm.anchor_position.x) ? 0 : elm.anchor_position.x,
9656
- pcbY: elm.anchor_position.y,
9657
- pcbRotation: ccwRotation ?? 0
9658
- });
9659
- silkscreenText._footprinterFontSize = elm.font_size + 0.2;
9660
- components.push(silkscreenText);
9661
- }
9662
- } else if (elm.type === "pcb_trace") {
9663
- components.push(
9664
- new PcbTrace({
9665
- route: elm.route
9666
- })
9667
- );
9668
- } else if (elm.type === "pcb_via") {
9669
- const layers = elm.layers ?? [];
9670
- const pcbVia = new PcbVia({
9671
- pcbX: elm.x,
9672
- pcbY: elm.y,
9673
- holeDiameter: elm.hole_diameter,
9674
- outerDiameter: elm.outer_diameter,
9675
- fromLayer: elm.from_layer ?? layers[0],
9676
- toLayer: elm.to_layer ?? layers[layers.length - 1],
9677
- layers,
9678
- netIsAssignable: elm.net_is_assignable,
9679
- isTented: elm.is_tented
9642
+ matchCenter = getCenterOfPcbPrimitives(pcbMatches);
9643
+ }
9644
+ if (matchCenter) {
9645
+ const subcircuit = this.getSubcircuit();
9646
+ const isBoardPinout = this._shouldIncludeInBoardPinout();
9647
+ const pcb_port = db.pcb_port.insert({
9648
+ pcb_component_id: parentWithPcbComponentId.pcb_component_id,
9649
+ layers: this.getAvailablePcbLayers(),
9650
+ subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
9651
+ pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
9652
+ ...isBoardPinout ? { is_board_pinout: true } : {},
9653
+ ...matchCenter,
9654
+ source_port_id: this.source_port_id,
9655
+ is_board_pinout: this._isBoardPinoutFromAttributes()
9680
9656
  });
9681
- pcbVia._importedPcbTraceId = elm.pcb_trace_id;
9682
- components.push(pcbVia);
9683
- } else if (elm.type === "pcb_silkscreen_rect") {
9684
- components.push(
9685
- new SilkscreenRect({
9686
- pcbX: elm.center.x,
9687
- pcbY: elm.center.y,
9688
- width: elm.width,
9689
- height: elm.height,
9690
- layer: elm.layer,
9691
- strokeWidth: elm.stroke_width,
9692
- filled: elm.is_filled,
9693
- cornerRadius: elm.corner_radius
9694
- })
9695
- );
9696
- } else if (elm.type === "pcb_silkscreen_circle") {
9697
- components.push(
9698
- new SilkscreenCircle({
9699
- pcbX: elm.center.x,
9700
- pcbY: elm.center.y,
9701
- radius: elm.radius,
9702
- layer: elm.layer,
9703
- strokeWidth: elm.stroke_width
9704
- })
9705
- );
9706
- } else if (elm.type === "pcb_silkscreen_line") {
9707
- components.push(
9708
- new SilkscreenLine({
9709
- x1: elm.x1,
9710
- y1: elm.y1,
9711
- x2: elm.x2,
9712
- y2: elm.y2,
9713
- layer: elm.layer,
9714
- strokeWidth: elm.stroke_width
9715
- })
9716
- );
9717
- } else if (elm.type === "pcb_fabrication_note_text") {
9718
- components.push(
9719
- new FabricationNoteText({
9720
- pcbX: elm.anchor_position.x,
9721
- pcbY: elm.anchor_position.y,
9722
- text: elm.text,
9723
- fontSize: elm.font_size,
9724
- anchorAlignment: elm.anchor_alignment,
9725
- color: elm.color,
9726
- font: elm.font
9727
- })
9728
- );
9729
- } else if (elm.type === "pcb_fabrication_note_path") {
9730
- components.push(
9731
- new FabricationNotePath({
9732
- route: elm.route,
9733
- strokeWidth: elm.stroke_width,
9734
- color: elm.color,
9735
- layer: elm.layer
9736
- })
9737
- );
9738
- } else if (elm.type === "pcb_fabrication_note_rect") {
9739
- components.push(
9740
- new FabricationNoteRect({
9741
- pcbX: elm.center.x,
9742
- pcbY: elm.center.y,
9743
- width: elm.width,
9744
- height: elm.height,
9745
- strokeWidth: elm.stroke_width,
9746
- isFilled: elm.is_filled,
9747
- color: elm.color,
9748
- layer: elm.layer,
9749
- cornerRadius: elm.corner_radius,
9750
- hasStroke: elm.has_stroke,
9751
- isStrokeDashed: elm.is_stroke_dashed
9752
- })
9753
- );
9754
- } else if (elm.type === "pcb_note_text") {
9755
- components.push(
9756
- new PcbNoteText({
9757
- pcbX: elm.anchor_position.x,
9758
- pcbY: elm.anchor_position.y,
9759
- text: elm.text ?? "",
9760
- fontSize: elm.font_size,
9761
- anchorAlignment: elm.anchor_alignment,
9762
- color: elm.color,
9763
- font: elm.font
9764
- })
9765
- );
9766
- } else if (elm.type === "pcb_note_rect") {
9767
- components.push(
9768
- new PcbNoteRect({
9769
- pcbX: elm.center.x,
9770
- pcbY: elm.center.y,
9771
- width: elm.width,
9772
- height: elm.height,
9773
- strokeWidth: elm.stroke_width,
9774
- isFilled: elm.is_filled,
9775
- color: elm.color,
9776
- cornerRadius: elm.corner_radius,
9777
- hasStroke: elm.has_stroke,
9778
- isStrokeDashed: elm.is_stroke_dashed
9779
- })
9780
- );
9781
- } else if (elm.type === "pcb_note_path") {
9782
- components.push(
9783
- new PcbNotePath({
9784
- route: elm.route,
9785
- strokeWidth: elm.stroke_width,
9786
- color: elm.color
9787
- })
9788
- );
9789
- } else if (elm.type === "pcb_note_line") {
9790
- components.push(
9791
- new PcbNoteLine({
9792
- x1: elm.x1,
9793
- y1: elm.y1,
9794
- x2: elm.x2,
9795
- y2: elm.y2,
9796
- strokeWidth: elm.stroke_width,
9797
- color: elm.color,
9798
- isDashed: elm.is_dashed
9799
- })
9800
- );
9801
- } else if (elm.type === "pcb_courtyard_rect") {
9802
- components.push(
9803
- new CourtyardRect({
9804
- pcbX: elm.center.x,
9805
- pcbY: elm.center.y,
9806
- width: elm.width,
9807
- height: elm.height,
9808
- layer: elm.layer
9809
- })
9810
- );
9811
- } else if (elm.type === "pcb_courtyard_circle") {
9812
- components.push(
9813
- new CourtyardCircle({
9814
- pcbX: elm.center.x,
9815
- pcbY: elm.center.y,
9816
- radius: elm.radius,
9817
- layer: elm.layer
9818
- })
9819
- );
9820
- } else if (elm.type === "pcb_courtyard_outline") {
9821
- components.push(
9822
- new CourtyardOutline({
9823
- outline: elm.outline,
9824
- layer: elm.layer
9825
- })
9826
- );
9827
- } else if (elm.type === "schematic_line") {
9828
- addSchematicPrimitive(
9829
- elm,
9830
- new SchematicLine({
9831
- x1: elm.x1,
9832
- y1: elm.y1,
9833
- x2: elm.x2,
9834
- y2: elm.y2,
9835
- strokeWidth: optional(elm.stroke_width),
9836
- color: elm.color,
9837
- isDashed: elm.is_dashed
9838
- })
9839
- );
9840
- } else if (elm.type === "schematic_rect") {
9841
- addSchematicPrimitive(
9842
- elm,
9843
- new SchematicRect({
9844
- schX: elm.center.x,
9845
- schY: elm.center.y,
9846
- width: elm.width,
9847
- height: elm.height,
9848
- rotation: elm.rotation,
9849
- strokeWidth: optional(elm.stroke_width),
9850
- color: elm.color,
9851
- isFilled: elm.is_filled,
9852
- fillColor: elm.fill_color,
9853
- isDashed: elm.is_dashed
9854
- })
9855
- );
9856
- } else if (elm.type === "schematic_circle") {
9857
- addSchematicPrimitive(
9858
- elm,
9859
- new SchematicCircle({
9860
- center: elm.center,
9861
- radius: elm.radius,
9862
- strokeWidth: optional(elm.stroke_width),
9863
- color: elm.color,
9864
- isFilled: elm.is_filled,
9865
- fillColor: elm.fill_color,
9866
- isDashed: elm.is_dashed
9867
- })
9868
- );
9869
- } else if (elm.type === "schematic_arc") {
9870
- addSchematicPrimitive(
9871
- elm,
9872
- new SchematicArc({
9873
- center: elm.center,
9874
- radius: elm.radius,
9875
- startAngleDegrees: elm.start_angle_degrees,
9876
- endAngleDegrees: elm.end_angle_degrees,
9877
- direction: elm.direction,
9878
- strokeWidth: optional(elm.stroke_width),
9879
- color: elm.color,
9880
- isDashed: elm.is_dashed
9881
- })
9882
- );
9883
- } else if (elm.type === "schematic_text") {
9884
- addSchematicPrimitive(
9885
- elm,
9886
- new SchematicText({
9887
- schX: elm.position.x,
9888
- schY: elm.position.y,
9889
- text: elm.text,
9890
- fontSize: elm.font_size,
9891
- anchor: elm.anchor,
9892
- color: elm.color,
9893
- schRotation: elm.rotation
9894
- })
9895
- );
9896
- } else if (elm.type === "schematic_path") {
9897
- addSchematicPrimitive(
9898
- elm,
9899
- new SchematicPath({
9900
- points: elm.points,
9901
- strokeWidth: optional(elm.stroke_width),
9902
- strokeColor: elm.stroke_color,
9903
- dashLength: elm.dash_length,
9904
- dashGap: elm.dash_gap,
9905
- isFilled: elm.is_filled,
9906
- fillColor: elm.fill_color
9907
- })
9657
+ this.pcb_port_id = pcb_port.pcb_port_id;
9658
+ } else {
9659
+ const pcbMatch = pcbMatches[0];
9660
+ throw new Error(
9661
+ `${pcbMatch.getString()} does not have a center or _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`
9908
9662
  );
9909
- } else if (elm.type === "schematic_port") {
9910
- const schematicComponentId = elm.schematic_component_id;
9911
- if (typeof schematicComponentId !== "string") continue;
9912
- const schematicComponent = schematicComponentsByImportedId.get(schematicComponentId);
9913
- const schematicSymbolId = schematicComponent?.schematic_symbol_id;
9914
- const parentSymbol = typeof schematicSymbolId === "string" ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
9915
- if (parentSymbol && schematicComponent?.is_box_with_pins === true && elm.center && elm.side_of_component) {
9916
- const distance19 = elm.distance_from_component_edge;
9917
- const facingDirection = getFacingDirectionFromSide(
9918
- elm.side_of_component
9919
- );
9920
- if (!facingDirection) continue;
9921
- const directionVector = getUnitVectorFromDirection2(facingDirection);
9922
- const stemDirection = elm.facing_direction === facingDirection ? 1 : -1;
9923
- const portCenter = {
9924
- x: elm.center.x + directionVector.x * distance19 * stemDirection,
9925
- y: elm.center.y + directionVector.y * distance19 * stemDirection
9926
- };
9927
- parentSymbol.add(
9928
- new SchematicLine({
9929
- x1: elm.center.x,
9930
- y1: elm.center.y,
9931
- x2: portCenter.x,
9932
- y2: portCenter.y,
9933
- isDashed: false
9934
- })
9935
- );
9663
+ }
9664
+ }
9665
+ updatePcbPortRender() {
9666
+ if (this.root?.pcbDisabled) return;
9667
+ const { db } = this.root;
9668
+ if (this.pcb_port_id) return;
9669
+ if (this.isGroupPort()) {
9670
+ const connectedPorts = this._getConnectedPortsFromConnectsTo();
9671
+ if (connectedPorts.length === 0) return;
9672
+ const connectedPort = connectedPorts[0];
9673
+ if (!connectedPort.pcb_port_id) return;
9674
+ const connectedPcbPort = db.pcb_port.get(connectedPort.pcb_port_id);
9675
+ const matchCenter2 = { x: connectedPcbPort.x, y: connectedPcbPort.y };
9676
+ const subcircuit2 = this.getSubcircuit();
9677
+ const pcb_port2 = db.pcb_port.insert({
9678
+ pcb_component_id: void 0,
9679
+ layers: connectedPort.getAvailablePcbLayers(),
9680
+ subcircuit_id: subcircuit2?.subcircuit_id ?? void 0,
9681
+ pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
9682
+ ...matchCenter2,
9683
+ source_port_id: this.source_port_id,
9684
+ is_board_pinout: false
9685
+ });
9686
+ this.pcb_port_id = pcb_port2.pcb_port_id;
9687
+ return;
9688
+ }
9689
+ const pcbMatches = this.matchedComponents.filter((c) => c.isPcbPrimitive);
9690
+ if (pcbMatches.length === 0) return;
9691
+ let matchCenter = null;
9692
+ if (pcbMatches.length === 1) {
9693
+ matchCenter = pcbMatches[0]._getPcbCircuitJsonBounds().center;
9694
+ }
9695
+ if (pcbMatches.length > 1) {
9696
+ try {
9697
+ if (areAllPcbPrimitivesOverlapping(pcbMatches)) {
9698
+ matchCenter = getCenterOfPcbPrimitives(pcbMatches);
9699
+ }
9700
+ } catch {
9936
9701
  }
9937
9702
  }
9703
+ if (!matchCenter) return;
9704
+ const parentNormalComponent = this.getParentNormalComponent();
9705
+ const parentWithPcbComponentId = this.parent?.pcb_component_id ? this.parent : parentNormalComponent;
9706
+ const subcircuit = this.getSubcircuit();
9707
+ const isBoardPinout = this._shouldIncludeInBoardPinout();
9708
+ const pcb_port = db.pcb_port.insert({
9709
+ pcb_component_id: parentWithPcbComponentId?.pcb_component_id,
9710
+ layers: this.getAvailablePcbLayers(),
9711
+ subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
9712
+ pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
9713
+ ...isBoardPinout ? { is_board_pinout: true } : {},
9714
+ ...matchCenter,
9715
+ source_port_id: this.source_port_id,
9716
+ is_board_pinout: this._isBoardPinoutFromAttributes()
9717
+ });
9718
+ this.pcb_port_id = pcb_port.pcb_port_id;
9938
9719
  }
9939
- return components;
9940
- };
9941
-
9942
- // lib/utils/filterPinLabels.ts
9943
- import { chipProps } from "@tscircuit/props";
9944
- function filterPinLabels(pinLabels) {
9945
- if (!pinLabels)
9946
- return {
9947
- validPinLabels: pinLabels,
9948
- invalidPinLabelsMessages: []
9949
- };
9950
- const validPinLabels = {};
9951
- const invalidPinLabelsMessages = [];
9952
- for (const [pin, labelOrLabels] of Object.entries(pinLabels)) {
9953
- const labels = Array.isArray(labelOrLabels) ? labelOrLabels.slice() : [labelOrLabels];
9954
- const validLabels = [];
9955
- for (const label of labels) {
9956
- if (isValidPinLabel(pin, label)) {
9957
- validLabels.push(label);
9958
- } else {
9959
- invalidPinLabelsMessages.push(
9960
- `Invalid pin label: ${pin} = '${label}' - excluding from component. Pin labels can only contain letters, numbers and underscores.`
9961
- );
9962
- }
9720
+ /**
9721
+ * Get the best display label for this port based on port_hints
9722
+ * Filters out generic patterns and applies showPinAliases logic
9723
+ */
9724
+ _getBestDisplayPinLabel() {
9725
+ const { db } = this.root;
9726
+ const sourcePort = db.source_port.get(this.source_port_id);
9727
+ const labelHints = [];
9728
+ for (const portHint of sourcePort?.port_hints ?? []) {
9729
+ if (portHint.match(/^(pin)?\d+$/)) continue;
9730
+ if (portHint.match(/^(left|right)/) && !sourcePort?.name.match(/^(left|right)/))
9731
+ continue;
9732
+ labelHints.push(portHint);
9963
9733
  }
9964
- if (validLabels.length > 0) {
9965
- validPinLabels[pin] = Array.isArray(labelOrLabels) ? validLabels : validLabels[0];
9734
+ const parentNormalComponent = this.getParentNormalComponent();
9735
+ const showPinAliases = parentNormalComponent?.props?.showPinAliases;
9736
+ if (showPinAliases && labelHints.length > 0) {
9737
+ return labelHints.join("/");
9738
+ } else if (labelHints.length > 0) {
9739
+ return labelHints[0];
9966
9740
  }
9741
+ return void 0;
9967
9742
  }
9968
- return {
9969
- validPinLabels: Object.keys(validPinLabels).length > 0 ? validPinLabels : void 0,
9970
- invalidPinLabelsMessages
9971
- };
9972
- }
9973
- function isValidPinLabel(pin, label) {
9974
- try {
9975
- const testProps = {
9976
- name: "test",
9977
- footprint: "test",
9978
- pinLabels: { [pin]: label }
9979
- };
9980
- const result = chipProps.safeParse(testProps);
9981
- return result.success;
9982
- } catch (error) {
9983
- return false;
9984
- }
9985
- }
9986
-
9987
- // lib/utils/get-bounds-of-pcb-components.ts
9988
- var NON_PHYSICAL_PCB_PRIMITIVE_PREFIXES = [
9989
- "Silkscreen",
9990
- "PcbNote",
9991
- "Courtyard",
9992
- "FabricationNote"
9993
- ];
9994
- function getBoundsOfPcbComponents(components) {
9995
- let minX = Infinity;
9996
- let minY = Infinity;
9997
- let maxX = -Infinity;
9998
- let maxY = -Infinity;
9999
- let hasValidComponents = false;
10000
- for (const child of components) {
10001
- if (child.isPcbPrimitive && !NON_PHYSICAL_PCB_PRIMITIVE_PREFIXES.some(
10002
- (prefix) => child.componentName.startsWith(prefix)
10003
- )) {
10004
- const { x, y } = child._getGlobalPcbPositionBeforeLayout();
10005
- const { width: width2, height: height2 } = child.getPcbSize();
10006
- minX = Math.min(minX, x - width2 / 2);
10007
- minY = Math.min(minY, y - height2 / 2);
10008
- maxX = Math.max(maxX, x + width2 / 2);
10009
- maxY = Math.max(maxY, y + height2 / 2);
10010
- hasValidComponents = true;
10011
- } else if (child.children.length > 0) {
10012
- const childBounds = getBoundsOfPcbComponents(child.children);
10013
- if (childBounds.width > 0 || childBounds.height > 0) {
10014
- minX = Math.min(minX, childBounds.minX);
10015
- minY = Math.min(minY, childBounds.minY);
10016
- maxX = Math.max(maxX, childBounds.maxX);
10017
- maxY = Math.max(maxY, childBounds.maxY);
10018
- hasValidComponents = true;
10019
- }
9743
+ doInitialSchematicPortRender() {
9744
+ const collapsedAncestor = this.getCollapsedSchematicBoxAncestor();
9745
+ if (collapsedAncestor && this.parent !== collapsedAncestor) return;
9746
+ const { db } = this.root;
9747
+ const { _parsedProps: props } = this;
9748
+ const { schX, schY } = props;
9749
+ const container = schX !== void 0 && schY !== void 0 ? this.getParentNormalComponent() : this.getPrimitiveContainer();
9750
+ if (!container) return;
9751
+ if (!this._hasSchematicPort()) return;
9752
+ const containerCenter = container._getGlobalSchematicPositionBeforeLayout();
9753
+ const portCenter = this._getGlobalSchematicPositionBeforeLayout();
9754
+ let localPortInfo = null;
9755
+ const containerDims = container._getSchematicBoxDimensions();
9756
+ if (containerDims && props.pinNumber !== void 0) {
9757
+ localPortInfo = containerDims.getPortPositionByPinNumber(props.pinNumber);
9758
+ }
9759
+ if (this.getSubcircuit().props._schDebugObjectsEnabled) {
9760
+ db.schematic_debug_object.insert({
9761
+ shape: "rect",
9762
+ center: portCenter,
9763
+ size: {
9764
+ width: 0.1,
9765
+ height: 0.1
9766
+ },
9767
+ label: "obstacle"
9768
+ });
9769
+ }
9770
+ if (!localPortInfo?.side) {
9771
+ this.facingDirection = getRelativeDirection(containerCenter, portCenter);
9772
+ } else {
9773
+ this.facingDirection = {
9774
+ left: "left",
9775
+ right: "right",
9776
+ top: "up",
9777
+ bottom: "down"
9778
+ }[localPortInfo.side];
9779
+ }
9780
+ const bestDisplayPinLabel = this._getBestDisplayPinLabel();
9781
+ const parentNormalComponent = this.getParentNormalComponent();
9782
+ const sideOfComponent = localPortInfo?.side ?? (props.direction === "up" ? "top" : props.direction === "down" ? "bottom" : props.direction);
9783
+ const schematicPortInsertProps = {
9784
+ type: "schematic_port",
9785
+ schematic_component_id: parentNormalComponent?.schematic_component_id,
9786
+ center: portCenter,
9787
+ source_port_id: this.source_port_id,
9788
+ facing_direction: this.facingDirection,
9789
+ distance_from_component_edge: props.schStemLength ?? 0.4,
9790
+ side_of_component: sideOfComponent,
9791
+ pin_number: props.pinNumber,
9792
+ true_ccw_index: localPortInfo?.trueIndex,
9793
+ display_pin_label: bestDisplayPinLabel,
9794
+ is_connected: false
9795
+ };
9796
+ for (const attributes of this._getMatchingPinAttributes()) {
9797
+ if (attributes.requiresPower) {
9798
+ schematicPortInsertProps.has_input_arrow = true;
9799
+ }
9800
+ if (attributes.providesPower) {
9801
+ schematicPortInsertProps.has_output_arrow = true;
9802
+ }
9803
+ }
9804
+ const schematic_port = db.schematic_port.insert(schematicPortInsertProps);
9805
+ this.schematic_port_id = schematic_port.schematic_port_id;
9806
+ if (props.schStemLength !== void 0 && props.schStemLength !== 0) {
9807
+ const { schStemLength, direction } = props;
9808
+ let x2 = portCenter.x;
9809
+ let y2 = portCenter.y;
9810
+ if (direction === "right") x2 -= schStemLength;
9811
+ else if (direction === "left") x2 += schStemLength;
9812
+ else if (direction === "up") y2 -= schStemLength;
9813
+ else if (direction === "down") y2 += schStemLength;
9814
+ const stemLine = db.schematic_line.insert({
9815
+ schematic_component_id: parentNormalComponent?.schematic_component_id,
9816
+ x1: portCenter.x,
9817
+ y1: portCenter.y,
9818
+ x2,
9819
+ y2,
9820
+ stroke_width: 0.02,
9821
+ color: SCHEMATIC_COMPONENT_OUTLINE_COLOR,
9822
+ is_dashed: false
9823
+ });
9824
+ this.schematic_stem_line_id = stemLine.schematic_line_id;
10020
9825
  }
10021
9826
  }
10022
- if (!hasValidComponents) {
10023
- return {
10024
- minX: 0,
10025
- minY: 0,
10026
- maxX: 0,
10027
- maxY: 0,
10028
- width: 0,
10029
- height: 0
10030
- };
10031
- }
10032
- let width = maxX - minX;
10033
- let height = maxY - minY;
10034
- if (width < 0) width = 0;
10035
- if (height < 0) height = 0;
10036
- return {
10037
- minX,
10038
- minY,
10039
- maxX,
10040
- maxY,
10041
- width,
10042
- height
10043
- };
10044
- }
10045
-
10046
- // lib/utils/get-relative-direction.ts
10047
- function getRelativeDirection(pointA, pointB) {
10048
- const dx = pointB.x - pointA.x;
10049
- const dy = pointB.y - pointA.y;
10050
- if (Math.abs(dx) > Math.abs(dy)) {
10051
- return dx >= 0 ? "right" : "left";
10052
- }
10053
- return dy >= 0 ? "up" : "down";
10054
- }
10055
-
10056
- // lib/components/primitive-components/Port/Port.ts
10057
- import "schematic-symbols";
10058
- import { applyToPoint as applyToPoint22, compose as compose4, translate as translate3 } from "transformation-matrix";
10059
- import "zod";
10060
-
10061
- // lib/components/primitive-components/Port/getCenterOfPcbPrimitives.ts
10062
- var getCenterOfPcbPrimitives = (pcbPrimitives) => {
10063
- if (pcbPrimitives.length === 0) {
10064
- throw new Error("Cannot get center of empty PCB primitives array");
10065
- }
10066
- const positions = pcbPrimitives.map((p) => p._getPcbCircuitJsonBounds().center).filter(Boolean);
10067
- const sumX = positions.reduce((sum, pos) => sum + pos.x, 0);
10068
- const sumY = positions.reduce((sum, pos) => sum + pos.y, 0);
10069
- return {
10070
- x: sumX / positions.length,
10071
- y: sumY / positions.length
10072
- };
10073
- };
10074
-
10075
- // lib/components/primitive-components/Port/Port.ts
10076
- import { portProps } from "@tscircuit/props";
10077
-
10078
- // lib/components/primitive-components/Port/apply-pin-attributes-to-source-port.ts
10079
- var applyPinAttributesToSourcePort = (sourcePortProps, attributes) => {
10080
- if (attributes.mustBeConnected !== void 0) {
10081
- sourcePortProps.must_be_connected = attributes.mustBeConnected;
10082
- }
10083
- if (attributes.providesPower !== void 0) {
10084
- sourcePortProps.provides_power = attributes.providesPower;
10085
- }
10086
- if (attributes.requiresPower !== void 0) {
10087
- sourcePortProps.requires_power = attributes.requiresPower;
10088
- }
10089
- if (attributes.providesGround !== void 0) {
10090
- sourcePortProps.provides_ground = attributes.providesGround;
10091
- }
10092
- if (attributes.requiresGround !== void 0) {
10093
- sourcePortProps.requires_ground = attributes.requiresGround;
10094
- }
10095
- if (attributes.providesVoltage !== void 0) {
10096
- sourcePortProps.provides_voltage = attributes.providesVoltage;
10097
- }
10098
- if (attributes.requiresVoltage !== void 0) {
10099
- sourcePortProps.requires_voltage = attributes.requiresVoltage;
10100
- }
10101
- if (attributes.doNotConnect !== void 0) {
10102
- sourcePortProps.do_not_connect = attributes.doNotConnect;
9827
+ doInitialSchematicSymbolResize() {
9828
+ if (this.root?.schematicDisabled) return;
9829
+ if (!this.schematic_port_id) return;
9830
+ const symbol = this._getSymbolAncestor();
9831
+ const transform = symbol?.getUserCoordinateToResizedSymbolTransform();
9832
+ if (!transform) return;
9833
+ const { db } = this.root;
9834
+ const schPort = db.schematic_port.get(this.schematic_port_id);
9835
+ if (schPort) {
9836
+ const newCenter = applyToPoint22(transform, schPort.center);
9837
+ db.schematic_port.update(this.schematic_port_id, {
9838
+ center: newCenter
9839
+ });
9840
+ if (this.schematic_stem_line_id) {
9841
+ const line = db.schematic_line.get(this.schematic_stem_line_id);
9842
+ if (line) {
9843
+ const p1 = applyToPoint22(transform, { x: line.x1, y: line.y1 });
9844
+ const p2 = applyToPoint22(transform, { x: line.x2, y: line.y2 });
9845
+ db.schematic_line.update(this.schematic_stem_line_id, {
9846
+ x1: p1.x,
9847
+ y1: p1.y,
9848
+ x2: p2.x,
9849
+ y2: p2.y
9850
+ });
9851
+ const scaledStemLength = Math.sqrt(
9852
+ (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2
9853
+ );
9854
+ db.schematic_port.update(this.schematic_port_id, {
9855
+ distance_from_component_edge: scaledStemLength
9856
+ });
9857
+ }
9858
+ }
9859
+ }
10103
9860
  }
10104
- if (attributes.includeInBoardPinout !== void 0) {
10105
- sourcePortProps.include_in_board_pinout = attributes.includeInBoardPinout;
9861
+ _getSubcircuitConnectivityKey() {
9862
+ return this.root?.db.source_port.get(this.source_port_id)?.subcircuit_connectivity_map_key;
10106
9863
  }
10107
- if (attributes.canUseInternalPullup !== void 0) {
10108
- sourcePortProps.can_use_internal_pullup = attributes.canUseInternalPullup;
9864
+ _setPositionFromLayout(newCenter) {
9865
+ const { db } = this.root;
9866
+ if (!this.pcb_port_id) return;
9867
+ db.pcb_port.update(this.pcb_port_id, {
9868
+ x: newCenter.x,
9869
+ y: newCenter.y
9870
+ });
10109
9871
  }
10110
- if (attributes.isUsingInternalPullup !== void 0) {
10111
- sourcePortProps.is_using_internal_pullup = attributes.isUsingInternalPullup;
9872
+ _hasMatchedPcbPrimitive() {
9873
+ return this.matchedComponents.some((c) => c.isPcbPrimitive);
10112
9874
  }
10113
- if (attributes.needsExternalPullup !== void 0) {
10114
- sourcePortProps.needs_external_pullup = attributes.needsExternalPullup;
9875
+ /**
9876
+ * Return the text that should be used for the net label for this port if a
9877
+ * trace can't be drawn. This net label text usually doesn't appear at this
9878
+ * port, but appears at the port it connects to.
9879
+ */
9880
+ _getNetLabelText() {
9881
+ return `${this.parent?.props.name}_${this.props.name}`;
10115
9882
  }
10116
- if (attributes.canUseInternalPulldown !== void 0) {
10117
- sourcePortProps.can_use_internal_pulldown = attributes.canUseInternalPulldown;
9883
+ };
9884
+
9885
+ // lib/components/primitive-components/Symbol/Symbol.ts
9886
+ import { symbolProps } from "@tscircuit/props";
9887
+ import { compose as compose4, translate as translate3, scale } from "transformation-matrix";
9888
+ var SymbolComponent = class extends PrimitiveComponent2 {
9889
+ isPrimitiveContainer = true;
9890
+ schematic_symbol_id;
9891
+ userCoordinateToResizedSymbolTransformMat;
9892
+ schematicSymbolBoundsInUserCoordinates;
9893
+ get config() {
9894
+ return {
9895
+ componentName: "Symbol",
9896
+ zodProps: symbolProps
9897
+ };
10118
9898
  }
10119
- if (attributes.isUsingInternalPulldown !== void 0) {
10120
- sourcePortProps.is_using_internal_pulldown = attributes.isUsingInternalPulldown;
9899
+ hasExplicitSize() {
9900
+ const { _parsedProps: props } = this;
9901
+ return props.width !== void 0 || props.height !== void 0;
10121
9902
  }
10122
- if (attributes.needsExternalPulldown !== void 0) {
10123
- sourcePortProps.needs_external_pulldown = attributes.needsExternalPulldown;
9903
+ /**
9904
+ * Create the schematic_symbol element in SymbolContainerRender phase.
9905
+ * This runs before SchematicPrimitiveRender, ensuring children can
9906
+ * reference the schematic_symbol_id when they render.
9907
+ */
9908
+ doInitialSymbolContainerRender() {
9909
+ if (this.root?.schematicDisabled) return;
9910
+ const { db } = this.root;
9911
+ const { _parsedProps: props } = this;
9912
+ const parentNormal = this.getParentNormalComponent();
9913
+ const kicadSymbolMetadata = parentNormal?._parsedProps?.kicadSymbolMetadata;
9914
+ const schematic_symbol = db.schematic_symbol.insert({
9915
+ name: props.name,
9916
+ metadata: kicadSymbolMetadata ? { kicad_symbol: kicadSymbolMetadata } : void 0
9917
+ });
9918
+ this.schematic_symbol_id = schematic_symbol.schematic_symbol_id;
10124
9919
  }
10125
- if (attributes.canUseOpenDrain !== void 0) {
10126
- sourcePortProps.can_use_open_drain = attributes.canUseOpenDrain;
9920
+ getSchematicSymbolBounds() {
9921
+ if (this.schematicSymbolBoundsInUserCoordinates) {
9922
+ return this.schematicSymbolBoundsInUserCoordinates;
9923
+ }
9924
+ this._computeSchematicSymbolBounds();
9925
+ return this.schematicSymbolBoundsInUserCoordinates ?? null;
10127
9926
  }
10128
- if (attributes.isUsingOpenDrain !== void 0) {
10129
- sourcePortProps.is_using_open_drain = attributes.isUsingOpenDrain;
9927
+ getUserCoordinateToResizedSymbolTransform() {
9928
+ if (!this.hasExplicitSize()) {
9929
+ return null;
9930
+ }
9931
+ if (this.userCoordinateToResizedSymbolTransformMat) {
9932
+ return this.userCoordinateToResizedSymbolTransformMat;
9933
+ }
9934
+ this._computeUserCoordinateToResizedSymbolTransform();
9935
+ return this.userCoordinateToResizedSymbolTransformMat ?? null;
10130
9936
  }
10131
- if (attributes.canUsePushPull !== void 0) {
10132
- sourcePortProps.can_use_push_pull = attributes.canUsePushPull;
9937
+ _computeSchematicSymbolBounds() {
9938
+ if (this.root?.schematicDisabled) return;
9939
+ const { db } = this.root;
9940
+ const schematicElements = [];
9941
+ for (const child of this.children) {
9942
+ if (child.componentName === "Port") {
9943
+ const portId = child.schematic_port_id;
9944
+ if (portId) {
9945
+ const port = db.schematic_port.get(portId);
9946
+ if (port) schematicElements.push(port);
9947
+ }
9948
+ continue;
9949
+ }
9950
+ if (!child.isSchematicPrimitive) continue;
9951
+ if (child.componentName === "SchematicLine") {
9952
+ const line = db.schematic_line.get(child.schematic_line_id);
9953
+ if (line) schematicElements.push(line);
9954
+ } else if (child.componentName === "SchematicRect") {
9955
+ const rect = db.schematic_rect.get(child.schematic_rect_id);
9956
+ if (rect) schematicElements.push(rect);
9957
+ } else if (child.componentName === "SchematicCircle") {
9958
+ const circle = db.schematic_circle.get(
9959
+ child.schematic_circle_id
9960
+ );
9961
+ if (circle) schematicElements.push(circle);
9962
+ } else if (child.componentName === "SchematicArc") {
9963
+ const arc = db.schematic_arc.get(child.schematic_arc_id);
9964
+ if (arc) schematicElements.push(arc);
9965
+ } else if (child.componentName === "SchematicText") {
9966
+ const text = db.schematic_text.get(child.schematic_text_id);
9967
+ if (text) schematicElements.push(text);
9968
+ } else if (child.componentName === "SchematicPath") {
9969
+ const pathIds = child.schematic_path_ids;
9970
+ if (pathIds) {
9971
+ for (const pathId of pathIds) {
9972
+ const path = db.schematic_path.get(pathId);
9973
+ if (path) schematicElements.push(path);
9974
+ }
9975
+ }
9976
+ }
9977
+ }
9978
+ if (schematicElements.length === 0) return;
9979
+ const bounds = getBoundsForSchematic(schematicElements);
9980
+ this.schematicSymbolBoundsInUserCoordinates = bounds;
10133
9981
  }
10134
- if (attributes.isUsingPushPull !== void 0) {
10135
- sourcePortProps.is_using_push_pull = attributes.isUsingPushPull;
9982
+ _computeUserCoordinateToResizedSymbolTransform() {
9983
+ const bounds = this.getSchematicSymbolBounds();
9984
+ if (!bounds) return;
9985
+ const { _parsedProps: props } = this;
9986
+ const targetWidth = props.width;
9987
+ const targetHeight = props.height;
9988
+ if (targetWidth === void 0 && targetHeight === void 0) return;
9989
+ const currentWidth = bounds.maxX - bounds.minX;
9990
+ const currentHeight = bounds.maxY - bounds.minY;
9991
+ if (currentWidth === 0 && currentHeight === 0) return;
9992
+ const currentCenterX = (bounds.minX + bounds.maxX) / 2;
9993
+ const currentCenterY = (bounds.minY + bounds.maxY) / 2;
9994
+ const scaleX = targetWidth !== void 0 && currentWidth > 0 ? targetWidth / currentWidth : 1;
9995
+ const scaleY = targetHeight !== void 0 && currentHeight > 0 ? targetHeight / currentHeight : 1;
9996
+ const globalPos = this._getGlobalSchematicPositionBeforeLayout();
9997
+ this.userCoordinateToResizedSymbolTransformMat = compose4(
9998
+ translate3(globalPos.x, globalPos.y),
9999
+ scale(scaleX, scaleY),
10000
+ translate3(-currentCenterX, -currentCenterY)
10001
+ );
10136
10002
  }
10137
- if (attributes.shouldHaveDecouplingCapacitor !== void 0) {
10138
- sourcePortProps.should_have_decoupling_capacitor = attributes.shouldHaveDecouplingCapacitor;
10003
+ };
10004
+
10005
+ // lib/utils/createPinrowSilkscreenText.ts
10006
+ var createPinrowSilkscreenText = ({
10007
+ elm,
10008
+ pinLabels,
10009
+ layer,
10010
+ readableRotation,
10011
+ anchorAlignment
10012
+ }) => {
10013
+ const pinNum = elm.text.replace(/[{}]/g, "").toLowerCase();
10014
+ let label = pinNum;
10015
+ if (Array.isArray(pinLabels)) {
10016
+ const index = parseInt(pinNum.replace(/[^\d]/g, ""), 10) - 1;
10017
+ label = String(pinLabels[index] ?? pinNum);
10018
+ } else if (typeof pinLabels === "object") {
10019
+ label = String(pinLabels[pinNum] ?? pinNum);
10139
10020
  }
10140
- if (attributes.recommendedDecouplingCapacitorCapacitance !== void 0) {
10141
- sourcePortProps.recommended_decoupling_capacitor_capacitance = attributes.recommendedDecouplingCapacitorCapacitance;
10021
+ const silkscreenText = new SilkscreenText({
10022
+ anchorAlignment: anchorAlignment || "center",
10023
+ text: label ?? pinNum,
10024
+ layer: layer || "top",
10025
+ pcbX: isNaN(elm.anchor_position.x) ? 0 : elm.anchor_position.x,
10026
+ pcbY: elm.anchor_position.y,
10027
+ pcbRotation: readableRotation ?? 0
10028
+ });
10029
+ silkscreenText._footprinterFontSize = elm.font_size + 0.2;
10030
+ return silkscreenText;
10031
+ };
10032
+
10033
+ // lib/utils/createComponentsFromCircuitJson.ts
10034
+ var calculateCcwRotation = (componentRotationStr, elementCcwRotation) => {
10035
+ const componentAngle = parseInt(componentRotationStr || "0", 10);
10036
+ let totalRotation;
10037
+ if (elementCcwRotation !== void 0 && elementCcwRotation !== null) {
10038
+ totalRotation = elementCcwRotation - componentAngle;
10039
+ } else {
10040
+ totalRotation = componentAngle;
10142
10041
  }
10042
+ const normalizedRotation = (totalRotation % 360 + 360) % 360;
10043
+ return normalizedRotation;
10143
10044
  };
10144
-
10145
- // lib/components/primitive-components/Port/Port.ts
10146
- var Port = class extends PrimitiveComponent2 {
10147
- source_port_id = null;
10148
- pcb_port_id = null;
10149
- schematic_port_id = null;
10150
- schematic_stem_line_id = null;
10151
- schematicSymbolPortDef = null;
10152
- matchedComponents;
10153
- _isPrimaryPort = true;
10154
- _primaryPinNumber = null;
10155
- facingDirection = null;
10156
- originDescription = null;
10157
- get config() {
10158
- return {
10159
- componentName: "Port",
10160
- zodProps: portProps
10161
- };
10045
+ var optional = (value) => value ?? void 0;
10046
+ var getFacingDirectionFromSide = (side) => {
10047
+ switch (side) {
10048
+ case "left":
10049
+ case "right":
10050
+ return side;
10051
+ case "top":
10052
+ return "up";
10053
+ case "bottom":
10054
+ return "down";
10162
10055
  }
10163
- constructor(props, opts = {}) {
10164
- if (!props.name && props.pinNumber !== void 0)
10165
- props.name = `pin${props.pinNumber}`;
10166
- if (!props.name) {
10167
- throw new Error("Port must have a name or a pinNumber");
10168
- }
10169
- super(props);
10170
- if (opts.originDescription) {
10171
- this.originDescription = opts.originDescription;
10056
+ return null;
10057
+ };
10058
+ var schematicPrimitiveTypesWithStrokeWidth = [
10059
+ "schematic_line",
10060
+ "schematic_rect",
10061
+ "schematic_circle",
10062
+ "schematic_arc",
10063
+ "schematic_path"
10064
+ ];
10065
+ var isSchematicPrimitiveWithStrokeWidth = (elm) => schematicPrimitiveTypesWithStrokeWidth.includes(
10066
+ elm.type
10067
+ );
10068
+ var getSchematicSymbolId = (elm) => {
10069
+ if (!("schematic_symbol_id" in elm)) return void 0;
10070
+ return typeof elm.schematic_symbol_id === "string" ? elm.schematic_symbol_id : void 0;
10071
+ };
10072
+ var createComponentsFromCircuitJson = ({
10073
+ componentName,
10074
+ componentRotation,
10075
+ footprinterString,
10076
+ pinLabels,
10077
+ pcbPinLabels
10078
+ }, circuitJson) => {
10079
+ const components = [];
10080
+ const schematicSymbolsByImportedId = /* @__PURE__ */ new Map();
10081
+ const schematicComponentsByImportedId = /* @__PURE__ */ new Map();
10082
+ const sourcePortsByImportedId = /* @__PURE__ */ new Map();
10083
+ const schematicStrokeWidthBySymbolId = /* @__PURE__ */ new Map();
10084
+ for (const elm of circuitJson) {
10085
+ if (elm.type === "source_port") {
10086
+ sourcePortsByImportedId.set(elm.source_port_id, elm);
10172
10087
  }
10173
- this.matchedComponents = [];
10174
10088
  }
10175
- isGroupPort() {
10176
- return this.parent?.componentName === "Group";
10177
- }
10178
- isComponentPort() {
10179
- return !this.isGroupPort();
10089
+ for (const elm of circuitJson) {
10090
+ if (elm.type !== "schematic_symbol") continue;
10091
+ const schematicSymbol = new SymbolComponent({
10092
+ name: elm.name
10093
+ });
10094
+ schematicSymbolsByImportedId.set(elm.schematic_symbol_id, schematicSymbol);
10095
+ components.push(schematicSymbol);
10180
10096
  }
10181
- _getConnectedPortsFromConnectsTo() {
10182
- const { _parsedProps: props } = this;
10183
- const connectsTo = props.connectsTo;
10184
- if (!connectsTo) return [];
10185
- const connectedPorts = [];
10186
- const connectsToArray = Array.isArray(connectsTo) ? connectsTo : [connectsTo];
10187
- for (const connection of connectsToArray) {
10188
- const port = this.getSubcircuit().selectOne(connection, {
10189
- type: "port"
10190
- });
10191
- if (port) {
10192
- connectedPorts.push(port);
10193
- }
10097
+ for (const elm of circuitJson) {
10098
+ if (!isSchematicPrimitiveWithStrokeWidth(elm)) continue;
10099
+ const schematicSymbolId = getSchematicSymbolId(elm);
10100
+ const strokeWidth = elm.stroke_width;
10101
+ if (!schematicSymbolId || typeof strokeWidth !== "number" || !Number.isFinite(strokeWidth) || schematicStrokeWidthBySymbolId.has(schematicSymbolId)) {
10102
+ continue;
10194
10103
  }
10195
- return connectedPorts;
10104
+ schematicStrokeWidthBySymbolId.set(schematicSymbolId, strokeWidth);
10196
10105
  }
10197
- _isBoardPinoutFromAttributes() {
10198
- const parent = this.parent;
10199
- if (parent?._parsedProps?.pinAttributes) {
10200
- const pinAttributes = parent._parsedProps.pinAttributes;
10201
- for (const alias of this.getNameAndAliases()) {
10202
- if (pinAttributes[alias]?.includeInBoardPinout) {
10203
- return true;
10204
- }
10106
+ for (const elm of circuitJson) {
10107
+ if (elm.type !== "schematic_component") continue;
10108
+ schematicComponentsByImportedId.set(elm.schematic_component_id, elm);
10109
+ }
10110
+ const addSchematicPrimitive = (elm, primitive) => {
10111
+ const schematicSymbolId = getSchematicSymbolId(elm);
10112
+ const parentSymbol = schematicSymbolId ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
10113
+ if (parentSymbol) {
10114
+ parentSymbol.add(primitive);
10115
+ } else {
10116
+ components.push(primitive);
10117
+ }
10118
+ };
10119
+ for (const elm of circuitJson) {
10120
+ if (elm.type === "pcb_smtpad" && elm.shape === "rect") {
10121
+ components.push(
10122
+ new SmtPad({
10123
+ pcbX: elm.x,
10124
+ pcbY: elm.y,
10125
+ layer: elm.layer,
10126
+ shape: "rect",
10127
+ height: elm.height,
10128
+ width: elm.width,
10129
+ portHints: elm.port_hints,
10130
+ rectBorderRadius: elm.rect_border_radius
10131
+ })
10132
+ );
10133
+ } else if (elm.type === "pcb_smtpad" && elm.shape === "circle") {
10134
+ components.push(
10135
+ new SmtPad({
10136
+ pcbX: elm.x,
10137
+ pcbY: elm.y,
10138
+ layer: elm.layer,
10139
+ shape: "circle",
10140
+ radius: elm.radius,
10141
+ portHints: elm.port_hints
10142
+ })
10143
+ );
10144
+ } else if (elm.type === "pcb_smtpad" && elm.shape === "pill") {
10145
+ components.push(
10146
+ new SmtPad({
10147
+ shape: "pill",
10148
+ height: elm.height,
10149
+ width: elm.width,
10150
+ radius: elm.radius,
10151
+ portHints: elm.port_hints,
10152
+ pcbX: elm.x,
10153
+ pcbY: elm.y,
10154
+ layer: elm.layer
10155
+ })
10156
+ );
10157
+ } else if (elm.type === "pcb_smtpad" && elm.shape === "polygon") {
10158
+ components.push(
10159
+ new SmtPad({
10160
+ shape: "polygon",
10161
+ points: elm.points,
10162
+ portHints: elm.port_hints,
10163
+ layer: elm.layer
10164
+ })
10165
+ );
10166
+ } else if (elm.type === "pcb_silkscreen_path") {
10167
+ components.push(
10168
+ new SilkscreenPath({
10169
+ layer: elm.layer,
10170
+ route: elm.route,
10171
+ strokeWidth: elm.stroke_width
10172
+ })
10173
+ );
10174
+ } else if (elm.type === "pcb_copper_text") {
10175
+ components.push(
10176
+ new CopperText({
10177
+ text: elm.text,
10178
+ pcbX: elm.anchor_position.x,
10179
+ pcbY: elm.anchor_position.y,
10180
+ pcbRotation: elm.ccw_rotation,
10181
+ anchorAlignment: elm.anchor_alignment,
10182
+ fontSize: elm.font_size,
10183
+ layer: elm.layer,
10184
+ mirrored: elm.is_mirrored,
10185
+ knockout: elm.is_knockout
10186
+ })
10187
+ );
10188
+ } else if (elm.type === "pcb_plated_hole") {
10189
+ if (elm.shape === "circle") {
10190
+ components.push(
10191
+ new PlatedHole({
10192
+ pcbX: elm.x,
10193
+ pcbY: elm.y,
10194
+ shape: "circle",
10195
+ holeDiameter: elm.hole_diameter,
10196
+ outerDiameter: elm.outer_diameter,
10197
+ portHints: elm.port_hints
10198
+ })
10199
+ );
10200
+ } else if (elm.shape === "circular_hole_with_rect_pad") {
10201
+ components.push(
10202
+ new PlatedHole({
10203
+ pcbX: elm.x,
10204
+ pcbY: elm.y,
10205
+ shape: "circular_hole_with_rect_pad",
10206
+ holeDiameter: elm.hole_diameter,
10207
+ rectPadHeight: elm.rect_pad_height,
10208
+ rectPadWidth: elm.rect_pad_width,
10209
+ portHints: elm.port_hints,
10210
+ rectBorderRadius: elm.rect_border_radius,
10211
+ holeOffsetX: elm.hole_offset_x,
10212
+ holeOffsetY: elm.hole_offset_y
10213
+ })
10214
+ );
10215
+ } else if (elm.shape === "pill" || elm.shape === "oval") {
10216
+ components.push(
10217
+ new PlatedHole({
10218
+ pcbX: elm.x,
10219
+ pcbY: elm.y,
10220
+ shape: elm.shape,
10221
+ holeWidth: elm.hole_width,
10222
+ holeHeight: elm.hole_height,
10223
+ outerWidth: elm.outer_width,
10224
+ outerHeight: elm.outer_height,
10225
+ portHints: elm.port_hints
10226
+ })
10227
+ );
10228
+ } else if (elm.shape === "pill_hole_with_rect_pad") {
10229
+ components.push(
10230
+ new PlatedHole({
10231
+ pcbX: elm.x,
10232
+ pcbY: elm.y,
10233
+ shape: "pill_hole_with_rect_pad",
10234
+ holeShape: "pill",
10235
+ padShape: "rect",
10236
+ holeWidth: elm.hole_width,
10237
+ holeHeight: elm.hole_height,
10238
+ rectPadWidth: elm.rect_pad_width,
10239
+ rectPadHeight: elm.rect_pad_height,
10240
+ rectBorderRadius: elm.rect_border_radius,
10241
+ portHints: elm.port_hints,
10242
+ holeOffsetX: elm.hole_offset_x,
10243
+ holeOffsetY: elm.hole_offset_y
10244
+ })
10245
+ );
10246
+ } else if (elm.shape === "rotated_pill_hole_with_rect_pad") {
10247
+ components.push(
10248
+ new PlatedHole({
10249
+ pcbX: elm.x,
10250
+ pcbY: elm.y,
10251
+ shape: "pill_hole_with_rect_pad",
10252
+ holeShape: "pill",
10253
+ padShape: "rect",
10254
+ holeWidth: elm.hole_width,
10255
+ holeHeight: elm.hole_height,
10256
+ rectPadWidth: elm.rect_pad_width,
10257
+ rectPadHeight: elm.rect_pad_height,
10258
+ rectBorderRadius: elm.rect_border_radius,
10259
+ portHints: elm.port_hints,
10260
+ holeOffsetX: elm.hole_offset_x,
10261
+ holeOffsetY: elm.hole_offset_y,
10262
+ pcbRotation: elm.hole_ccw_rotation
10263
+ })
10264
+ );
10265
+ } else if (elm.shape === "hole_with_polygon_pad") {
10266
+ components.push(
10267
+ new PlatedHole({
10268
+ pcbX: elm.x,
10269
+ pcbY: elm.y,
10270
+ shape: "hole_with_polygon_pad",
10271
+ holeShape: elm.hole_shape || "circle",
10272
+ holeDiameter: elm.hole_diameter,
10273
+ holeWidth: elm.hole_width,
10274
+ holeHeight: elm.hole_height,
10275
+ padOutline: elm.pad_outline || [],
10276
+ holeOffsetX: elm.hole_offset_x,
10277
+ holeOffsetY: elm.hole_offset_y,
10278
+ portHints: elm.port_hints
10279
+ })
10280
+ );
10205
10281
  }
10206
- }
10207
- }
10208
- _getGlobalPcbPositionBeforeLayout() {
10209
- const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive);
10210
- const parentComponent = this.parent;
10211
- if (parentComponent && !parentComponent.props.footprint) {
10212
- throw new Error(
10213
- `${parentComponent.componentName} "${parentComponent.props.name}" does not have a footprint. Add a footprint prop, e.g. <${parentComponent.componentName.toLowerCase()} footprint="..." />`
10282
+ } else if (elm.type === "pcb_keepout" && elm.shape === "circle") {
10283
+ components.push(
10284
+ new Keepout({
10285
+ pcbX: elm.center.x,
10286
+ pcbY: elm.center.y,
10287
+ shape: "circle",
10288
+ radius: elm.radius
10289
+ })
10214
10290
  );
10215
- }
10216
- if (!matchedPcbElm) {
10217
- throw new Error(
10218
- `Port ${this} has no matching PCB primitives. This often means the footprint's pads lack matching port hints.`
10291
+ } else if (elm.type === "pcb_keepout" && elm.shape === "rect") {
10292
+ components.push(
10293
+ new Keepout({
10294
+ pcbX: elm.center.x,
10295
+ pcbY: elm.center.y,
10296
+ shape: "rect",
10297
+ width: elm.width,
10298
+ height: elm.height
10299
+ })
10219
10300
  );
10220
- }
10221
- return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 };
10222
- }
10223
- _getPcbCircuitJsonBounds() {
10224
- if (!this.pcb_port_id) {
10225
- return super._getPcbCircuitJsonBounds();
10226
- }
10227
- const { db } = this.root;
10228
- const pcb_port = db.pcb_port.get(this.pcb_port_id);
10229
- return {
10230
- center: { x: pcb_port.x, y: pcb_port.y },
10231
- bounds: { left: 0, top: 0, right: 0, bottom: 0 },
10232
- width: 0,
10233
- height: 0
10234
- };
10235
- }
10236
- _getGlobalPcbPositionAfterLayout() {
10237
- return this._getPcbCircuitJsonBounds().center;
10238
- }
10239
- _getPortsInternallyConnectedToThisPort() {
10240
- const parent = this.parent;
10241
- if (!parent || !parent._getInternallyConnectedPins) return [];
10242
- const internallyConnectedPorts = parent._getInternallyConnectedPins();
10243
- for (const ports of internallyConnectedPorts) {
10244
- if (ports.some((port) => port === this)) {
10245
- return ports;
10301
+ } else if (elm.type === "pcb_hole" && elm.hole_shape === "circle") {
10302
+ components.push(
10303
+ new Hole({
10304
+ pcbX: elm.x,
10305
+ pcbY: elm.y,
10306
+ diameter: elm.hole_diameter
10307
+ })
10308
+ );
10309
+ } else if (elm.type === "pcb_hole" && elm.hole_shape === "rect") {
10310
+ components.push(
10311
+ new Hole({
10312
+ pcbX: elm.x,
10313
+ pcbY: elm.y,
10314
+ shape: "rect",
10315
+ width: elm.hole_width,
10316
+ height: elm.hole_height
10317
+ })
10318
+ );
10319
+ } else if (elm.type === "pcb_hole" && elm.hole_shape === "pill") {
10320
+ components.push(
10321
+ new Hole({
10322
+ pcbX: elm.x,
10323
+ pcbY: elm.y,
10324
+ shape: "pill",
10325
+ width: elm.hole_width,
10326
+ height: elm.hole_height
10327
+ })
10328
+ );
10329
+ } else if (elm.type === "pcb_hole" && elm.hole_shape === "oval") {
10330
+ components.push(
10331
+ new Hole({
10332
+ pcbX: elm.x,
10333
+ pcbY: elm.y,
10334
+ shape: "oval",
10335
+ width: elm.hole_width,
10336
+ height: elm.hole_height
10337
+ })
10338
+ );
10339
+ } else if (elm.type === "pcb_hole" && elm.hole_shape === "rotated_pill") {
10340
+ components.push(
10341
+ new Hole({
10342
+ pcbX: elm.x,
10343
+ pcbY: elm.y,
10344
+ shape: "pill",
10345
+ width: elm.hole_width,
10346
+ height: elm.hole_height,
10347
+ pcbRotation: elm.ccw_rotation
10348
+ })
10349
+ );
10350
+ } else if (elm.type === "pcb_cutout") {
10351
+ if (elm.shape === "rect") {
10352
+ components.push(
10353
+ new Cutout({
10354
+ pcbX: elm.center.x,
10355
+ pcbY: elm.center.y,
10356
+ shape: "rect",
10357
+ width: elm.width,
10358
+ height: elm.height
10359
+ })
10360
+ );
10361
+ } else if (elm.shape === "circle") {
10362
+ components.push(
10363
+ new Cutout({
10364
+ pcbX: elm.center.x,
10365
+ pcbY: elm.center.y,
10366
+ shape: "circle",
10367
+ radius: elm.radius
10368
+ })
10369
+ );
10370
+ } else if (elm.shape === "polygon") {
10371
+ components.push(
10372
+ new Cutout({
10373
+ shape: "polygon",
10374
+ points: elm.points
10375
+ })
10376
+ );
10246
10377
  }
10247
- }
10248
- return [];
10249
- }
10250
- /**
10251
- * Return true if this port has a schematic representation and can be rendered
10252
- * to the schematic.
10253
- *
10254
- * Sometimes things like mounting holes don't have a schematic representation
10255
- * and aren't rendered to the schematic.
10256
- *
10257
- * It's common for a schematic symbol to not have a representation for all of
10258
- * the pins on a footprint, e.g. a pushbutton has 4 pins but is typically
10259
- * represented by a two-pin symbol. In these cases, it's best to use
10260
- * internallyConnectedPorts or externallyConnectedPorts to ensure the things
10261
- * are rendered properly.
10262
- */
10263
- _hasSchematicPort() {
10264
- const { schX, schY } = this._parsedProps;
10265
- if (schX !== void 0 && schY !== void 0) {
10266
- return true;
10267
- }
10268
- const parentNormalComponent = this.getParentNormalComponent();
10269
- const symbol = parentNormalComponent?.getSchematicSymbol();
10270
- if (symbol) {
10271
- if (this.schematicSymbolPortDef) return true;
10272
- const portsInternallyConnectedToThisPort = this._getPortsInternallyConnectedToThisPort();
10273
- if (portsInternallyConnectedToThisPort.some((p) => p.schematicSymbolPortDef))
10274
- return true;
10275
- return false;
10276
- }
10277
- const parentBoxDim = parentNormalComponent?._getSchematicBoxDimensions();
10278
- if (parentBoxDim && this._parsedProps.pinNumber !== void 0) {
10279
- const localPortPosition = parentBoxDim.getPortPositionByPinNumber(
10280
- this._parsedProps.pinNumber
10378
+ } else if (elm.type === "pcb_silkscreen_text") {
10379
+ const ccwRotation = calculateCcwRotation(
10380
+ componentRotation,
10381
+ elm.ccw_rotation
10382
+ );
10383
+ if (footprinterString?.includes("pinrow") && elm.text.includes("PIN")) {
10384
+ components.push(
10385
+ createPinrowSilkscreenText({
10386
+ elm,
10387
+ pinLabels: pcbPinLabels ?? pinLabels ?? {},
10388
+ layer: elm.layer,
10389
+ readableRotation: ccwRotation,
10390
+ anchorAlignment: elm.anchor_alignment
10391
+ })
10392
+ );
10393
+ } else {
10394
+ const silkscreenText = new SilkscreenText({
10395
+ anchorAlignment: elm.anchor_alignment || "center",
10396
+ text: componentName || elm.text,
10397
+ pcbX: Number.isNaN(elm.anchor_position.x) ? 0 : elm.anchor_position.x,
10398
+ pcbY: elm.anchor_position.y,
10399
+ pcbRotation: ccwRotation ?? 0
10400
+ });
10401
+ silkscreenText._footprinterFontSize = elm.font_size + 0.2;
10402
+ components.push(silkscreenText);
10403
+ }
10404
+ } else if (elm.type === "pcb_trace") {
10405
+ components.push(
10406
+ new PcbTrace({
10407
+ route: elm.route
10408
+ })
10409
+ );
10410
+ } else if (elm.type === "pcb_via") {
10411
+ const layers = elm.layers ?? [];
10412
+ const pcbVia = new PcbVia({
10413
+ pcbX: elm.x,
10414
+ pcbY: elm.y,
10415
+ holeDiameter: elm.hole_diameter,
10416
+ outerDiameter: elm.outer_diameter,
10417
+ fromLayer: elm.from_layer ?? layers[0],
10418
+ toLayer: elm.to_layer ?? layers[layers.length - 1],
10419
+ layers,
10420
+ netIsAssignable: elm.net_is_assignable,
10421
+ isTented: elm.is_tented
10422
+ });
10423
+ pcbVia._importedPcbTraceId = elm.pcb_trace_id;
10424
+ components.push(pcbVia);
10425
+ } else if (elm.type === "pcb_silkscreen_rect") {
10426
+ components.push(
10427
+ new SilkscreenRect({
10428
+ pcbX: elm.center.x,
10429
+ pcbY: elm.center.y,
10430
+ width: elm.width,
10431
+ height: elm.height,
10432
+ layer: elm.layer,
10433
+ strokeWidth: elm.stroke_width,
10434
+ filled: elm.is_filled,
10435
+ cornerRadius: elm.corner_radius
10436
+ })
10437
+ );
10438
+ } else if (elm.type === "pcb_silkscreen_circle") {
10439
+ components.push(
10440
+ new SilkscreenCircle({
10441
+ pcbX: elm.center.x,
10442
+ pcbY: elm.center.y,
10443
+ radius: elm.radius,
10444
+ layer: elm.layer,
10445
+ strokeWidth: elm.stroke_width
10446
+ })
10447
+ );
10448
+ } else if (elm.type === "pcb_silkscreen_line") {
10449
+ components.push(
10450
+ new SilkscreenLine({
10451
+ x1: elm.x1,
10452
+ y1: elm.y1,
10453
+ x2: elm.x2,
10454
+ y2: elm.y2,
10455
+ layer: elm.layer,
10456
+ strokeWidth: elm.stroke_width
10457
+ })
10458
+ );
10459
+ } else if (elm.type === "pcb_fabrication_note_text") {
10460
+ components.push(
10461
+ new FabricationNoteText({
10462
+ pcbX: elm.anchor_position.x,
10463
+ pcbY: elm.anchor_position.y,
10464
+ text: elm.text,
10465
+ fontSize: elm.font_size,
10466
+ anchorAlignment: elm.anchor_alignment,
10467
+ color: elm.color,
10468
+ font: elm.font
10469
+ })
10281
10470
  );
10282
- if (localPortPosition) return true;
10283
- }
10284
- return false;
10285
- }
10286
- _getGlobalSchematicPositionBeforeLayout() {
10287
- const { schX, schY } = this._parsedProps;
10288
- if (schX !== void 0 && schY !== void 0) {
10289
- return { x: schX, y: schY };
10290
- }
10291
- const parentNormalComponent = this.getParentNormalComponent();
10292
- const symbol = parentNormalComponent?.getSchematicSymbol();
10293
- if (symbol) {
10294
- let schematicSymbolPortDef = this.schematicSymbolPortDef;
10295
- if (!schematicSymbolPortDef) {
10296
- schematicSymbolPortDef = this._getPortsInternallyConnectedToThisPort().find(
10297
- (p) => p.schematicSymbolPortDef
10298
- )?.schematicSymbolPortDef ?? null;
10299
- if (!schematicSymbolPortDef) {
10300
- throw new Error(
10301
- `Couldn't find schematicSymbolPortDef for port ${this.getString()}, searched internally connected ports and none had a schematicSymbolPortDef. Why are we trying to get the schematic position of this port?`
10302
- );
10303
- }
10304
- }
10305
- const transform = compose4(
10306
- parentNormalComponent.computeSchematicGlobalTransform(),
10307
- translate3(-symbol.center.x, -symbol.center.y)
10471
+ } else if (elm.type === "pcb_fabrication_note_path") {
10472
+ components.push(
10473
+ new FabricationNotePath({
10474
+ route: elm.route,
10475
+ strokeWidth: elm.stroke_width,
10476
+ color: elm.color,
10477
+ layer: elm.layer
10478
+ })
10308
10479
  );
10309
- return applyToPoint22(transform, schematicSymbolPortDef);
10310
- }
10311
- const parentBoxDim = parentNormalComponent?._getSchematicBoxDimensions();
10312
- if (parentBoxDim && this._parsedProps.pinNumber !== void 0) {
10313
- const localPortPosition = parentBoxDim.getPortPositionByPinNumber(
10314
- this._parsedProps.pinNumber
10480
+ } else if (elm.type === "pcb_fabrication_note_rect") {
10481
+ components.push(
10482
+ new FabricationNoteRect({
10483
+ pcbX: elm.center.x,
10484
+ pcbY: elm.center.y,
10485
+ width: elm.width,
10486
+ height: elm.height,
10487
+ strokeWidth: elm.stroke_width,
10488
+ isFilled: elm.is_filled,
10489
+ color: elm.color,
10490
+ layer: elm.layer,
10491
+ cornerRadius: elm.corner_radius,
10492
+ hasStroke: elm.has_stroke,
10493
+ isStrokeDashed: elm.is_stroke_dashed
10494
+ })
10315
10495
  );
10316
- if (!localPortPosition) {
10317
- throw new Error(
10318
- `Couldn't find position for schematic_port for port ${this.getString()} inside of the schematic box`
10319
- );
10320
- }
10321
- return applyToPoint22(
10322
- parentNormalComponent.computeSchematicGlobalTransform(),
10323
- localPortPosition
10496
+ } else if (elm.type === "pcb_note_text") {
10497
+ components.push(
10498
+ new PcbNoteText({
10499
+ pcbX: elm.anchor_position.x,
10500
+ pcbY: elm.anchor_position.y,
10501
+ text: elm.text ?? "",
10502
+ fontSize: elm.font_size,
10503
+ anchorAlignment: elm.anchor_alignment,
10504
+ color: elm.color,
10505
+ font: elm.font
10506
+ })
10324
10507
  );
10325
- }
10326
- throw new Error(
10327
- `Couldn't find position for schematic_port for port ${this.getString()}`
10328
- );
10329
- }
10330
- _getGlobalSchematicPositionAfterLayout() {
10331
- const { db } = this.root;
10332
- if (!this.schematic_port_id) {
10333
- throw new Error(
10334
- `Can't get schematic port position after layout for "${this.getString()}", no schematic_port_id`
10508
+ } else if (elm.type === "pcb_note_rect") {
10509
+ components.push(
10510
+ new PcbNoteRect({
10511
+ pcbX: elm.center.x,
10512
+ pcbY: elm.center.y,
10513
+ width: elm.width,
10514
+ height: elm.height,
10515
+ strokeWidth: elm.stroke_width,
10516
+ isFilled: elm.is_filled,
10517
+ color: elm.color,
10518
+ cornerRadius: elm.corner_radius,
10519
+ hasStroke: elm.has_stroke,
10520
+ isStrokeDashed: elm.is_stroke_dashed
10521
+ })
10335
10522
  );
10336
- }
10337
- const schematic_port = db.schematic_port.get(this.schematic_port_id);
10338
- if (!schematic_port)
10339
- throw new Error(
10340
- `Schematic port not found when trying to get post-layout position: ${this.schematic_port_id}`
10523
+ } else if (elm.type === "pcb_note_path") {
10524
+ components.push(
10525
+ new PcbNotePath({
10526
+ route: elm.route,
10527
+ strokeWidth: elm.stroke_width,
10528
+ color: elm.color
10529
+ })
10341
10530
  );
10342
- return schematic_port.center;
10343
- }
10344
- /**
10345
- * Smtpads and platedholes call this method to register themselves as a match
10346
- * for this port. All the matching is done by primitives other than the Port,
10347
- * but everyone registers themselves as a match with their Port.
10348
- */
10349
- registerMatch(component) {
10350
- this.matchedComponents.push(component);
10351
- }
10352
- getNameAndAliases() {
10353
- const { _parsedProps: props } = this;
10354
- return Array.from(
10355
- /* @__PURE__ */ new Set([
10356
- ...props.name ? [props.name] : [],
10357
- ...props.aliases ?? [],
10358
- ...typeof props.pinNumber === "number" ? [`pin${props.pinNumber}`, props.pinNumber.toString()] : [],
10359
- ...this.externallyAddedAliases ?? []
10360
- ])
10361
- );
10362
- }
10363
- _getMatchingPinAttributes() {
10364
- const parentProps = this.parent?._parsedProps;
10365
- const pinAttributes = parentProps?.pinAttributes;
10366
- const noConnect = parentProps?.noConnect;
10367
- const matches = [];
10368
- for (const alias of this.getNameAndAliases()) {
10369
- if (pinAttributes) {
10370
- const attributes = pinAttributes[alias];
10371
- if (attributes) {
10372
- matches.push(attributes);
10373
- }
10374
- }
10375
- if (noConnect?.includes(alias)) {
10376
- matches.push({ doNotConnect: true });
10377
- }
10378
- }
10379
- if (matches.length === 0) {
10380
- return [];
10381
- }
10382
- return matches;
10383
- }
10384
- _shouldIncludeInBoardPinout() {
10385
- return this._getMatchingPinAttributes().some(
10386
- (attributes) => attributes.includeInBoardPinout === true
10387
- );
10388
- }
10389
- isMatchingPort(port) {
10390
- return this.isMatchingAnyOf(port.getNameAndAliases());
10391
- }
10392
- getPortSelector() {
10393
- const parentComponent = this.getParentNormalComponent() ?? this.parent;
10394
- return `.${parentComponent?.props.name} > port.${this.props.name}`;
10395
- }
10396
- getAvailablePcbLayers() {
10397
- const { layer, layers } = this._parsedProps;
10398
- if (layers) return layers;
10399
- if (layer) return [layer];
10400
- return Array.from(
10401
- new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
10402
- );
10403
- }
10404
- /**
10405
- * Return traces that are explicitly connected to this port (not via a net)
10406
- */
10407
- _getDirectlyConnectedTraces() {
10408
- const allSubcircuitTraces = this.getSubcircuit().selectAll(
10409
- "trace"
10410
- );
10411
- const connectedTraces = allSubcircuitTraces.filter((trace) => !trace._couldNotFindPort).filter((trace) => trace._isExplicitlyConnectedToPort(this));
10412
- return connectedTraces;
10413
- }
10414
- doInitialSourceRender() {
10415
- const { db } = this.root;
10416
- const { _parsedProps: props } = this;
10417
- const port_hints = this.getNameAndAliases();
10418
- const parentNormalComponent = this.getParentNormalComponent();
10419
- const parentWithSourceId = this.parent?.source_component_id ? this.parent : parentNormalComponent;
10420
- const source_component_id = parentWithSourceId?.source_component_id ?? null;
10421
- const pinAttributes = this._getMatchingPinAttributes();
10422
- const portAttributesFromParent = {};
10423
- for (const attributes of pinAttributes) {
10424
- applyPinAttributesToSourcePort(portAttributesFromParent, attributes);
10425
- }
10426
- const source_port = db.source_port.insert({
10427
- name: props.name,
10428
- pin_number: props.pinNumber,
10429
- port_hints,
10430
- source_component_id,
10431
- subcircuit_id: this.getSubcircuit()?.subcircuit_id,
10432
- ...portAttributesFromParent
10433
- });
10434
- this.source_port_id = source_port.source_port_id;
10435
- }
10436
- doInitialSourceParentAttachment() {
10437
- const { db } = this.root;
10438
- const parentNormalComponent = this.getParentNormalComponent();
10439
- const parentWithSourceId = this.parent?.source_component_id ? this.parent : parentNormalComponent;
10440
- if (this.isGroupPort()) {
10441
- db.source_port.update(this.source_port_id, {
10442
- source_component_id: null,
10443
- subcircuit_id: this.getSubcircuit()?.subcircuit_id
10444
- });
10445
- return;
10446
- }
10447
- if (!parentWithSourceId?.source_component_id) {
10448
- throw new Error(
10449
- `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`
10531
+ } else if (elm.type === "pcb_note_line") {
10532
+ components.push(
10533
+ new PcbNoteLine({
10534
+ x1: elm.x1,
10535
+ y1: elm.y1,
10536
+ x2: elm.x2,
10537
+ y2: elm.y2,
10538
+ strokeWidth: elm.stroke_width,
10539
+ color: elm.color,
10540
+ isDashed: elm.is_dashed
10541
+ })
10542
+ );
10543
+ } else if (elm.type === "pcb_courtyard_rect") {
10544
+ components.push(
10545
+ new CourtyardRect({
10546
+ pcbX: elm.center.x,
10547
+ pcbY: elm.center.y,
10548
+ width: elm.width,
10549
+ height: elm.height,
10550
+ layer: elm.layer
10551
+ })
10552
+ );
10553
+ } else if (elm.type === "pcb_courtyard_circle") {
10554
+ components.push(
10555
+ new CourtyardCircle({
10556
+ pcbX: elm.center.x,
10557
+ pcbY: elm.center.y,
10558
+ radius: elm.radius,
10559
+ layer: elm.layer
10560
+ })
10450
10561
  );
10451
- }
10452
- db.source_port.update(this.source_port_id, {
10453
- source_component_id: parentWithSourceId.source_component_id,
10454
- subcircuit_id: this.getSubcircuit()?.subcircuit_id
10455
- });
10456
- this.source_component_id = parentWithSourceId.source_component_id;
10457
- }
10458
- doInitialPcbPortRender() {
10459
- if (this.root?.pcbDisabled) return;
10460
- const { db } = this.root;
10461
- const { matchedComponents } = this;
10462
- if (this.isGroupPort()) {
10463
- const connectedPorts = this._getConnectedPortsFromConnectsTo();
10464
- if (connectedPorts.length === 0) {
10465
- return;
10466
- }
10467
- const connectedPort = connectedPorts[0];
10468
- if (!connectedPort.pcb_port_id) {
10469
- return;
10470
- }
10471
- const connectedPcbPort = db.pcb_port.get(connectedPort.pcb_port_id);
10472
- const matchCenter2 = { x: connectedPcbPort.x, y: connectedPcbPort.y };
10473
- const subcircuit = this.getSubcircuit();
10474
- const pcb_port = db.pcb_port.insert({
10475
- pcb_component_id: void 0,
10476
- layers: connectedPort.getAvailablePcbLayers(),
10477
- subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
10478
- pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
10479
- ...matchCenter2,
10480
- source_port_id: this.source_port_id,
10481
- is_board_pinout: false
10482
- });
10483
- this.pcb_port_id = pcb_port.pcb_port_id;
10484
- return;
10485
- }
10486
- const parentNormalComponent = this.getParentNormalComponent();
10487
- const parentWithPcbComponentId = this.parent?.pcb_component_id ? this.parent : parentNormalComponent;
10488
- if (!parentWithPcbComponentId?.pcb_component_id) {
10489
- throw new Error(
10490
- `${this.getString()} has no parent pcb component, cannot render pcb_port (parent: ${this.parent?.getString()}, parentNormalComponent: ${parentNormalComponent?.getString()})`
10562
+ } else if (elm.type === "pcb_courtyard_outline") {
10563
+ components.push(
10564
+ new CourtyardOutline({
10565
+ outline: elm.outline,
10566
+ layer: elm.layer
10567
+ })
10491
10568
  );
10492
- }
10493
- const pcbMatches = matchedComponents.filter((c) => c.isPcbPrimitive);
10494
- if (pcbMatches.length === 0) return;
10495
- let matchCenter = null;
10496
- if (pcbMatches.length === 1) {
10497
- matchCenter = pcbMatches[0]._getPcbCircuitJsonBounds().center;
10498
- }
10499
- if (pcbMatches.length > 1) {
10500
- if (!areAllPcbPrimitivesOverlapping(pcbMatches)) {
10501
- const portName = this.props.name;
10502
- const componentName = this.getParentNormalComponent()?.props.name ?? "unknown";
10503
- const altAliases = this.getNameAndAliases().filter(
10504
- (h) => h !== portName
10505
- );
10506
- const altMsg = altAliases.length > 0 ? ` (consider using alternate aliases: ${altAliases.join(", ")})` : "";
10507
- db.source_ambiguous_port_reference.insert({
10508
- error_type: "source_ambiguous_port_reference",
10509
- message: `${componentName}.${portName} is ambiguous: ${componentName}.${portName} references multiple non-overlapping pads: ${pcbMatches.map((c) => c.getString()).join(", ")}${altMsg}`,
10510
- source_port_id: this.source_port_id ?? void 0,
10511
- source_component_id: this.getParentNormalComponent()?.source_component_id ?? void 0
10512
- });
10513
- return;
10514
- }
10515
- matchCenter = getCenterOfPcbPrimitives(pcbMatches);
10516
- }
10517
- if (matchCenter) {
10518
- const subcircuit = this.getSubcircuit();
10519
- const isBoardPinout = this._shouldIncludeInBoardPinout();
10520
- const pcb_port = db.pcb_port.insert({
10521
- pcb_component_id: parentWithPcbComponentId.pcb_component_id,
10522
- layers: this.getAvailablePcbLayers(),
10523
- subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
10524
- pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
10525
- ...isBoardPinout ? { is_board_pinout: true } : {},
10526
- ...matchCenter,
10527
- source_port_id: this.source_port_id,
10528
- is_board_pinout: this._isBoardPinoutFromAttributes()
10529
- });
10530
- this.pcb_port_id = pcb_port.pcb_port_id;
10531
- } else {
10532
- const pcbMatch = pcbMatches[0];
10533
- throw new Error(
10534
- `${pcbMatch.getString()} does not have a center or _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`
10569
+ } else if (elm.type === "schematic_line") {
10570
+ addSchematicPrimitive(
10571
+ elm,
10572
+ new SchematicLine({
10573
+ x1: elm.x1,
10574
+ y1: elm.y1,
10575
+ x2: elm.x2,
10576
+ y2: elm.y2,
10577
+ strokeWidth: optional(elm.stroke_width),
10578
+ color: elm.color,
10579
+ isDashed: elm.is_dashed
10580
+ })
10535
10581
  );
10582
+ } else if (elm.type === "schematic_rect") {
10583
+ addSchematicPrimitive(
10584
+ elm,
10585
+ new SchematicRect({
10586
+ schX: elm.center.x,
10587
+ schY: elm.center.y,
10588
+ width: elm.width,
10589
+ height: elm.height,
10590
+ rotation: elm.rotation,
10591
+ strokeWidth: optional(elm.stroke_width),
10592
+ color: elm.color,
10593
+ isFilled: elm.is_filled,
10594
+ fillColor: elm.fill_color,
10595
+ isDashed: elm.is_dashed
10596
+ })
10597
+ );
10598
+ } else if (elm.type === "schematic_circle") {
10599
+ addSchematicPrimitive(
10600
+ elm,
10601
+ new SchematicCircle({
10602
+ center: elm.center,
10603
+ radius: elm.radius,
10604
+ strokeWidth: optional(elm.stroke_width),
10605
+ color: elm.color,
10606
+ isFilled: elm.is_filled,
10607
+ fillColor: elm.fill_color,
10608
+ isDashed: elm.is_dashed
10609
+ })
10610
+ );
10611
+ } else if (elm.type === "schematic_arc") {
10612
+ addSchematicPrimitive(
10613
+ elm,
10614
+ new SchematicArc({
10615
+ center: elm.center,
10616
+ radius: elm.radius,
10617
+ startAngleDegrees: elm.start_angle_degrees,
10618
+ endAngleDegrees: elm.end_angle_degrees,
10619
+ direction: elm.direction,
10620
+ strokeWidth: optional(elm.stroke_width),
10621
+ color: elm.color,
10622
+ isDashed: elm.is_dashed
10623
+ })
10624
+ );
10625
+ } else if (elm.type === "schematic_text") {
10626
+ addSchematicPrimitive(
10627
+ elm,
10628
+ new SchematicText({
10629
+ schX: elm.position.x,
10630
+ schY: elm.position.y,
10631
+ text: elm.text,
10632
+ fontSize: elm.font_size,
10633
+ anchor: elm.anchor,
10634
+ color: elm.color,
10635
+ schRotation: elm.rotation
10636
+ })
10637
+ );
10638
+ } else if (elm.type === "schematic_path") {
10639
+ addSchematicPrimitive(
10640
+ elm,
10641
+ new SchematicPath({
10642
+ points: elm.points,
10643
+ strokeWidth: optional(elm.stroke_width),
10644
+ strokeColor: elm.stroke_color,
10645
+ dashLength: elm.dash_length,
10646
+ dashGap: elm.dash_gap,
10647
+ isFilled: elm.is_filled,
10648
+ fillColor: elm.fill_color
10649
+ })
10650
+ );
10651
+ } else if (elm.type === "schematic_port") {
10652
+ const schematicComponentId = elm.schematic_component_id;
10653
+ if (typeof schematicComponentId !== "string") continue;
10654
+ const schematicComponent = schematicComponentsByImportedId.get(schematicComponentId);
10655
+ const schematicSymbolId = schematicComponent?.schematic_symbol_id;
10656
+ const parentSymbol = typeof schematicSymbolId === "string" ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
10657
+ if (parentSymbol && schematicComponent?.is_box_with_pins === true && elm.center && elm.side_of_component) {
10658
+ const distance19 = elm.distance_from_component_edge;
10659
+ const facingDirection = getFacingDirectionFromSide(
10660
+ elm.side_of_component
10661
+ );
10662
+ if (!facingDirection) continue;
10663
+ const directionVector = getUnitVectorFromDirection2(facingDirection);
10664
+ const stemDirection = elm.facing_direction === facingDirection ? 1 : -1;
10665
+ const portCenter = {
10666
+ x: elm.center.x + directionVector.x * distance19 * stemDirection,
10667
+ y: elm.center.y + directionVector.y * distance19 * stemDirection
10668
+ };
10669
+ const sourcePort = sourcePortsByImportedId.get(elm.source_port_id);
10670
+ const aliases = Array.from(
10671
+ new Set(
10672
+ [
10673
+ elm.display_pin_label,
10674
+ sourcePort?.name,
10675
+ ...sourcePort?.port_hints ?? []
10676
+ ].filter(
10677
+ (alias) => typeof alias === "string" && alias.length > 0
10678
+ )
10679
+ )
10680
+ );
10681
+ const fallbackPortName = elm.pin_number ? `pin${elm.pin_number}` : elm.schematic_port_id;
10682
+ const portName = aliases[0] ?? elm.source_port_id ?? fallbackPortName;
10683
+ parentSymbol.add(
10684
+ new Port({
10685
+ pinNumber: elm.pin_number,
10686
+ name: portName,
10687
+ aliases,
10688
+ schX: elm.center.x,
10689
+ schY: elm.center.y,
10690
+ direction: facingDirection
10691
+ })
10692
+ );
10693
+ parentSymbol.add(
10694
+ new SchematicLine({
10695
+ x1: elm.center.x,
10696
+ y1: elm.center.y,
10697
+ x2: portCenter.x,
10698
+ y2: portCenter.y,
10699
+ strokeWidth: typeof schematicSymbolId === "string" ? optional(
10700
+ schematicStrokeWidthBySymbolId.get(schematicSymbolId)
10701
+ ) : void 0,
10702
+ isDashed: false
10703
+ })
10704
+ );
10705
+ }
10536
10706
  }
10537
10707
  }
10538
- updatePcbPortRender() {
10539
- if (this.root?.pcbDisabled) return;
10540
- const { db } = this.root;
10541
- if (this.pcb_port_id) return;
10542
- if (this.isGroupPort()) {
10543
- const connectedPorts = this._getConnectedPortsFromConnectsTo();
10544
- if (connectedPorts.length === 0) return;
10545
- const connectedPort = connectedPorts[0];
10546
- if (!connectedPort.pcb_port_id) return;
10547
- const connectedPcbPort = db.pcb_port.get(connectedPort.pcb_port_id);
10548
- const matchCenter2 = { x: connectedPcbPort.x, y: connectedPcbPort.y };
10549
- const subcircuit2 = this.getSubcircuit();
10550
- const pcb_port2 = db.pcb_port.insert({
10551
- pcb_component_id: void 0,
10552
- layers: connectedPort.getAvailablePcbLayers(),
10553
- subcircuit_id: subcircuit2?.subcircuit_id ?? void 0,
10554
- pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
10555
- ...matchCenter2,
10556
- source_port_id: this.source_port_id,
10557
- is_board_pinout: false
10558
- });
10559
- this.pcb_port_id = pcb_port2.pcb_port_id;
10560
- return;
10561
- }
10562
- const pcbMatches = this.matchedComponents.filter((c) => c.isPcbPrimitive);
10563
- if (pcbMatches.length === 0) return;
10564
- let matchCenter = null;
10565
- if (pcbMatches.length === 1) {
10566
- matchCenter = pcbMatches[0]._getPcbCircuitJsonBounds().center;
10567
- }
10568
- if (pcbMatches.length > 1) {
10569
- try {
10570
- if (areAllPcbPrimitivesOverlapping(pcbMatches)) {
10571
- matchCenter = getCenterOfPcbPrimitives(pcbMatches);
10572
- }
10573
- } catch {
10574
- }
10575
- }
10576
- if (!matchCenter) return;
10577
- const parentNormalComponent = this.getParentNormalComponent();
10578
- const parentWithPcbComponentId = this.parent?.pcb_component_id ? this.parent : parentNormalComponent;
10579
- const subcircuit = this.getSubcircuit();
10580
- const isBoardPinout = this._shouldIncludeInBoardPinout();
10581
- const pcb_port = db.pcb_port.insert({
10582
- pcb_component_id: parentWithPcbComponentId?.pcb_component_id,
10583
- layers: this.getAvailablePcbLayers(),
10584
- subcircuit_id: subcircuit?.subcircuit_id ?? void 0,
10585
- pcb_group_id: this.getGroup()?.pcb_group_id ?? void 0,
10586
- ...isBoardPinout ? { is_board_pinout: true } : {},
10587
- ...matchCenter,
10588
- source_port_id: this.source_port_id,
10589
- is_board_pinout: this._isBoardPinoutFromAttributes()
10590
- });
10591
- this.pcb_port_id = pcb_port.pcb_port_id;
10592
- }
10593
- /**
10594
- * Get the best display label for this port based on port_hints
10595
- * Filters out generic patterns and applies showPinAliases logic
10596
- */
10597
- _getBestDisplayPinLabel() {
10598
- const { db } = this.root;
10599
- const sourcePort = db.source_port.get(this.source_port_id);
10600
- const labelHints = [];
10601
- for (const portHint of sourcePort?.port_hints ?? []) {
10602
- if (portHint.match(/^(pin)?\d+$/)) continue;
10603
- if (portHint.match(/^(left|right)/) && !sourcePort?.name.match(/^(left|right)/))
10604
- continue;
10605
- labelHints.push(portHint);
10606
- }
10607
- const parentNormalComponent = this.getParentNormalComponent();
10608
- const showPinAliases = parentNormalComponent?.props?.showPinAliases;
10609
- if (showPinAliases && labelHints.length > 0) {
10610
- return labelHints.join("/");
10611
- } else if (labelHints.length > 0) {
10612
- return labelHints[0];
10613
- }
10614
- return void 0;
10615
- }
10616
- doInitialSchematicPortRender() {
10617
- const collapsedAncestor = this.getCollapsedSchematicBoxAncestor();
10618
- if (collapsedAncestor && this.parent !== collapsedAncestor) return;
10619
- const { db } = this.root;
10620
- const { _parsedProps: props } = this;
10621
- const { schX, schY } = props;
10622
- const container = schX !== void 0 && schY !== void 0 ? this.getParentNormalComponent() : this.getPrimitiveContainer();
10623
- if (!container) return;
10624
- if (!this._hasSchematicPort()) return;
10625
- const containerCenter = container._getGlobalSchematicPositionBeforeLayout();
10626
- const portCenter = this._getGlobalSchematicPositionBeforeLayout();
10627
- let localPortInfo = null;
10628
- const containerDims = container._getSchematicBoxDimensions();
10629
- if (containerDims && props.pinNumber !== void 0) {
10630
- localPortInfo = containerDims.getPortPositionByPinNumber(props.pinNumber);
10631
- }
10632
- if (this.getSubcircuit().props._schDebugObjectsEnabled) {
10633
- db.schematic_debug_object.insert({
10634
- shape: "rect",
10635
- center: portCenter,
10636
- size: {
10637
- width: 0.1,
10638
- height: 0.1
10639
- },
10640
- label: "obstacle"
10641
- });
10642
- }
10643
- if (!localPortInfo?.side) {
10644
- this.facingDirection = getRelativeDirection(containerCenter, portCenter);
10645
- } else {
10646
- this.facingDirection = {
10647
- left: "left",
10648
- right: "right",
10649
- top: "up",
10650
- bottom: "down"
10651
- }[localPortInfo.side];
10652
- }
10653
- const bestDisplayPinLabel = this._getBestDisplayPinLabel();
10654
- const parentNormalComponent = this.getParentNormalComponent();
10655
- const sideOfComponent = localPortInfo?.side ?? (props.direction === "up" ? "top" : props.direction === "down" ? "bottom" : props.direction);
10656
- const schematicPortInsertProps = {
10657
- type: "schematic_port",
10658
- schematic_component_id: parentNormalComponent?.schematic_component_id,
10659
- center: portCenter,
10660
- source_port_id: this.source_port_id,
10661
- facing_direction: this.facingDirection,
10662
- distance_from_component_edge: props.schStemLength ?? 0.4,
10663
- side_of_component: sideOfComponent,
10664
- pin_number: props.pinNumber,
10665
- true_ccw_index: localPortInfo?.trueIndex,
10666
- display_pin_label: bestDisplayPinLabel,
10667
- is_connected: false
10708
+ return components;
10709
+ };
10710
+
10711
+ // lib/utils/filterPinLabels.ts
10712
+ import { chipProps } from "@tscircuit/props";
10713
+ function filterPinLabels(pinLabels) {
10714
+ if (!pinLabels)
10715
+ return {
10716
+ validPinLabels: pinLabels,
10717
+ invalidPinLabelsMessages: []
10668
10718
  };
10669
- for (const attributes of this._getMatchingPinAttributes()) {
10670
- if (attributes.requiresPower) {
10671
- schematicPortInsertProps.has_input_arrow = true;
10672
- }
10673
- if (attributes.providesPower) {
10674
- schematicPortInsertProps.has_output_arrow = true;
10719
+ const validPinLabels = {};
10720
+ const invalidPinLabelsMessages = [];
10721
+ for (const [pin, labelOrLabels] of Object.entries(pinLabels)) {
10722
+ const labels = Array.isArray(labelOrLabels) ? labelOrLabels.slice() : [labelOrLabels];
10723
+ const validLabels = [];
10724
+ for (const label of labels) {
10725
+ if (isValidPinLabel(pin, label)) {
10726
+ validLabels.push(label);
10727
+ } else {
10728
+ invalidPinLabelsMessages.push(
10729
+ `Invalid pin label: ${pin} = '${label}' - excluding from component. Pin labels can only contain letters, numbers and underscores.`
10730
+ );
10675
10731
  }
10676
10732
  }
10677
- const schematic_port = db.schematic_port.insert(schematicPortInsertProps);
10678
- this.schematic_port_id = schematic_port.schematic_port_id;
10679
- if (props.schStemLength !== void 0 && props.schStemLength !== 0) {
10680
- const { schStemLength, direction } = props;
10681
- let x2 = portCenter.x;
10682
- let y2 = portCenter.y;
10683
- if (direction === "right") x2 -= schStemLength;
10684
- else if (direction === "left") x2 += schStemLength;
10685
- else if (direction === "up") y2 -= schStemLength;
10686
- else if (direction === "down") y2 += schStemLength;
10687
- const stemLine = db.schematic_line.insert({
10688
- schematic_component_id: parentNormalComponent?.schematic_component_id,
10689
- x1: portCenter.x,
10690
- y1: portCenter.y,
10691
- x2,
10692
- y2,
10693
- stroke_width: 0.02,
10694
- color: SCHEMATIC_COMPONENT_OUTLINE_COLOR,
10695
- is_dashed: false
10696
- });
10697
- this.schematic_stem_line_id = stemLine.schematic_line_id;
10733
+ if (validLabels.length > 0) {
10734
+ validPinLabels[pin] = Array.isArray(labelOrLabels) ? validLabels : validLabels[0];
10698
10735
  }
10699
10736
  }
10700
- doInitialSchematicSymbolResize() {
10701
- if (this.root?.schematicDisabled) return;
10702
- if (!this.schematic_port_id) return;
10703
- const symbol = this._getSymbolAncestor();
10704
- const transform = symbol?.getUserCoordinateToResizedSymbolTransform();
10705
- if (!transform) return;
10706
- const { db } = this.root;
10707
- const schPort = db.schematic_port.get(this.schematic_port_id);
10708
- if (schPort) {
10709
- const newCenter = applyToPoint22(transform, schPort.center);
10710
- db.schematic_port.update(this.schematic_port_id, {
10711
- center: newCenter
10712
- });
10713
- if (this.schematic_stem_line_id) {
10714
- const line = db.schematic_line.get(this.schematic_stem_line_id);
10715
- if (line) {
10716
- const p1 = applyToPoint22(transform, { x: line.x1, y: line.y1 });
10717
- const p2 = applyToPoint22(transform, { x: line.x2, y: line.y2 });
10718
- db.schematic_line.update(this.schematic_stem_line_id, {
10719
- x1: p1.x,
10720
- y1: p1.y,
10721
- x2: p2.x,
10722
- y2: p2.y
10723
- });
10724
- const scaledStemLength = Math.sqrt(
10725
- (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2
10726
- );
10727
- db.schematic_port.update(this.schematic_port_id, {
10728
- distance_from_component_edge: scaledStemLength
10729
- });
10730
- }
10737
+ return {
10738
+ validPinLabels: Object.keys(validPinLabels).length > 0 ? validPinLabels : void 0,
10739
+ invalidPinLabelsMessages
10740
+ };
10741
+ }
10742
+ function isValidPinLabel(pin, label) {
10743
+ try {
10744
+ const testProps = {
10745
+ name: "test",
10746
+ footprint: "test",
10747
+ pinLabels: { [pin]: label }
10748
+ };
10749
+ const result = chipProps.safeParse(testProps);
10750
+ return result.success;
10751
+ } catch (error) {
10752
+ return false;
10753
+ }
10754
+ }
10755
+
10756
+ // lib/utils/get-bounds-of-pcb-components.ts
10757
+ var NON_PHYSICAL_PCB_PRIMITIVE_PREFIXES = [
10758
+ "Silkscreen",
10759
+ "PcbNote",
10760
+ "Courtyard",
10761
+ "FabricationNote"
10762
+ ];
10763
+ function getBoundsOfPcbComponents(components) {
10764
+ let minX = Infinity;
10765
+ let minY = Infinity;
10766
+ let maxX = -Infinity;
10767
+ let maxY = -Infinity;
10768
+ let hasValidComponents = false;
10769
+ for (const child of components) {
10770
+ if (child.isPcbPrimitive && !NON_PHYSICAL_PCB_PRIMITIVE_PREFIXES.some(
10771
+ (prefix) => child.componentName.startsWith(prefix)
10772
+ )) {
10773
+ const { x, y } = child._getGlobalPcbPositionBeforeLayout();
10774
+ const { width: width2, height: height2 } = child.getPcbSize();
10775
+ minX = Math.min(minX, x - width2 / 2);
10776
+ minY = Math.min(minY, y - height2 / 2);
10777
+ maxX = Math.max(maxX, x + width2 / 2);
10778
+ maxY = Math.max(maxY, y + height2 / 2);
10779
+ hasValidComponents = true;
10780
+ } else if (child.children.length > 0) {
10781
+ const childBounds = getBoundsOfPcbComponents(child.children);
10782
+ if (childBounds.width > 0 || childBounds.height > 0) {
10783
+ minX = Math.min(minX, childBounds.minX);
10784
+ minY = Math.min(minY, childBounds.minY);
10785
+ maxX = Math.max(maxX, childBounds.maxX);
10786
+ maxY = Math.max(maxY, childBounds.maxY);
10787
+ hasValidComponents = true;
10731
10788
  }
10732
10789
  }
10733
10790
  }
10734
- _getSubcircuitConnectivityKey() {
10735
- return this.root?.db.source_port.get(this.source_port_id)?.subcircuit_connectivity_map_key;
10736
- }
10737
- _setPositionFromLayout(newCenter) {
10738
- const { db } = this.root;
10739
- if (!this.pcb_port_id) return;
10740
- db.pcb_port.update(this.pcb_port_id, {
10741
- x: newCenter.x,
10742
- y: newCenter.y
10743
- });
10744
- }
10745
- _hasMatchedPcbPrimitive() {
10746
- return this.matchedComponents.some((c) => c.isPcbPrimitive);
10747
- }
10748
- /**
10749
- * Return the text that should be used for the net label for this port if a
10750
- * trace can't be drawn. This net label text usually doesn't appear at this
10751
- * port, but appears at the port it connects to.
10752
- */
10753
- _getNetLabelText() {
10754
- return `${this.parent?.props.name}_${this.props.name}`;
10791
+ if (!hasValidComponents) {
10792
+ return {
10793
+ minX: 0,
10794
+ minY: 0,
10795
+ maxX: 0,
10796
+ maxY: 0,
10797
+ width: 0,
10798
+ height: 0
10799
+ };
10755
10800
  }
10756
- };
10801
+ let width = maxX - minX;
10802
+ let height = maxY - minY;
10803
+ if (width < 0) width = 0;
10804
+ if (height < 0) height = 0;
10805
+ return {
10806
+ minX,
10807
+ minY,
10808
+ maxX,
10809
+ maxY,
10810
+ width,
10811
+ height
10812
+ };
10813
+ }
10757
10814
 
10758
10815
  // lib/utils/getPortFromHints.ts
10759
10816
  var getPinNumberFromLabels = (labels) => {
@@ -23429,7 +23486,7 @@ import { identity as identity5 } from "transformation-matrix";
23429
23486
  var package_default = {
23430
23487
  name: "@tscircuit/core",
23431
23488
  type: "module",
23432
- version: "0.0.1304",
23489
+ version: "0.0.1306",
23433
23490
  types: "dist/index.d.ts",
23434
23491
  main: "dist/index.js",
23435
23492
  module: "dist/index.js",