@tscircuit/core 0.0.1306 → 0.0.1308

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