circuit-to-svg 0.0.311 → 0.0.313

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -72,6 +72,16 @@ interface Options$4 {
72
72
  showSolderMask?: boolean;
73
73
  grid?: PcbGridOptions;
74
74
  showAnchorOffsets?: boolean;
75
+ viewport?: {
76
+ minX: number;
77
+ minY: number;
78
+ maxX: number;
79
+ maxY: number;
80
+ };
81
+ viewportTarget?: {
82
+ pcb_panel_id?: string;
83
+ pcb_board_id?: string;
84
+ };
75
85
  }
76
86
  interface PcbContext {
77
87
  transform: Matrix;
package/dist/index.js CHANGED
@@ -3877,10 +3877,10 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
3877
3877
  maskPoints = points.map(([px, py]) => {
3878
3878
  const dx = px - centroidX;
3879
3879
  const dy = py - centroidY;
3880
- const distance5 = Math.sqrt(dx * dx + dy * dy);
3881
- if (distance5 === 0) return [px, py];
3882
- const normalizedDx = dx / distance5;
3883
- const normalizedDy = dy / distance5;
3880
+ const distance6 = Math.sqrt(dx * dx + dy * dy);
3881
+ if (distance6 === 0) return [px, py];
3882
+ const normalizedDx = dx / distance6;
3883
+ const normalizedDy = dy / distance6;
3884
3884
  return [
3885
3885
  px + normalizedDx * soldermaskMargin,
3886
3886
  py + normalizedDy * soldermaskMargin
@@ -4086,11 +4086,11 @@ function createAnchorOffsetIndicators(params) {
4086
4086
  function getTrimmedConnectorLine(x1, y1, x2, y2) {
4087
4087
  const dx = x2 - x1;
4088
4088
  const dy = y2 - y1;
4089
- const distance5 = Math.hypot(dx, dy);
4089
+ const distance6 = Math.hypot(dx, dy);
4090
4090
  const totalTrim = CONNECTOR_GROUP_GAP_PX + CONNECTOR_COMPONENT_GAP_PX;
4091
- if (!(distance5 > totalTrim)) return { x1, y1, x2, y2 };
4092
- const ux = dx / distance5;
4093
- const uy = dy / distance5;
4091
+ if (!(distance6 > totalTrim)) return { x1, y1, x2, y2 };
4092
+ const ux = dx / distance6;
4093
+ const uy = dy / distance6;
4094
4094
  return {
4095
4095
  x1: x1 + ux * CONNECTOR_GROUP_GAP_PX,
4096
4096
  y1: y1 + uy * CONNECTOR_GROUP_GAP_PX,
@@ -5047,9 +5047,9 @@ var findNearestPointInNet = (sourcePoint, netId, connectivity, circuitJson) => {
5047
5047
  if (pos) {
5048
5048
  const dx = sourcePoint.x - pos.x;
5049
5049
  const dy = sourcePoint.y - pos.y;
5050
- const distance5 = Math.sqrt(dx * dx + dy * dy);
5051
- if (distance5 > 0 && distance5 < minDistance) {
5052
- minDistance = distance5;
5050
+ const distance6 = Math.sqrt(dx * dx + dy * dy);
5051
+ if (distance6 > 0 && distance6 < minDistance) {
5052
+ minDistance = distance6;
5053
5053
  nearestPoint = pos;
5054
5054
  }
5055
5055
  }
@@ -5270,12 +5270,86 @@ import {
5270
5270
  translate as translate5,
5271
5271
  toString as matrixToString8
5272
5272
  } from "transformation-matrix";
5273
+ var KEEPOUT_PATTERN_ID = "pcb-keepout-pattern";
5274
+ var KEEPOUT_PATTERN_SIZE = 20;
5275
+ var KEEPOUT_LINE_SPACING = 5;
5276
+ var KEEPOUT_BACKGROUND_COLOR = "rgba(255, 107, 107, 0.2)";
5277
+ function createKeepoutPatternLines(keepoutColor) {
5278
+ const patternLines = [];
5279
+ for (let i = -KEEPOUT_PATTERN_SIZE; i <= KEEPOUT_PATTERN_SIZE; i += KEEPOUT_LINE_SPACING) {
5280
+ patternLines.push({
5281
+ name: "line",
5282
+ type: "element",
5283
+ value: "",
5284
+ attributes: {
5285
+ x1: i.toString(),
5286
+ y1: "0",
5287
+ x2: (i + KEEPOUT_PATTERN_SIZE).toString(),
5288
+ y2: KEEPOUT_PATTERN_SIZE.toString(),
5289
+ stroke: keepoutColor,
5290
+ "stroke-width": "1"
5291
+ },
5292
+ children: []
5293
+ });
5294
+ }
5295
+ return patternLines;
5296
+ }
5297
+ function createKeepoutPatternDefs(keepoutColor) {
5298
+ return {
5299
+ name: "defs",
5300
+ type: "element",
5301
+ value: "",
5302
+ attributes: {},
5303
+ children: [
5304
+ {
5305
+ name: "pattern",
5306
+ type: "element",
5307
+ value: "",
5308
+ attributes: {
5309
+ id: KEEPOUT_PATTERN_ID,
5310
+ width: KEEPOUT_PATTERN_SIZE.toString(),
5311
+ height: KEEPOUT_PATTERN_SIZE.toString(),
5312
+ patternUnits: "userSpaceOnUse"
5313
+ },
5314
+ children: createKeepoutPatternLines(keepoutColor)
5315
+ }
5316
+ ]
5317
+ };
5318
+ }
5319
+ function createKeepoutBaseAttributes(keepoutId, layer, shapeClass, description) {
5320
+ const attributes = {
5321
+ class: `pcb-keepout ${shapeClass} pcb-keepout-background`,
5322
+ "data-type": "pcb_keepout",
5323
+ "data-pcb-layer": layer,
5324
+ "data-pcb-keepout-id": keepoutId,
5325
+ stroke: "none"
5326
+ };
5327
+ if (description) {
5328
+ attributes["data-description"] = description;
5329
+ }
5330
+ return attributes;
5331
+ }
5332
+ function createKeepoutPatternAttributes(keepoutId, layer, shapeClass, description) {
5333
+ const attributes = {
5334
+ class: `pcb-keepout ${shapeClass} pcb-keepout-pattern`,
5335
+ fill: `url(#${KEEPOUT_PATTERN_ID})`,
5336
+ "data-type": "pcb_keepout",
5337
+ "data-pcb-layer": layer,
5338
+ "data-pcb-keepout-id": keepoutId,
5339
+ stroke: "none"
5340
+ };
5341
+ if (description) {
5342
+ attributes["data-description"] = description;
5343
+ }
5344
+ return attributes;
5345
+ }
5273
5346
  function createSvgObjectsFromPcbKeepout(keepout, ctx) {
5274
5347
  const { transform, layer: layerFilter, colorMap: colorMap2 } = ctx;
5275
5348
  if (layerFilter && !keepout.layers.includes(layerFilter)) {
5276
5349
  return [];
5277
5350
  }
5278
5351
  const svgObjects = [];
5352
+ const keepoutColor = colorMap2.keepout;
5279
5353
  for (const layer of keepout.layers) {
5280
5354
  if (layerFilter && layer !== layerFilter) {
5281
5355
  continue;
@@ -5288,32 +5362,50 @@ function createSvgObjectsFromPcbKeepout(keepout, ctx) {
5288
5362
  ]);
5289
5363
  const scaledWidth = rectKeepout.width * Math.abs(transform.a);
5290
5364
  const scaledHeight = rectKeepout.height * Math.abs(transform.d);
5291
- const transformedStrokeWidth = 0.1 * Math.abs(transform.a);
5292
- const attributes = {
5293
- class: "pcb-keepout pcb-keepout-rect",
5365
+ const baseTransform = matrixToString8(compose5(translate5(cx, cy)));
5366
+ const backgroundAttributes = {
5367
+ ...createKeepoutBaseAttributes(
5368
+ rectKeepout.pcb_keepout_id,
5369
+ layer,
5370
+ "pcb-keepout-rect",
5371
+ rectKeepout.description
5372
+ ),
5294
5373
  x: (-scaledWidth / 2).toString(),
5295
5374
  y: (-scaledHeight / 2).toString(),
5296
5375
  width: scaledWidth.toString(),
5297
5376
  height: scaledHeight.toString(),
5298
- fill: "none",
5299
- stroke: colorMap2.keepout ?? "#FF6B6B",
5300
- "stroke-width": transformedStrokeWidth.toString(),
5301
- "stroke-dasharray": `${transformedStrokeWidth * 3} ${transformedStrokeWidth * 2}`,
5302
- transform: matrixToString8(compose5(translate5(cx, cy))),
5303
- "data-type": "pcb_keepout",
5304
- "data-pcb-layer": layer,
5305
- "data-pcb-keepout-id": rectKeepout.pcb_keepout_id
5377
+ fill: KEEPOUT_BACKGROUND_COLOR,
5378
+ transform: baseTransform
5306
5379
  };
5307
- if (rectKeepout.description) {
5308
- attributes["data-description"] = rectKeepout.description;
5309
- }
5310
- svgObjects.push({
5311
- name: "rect",
5312
- type: "element",
5313
- attributes,
5314
- children: [],
5315
- value: ""
5316
- });
5380
+ const patternAttributes = {
5381
+ ...createKeepoutPatternAttributes(
5382
+ rectKeepout.pcb_keepout_id,
5383
+ layer,
5384
+ "pcb-keepout-rect",
5385
+ rectKeepout.description
5386
+ ),
5387
+ x: (-scaledWidth / 2).toString(),
5388
+ y: (-scaledHeight / 2).toString(),
5389
+ width: scaledWidth.toString(),
5390
+ height: scaledHeight.toString(),
5391
+ transform: baseTransform
5392
+ };
5393
+ svgObjects.push(
5394
+ {
5395
+ name: "rect",
5396
+ type: "element",
5397
+ attributes: backgroundAttributes,
5398
+ children: [],
5399
+ value: ""
5400
+ },
5401
+ {
5402
+ name: "rect",
5403
+ type: "element",
5404
+ attributes: patternAttributes,
5405
+ children: [],
5406
+ value: ""
5407
+ }
5408
+ );
5317
5409
  } else if (keepout.shape === "circle") {
5318
5410
  const circleKeepout = keepout;
5319
5411
  const [cx, cy] = applyToPoint33(transform, [
@@ -5321,30 +5413,45 @@ function createSvgObjectsFromPcbKeepout(keepout, ctx) {
5321
5413
  circleKeepout.center.y
5322
5414
  ]);
5323
5415
  const scaledRadius = circleKeepout.radius * Math.abs(transform.a);
5324
- const transformedStrokeWidth = 0.1 * Math.abs(transform.a);
5325
- const attributes = {
5326
- class: "pcb-keepout pcb-keepout-circle",
5416
+ const backgroundAttributes = {
5417
+ ...createKeepoutBaseAttributes(
5418
+ circleKeepout.pcb_keepout_id,
5419
+ layer,
5420
+ "pcb-keepout-circle",
5421
+ circleKeepout.description
5422
+ ),
5327
5423
  cx: cx.toString(),
5328
5424
  cy: cy.toString(),
5329
5425
  r: scaledRadius.toString(),
5330
- fill: "none",
5331
- stroke: colorMap2.keepout ?? "#FF6B6B",
5332
- "stroke-width": transformedStrokeWidth.toString(),
5333
- "stroke-dasharray": `${transformedStrokeWidth * 3} ${transformedStrokeWidth * 2}`,
5334
- "data-type": "pcb_keepout",
5335
- "data-pcb-layer": layer,
5336
- "data-pcb-keepout-id": circleKeepout.pcb_keepout_id
5426
+ fill: KEEPOUT_BACKGROUND_COLOR
5337
5427
  };
5338
- if (circleKeepout.description) {
5339
- attributes["data-description"] = circleKeepout.description;
5340
- }
5341
- svgObjects.push({
5342
- name: "circle",
5343
- type: "element",
5344
- attributes,
5345
- children: [],
5346
- value: ""
5347
- });
5428
+ const patternAttributes = {
5429
+ ...createKeepoutPatternAttributes(
5430
+ circleKeepout.pcb_keepout_id,
5431
+ layer,
5432
+ "pcb-keepout-circle",
5433
+ circleKeepout.description
5434
+ ),
5435
+ cx: cx.toString(),
5436
+ cy: cy.toString(),
5437
+ r: scaledRadius.toString()
5438
+ };
5439
+ svgObjects.push(
5440
+ {
5441
+ name: "circle",
5442
+ type: "element",
5443
+ attributes: backgroundAttributes,
5444
+ children: [],
5445
+ value: ""
5446
+ },
5447
+ {
5448
+ name: "circle",
5449
+ type: "element",
5450
+ attributes: patternAttributes,
5451
+ children: [],
5452
+ value: ""
5453
+ }
5454
+ );
5348
5455
  }
5349
5456
  }
5350
5457
  return svgObjects;
@@ -5927,7 +6034,7 @@ function getSoftwareUsedString(circuitJson) {
5927
6034
  var package_default = {
5928
6035
  name: "circuit-to-svg",
5929
6036
  type: "module",
5930
- version: "0.0.310",
6037
+ version: "0.0.312",
5931
6038
  description: "Convert Circuit JSON to SVG",
5932
6039
  main: "dist/index.js",
5933
6040
  files: [
@@ -6501,6 +6608,128 @@ function getPcbBoundsFromCircuitJson(circuitJson) {
6501
6608
  }
6502
6609
  }
6503
6610
 
6611
+ // lib/utils/get-viewport-bounds.ts
6612
+ import { distance as distance3 } from "circuit-json";
6613
+ var getViewportBounds = ({
6614
+ circuitJson,
6615
+ drawPaddingOutsideBoard,
6616
+ baseBounds,
6617
+ viewportOptions
6618
+ }) => {
6619
+ let padding = drawPaddingOutsideBoard ? 1 : 0;
6620
+ let boundsMinX = drawPaddingOutsideBoard || !baseBounds.hasBoardBounds ? baseBounds.minX : baseBounds.boardMinX;
6621
+ let boundsMinY = drawPaddingOutsideBoard || !baseBounds.hasBoardBounds ? baseBounds.minY : baseBounds.boardMinY;
6622
+ let boundsMaxX = drawPaddingOutsideBoard || !baseBounds.hasBoardBounds ? baseBounds.maxX : baseBounds.boardMaxX;
6623
+ let boundsMaxY = drawPaddingOutsideBoard || !baseBounds.hasBoardBounds ? baseBounds.maxY : baseBounds.boardMaxY;
6624
+ let hasPanelBounds = false;
6625
+ let panelMinX = Number.POSITIVE_INFINITY;
6626
+ let panelMinY = Number.POSITIVE_INFINITY;
6627
+ let panelMaxX = Number.NEGATIVE_INFINITY;
6628
+ let panelMaxY = Number.NEGATIVE_INFINITY;
6629
+ const panelBoundsById = /* @__PURE__ */ new Map();
6630
+ const boardBoundsById = /* @__PURE__ */ new Map();
6631
+ for (const elm of circuitJson) {
6632
+ if (elm.type === "pcb_panel") {
6633
+ const panel = elm;
6634
+ const panelBounds = rectBounds(panel.center, panel.width, panel.height);
6635
+ if (!panelBounds) continue;
6636
+ panelMinX = Math.min(panelMinX, panelBounds.minX);
6637
+ panelMinY = Math.min(panelMinY, panelBounds.minY);
6638
+ panelMaxX = Math.max(panelMaxX, panelBounds.maxX);
6639
+ panelMaxY = Math.max(panelMaxY, panelBounds.maxY);
6640
+ hasPanelBounds = true;
6641
+ if (panel.pcb_panel_id) {
6642
+ panelBoundsById.set(panel.pcb_panel_id, panelBounds);
6643
+ }
6644
+ } else if (elm.type === "pcb_board") {
6645
+ const board = elm;
6646
+ const outlineBounds = getOutlineBounds(board.outline);
6647
+ const boardBounds = outlineBounds ?? rectBounds(board.center, board.width, board.height);
6648
+ if (boardBounds && board.pcb_board_id) {
6649
+ boardBoundsById.set(board.pcb_board_id, boardBounds);
6650
+ }
6651
+ }
6652
+ }
6653
+ if (viewportOptions?.viewport) {
6654
+ const { minX, minY, maxX, maxY } = viewportOptions.viewport;
6655
+ boundsMinX = minX;
6656
+ boundsMinY = minY;
6657
+ boundsMaxX = maxX;
6658
+ boundsMaxY = maxY;
6659
+ padding = 0;
6660
+ } else if (viewportOptions?.viewportTarget?.pcb_panel_id) {
6661
+ const panelBounds = panelBoundsById.get(
6662
+ viewportOptions.viewportTarget.pcb_panel_id
6663
+ );
6664
+ if (!panelBounds) {
6665
+ throw new Error(
6666
+ `Viewport target panel '${viewportOptions.viewportTarget.pcb_panel_id}' not found`
6667
+ );
6668
+ }
6669
+ boundsMinX = panelBounds.minX;
6670
+ boundsMinY = panelBounds.minY;
6671
+ boundsMaxX = panelBounds.maxX;
6672
+ boundsMaxY = panelBounds.maxY;
6673
+ padding = 0;
6674
+ } else if (viewportOptions?.viewportTarget?.pcb_board_id) {
6675
+ const boardBounds = boardBoundsById.get(
6676
+ viewportOptions.viewportTarget.pcb_board_id
6677
+ );
6678
+ if (!boardBounds) {
6679
+ throw new Error(
6680
+ `Viewport target board '${viewportOptions.viewportTarget.pcb_board_id}' not found`
6681
+ );
6682
+ }
6683
+ boundsMinX = boardBounds.minX;
6684
+ boundsMinY = boardBounds.minY;
6685
+ boundsMaxX = boardBounds.maxX;
6686
+ boundsMaxY = boardBounds.maxY;
6687
+ padding = 0;
6688
+ } else if (hasPanelBounds) {
6689
+ boundsMinX = panelMinX;
6690
+ boundsMinY = panelMinY;
6691
+ boundsMaxX = panelMaxX;
6692
+ boundsMaxY = panelMaxY;
6693
+ }
6694
+ return { boundsMinX, boundsMinY, boundsMaxX, boundsMaxY, padding };
6695
+ };
6696
+ function rectBounds(center, width, height) {
6697
+ if (!center || width === void 0 || height === void 0) return;
6698
+ const cx = distance3.parse(center.x);
6699
+ const cy = distance3.parse(center.y);
6700
+ if (cx === void 0 || cy === void 0) return;
6701
+ const numericWidth = distance3.parse(width);
6702
+ const numericHeight = distance3.parse(height);
6703
+ if (numericWidth === void 0 || numericHeight === void 0) return;
6704
+ const halfW = numericWidth / 2;
6705
+ const halfH = numericHeight / 2;
6706
+ return {
6707
+ minX: cx - halfW,
6708
+ minY: cy - halfH,
6709
+ maxX: cx + halfW,
6710
+ maxY: cy + halfH
6711
+ };
6712
+ }
6713
+ function getOutlineBounds(outline) {
6714
+ if (!outline || outline.length < 3) return;
6715
+ let minX = Number.POSITIVE_INFINITY;
6716
+ let minY = Number.POSITIVE_INFINITY;
6717
+ let maxX = Number.NEGATIVE_INFINITY;
6718
+ let maxY = Number.NEGATIVE_INFINITY;
6719
+ for (const pt of outline) {
6720
+ const x = distance3.parse(pt.x);
6721
+ const y = distance3.parse(pt.y);
6722
+ if (x === void 0 || y === void 0) continue;
6723
+ minX = Math.min(minX, x);
6724
+ minY = Math.min(minY, y);
6725
+ maxX = Math.max(maxX, x);
6726
+ maxY = Math.max(maxY, y);
6727
+ }
6728
+ if (!Number.isFinite(minX) || !Number.isFinite(minY)) return;
6729
+ if (!Number.isFinite(maxX) || !Number.isFinite(maxY)) return;
6730
+ return { minX, minY, maxX, maxY };
6731
+ }
6732
+
6504
6733
  // lib/pcb/convert-circuit-json-to-pcb-svg.ts
6505
6734
  function convertCircuitJsonToPcbSvg(circuitJson, options) {
6506
6735
  const drawPaddingOutsideBoard = options?.drawPaddingOutsideBoard ?? true;
@@ -6555,20 +6784,34 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
6555
6784
  boardMaxY,
6556
6785
  hasBoardBounds
6557
6786
  } = getPcbBoundsFromCircuitJson(circuitJson);
6558
- const padding = drawPaddingOutsideBoard ? 1 : 0;
6559
- const boundsMinX = drawPaddingOutsideBoard || !hasBoardBounds ? minX : boardMinX;
6560
- const boundsMinY = drawPaddingOutsideBoard || !hasBoardBounds ? minY : boardMinY;
6561
- const boundsMaxX = drawPaddingOutsideBoard || !hasBoardBounds ? maxX : boardMaxX;
6562
- const boundsMaxY = drawPaddingOutsideBoard || !hasBoardBounds ? maxY : boardMaxY;
6787
+ const { boundsMinX, boundsMinY, boundsMaxX, boundsMaxY, padding } = getViewportBounds({
6788
+ circuitJson,
6789
+ drawPaddingOutsideBoard,
6790
+ baseBounds: {
6791
+ minX,
6792
+ minY,
6793
+ maxX,
6794
+ maxY,
6795
+ boardMinX,
6796
+ boardMinY,
6797
+ boardMaxX,
6798
+ boardMaxY,
6799
+ hasBoardBounds
6800
+ },
6801
+ viewportOptions: {
6802
+ viewport: options?.viewport,
6803
+ viewportTarget: options?.viewportTarget
6804
+ }
6805
+ });
6563
6806
  const circuitWidth = boundsMaxX - boundsMinX + 2 * padding;
6564
6807
  const circuitHeight = boundsMaxY - boundsMinY + 2 * padding;
6565
6808
  let svgWidth = options?.width ?? 800;
6566
6809
  let svgHeight = options?.height ?? 600;
6567
6810
  if (options?.matchBoardAspectRatio) {
6568
- const boardWidth = boardMaxX - boardMinX;
6569
- const boardHeight = boardMaxY - boardMinY;
6570
- if (boardWidth > 0 && boardHeight > 0) {
6571
- const aspect = boardWidth / boardHeight;
6811
+ const viewportWidth = boundsMaxX - boundsMinX;
6812
+ const viewportHeight = boundsMaxY - boundsMinY;
6813
+ if (viewportWidth > 0 && viewportHeight > 0) {
6814
+ const aspect = viewportWidth / viewportHeight;
6572
6815
  if (options?.width && !options?.height) {
6573
6816
  svgHeight = options.width / aspect;
6574
6817
  } else if (options?.height && !options?.width) {
@@ -6649,6 +6892,14 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
6649
6892
  if (gridObjects.defs) {
6650
6893
  children.push(gridObjects.defs);
6651
6894
  }
6895
+ const hasKeepouts = circuitJson.some((elm) => elm.type === "pcb_keepout");
6896
+ if (hasKeepouts) {
6897
+ children.push(
6898
+ createKeepoutPatternDefs(
6899
+ colorMap2.keepout ?? DEFAULT_PCB_COLOR_MAP.keepout
6900
+ )
6901
+ );
6902
+ }
6652
6903
  children.push({
6653
6904
  name: "rect",
6654
6905
  type: "element",
@@ -13506,7 +13757,7 @@ function formatNumber2(value) {
13506
13757
  }
13507
13758
 
13508
13759
  // lib/pcb/convert-circuit-json-to-solder-paste-mask.ts
13509
- import { distance as distance4 } from "circuit-json";
13760
+ import { distance as distance5 } from "circuit-json";
13510
13761
  import { stringify as stringify7 } from "svgson";
13511
13762
  import {
13512
13763
  applyToPoint as applyToPoint77,
@@ -13624,8 +13875,8 @@ function convertCircuitJsonToSolderPasteMask(circuitJson, options) {
13624
13875
  }
13625
13876
  } else if (item.type === "pcb_panel") {
13626
13877
  const panel = item;
13627
- const width = distance4.parse(panel.width);
13628
- const height = distance4.parse(panel.height);
13878
+ const width = distance5.parse(panel.width);
13879
+ const height = distance5.parse(panel.height);
13629
13880
  if (width !== void 0 && height !== void 0) {
13630
13881
  const center = panel.center ?? { x: width / 2, y: height / 2 };
13631
13882
  updateBounds(center, width, height);