circuit-to-svg 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1 +1,59 @@
1
- # @tscircuit/soup-to-svg
1
+ # circuit-to-svg
2
+
3
+ A TypeScript library for converting circuit descriptions (soup) to SVG representations.
4
+
5
+ ## Overview
6
+
7
+ This library provides functionality to convert circuit descriptions, referred to as "soup", into SVG (Scalable Vector Graphics) representations. It supports both schematic and PCB (Printed Circuit Board) layouts.
8
+
9
+ ## Features
10
+
11
+ - Convert schematic circuit descriptions to SVG
12
+ - Convert PCB layouts to SVG
13
+ - Support for various circuit elements:
14
+ - Components
15
+ - Traces
16
+ - Text labels
17
+ - Net labels
18
+ - PCB boards
19
+ - PCB components
20
+ - PCB traces
21
+ - PCB holes and pads
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @tscircuit/circuit-to-svg
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```typescript
32
+ import { soupToSvg, pcbSoupToSvg } from '@tscircuit/circuit-to-svg';
33
+
34
+ // For schematic circuits
35
+ const schematicSoup = [...]; // Your schematic circuit description
36
+ const schematicSvg = soupToSvg(schematicSoup);
37
+
38
+ // For PCB layouts
39
+ const pcbSoup = [...]; // Your PCB layout description
40
+ const pcbSvg = pcbSoupToSvg(pcbSoup);
41
+ ```
42
+
43
+ ## API
44
+
45
+ ### `soupToSvg(soup: AnySoupElement[]): string`
46
+
47
+ Converts a schematic circuit description to an SVG string.
48
+
49
+ ### `pcbSoupToSvg(soup: AnySoupElement[]): string`
50
+
51
+ Converts a PCB layout description to an SVG string.
52
+
53
+ ## Contributing
54
+
55
+ Contributions are welcome! Please feel free to submit a Pull Request.
56
+
57
+ ## License
58
+
59
+ This project is licensed under the MIT License.
package/dist/index.js CHANGED
@@ -26,59 +26,95 @@ __export(src_exports, {
26
26
  module.exports = __toCommonJS(src_exports);
27
27
 
28
28
  // src/lib/pcb-soup-to-svg.ts
29
+ var import_svgson = require("svgson");
30
+ var import_transformation_matrix = require("transformation-matrix");
29
31
  function pcbSoupToSvg(soup) {
30
- const svgContent = [];
31
32
  let minX = Number.POSITIVE_INFINITY;
32
33
  let minY = Number.POSITIVE_INFINITY;
33
34
  let maxX = Number.NEGATIVE_INFINITY;
34
35
  let maxY = Number.NEGATIVE_INFINITY;
35
- const board = soup.find((item) => item.type === "pcb_board");
36
- if (board && "center" in board && "width" in board && "height" in board) {
37
- updateBounds(board.center, board.width, board.height);
38
- }
39
- for (const component of soup.filter((item) => item.type === "pcb_component")) {
40
- if ("center" in component && "width" in component && "height" in component) {
41
- updateBounds(component.center, component.width, component.height);
42
- svgContent.push(createPcbComponent(component));
43
- }
44
- }
45
- for (const trace of soup.filter((item) => item.type === "pcb_trace")) {
46
- svgContent.push(createPcbTrace(trace));
47
- if ("route" in trace) {
48
- updateTraceBounds(trace.route);
36
+ for (const item of soup) {
37
+ if ("center" in item && "width" in item && "height" in item) {
38
+ updateBounds(item.center, item.width, item.height);
39
+ } else if ("x" in item && "y" in item) {
40
+ updateBounds({ x: item.x, y: item.y }, 0, 0);
41
+ } else if ("route" in item) {
42
+ updateTraceBounds(item.route);
49
43
  }
50
44
  }
51
- for (const item of soup.filter((i) => i.type === "pcb_plated_hole" || i.type === "pcb_smtpad")) {
52
- if ("x" in item && "y" in item && ("outer_diameter" in item || "width" in item && "height" in item)) {
53
- const diameter = "outer_diameter" in item ? item.outer_diameter : item.width;
54
- const height2 = "outer_diameter" in item ? item.outer_diameter : item.height;
55
- updateBounds({ x: item.x, y: item.y }, diameter, height2);
56
- svgContent.push(createPcbHole(item));
45
+ const padding = 1;
46
+ const circuitWidth = maxX - minX + 2 * padding;
47
+ const circuitHeight = maxY - minY + 2 * padding;
48
+ const svgWidth = 800;
49
+ const svgHeight = 600;
50
+ const scaleX = svgWidth / circuitWidth;
51
+ const scaleY = svgHeight / circuitHeight;
52
+ const scaleFactor = Math.min(scaleX, scaleY);
53
+ const offsetX = (svgWidth - circuitWidth * scaleFactor) / 2;
54
+ const offsetY = (svgHeight - circuitHeight * scaleFactor) / 2;
55
+ const transform = (0, import_transformation_matrix.compose)(
56
+ (0, import_transformation_matrix.translate)(offsetX - minX * scaleFactor + padding * scaleFactor, svgHeight - offsetY + minY * scaleFactor - padding * scaleFactor),
57
+ (0, import_transformation_matrix.scale)(scaleFactor, -scaleFactor)
58
+ // Flip in y-direction
59
+ );
60
+ const svgElements = soup.map((item) => {
61
+ const element = createSvgElement(item, transform);
62
+ if (element === null) {
63
+ console.warn("Null element created for item:", item);
57
64
  }
65
+ return element;
66
+ }).filter((element) => element !== null);
67
+ const svgObject = {
68
+ name: "svg",
69
+ type: "element",
70
+ attributes: {
71
+ xmlns: "http://www.w3.org/2000/svg",
72
+ width: svgWidth.toString(),
73
+ height: svgHeight.toString()
74
+ },
75
+ children: [
76
+ {
77
+ name: "style",
78
+ type: "element",
79
+ children: [
80
+ {
81
+ type: "text",
82
+ value: `
83
+ .pcb-board { fill: #000; }
84
+ .pcb-trace { stroke: #FF0000; stroke-width: 0.3; fill: none; }
85
+ .pcb-hole { fill: #FF00FF; }
86
+ .pcb-pad { fill: #FF0000; }
87
+ .pcb-boundary { fill: none; stroke: #FFFFFF; stroke-width: 0.5; }
88
+ `
89
+ }
90
+ ]
91
+ },
92
+ {
93
+ name: "rect",
94
+ type: "element",
95
+ attributes: {
96
+ class: "pcb-board",
97
+ x: "0",
98
+ y: "0",
99
+ width: svgWidth.toString(),
100
+ height: svgHeight.toString()
101
+ }
102
+ },
103
+ ...svgElements,
104
+ createPcbBoundary(transform, minX, minY, maxX, maxY)
105
+ ].filter((child) => child !== null)
106
+ };
107
+ console.log("SVG Object:", JSON.stringify(svgObject, null, 2));
108
+ try {
109
+ return (0, import_svgson.stringify)(svgObject);
110
+ } catch (error) {
111
+ console.error("Error stringifying SVG object:", error);
112
+ console.log("Problematic SVG object:", JSON.stringify(svgObject, null, 2));
113
+ throw error;
58
114
  }
59
- const padding = 5;
60
- const width = maxX - minX + 2 * padding;
61
- const height = maxY - minY + 2 * padding;
62
- const viewBox = `${minX - padding} ${-maxY - padding} ${width} ${height}`;
63
- return `
64
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" width="800" height="600">
65
- <style>
66
- .pcb-board { fill: #000; }
67
- .pcb-trace { stroke: #CC0000; stroke-width: 0.1; fill: none; }
68
- .pcb-hole { fill: #FF00FF; }
69
- .pcb-pad { fill: #CC0000; }
70
- .pcb-outline { fill: none; stroke: white; stroke-width: 0.1; }
71
- </style>
72
- <g transform="scale(1, -1)">
73
- <rect class="pcb-board" x="${minX - padding}" y="${minY - padding}" width="${width}" height="${height}" />
74
- ${svgContent.join("\n")}
75
- <rect class="pcb-outline" x="${minX}" y="${minY}" width="${maxX - minX}" height="${maxY - minY}" />
76
- </g>
77
- </svg>
78
- `;
79
- function updateBounds(center, width2, height2) {
80
- const halfWidth = width2 / 2;
81
- const halfHeight = height2 / 2;
115
+ function updateBounds(center, width, height) {
116
+ const halfWidth = width / 2;
117
+ const halfHeight = height / 2;
82
118
  minX = Math.min(minX, center.x - halfWidth);
83
119
  minY = Math.min(minY, center.y - halfHeight);
84
120
  maxX = Math.max(maxX, center.x + halfWidth);
@@ -93,85 +129,215 @@ function pcbSoupToSvg(soup) {
93
129
  }
94
130
  }
95
131
  }
96
- function createPcbComponent(component) {
132
+ function createSvgElement(item, transform) {
133
+ switch (item.type) {
134
+ case "pcb_component":
135
+ return createPcbComponent(item, transform);
136
+ case "pcb_trace":
137
+ return createPcbTrace(item, transform);
138
+ case "pcb_plated_hole":
139
+ return createPcbHole(item, transform);
140
+ case "pcb_smtpad":
141
+ return createPcbSMTPad(item, transform);
142
+ default:
143
+ return null;
144
+ }
145
+ }
146
+ function createPcbComponent(component, transform) {
97
147
  const { center, width, height, rotation = 0 } = component;
98
- const transform = `translate(${center.x}, ${center.y}) rotate(${-rotation})`;
99
- return `
100
- <g transform="${transform}">
101
- <rect class="pcb-component" x="${-width / 2}" y="${-height / 2}" width="${width}" height="${height}" />
102
- <rect class="pcb-component-outline" x="${-width / 2}" y="${-height / 2}" width="${width}" height="${height}" />
103
- </g>
104
- `;
148
+ const [x, y] = (0, import_transformation_matrix.applyToPoint)(transform, [center.x, center.y]);
149
+ const scaledWidth = width * Math.abs(transform.a);
150
+ const scaledHeight = height * Math.abs(transform.d);
151
+ const transformStr = `translate(${x}, ${y}) rotate(${-rotation}) scale(1, -1)`;
152
+ return {
153
+ name: "g",
154
+ type: "element",
155
+ attributes: { transform: transformStr },
156
+ children: [
157
+ {
158
+ name: "rect",
159
+ type: "element",
160
+ attributes: {
161
+ class: "pcb-component",
162
+ x: (-scaledWidth / 2).toString(),
163
+ y: (-scaledHeight / 2).toString(),
164
+ width: scaledWidth.toString(),
165
+ height: scaledHeight.toString()
166
+ }
167
+ },
168
+ {
169
+ name: "rect",
170
+ type: "element",
171
+ attributes: {
172
+ class: "pcb-component-outline",
173
+ x: (-scaledWidth / 2).toString(),
174
+ y: (-scaledHeight / 2).toString(),
175
+ width: scaledWidth.toString(),
176
+ height: scaledHeight.toString()
177
+ }
178
+ }
179
+ ]
180
+ };
181
+ }
182
+ function createPcbHole(hole, transform) {
183
+ const [x, y] = (0, import_transformation_matrix.applyToPoint)(transform, [hole.x, hole.y]);
184
+ const scaledRadius = hole.outer_diameter / 2 * Math.abs(transform.a);
185
+ return {
186
+ name: "circle",
187
+ type: "element",
188
+ attributes: {
189
+ class: "pcb-hole",
190
+ cx: x.toString(),
191
+ cy: y.toString(),
192
+ r: scaledRadius.toString()
193
+ }
194
+ };
195
+ }
196
+ function createPcbSMTPad(pad, transform) {
197
+ const [x, y] = (0, import_transformation_matrix.applyToPoint)(transform, [pad.x, pad.y]);
198
+ const width = pad.width * Math.abs(transform.a);
199
+ const height = pad.height * Math.abs(transform.d);
200
+ return {
201
+ name: "rect",
202
+ type: "element",
203
+ attributes: {
204
+ class: "pcb-pad",
205
+ x: (x - width / 2).toString(),
206
+ y: (y - height / 2).toString(),
207
+ width: width.toString(),
208
+ height: height.toString()
209
+ }
210
+ };
105
211
  }
106
- function createPcbTrace(trace) {
212
+ function createPcbTrace(trace, transform) {
213
+ if (!trace.route || !Array.isArray(trace.route)) return null;
107
214
  const path = trace.route.map((point, index) => {
108
- return index === 0 ? `M ${point.x} ${point.y}` : `L ${point.x} ${point.y}`;
215
+ const [x, y] = (0, import_transformation_matrix.applyToPoint)(transform, [point.x, point.y]);
216
+ return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
109
217
  }).join(" ");
110
- return `<path class="pcb-trace" d="${path}" />`;
218
+ return {
219
+ name: "path",
220
+ type: "element",
221
+ attributes: {
222
+ class: "pcb-trace",
223
+ d: path
224
+ }
225
+ };
111
226
  }
112
- function createPcbHole(hole) {
113
- if (hole.type === "pcb_plated_hole") {
114
- return `<circle class="pcb-hole" cx="${hole.x}" cy="${hole.y}" r="${hole.outer_diameter / 2}" />`;
115
- }
116
- return `<rect class="pcb-pad" x="${hole.x - hole.width / 2}" y="${hole.y - hole.height / 2}" width="${hole.width}" height="${hole.height}" />`;
227
+ function createPcbBoundary(transform, minX, minY, maxX, maxY) {
228
+ const [x1, y1] = (0, import_transformation_matrix.applyToPoint)(transform, [minX, minY]);
229
+ const [x2, y2] = (0, import_transformation_matrix.applyToPoint)(transform, [maxX, maxY]);
230
+ const width = Math.abs(x2 - x1);
231
+ const height = Math.abs(y2 - y1);
232
+ const x = Math.min(x1, x2);
233
+ const y = Math.min(y1, y2);
234
+ return {
235
+ name: "rect",
236
+ type: "element",
237
+ attributes: {
238
+ class: "pcb-boundary",
239
+ x: x.toString(),
240
+ y: y.toString(),
241
+ width: width.toString(),
242
+ height: height.toString()
243
+ }
244
+ };
117
245
  }
118
246
 
119
247
  // src/lib/soup-to-svg.ts
248
+ var import_svgson2 = require("svgson");
120
249
  function soupToSvg(soup) {
121
- const svgContent = [];
122
250
  let minX = Number.POSITIVE_INFINITY;
123
251
  let minY = Number.POSITIVE_INFINITY;
124
252
  let maxX = Number.NEGATIVE_INFINITY;
125
253
  let maxY = Number.NEGATIVE_INFINITY;
254
+ const portSize = 0.2;
255
+ const portPositions = /* @__PURE__ */ new Map();
256
+ for (const item of soup) {
257
+ if (item.type === "schematic_component") {
258
+ updateBounds(item.center, item.size, item.rotation || 0);
259
+ } else if (item.type === "schematic_port") {
260
+ updateBounds(item.center, { width: portSize, height: portSize }, 0);
261
+ portPositions.set(item.schematic_port_id, item.center);
262
+ } else if (item.type === "schematic_text") {
263
+ updateBounds(item.position, { width: 0, height: 0 }, 0);
264
+ }
265
+ }
266
+ const height = maxY - minY;
267
+ const flipY = (y) => height - (y - minY) + minY;
268
+ const svgChildren = [];
269
+ const componentMap = /* @__PURE__ */ new Map();
126
270
  for (const component of soup.filter(
127
271
  (item) => item.type === "schematic_component"
128
272
  )) {
129
- const svg = createSchematicComponent(component);
130
- svgContent.push(svg);
131
- if ("center" in component && "size" in component && "rotation" in component) {
132
- updateBounds(component.center, component.size, component.rotation);
273
+ const flippedCenter = {
274
+ x: component.center.x,
275
+ y: flipY(component.center.y)
276
+ };
277
+ const svg = createSchematicComponent(
278
+ flippedCenter,
279
+ component.size,
280
+ component.rotation || 0
281
+ );
282
+ svgChildren.push(svg);
283
+ componentMap.set(component.schematic_component_id, component);
284
+ }
285
+ for (const port of soup.filter((item) => item.type === "schematic_port")) {
286
+ const flippedCenter = { x: port.center.x, y: flipY(port.center.y) };
287
+ const svg = createSchematicPort(flippedCenter);
288
+ svgChildren.push(svg);
289
+ const component = componentMap.get(port.schematic_component_id);
290
+ if (component) {
291
+ const line = createPortToComponentLine(
292
+ flippedCenter,
293
+ component,
294
+ port.facing_direction || "right"
295
+ );
296
+ svgChildren.push(line);
133
297
  }
134
298
  }
135
299
  for (const trace of soup.filter((item) => item.type === "schematic_trace")) {
136
- const svg = createSchematicTrace(trace);
137
- svgContent.push(svg);
138
- updateTraceBounds(trace.edges);
300
+ const svg = createSchematicTrace(trace, flipY, portPositions);
301
+ if (svg) svgChildren.push(svg);
139
302
  }
140
303
  for (const text of soup.filter((item) => item.type === "schematic_text")) {
141
- const svg = createSchematicText(text);
142
- svgContent.push(svg);
143
- if ("position" in text) {
144
- updateTextBounds(text.position);
145
- }
146
- }
147
- for (const label of soup.filter(
148
- (item) => item.type === "schematic_net_label"
149
- )) {
150
- const svg = createSchematicNetLabel(label);
151
- svgContent.push(svg);
152
- const width2 = label.text.length * 0.15 + 0.3;
153
- const height2 = 0.3;
154
- minX = Math.min(minX, label.center.x);
155
- minY = Math.min(minY, label.center.y - height2 / 2);
156
- maxX = Math.max(maxX, label.center.x + width2);
157
- maxY = Math.max(maxY, label.center.y + height2 / 2);
304
+ const flippedPosition = { x: text.position.x, y: flipY(text.position.y) };
305
+ const svg = createSchematicText(text, flippedPosition);
306
+ svgChildren.push(svg);
158
307
  }
159
308
  const padding = 1;
160
309
  const width = maxX - minX + 2 * padding;
161
- const height = maxY - minY + 2 * padding;
162
- const viewBox = `${minX - padding} ${minY - padding} ${width} ${height}`;
163
- return `
164
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" width="1200" height="600">
165
- <style>
166
- .component { fill: none; stroke: red; stroke-width: 0.05; }
167
- .component-pin { fill: none; stroke: blue; stroke-width: 0.05; }
168
- .trace { stroke: green; stroke-width: 0.05; fill: none; }
169
- .text { font-family: Arial, sans-serif; font-size: 0.2px; }
170
- .net-label { font-family: Arial, sans-serif; font-size: 0.2px; fill: gray; }
171
- </style>
172
- ${svgContent.join("\n")}
173
- </svg>
174
- `;
310
+ const viewBox = `${minX - padding} ${minY - padding} ${width} ${height + 2 * padding}`;
311
+ const svgObject = {
312
+ name: "svg",
313
+ type: "element",
314
+ attributes: {
315
+ xmlns: "http://www.w3.org/2000/svg",
316
+ viewBox,
317
+ width: "1200",
318
+ height: "600"
319
+ },
320
+ children: [
321
+ {
322
+ name: "style",
323
+ type: "element",
324
+ children: [
325
+ {
326
+ type: "text",
327
+ value: `
328
+ .component { fill: none; stroke: red; stroke-width: 0.03; }
329
+ .component-pin { fill: none; stroke: red; stroke-width: 0.03; }
330
+ .trace { stroke: green; stroke-width: 0.03; fill: none; }
331
+ .text { font-family: Arial, sans-serif; font-size: 0.2px; }
332
+ .port { fill: none; stroke: blue; stroke-width: 0.03; }
333
+ `
334
+ }
335
+ ]
336
+ },
337
+ ...svgChildren
338
+ ]
339
+ };
340
+ return (0, import_svgson2.stringify)({ value: "", ...svgObject });
175
341
  function updateBounds(center, size, rotation) {
176
342
  const corners = [
177
343
  { x: -size.width / 2, y: -size.height / 2 },
@@ -188,121 +354,133 @@ function soupToSvg(soup) {
188
354
  maxY = Math.max(maxY, rotatedY);
189
355
  }
190
356
  }
191
- function updateTraceBounds(edges) {
192
- for (const edge of edges) {
193
- minX = Math.min(minX, edge.from.x, edge.to.x);
194
- minY = Math.min(minY, edge.from.y, edge.to.y);
195
- maxX = Math.max(maxX, edge.from.x, edge.to.x);
196
- maxY = Math.max(maxY, edge.from.y, edge.to.y);
197
- }
198
- }
199
- function updateTextBounds(position) {
200
- minX = Math.min(minX, position.x);
201
- minY = Math.min(minY, position.y);
202
- maxX = Math.max(maxX, position.x);
203
- maxY = Math.max(maxY, position.y);
204
- }
205
357
  }
206
- function createSchematicComponent(component) {
207
- const { center, size, rotation } = component;
358
+ function createSchematicComponent(center, size, rotation) {
208
359
  const transform = `translate(${center.x}, ${center.y}) rotate(${rotation * 180 / Math.PI})`;
209
- const pinSize = 0.2;
210
- return `
211
- <g transform="${transform}">
212
- <rect
213
- class="component"
214
- x="${-size.width / 2}"
215
- y="${-size.height / 2}"
216
- width="${size.width}"
217
- height="${size.height}"
218
- />
219
- <rect
220
- class="component-pin"
221
- x="${-size.width / 2 - pinSize / 2}"
222
- y="${-pinSize / 2}"
223
- width="${pinSize}"
224
- height="${pinSize}"
225
- />
226
- <rect
227
- class="component-pin"
228
- x="${size.width / 2 - pinSize / 2}"
229
- y="${-pinSize / 2}"
230
- width="${pinSize}"
231
- height="${pinSize}"
232
- />
233
- </g>
234
- `;
360
+ return {
361
+ name: "g",
362
+ type: "element",
363
+ attributes: { transform },
364
+ children: [
365
+ {
366
+ name: "rect",
367
+ type: "element",
368
+ attributes: {
369
+ class: "component",
370
+ x: (-size.width / 2).toString(),
371
+ y: (-size.height / 2).toString(),
372
+ width: size.width.toString(),
373
+ height: size.height.toString()
374
+ }
375
+ }
376
+ ]
377
+ };
235
378
  }
236
- function createSchematicTrace(trace) {
237
- const path = trace.edges.map((edge, index) => {
238
- const fromPoint = `${edge.from.x} ${edge.from.y}`;
239
- const toPoint = `${edge.to.x} ${edge.to.y}`;
240
- if (index === 0) {
241
- return `M ${fromPoint} L ${toPoint}`;
379
+ function createSchematicPort(center) {
380
+ const portSize = 0.2;
381
+ const x = center.x - portSize / 2;
382
+ const y = center.y - portSize / 2;
383
+ return {
384
+ name: "rect",
385
+ type: "element",
386
+ attributes: {
387
+ class: "port",
388
+ x: x.toString(),
389
+ y: y.toString(),
390
+ width: portSize.toString(),
391
+ height: portSize.toString()
242
392
  }
243
- const prevEdge = trace.edges[index - 1];
244
- if (prevEdge.to.x === edge.from.x && prevEdge.to.y === edge.from.y) {
245
- return `L ${toPoint}`;
393
+ };
394
+ }
395
+ function createPortToComponentLine(portCenter, component, facingDirection) {
396
+ const componentCenter = { x: component.center.x, y: portCenter.y };
397
+ const halfWidth = component.size.width / 2;
398
+ const halfHeight = component.size.height / 2;
399
+ let endX = portCenter.x;
400
+ let endY = portCenter.y;
401
+ switch (facingDirection) {
402
+ case "left":
403
+ endX = componentCenter.x - halfWidth;
404
+ break;
405
+ case "right":
406
+ endX = componentCenter.x + halfWidth;
407
+ break;
408
+ case "up":
409
+ endY = componentCenter.y - halfHeight;
410
+ break;
411
+ case "down":
412
+ endY = componentCenter.y + halfHeight;
413
+ break;
414
+ }
415
+ return {
416
+ name: "line",
417
+ type: "element",
418
+ attributes: {
419
+ class: "component-pin",
420
+ x1: portCenter.x.toString(),
421
+ y1: portCenter.y.toString(),
422
+ x2: endX.toString(),
423
+ y2: endY.toString()
246
424
  }
247
- return `M ${fromPoint} L ${toPoint}`;
248
- }).join(" ");
249
- return `<path class="trace" d="${path}" />`;
425
+ };
250
426
  }
251
- function createSchematicText(text) {
252
- return `
253
- <text
254
- class="text"
255
- x="${text.position.x}"
256
- y="${text.position.y}"
257
- text-anchor="${getTextAnchor(text.anchor)}"
258
- >${text.text}</text>
259
- `;
427
+ function createSchematicTrace(trace, flipY, portPositions) {
428
+ const edges = trace.edges;
429
+ if (edges.length === 0) return null;
430
+ let path = "";
431
+ edges.forEach((edge, index) => {
432
+ const fromPoint = edge.from.ti !== void 0 ? portPositions.get(edge.from.ti) : edge.from;
433
+ const toPoint = edge.to.ti !== void 0 ? portPositions.get(edge.to.ti) : edge.to;
434
+ if (!fromPoint || !toPoint) {
435
+ return;
436
+ }
437
+ const fromCoord = `${fromPoint.x} ${flipY(fromPoint.y)}`;
438
+ const toCoord = `${toPoint.x} ${flipY(toPoint.y)}`;
439
+ if (index === 0) {
440
+ path += `M ${fromCoord} L ${toCoord}`;
441
+ } else {
442
+ path += ` L ${toCoord}`;
443
+ }
444
+ });
445
+ if (trace.to_schematic_port_id) {
446
+ const finalPort = portPositions.get(trace.to_schematic_port_id);
447
+ if (finalPort) {
448
+ const lastFromPoint = path.split("M")[1]?.split("L")[0];
449
+ const lastEdge = edges[edges.length - 1];
450
+ const lastPoint = lastEdge.to.ti !== void 0 ? portPositions.get(lastEdge.to.ti) : lastEdge.to;
451
+ if (lastPoint.x !== finalPort.x || lastPoint.y !== finalPort.y) {
452
+ const finalCoord = `${finalPort.x} ${flipY(finalPort.y)}`;
453
+ path += ` M ${lastFromPoint} L ${finalCoord}`;
454
+ }
455
+ }
456
+ }
457
+ return path ? {
458
+ name: "path",
459
+ type: "element",
460
+ attributes: {
461
+ class: "trace",
462
+ d: path
463
+ }
464
+ } : null;
260
465
  }
261
- function createSchematicNetLabel(label) {
262
- const width = label.text.length * 0.15 + 0.3;
263
- const height = 0.3;
264
- const arrowTip = 0.15;
265
- const isLeftAnchor = label.anchor_side === "left";
266
- const labelCenterX = isLeftAnchor ? label.center.x - width / 3 - 0.2 : label.center.x;
267
- const path = isLeftAnchor ? `
268
- M ${labelCenterX + width},${label.center.y - height / 2}
269
- L ${labelCenterX + arrowTip},${label.center.y - height / 2}
270
- L ${labelCenterX},${label.center.y}
271
- L ${labelCenterX + arrowTip},${label.center.y + height / 2}
272
- L ${labelCenterX + width},${label.center.y + height / 2}
273
- Z
274
- ` : `
275
- M ${labelCenterX},${label.center.y - height / 2}
276
- L ${labelCenterX + width - arrowTip},${label.center.y - height / 2}
277
- L ${labelCenterX + width},${label.center.y}
278
- L ${labelCenterX + width - arrowTip},${label.center.y + height / 2}
279
- L ${labelCenterX},${label.center.y + height / 2}
280
- Z
281
- `;
282
- const textX = labelCenterX + width / 2;
283
- const connectingLine = `
284
- <line
285
- x1="${labelCenterX + width}"
286
- y1="${label.center.y}"
287
- x2="${label.center.x}"
288
- y2="${label.center.y}"
289
- stroke="green"
290
- stroke-width="0.05"
291
- />
292
- `;
293
- return `
294
- <g class="net-label">
295
- ${connectingLine}
296
- <path d="${path}" fill="white" stroke="black" stroke-width="0.02"/>
297
- <text
298
- x="${textX}"
299
- y="${label.center.y}"
300
- text-anchor="middle"
301
- dominant-baseline="central"
302
- fill="black"
303
- >${label.text}</text>
304
- </g>
305
- `;
466
+ function createSchematicText(text, position) {
467
+ return {
468
+ name: "text",
469
+ type: "element",
470
+ attributes: {
471
+ class: "text",
472
+ x: position.x.toString(),
473
+ y: position.y.toString(),
474
+ "text-anchor": getTextAnchor(text.anchor),
475
+ "dominant-baseline": "middle"
476
+ },
477
+ children: [
478
+ {
479
+ type: "text",
480
+ value: text.text ? text.text : ""
481
+ }
482
+ ]
483
+ };
306
484
  }
307
485
  function getTextAnchor(anchor) {
308
486
  switch (anchor) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/lib/pcb-soup-to-svg.ts","../src/lib/soup-to-svg.ts"],"sourcesContent":["export * from \"./lib\";\n\n","import type { AnySoupElement } from \"@tscircuit/soup\";\n\nfunction pcbSoupToSvg(soup: AnySoupElement[]): string {\n const svgContent: string[] = [];\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n \n // Process PCB board\n const board = soup.find((item) => item.type === \"pcb_board\");\n if (board && \"center\" in board && \"width\" in board && \"height\" in board) {\n updateBounds(board.center, board.width, board.height);\n }\n \n // Process PCB components\n for (const component of soup.filter((item) => item.type === \"pcb_component\")) {\n if (\"center\" in component && \"width\" in component && \"height\" in component) {\n updateBounds((component as { center: { x: number; y: number }; width: number; height: number }).center, component.width, component.height);\n svgContent.push(createPcbComponent(component));\n }\n }\n \n // Process PCB traces\n for (const trace of soup.filter((item) => item.type === \"pcb_trace\")) {\n svgContent.push(createPcbTrace(trace));\n if ('route' in trace) {\n updateTraceBounds(trace.route);\n }\n }\n \n // Process PCB plated holes and SMT pads\n for (const item of soup.filter((i) => i.type === \"pcb_plated_hole\" || i.type === \"pcb_smtpad\")) {\n if ('x' in item && 'y' in item && ('outer_diameter' in item || ('width' in item && 'height' in item))) {\n const diameter = 'outer_diameter' in item ? item.outer_diameter : item.width;\n const height = 'outer_diameter' in item ? item.outer_diameter : item.height;\n updateBounds({ x: item.x, y: item.y }, diameter, height);\n svgContent.push(createPcbHole(item));\n }\n }\n \n const padding = 5;\n const width = maxX - minX + 2 * padding;\n const height = maxY - minY + 2 * padding;\n const viewBox = `${minX - padding} ${-maxY - padding} ${width} ${height}`;\n \n return `\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${viewBox}\" width=\"800\" height=\"600\">\n <style>\n .pcb-board { fill: #000; }\n .pcb-trace { stroke: #CC0000; stroke-width: 0.1; fill: none; }\n .pcb-hole { fill: #FF00FF; }\n .pcb-pad { fill: #CC0000; }\n .pcb-outline { fill: none; stroke: white; stroke-width: 0.1; }\n </style>\n <g transform=\"scale(1, -1)\">\n <rect class=\"pcb-board\" x=\"${minX - padding}\" y=\"${minY - padding}\" width=\"${width}\" height=\"${height}\" />\n ${svgContent.join(\"\\n\")}\n <rect class=\"pcb-outline\" x=\"${minX}\" y=\"${minY}\" width=\"${maxX - minX}\" height=\"${maxY - minY}\" />\n </g>\n </svg>\n `;\n \n function updateBounds(center: any, width: any, height: any) {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n minX = Math.min(minX, center.x - halfWidth);\n minY = Math.min(minY, center.y - halfHeight);\n maxX = Math.max(maxX, center.x + halfWidth);\n maxY = Math.max(maxY, center.y + halfHeight);\n }\n \n function updateTraceBounds(route: any[]) {\n for (const point of route) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n }\n \n function createPcbComponent(component: any): string {\n const { center, width, height, rotation = 0 } = component;\n const transform = `translate(${center.x}, ${center.y}) rotate(${-rotation})`;\n return `\n <g transform=\"${transform}\">\n <rect class=\"pcb-component\" x=\"${-width/2}\" y=\"${-height/2}\" width=\"${width}\" height=\"${height}\" />\n <rect class=\"pcb-component-outline\" x=\"${-width/2}\" y=\"${-height/2}\" width=\"${width}\" height=\"${height}\" />\n </g>\n `;\n }\n \n function createPcbTrace(trace: any): string {\n const path = trace.route.map((point: any, index: number) => {\n return index === 0 ? `M ${point.x} ${point.y}` : `L ${point.x} ${point.y}`;\n }).join(\" \");\n return `<path class=\"pcb-trace\" d=\"${path}\" />`;\n }\n \n function createPcbHole(hole: any): string {\n if (hole.type === \"pcb_plated_hole\") {\n return `<circle class=\"pcb-hole\" cx=\"${hole.x}\" cy=\"${hole.y}\" r=\"${hole.outer_diameter / 2}\" />`;\n }\n return `<rect class=\"pcb-pad\" x=\"${hole.x - hole.width/2}\" y=\"${hole.y - hole.height/2}\" width=\"${hole.width}\" height=\"${hole.height}\" />`;\n }\n\n\nexport { pcbSoupToSvg };\n\n","import type { AnySoupElement } from \"@tscircuit/soup\";\n\nfunction soupToSvg(soup: AnySoupElement[]): string {\n const svgContent: string[] = [];\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n\n // Process components\n for (const component of soup.filter(\n (item) => item.type === \"schematic_component\"\n )) {\n const svg = createSchematicComponent(component);\n svgContent.push(svg);\n if (\n \"center\" in component &&\n \"size\" in component &&\n \"rotation\" in component\n ) {\n updateBounds(component.center, component.size, component.rotation);\n }\n }\n\n // Process traces\n for (const trace of soup.filter((item) => item.type === \"schematic_trace\")) {\n const svg = createSchematicTrace(trace);\n svgContent.push(svg);\n updateTraceBounds(trace.edges);\n }\n\n // Process text\n for (const text of soup.filter((item) => item.type === \"schematic_text\")) {\n const svg = createSchematicText(text);\n svgContent.push(svg);\n if (\"position\" in text) {\n updateTextBounds(text.position);\n }\n }\n\n // Process net labels\n for (const label of soup.filter(\n (item) => item.type === \"schematic_net_label\"\n )) {\n const svg = createSchematicNetLabel(label);\n svgContent.push(svg);\n const width = label.text.length * 0.15 + 0.3;\n const height = 0.3;\n minX = Math.min(minX, label.center.x);\n minY = Math.min(minY, label.center.y - height / 2);\n maxX = Math.max(maxX, label.center.x + width);\n maxY = Math.max(maxY, label.center.y + height / 2);\n }\n\n const padding = 1;\n const width = maxX - minX + 2 * padding;\n const height = maxY - minY + 2 * padding;\n const viewBox = `${minX - padding} ${minY - padding} ${width} ${height}`;\n\n return `\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${viewBox}\" width=\"1200\" height=\"600\">\n <style>\n .component { fill: none; stroke: red; stroke-width: 0.05; }\n .component-pin { fill: none; stroke: blue; stroke-width: 0.05; }\n .trace { stroke: green; stroke-width: 0.05; fill: none; }\n .text { font-family: Arial, sans-serif; font-size: 0.2px; }\n .net-label { font-family: Arial, sans-serif; font-size: 0.2px; fill: gray; }\n </style>\n ${svgContent.join(\"\\n\")}\n </svg>\n `;\n\n function updateBounds(center: any, size: any, rotation: number) {\n const corners = [\n { x: -size.width / 2, y: -size.height / 2 },\n { x: size.width / 2, y: -size.height / 2 },\n { x: size.width / 2, y: size.height / 2 },\n { x: -size.width / 2, y: size.height / 2 },\n ];\n\n for (const corner of corners) {\n const rotatedX =\n corner.x * Math.cos(rotation) -\n corner.y * Math.sin(rotation) +\n center.x;\n const rotatedY =\n corner.x * Math.sin(rotation) +\n corner.y * Math.cos(rotation) +\n center.y;\n minX = Math.min(minX, rotatedX);\n minY = Math.min(minY, rotatedY);\n maxX = Math.max(maxX, rotatedX);\n maxY = Math.max(maxY, rotatedY);\n }\n }\n\n function updateTraceBounds(edges: any[]) {\n for (const edge of edges) {\n minX = Math.min(minX, edge.from.x, edge.to.x);\n minY = Math.min(minY, edge.from.y, edge.to.y);\n maxX = Math.max(maxX, edge.from.x, edge.to.x);\n maxY = Math.max(maxY, edge.from.y, edge.to.y);\n }\n }\n\n function updateTextBounds(position: { x: number; y: number }) {\n minX = Math.min(minX, position.x);\n minY = Math.min(minY, position.y);\n maxX = Math.max(maxX, position.x);\n maxY = Math.max(maxY, position.y);\n }\n}\n\nfunction createSchematicComponent(component: any): string {\n const { center, size, rotation } = component;\n const transform = `translate(${center.x}, ${center.y}) rotate(${\n (rotation * 180) / Math.PI\n })`;\n const pinSize = 0.2; // Size of the square pins\n\n return `\n <g transform=\"${transform}\">\n <rect \n class=\"component\" \n x=\"${-size.width / 2}\" \n y=\"${-size.height / 2}\" \n width=\"${size.width}\" \n height=\"${size.height}\" \n />\n <rect \n class=\"component-pin\"\n x=\"${-size.width / 2 - pinSize / 2}\" \n y=\"${-pinSize / 2}\" \n width=\"${pinSize}\" \n height=\"${pinSize}\" \n />\n <rect \n class=\"component-pin\"\n x=\"${size.width / 2 - pinSize / 2}\" \n y=\"${-pinSize / 2}\" \n width=\"${pinSize}\" \n height=\"${pinSize}\" \n />\n </g>\n `;\n}\n\nfunction createSchematicTrace(trace: any): string {\n const path = trace.edges.map((edge: any, index: number) => {\n const fromPoint = `${edge.from.x} ${edge.from.y}`;\n const toPoint = `${edge.to.x} ${edge.to.y}`;\n \n if (index === 0) {\n return `M ${fromPoint} L ${toPoint}`;\n }\n // Check if this is a 90-degree turn\n const prevEdge = trace.edges[index - 1];\n if (prevEdge.to.x === edge.from.x && prevEdge.to.y === edge.from.y) {\n return `L ${toPoint}`;\n }\n // Insert a move command for discontinuous segments\n return `M ${fromPoint} L ${toPoint}`;\n }).join(' ');\n\n return `<path class=\"trace\" d=\"${path}\" />`;\n}\n\nfunction createSchematicText(text: any): string {\n return `\n <text \n class=\"text\" \n x=\"${text.position.x}\" \n y=\"${text.position.y}\" \n text-anchor=\"${getTextAnchor(text.anchor)}\"\n >${text.text}</text>\n `;\n}\n\nfunction createSchematicNetLabel(label: any): string {\n const width = label.text.length * 0.15 + 0.3;\n const height = 0.3;\n const arrowTip = 0.15;\n const isLeftAnchor = label.anchor_side === \"left\";\n\n // Move the entire label to the left\n const labelCenterX = isLeftAnchor ? label.center.x - width / 3 - 0.2 : label.center.x;\n\n const path = isLeftAnchor\n ? `\n M ${labelCenterX + width},${label.center.y - height / 2}\n L ${labelCenterX + arrowTip},${label.center.y - height / 2}\n L ${labelCenterX},${label.center.y}\n L ${labelCenterX + arrowTip},${label.center.y + height / 2}\n L ${labelCenterX + width},${label.center.y + height / 2}\n Z\n `\n : `\n M ${labelCenterX},${label.center.y - height / 2}\n L ${labelCenterX + width - arrowTip},${label.center.y - height / 2}\n L ${labelCenterX + width},${label.center.y}\n L ${labelCenterX + width - arrowTip},${label.center.y + height / 2}\n L ${labelCenterX},${label.center.y + height / 2}\n Z\n `;\n\n // Keep text centered within the label\n const textX = labelCenterX + width / 2;\n\n // Add a line to connect the label to the resistor\n const connectingLine = `\n <line \n x1=\"${labelCenterX + width}\" \n y1=\"${label.center.y}\" \n x2=\"${label.center.x}\" \n y2=\"${label.center.y}\" \n stroke=\"green\" \n stroke-width=\"0.05\"\n />\n `;\n\n return `\n <g class=\"net-label\">\n ${connectingLine}\n <path d=\"${path}\" fill=\"white\" stroke=\"black\" stroke-width=\"0.02\"/>\n <text \n x=\"${textX}\" \n y=\"${label.center.y}\" \n text-anchor=\"middle\" \n dominant-baseline=\"central\"\n fill=\"black\"\n >${label.text}</text>\n </g>\n `;\n}\n\nfunction getTextAnchor(anchor: string): string {\n switch (anchor) {\n case \"left\":\n return \"start\";\n case \"right\":\n return \"end\";\n default:\n return \"middle\";\n }\n}\n\nexport { soupToSvg };\n"],"mappings":"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","names":["height","width","width","height"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/lib/pcb-soup-to-svg.ts","../src/lib/soup-to-svg.ts"],"sourcesContent":["export * from \"./lib\";\n\n","import type { AnySoupElement } from \"@tscircuit/soup\";\nimport { stringify, type INode } from \"svgson\";\nimport { applyToPoint, compose, scale, translate } from \"transformation-matrix\";\n\ninterface SvgObject {\n name: string;\n type: 'element' | 'text';\n attributes?: { [key: string]: string };\n children?: SvgObject[];\n value?: string;\n}\n\nfunction pcbSoupToSvg(soup: AnySoupElement[]): string {\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n\n // Process all elements to determine bounds\n for (const item of soup) {\n if (\"center\" in item && \"width\" in item && \"height\" in item) {\n updateBounds(item.center, item.width, item.height);\n } else if (\"x\" in item && \"y\" in item) {\n updateBounds({ x: item.x, y: item.y }, 0, 0);\n } else if (\"route\" in item) {\n updateTraceBounds(item.route);\n }\n }\n\n const padding = 1; // Reduced padding for tighter boundary\n const circuitWidth = maxX - minX + 2 * padding;\n const circuitHeight = maxY - minY + 2 * padding;\n\n const svgWidth = 800;\n const svgHeight = 600;\n\n // Calculate scale factor to fit the circuit within the SVG, maintaining aspect ratio\n const scaleX = svgWidth / circuitWidth;\n const scaleY = svgHeight / circuitHeight;\n const scaleFactor = Math.min(scaleX, scaleY);\n\n // Calculate centering offsets\n const offsetX = (svgWidth - circuitWidth * scaleFactor) / 2;\n const offsetY = (svgHeight - circuitHeight * scaleFactor) / 2;\n\n const transform = compose(\n translate(offsetX - minX * scaleFactor + padding * scaleFactor, svgHeight - offsetY + minY * scaleFactor - padding * scaleFactor),\n scale(scaleFactor, -scaleFactor) // Flip in y-direction\n );\n\n const svgElements = soup.map(item => {\n const element = createSvgElement(item, transform);\n if (element === null) {\n console.warn(\"Null element created for item:\", item);\n }\n return element;\n }).filter(element => element !== null);\n\n const svgObject: SvgObject = {\n name: 'svg',\n type: 'element',\n attributes: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: svgWidth.toString(),\n height: svgHeight.toString(),\n },\n children: [\n {\n name: 'style',\n type: 'element',\n children: [\n {\n type: 'text',\n value: `\n .pcb-board { fill: #000; }\n .pcb-trace { stroke: #FF0000; stroke-width: 0.3; fill: none; }\n .pcb-hole { fill: #FF00FF; }\n .pcb-pad { fill: #FF0000; }\n .pcb-boundary { fill: none; stroke: #FFFFFF; stroke-width: 0.5; }\n `\n }\n ]\n },\n {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'pcb-board',\n x: '0',\n y: '0',\n width: svgWidth.toString(),\n height: svgHeight.toString(),\n }\n },\n ...svgElements,\n createPcbBoundary(transform, minX, minY, maxX, maxY)\n ].filter(child => child !== null)\n };\n\n console.log('SVG Object:', JSON.stringify(svgObject, null, 2));\n\n try {\n return stringify(svgObject as INode);\n } catch (error) {\n console.error('Error stringifying SVG object:', error);\n console.log('Problematic SVG object:', JSON.stringify(svgObject, null, 2));\n throw error;\n }\n\n function updateBounds(center: any, width: any, height: any) {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n minX = Math.min(minX, center.x - halfWidth);\n minY = Math.min(minY, center.y - halfHeight);\n maxX = Math.max(maxX, center.x + halfWidth);\n maxY = Math.max(maxY, center.y + halfHeight);\n }\n\n function updateTraceBounds(route: any[]) {\n for (const point of route) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n}\n\nfunction createSvgElement(item: AnySoupElement, transform: any): any {\n switch (item.type) {\n case 'pcb_component':\n return createPcbComponent(item, transform);\n case 'pcb_trace':\n return createPcbTrace(item, transform);\n case 'pcb_plated_hole':\n return createPcbHole(item, transform);\n case 'pcb_smtpad':\n return createPcbSMTPad(item, transform);\n default:\n return null;\n }\n}\n\nfunction createPcbComponent(component: any, transform: any): any {\n const { center, width, height, rotation = 0 } = component;\n const [x, y] = applyToPoint(transform, [center.x, center.y]);\n const scaledWidth = width * Math.abs(transform.a);\n const scaledHeight = height * Math.abs(transform.d);\n const transformStr = `translate(${x}, ${y}) rotate(${-rotation}) scale(1, -1)`;\n\n return {\n name: 'g',\n type: 'element',\n attributes: { transform: transformStr },\n children: [\n {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'pcb-component',\n x: (-scaledWidth / 2).toString(),\n y: (-scaledHeight / 2).toString(),\n width: scaledWidth.toString(),\n height: scaledHeight.toString(),\n }\n },\n {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'pcb-component-outline',\n x: (-scaledWidth / 2).toString(),\n y: (-scaledHeight / 2).toString(),\n width: scaledWidth.toString(),\n height: scaledHeight.toString(),\n }\n }\n ]\n };\n}\n\nfunction createPcbHole(hole: any, transform: any): any {\n const [x, y] = applyToPoint(transform, [hole.x, hole.y]);\n const scaledRadius = (hole.outer_diameter / 2) * Math.abs(transform.a);\n return {\n name: 'circle',\n type: 'element',\n attributes: {\n class: 'pcb-hole',\n cx: x.toString(),\n cy: y.toString(),\n r: scaledRadius.toString(),\n }\n };\n}\n\nfunction createPcbSMTPad(pad: any, transform: any): any {\n const [x, y] = applyToPoint(transform, [pad.x, pad.y]);\n const width = pad.width * Math.abs(transform.a);\n const height = pad.height * Math.abs(transform.d);\n return {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'pcb-pad',\n x: (x - width / 2).toString(),\n y: (y - height / 2).toString(),\n width: width.toString(),\n height: height.toString(),\n }\n };\n}\n\nfunction createPcbTrace(trace: any, transform: any): any {\n if (!trace.route || !Array.isArray(trace.route)) return null;\n const path = trace.route\n .map((point: any, index: number) => {\n const [x, y] = applyToPoint(transform, [point.x, point.y]);\n return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;\n })\n .join(\" \");\n return {\n name: 'path',\n type: 'element',\n attributes: {\n class: 'pcb-trace',\n d: path,\n }\n };\n}\n\nfunction createPcbBoundary(transform: any, minX: number, minY: number, maxX: number, maxY: number): any {\n const [x1, y1] = applyToPoint(transform, [minX, minY]);\n const [x2, y2] = applyToPoint(transform, [maxX, maxY]);\n const width = Math.abs(x2 - x1);\n const height = Math.abs(y2 - y1);\n const x = Math.min(x1, x2);\n const y = Math.min(y1, y2);\n return {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'pcb-boundary',\n x: x.toString(),\n y: y.toString(),\n width: width.toString(),\n height: height.toString(),\n }\n };\n}\n\nexport { pcbSoupToSvg };\n","import type { AnySoupElement } from \"@tscircuit/soup\";\nimport { stringify } from \"svgson\";\n\nfunction soupToSvg(soup: AnySoupElement[]): string {\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n\n const portSize = 0.2;\n const portPositions = new Map();\n\n // First pass: find the bounds and collect port positions\n for (const item of soup) {\n if (item.type === \"schematic_component\") {\n updateBounds(item.center, item.size, item.rotation || 0);\n } else if (item.type === \"schematic_port\") {\n updateBounds(item.center, { width: portSize, height: portSize }, 0);\n portPositions.set(item.schematic_port_id, item.center);\n } else if (item.type === \"schematic_text\") {\n updateBounds(item.position, { width: 0, height: 0 }, 0);\n }\n }\n\n const height = maxY - minY;\n const flipY = (y: number) => height - (y - minY) + minY;\n\n const svgChildren: any[] = [];\n\n // Process components\n const componentMap = new Map();\n for (const component of soup.filter(\n (item) => item.type === \"schematic_component\"\n )) {\n const flippedCenter = {\n x: component.center.x,\n y: flipY(component.center.y),\n };\n const svg = createSchematicComponent(\n flippedCenter,\n component.size,\n component.rotation || 0\n );\n svgChildren.push(svg);\n componentMap.set(component.schematic_component_id, component);\n }\n\n // Process ports and add lines to component edges\n for (const port of soup.filter((item) => item.type === \"schematic_port\")) {\n const flippedCenter = { x: port.center.x, y: flipY(port.center.y) };\n const svg = createSchematicPort(flippedCenter);\n svgChildren.push(svg);\n\n const component = componentMap.get(port.schematic_component_id);\n if (component) {\n const line = createPortToComponentLine(\n flippedCenter,\n component,\n port.facing_direction || \"right\"\n );\n svgChildren.push(line);\n }\n }\n\n // Process schematic traces\n for (const trace of soup.filter((item) => item.type === \"schematic_trace\")) {\n const svg = createSchematicTrace(trace, flipY, portPositions);\n if (svg) svgChildren.push(svg);\n }\n\n // Process text\n for (const text of soup.filter((item) => item.type === \"schematic_text\")) {\n const flippedPosition = { x: text.position.x, y: flipY(text.position.y) };\n const svg = createSchematicText(text, flippedPosition);\n svgChildren.push(svg);\n }\n\n const padding = 1;\n const width = maxX - minX + 2 * padding;\n const viewBox = `${minX - padding} ${minY - padding} ${width} ${height + 2 * padding}`;\n\n const svgObject = {\n name: 'svg',\n type: 'element',\n attributes: {\n xmlns: 'http://www.w3.org/2000/svg',\n viewBox,\n width: '1200',\n height: '600',\n },\n children: [\n {\n name: 'style',\n type: 'element',\n children: [\n {\n type: 'text',\n value: `\n .component { fill: none; stroke: red; stroke-width: 0.03; }\n .component-pin { fill: none; stroke: red; stroke-width: 0.03; }\n .trace { stroke: green; stroke-width: 0.03; fill: none; }\n .text { font-family: Arial, sans-serif; font-size: 0.2px; }\n .port { fill: none; stroke: blue; stroke-width: 0.03; }\n `\n }\n ]\n },\n ...svgChildren\n ]\n };\n\n return stringify({ value: '', ...svgObject});\n\n function updateBounds(center: any, size: any, rotation: number) {\n const corners = [\n { x: -size.width / 2, y: -size.height / 2 },\n { x: size.width / 2, y: -size.height / 2 },\n { x: size.width / 2, y: size.height / 2 },\n { x: -size.width / 2, y: size.height / 2 },\n ];\n\n for (const corner of corners) {\n const rotatedX =\n corner.x * Math.cos(rotation) -\n corner.y * Math.sin(rotation) +\n center.x;\n const rotatedY =\n corner.x * Math.sin(rotation) +\n corner.y * Math.cos(rotation) +\n center.y;\n minX = Math.min(minX, rotatedX);\n minY = Math.min(minY, rotatedY);\n maxX = Math.max(maxX, rotatedX);\n maxY = Math.max(maxY, rotatedY);\n }\n }\n}\n\nfunction createSchematicComponent(\n center: { x: number; y: number },\n size: { width: number; height: number },\n rotation: number\n): any {\n const transform = `translate(${center.x}, ${center.y}) rotate(${(rotation * 180) / Math.PI})`;\n\n return {\n name: 'g',\n type: 'element',\n attributes: { transform },\n children: [\n {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'component',\n x: (-size.width / 2).toString(),\n y: (-size.height / 2).toString(),\n width: size.width.toString(),\n height: size.height.toString(),\n }\n }\n ]\n };\n}\n\nfunction createSchematicPort(center: { x: number; y: number }): any {\n const portSize = 0.2;\n const x = center.x - portSize / 2;\n const y = center.y - portSize / 2;\n\n return {\n name: 'rect',\n type: 'element',\n attributes: {\n class: 'port',\n x: x.toString(),\n y: y.toString(),\n width: portSize.toString(),\n height: portSize.toString(),\n }\n };\n}\n\nfunction createPortToComponentLine(\n portCenter: { x: number; y: number },\n component: any,\n facingDirection: string\n): any {\n const componentCenter = { x: component.center.x, y: portCenter.y };\n const halfWidth = component.size.width / 2;\n const halfHeight = component.size.height / 2;\n\n let endX = portCenter.x;\n let endY = portCenter.y;\n\n switch (facingDirection) {\n case \"left\":\n endX = componentCenter.x - halfWidth;\n break;\n case \"right\":\n endX = componentCenter.x + halfWidth;\n break;\n case \"up\":\n endY = componentCenter.y - halfHeight;\n break;\n case \"down\":\n endY = componentCenter.y + halfHeight;\n break;\n }\n\n return {\n name: 'line',\n type: 'element',\n attributes: {\n class: 'component-pin',\n x1: portCenter.x.toString(),\n y1: portCenter.y.toString(),\n x2: endX.toString(),\n y2: endY.toString(),\n }\n };\n}\n\nfunction createSchematicTrace(\n trace: any,\n flipY: (y: number) => number,\n portPositions: Map<string, { x: number; y: number }>\n): any {\n const edges = trace.edges;\n if (edges.length === 0) return null;\n\n let path = \"\";\n\n // Process all edges\n edges.forEach((edge: any, index: number) => {\n const fromPoint =\n edge.from.ti !== undefined ? portPositions.get(edge.from.ti) : edge.from;\n const toPoint =\n edge.to.ti !== undefined ? portPositions.get(edge.to.ti) : edge.to;\n\n if (!fromPoint || !toPoint) {\n return;\n }\n\n const fromCoord = `${fromPoint.x} ${flipY(fromPoint.y)}`;\n const toCoord = `${toPoint.x} ${flipY(toPoint.y)}`;\n\n if (index === 0) {\n path += `M ${fromCoord} L ${toCoord}`;\n } else {\n path += ` L ${toCoord}`;\n }\n });\n\n // Handle connection to final port if needed\n if (trace.to_schematic_port_id) {\n const finalPort = portPositions.get(trace.to_schematic_port_id);\n if (finalPort) {\n const lastFromPoint = path.split(\"M\")[1]?.split(\"L\")[0];\n const lastEdge = edges[edges.length - 1];\n const lastPoint =\n lastEdge.to.ti !== undefined\n ? portPositions.get(lastEdge.to.ti)\n : lastEdge.to;\n if (lastPoint.x !== finalPort.x || lastPoint.y !== finalPort.y) {\n const finalCoord = `${finalPort.x} ${flipY(finalPort.y)}`;\n path += ` M ${lastFromPoint} L ${finalCoord}`;\n }\n }\n }\n\n return path ? {\n name: 'path',\n type: 'element',\n attributes: {\n class: 'trace',\n d: path,\n }\n } : null;\n}\n\nfunction createSchematicText(\n text: any,\n position: { x: number; y: number }\n): any {\n return {\n name: 'text',\n type: 'element',\n attributes: {\n class: 'text',\n x: position.x.toString(),\n y: position.y.toString(),\n 'text-anchor': getTextAnchor(text.anchor),\n 'dominant-baseline': 'middle',\n },\n children: [\n {\n type: 'text',\n value: text.text ? text.text : \"\",\n }\n ]\n };\n}\n\nfunction getTextAnchor(anchor: string): string {\n switch (anchor) {\n case \"left\":\n return \"start\";\n case \"right\":\n return \"end\";\n default:\n return \"middle\";\n }\n}\n\nexport { soupToSvg };"],"mappings":"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","names":["import_svgson"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuit-to-svg",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Convert Circuit JSON to SVG",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -31,8 +31,12 @@
31
31
  "tsup": "^8.0.2",
32
32
  "typescript": "^5.4.5"
33
33
  },
34
+ "peerDependencies": {
35
+ "@tscircuit/soup": "^0.0.47"
36
+ },
34
37
  "dependencies": {
35
38
  "@tscircuit/routing": "^1.3.5",
36
- "@tscircuit/soup": "^0.0.39"
39
+ "svgson": "^5.3.1",
40
+ "transformation-matrix": "^2.16.1"
37
41
  }
38
42
  }