circuit-to-canvas 0.0.59 → 0.0.60

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
@@ -1,4 +1,4 @@
1
- import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PCBTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension } from 'circuit-json';
1
+ import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PcbTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -372,7 +372,7 @@ declare function drawSoldermaskRingForOval(ctx: CanvasContext, center: {
372
372
 
373
373
  interface DrawPcbTraceParams {
374
374
  ctx: CanvasContext;
375
- trace: PCBTrace;
375
+ trace: PcbTrace;
376
376
  realToCanvasMat: Matrix;
377
377
  colorMap: PcbColorMap;
378
378
  }
package/dist/index.js CHANGED
@@ -3101,32 +3101,165 @@ function processElementSoldermask(params) {
3101
3101
  }
3102
3102
  }
3103
3103
 
3104
- // lib/drawer/elements/pcb-trace.ts
3104
+ // lib/drawer/elements/pcb-trace/normalize-trace-direction.ts
3105
+ function normalizeTraceDirection(x, y) {
3106
+ const length = Math.hypot(x, y);
3107
+ if (length <= Number.EPSILON) return { x: 0, y: 0 };
3108
+ return { x: x / length, y: y / length };
3109
+ }
3110
+
3111
+ // lib/drawer/elements/pcb-trace/get-trace-direction-at.ts
3112
+ function getTraceDirectionAt(points, index) {
3113
+ const count = points.length;
3114
+ if (count < 2) return { x: 1, y: 0 };
3115
+ if (index <= 0) {
3116
+ const next2 = points[1];
3117
+ const current2 = points[0];
3118
+ if (!next2 || !current2) return { x: 1, y: 0 };
3119
+ const dir = normalizeTraceDirection(next2.x - current2.x, next2.y - current2.y);
3120
+ return dir.x === 0 && dir.y === 0 ? { x: 1, y: 0 } : dir;
3121
+ }
3122
+ if (index >= count - 1) {
3123
+ const prev2 = points[count - 2];
3124
+ const current2 = points[count - 1];
3125
+ if (!prev2 || !current2) return { x: 1, y: 0 };
3126
+ const dir = normalizeTraceDirection(current2.x - prev2.x, current2.y - prev2.y);
3127
+ return dir.x === 0 && dir.y === 0 ? { x: 1, y: 0 } : dir;
3128
+ }
3129
+ const prev = points[index - 1];
3130
+ const current = points[index];
3131
+ const next = points[index + 1];
3132
+ if (!prev || !current || !next) return { x: 1, y: 0 };
3133
+ const prevDir = normalizeTraceDirection(
3134
+ current.x - prev.x,
3135
+ current.y - prev.y
3136
+ );
3137
+ const nextDir = normalizeTraceDirection(
3138
+ next.x - current.x,
3139
+ next.y - current.y
3140
+ );
3141
+ const sum = normalizeTraceDirection(
3142
+ prevDir.x + nextDir.x,
3143
+ prevDir.y + nextDir.y
3144
+ );
3145
+ if (sum.x === 0 && sum.y === 0) {
3146
+ if (prevDir.x !== 0 || prevDir.y !== 0) return prevDir;
3147
+ if (nextDir.x !== 0 || nextDir.y !== 0) return nextDir;
3148
+ return { x: 1, y: 0 };
3149
+ }
3150
+ return sum;
3151
+ }
3152
+
3153
+ // lib/drawer/elements/pcb-trace/build-trace-polygon.ts
3154
+ function buildTracePolygon(points) {
3155
+ const left = [];
3156
+ const right = [];
3157
+ for (let i = 0; i < points.length; i++) {
3158
+ const point = points[i];
3159
+ if (!point) continue;
3160
+ const dir = getTraceDirectionAt(points, i);
3161
+ const normal = normalizeTraceDirection(-dir.y, dir.x);
3162
+ const offset = point.width / 2;
3163
+ left.push({
3164
+ x: point.x + normal.x * offset,
3165
+ y: point.y + normal.y * offset
3166
+ });
3167
+ right.push({
3168
+ x: point.x - normal.x * offset,
3169
+ y: point.y - normal.y * offset
3170
+ });
3171
+ }
3172
+ return left.concat(right.reverse());
3173
+ }
3174
+
3175
+ // lib/drawer/elements/pcb-trace/collect-trace-segments.ts
3176
+ function collectTraceSegments(route) {
3177
+ const segments = [];
3178
+ let current = [];
3179
+ let currentLayer = null;
3180
+ for (const routePoint of route ?? []) {
3181
+ if (!routePoint || routePoint.route_type !== "wire") {
3182
+ if (current.length >= 2) segments.push(current);
3183
+ current = [];
3184
+ currentLayer = null;
3185
+ continue;
3186
+ }
3187
+ const layer = routePoint.layer ?? currentLayer;
3188
+ if (!layer) continue;
3189
+ const x = routePoint.x;
3190
+ const y = routePoint.y;
3191
+ if (typeof x !== "number" || typeof y !== "number") continue;
3192
+ if (typeof routePoint.width !== "number") continue;
3193
+ const width = routePoint.width;
3194
+ if (currentLayer && layer !== currentLayer) {
3195
+ if (current.length >= 2) segments.push(current);
3196
+ current = [];
3197
+ }
3198
+ currentLayer = layer;
3199
+ current.push({
3200
+ route_type: "wire",
3201
+ x,
3202
+ y,
3203
+ width,
3204
+ layer,
3205
+ start_pcb_port_id: "start_pcb_port_id" in routePoint ? routePoint.start_pcb_port_id : void 0,
3206
+ end_pcb_port_id: "end_pcb_port_id" in routePoint ? routePoint.end_pcb_port_id : void 0
3207
+ });
3208
+ }
3209
+ if (current.length >= 2) segments.push(current);
3210
+ return segments;
3211
+ }
3212
+
3213
+ // lib/drawer/elements/pcb-trace/has-variable-width.ts
3214
+ function hasVariableWidth(points) {
3215
+ if (points.length < 2) return false;
3216
+ const baseWidth = points[0]?.width;
3217
+ if (typeof baseWidth !== "number") return false;
3218
+ return points.some(
3219
+ (point) => Math.abs(point.width - baseWidth) > Number.EPSILON
3220
+ );
3221
+ }
3222
+
3223
+ // lib/drawer/elements/pcb-trace/layer-to-color.ts
3105
3224
  function layerToColor4(layer, colorMap) {
3106
3225
  return colorMap.copper[layer] ?? colorMap.copper.top;
3107
3226
  }
3227
+
3228
+ // lib/drawer/elements/pcb-trace/pcb-trace.ts
3108
3229
  function drawPcbTrace(params) {
3109
3230
  const { ctx, trace, realToCanvasMat, colorMap } = params;
3110
3231
  if (!trace.route || !Array.isArray(trace.route) || trace.route.length < 2) {
3111
3232
  return;
3112
3233
  }
3113
- for (let i = 0; i < trace.route.length - 1; i++) {
3114
- const start = trace.route[i];
3115
- const end = trace.route[i + 1];
3116
- if (!start || !end) continue;
3117
- const layer = "layer" in start ? start.layer : "layer" in end ? end.layer : null;
3234
+ const segments = collectTraceSegments(trace.route);
3235
+ for (const segment of segments) {
3236
+ const layer = segment[0]?.layer;
3118
3237
  if (!layer) continue;
3119
3238
  const color = layerToColor4(layer, colorMap);
3120
- const traceWidth = "width" in start ? start.width : "width" in end ? end.width : 0.1;
3121
- drawLine({
3122
- ctx,
3123
- start: { x: start.x, y: start.y },
3124
- end: { x: end.x, y: end.y },
3125
- strokeWidth: traceWidth,
3126
- stroke: color,
3127
- realToCanvasMat,
3128
- lineCap: "round"
3129
- });
3239
+ if (hasVariableWidth(segment)) {
3240
+ const polygonPoints = buildTracePolygon(segment);
3241
+ drawPolygon({
3242
+ ctx,
3243
+ points: polygonPoints,
3244
+ fill: color,
3245
+ realToCanvasMat
3246
+ });
3247
+ continue;
3248
+ }
3249
+ for (let i = 0; i < segment.length - 1; i++) {
3250
+ const start = segment[i];
3251
+ const end = segment[i + 1];
3252
+ if (!start || !end) continue;
3253
+ drawLine({
3254
+ ctx,
3255
+ start: { x: start.x, y: start.y },
3256
+ end: { x: end.x, y: end.y },
3257
+ strokeWidth: start.width,
3258
+ stroke: color,
3259
+ realToCanvasMat,
3260
+ lineCap: "round"
3261
+ });
3262
+ }
3130
3263
  }
3131
3264
  }
3132
3265
 
@@ -63,7 +63,7 @@ import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
63
63
  import { drawPcbSilkscreenText } from "./elements/pcb-silkscreen-text"
64
64
  import { drawPcbSmtPad } from "./elements/pcb-smtpad"
65
65
  import { drawPcbSoldermask } from "./elements/pcb-soldermask"
66
- import { drawPcbTrace } from "./elements/pcb-trace"
66
+ import { drawPcbTrace } from "./elements/pcb-trace/pcb-trace"
67
67
  import { drawPcbVia } from "./elements/pcb-via"
68
68
  import { shouldDrawElement } from "./pcb-render-layer-filter"
69
69
  import {
@@ -15,7 +15,7 @@ export {
15
15
  drawSoldermaskRingForOval,
16
16
  } from "./soldermask-margin"
17
17
 
18
- export { drawPcbTrace, type DrawPcbTraceParams } from "./pcb-trace"
18
+ export { drawPcbTrace, type DrawPcbTraceParams } from "./pcb-trace/pcb-trace"
19
19
 
20
20
  export { drawPcbBoard, type DrawPcbBoardParams } from "./pcb-board"
21
21
 
@@ -0,0 +1,30 @@
1
+ import type { PcbTraceRoutePointWire } from "circuit-json"
2
+ import { getTraceDirectionAt } from "./get-trace-direction-at"
3
+ import { normalizeTraceDirection } from "./normalize-trace-direction"
4
+
5
+ // Builds a filled polygon representing a variable-width trace segment.
6
+ export function buildTracePolygon(
7
+ points: PcbTraceRoutePointWire[],
8
+ ): Array<{ x: number; y: number }> {
9
+ const left: Array<{ x: number; y: number }> = []
10
+ const right: Array<{ x: number; y: number }> = []
11
+
12
+ for (let i = 0; i < points.length; i++) {
13
+ const point = points[i]
14
+ if (!point) continue
15
+ const dir = getTraceDirectionAt(points, i)
16
+ const normal = normalizeTraceDirection(-dir.y, dir.x)
17
+ const offset = point.width / 2
18
+
19
+ left.push({
20
+ x: point.x + normal.x * offset,
21
+ y: point.y + normal.y * offset,
22
+ })
23
+ right.push({
24
+ x: point.x - normal.x * offset,
25
+ y: point.y - normal.y * offset,
26
+ })
27
+ }
28
+
29
+ return left.concat(right.reverse())
30
+ }
@@ -0,0 +1,55 @@
1
+ import type { LayerRef, PcbTrace, PcbTraceRoutePointWire } from "circuit-json"
2
+
3
+ // Splits a trace route into contiguous wire segments by layer.
4
+ export function collectTraceSegments(
5
+ route: PcbTrace["route"],
6
+ ): PcbTraceRoutePointWire[][] {
7
+ const segments: PcbTraceRoutePointWire[][] = []
8
+ let current: PcbTraceRoutePointWire[] = []
9
+ let currentLayer: LayerRef | null = null
10
+
11
+ for (const routePoint of route ?? []) {
12
+ if (!routePoint || routePoint.route_type !== "wire") {
13
+ if (current.length >= 2) segments.push(current)
14
+ current = []
15
+ currentLayer = null
16
+ continue
17
+ }
18
+
19
+ const layer: LayerRef | null = routePoint.layer ?? currentLayer
20
+
21
+ if (!layer) continue
22
+
23
+ const x = routePoint.x
24
+ const y = routePoint.y
25
+ if (typeof x !== "number" || typeof y !== "number") continue
26
+
27
+ if (typeof routePoint.width !== "number") continue
28
+ const width = routePoint.width
29
+
30
+ if (currentLayer && layer !== currentLayer) {
31
+ if (current.length >= 2) segments.push(current)
32
+ current = []
33
+ }
34
+
35
+ currentLayer = layer
36
+ current.push({
37
+ route_type: "wire",
38
+ x,
39
+ y,
40
+ width,
41
+ layer,
42
+ start_pcb_port_id:
43
+ "start_pcb_port_id" in routePoint
44
+ ? routePoint.start_pcb_port_id
45
+ : undefined,
46
+ end_pcb_port_id:
47
+ "end_pcb_port_id" in routePoint
48
+ ? routePoint.end_pcb_port_id
49
+ : undefined,
50
+ })
51
+ }
52
+
53
+ if (current.length >= 2) segments.push(current)
54
+ return segments
55
+ }
@@ -0,0 +1,56 @@
1
+ import type { PcbTraceRoutePointWire } from "circuit-json"
2
+ import { normalizeTraceDirection } from "./normalize-trace-direction"
3
+
4
+ // Returns a unit direction for a trace at a point index.
5
+ export function getTraceDirectionAt(
6
+ points: PcbTraceRoutePointWire[],
7
+ index: number,
8
+ ): {
9
+ x: number
10
+ y: number
11
+ } {
12
+ const count = points.length
13
+ if (count < 2) return { x: 1, y: 0 }
14
+
15
+ if (index <= 0) {
16
+ const next = points[1]
17
+ const current = points[0]
18
+ if (!next || !current) return { x: 1, y: 0 }
19
+ const dir = normalizeTraceDirection(next.x - current.x, next.y - current.y)
20
+ return dir.x === 0 && dir.y === 0 ? { x: 1, y: 0 } : dir
21
+ }
22
+
23
+ if (index >= count - 1) {
24
+ const prev = points[count - 2]
25
+ const current = points[count - 1]
26
+ if (!prev || !current) return { x: 1, y: 0 }
27
+ const dir = normalizeTraceDirection(current.x - prev.x, current.y - prev.y)
28
+ return dir.x === 0 && dir.y === 0 ? { x: 1, y: 0 } : dir
29
+ }
30
+
31
+ const prev = points[index - 1]
32
+ const current = points[index]
33
+ const next = points[index + 1]
34
+ if (!prev || !current || !next) return { x: 1, y: 0 }
35
+
36
+ const prevDir = normalizeTraceDirection(
37
+ current.x - prev.x,
38
+ current.y - prev.y,
39
+ )
40
+ const nextDir = normalizeTraceDirection(
41
+ next.x - current.x,
42
+ next.y - current.y,
43
+ )
44
+ const sum = normalizeTraceDirection(
45
+ prevDir.x + nextDir.x,
46
+ prevDir.y + nextDir.y,
47
+ )
48
+
49
+ if (sum.x === 0 && sum.y === 0) {
50
+ if (prevDir.x !== 0 || prevDir.y !== 0) return prevDir
51
+ if (nextDir.x !== 0 || nextDir.y !== 0) return nextDir
52
+ return { x: 1, y: 0 }
53
+ }
54
+
55
+ return sum
56
+ }
@@ -0,0 +1,11 @@
1
+ import type { PcbTraceRoutePointWire } from "circuit-json"
2
+
3
+ // Returns true when any wire point width differs from the first.
4
+ export function hasVariableWidth(points: PcbTraceRoutePointWire[]): boolean {
5
+ if (points.length < 2) return false
6
+ const baseWidth = points[0]?.width
7
+ if (typeof baseWidth !== "number") return false
8
+ return points.some(
9
+ (point) => Math.abs(point.width - baseWidth) > Number.EPSILON,
10
+ )
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { LayerRef } from "circuit-json"
2
+ import type { PcbColorMap } from "../../types"
3
+
4
+ // Resolves a copper layer to its configured trace color.
5
+ export function layerToColor(layer: LayerRef, colorMap: PcbColorMap): string {
6
+ return (
7
+ colorMap.copper[layer as keyof typeof colorMap.copper] ??
8
+ colorMap.copper.top
9
+ )
10
+ }
@@ -0,0 +1,9 @@
1
+ // Returns a unit-length trace direction; used to keep offsets/join math consistent.
2
+ export function normalizeTraceDirection(
3
+ x: number,
4
+ y: number,
5
+ ): { x: number; y: number } {
6
+ const length = Math.hypot(x, y)
7
+ if (length <= Number.EPSILON) return { x: 0, y: 0 }
8
+ return { x: x / length, y: y / length }
9
+ }
@@ -0,0 +1,60 @@
1
+ import type { PcbTrace } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import { drawLine } from "../../shapes/line"
4
+ import { drawPolygon } from "../../shapes/polygon"
5
+ import type { CanvasContext, PcbColorMap } from "../../types"
6
+ import { buildTracePolygon } from "./build-trace-polygon"
7
+ import { collectTraceSegments } from "./collect-trace-segments"
8
+ import { hasVariableWidth } from "./has-variable-width"
9
+ import { layerToColor } from "./layer-to-color"
10
+
11
+ export interface DrawPcbTraceParams {
12
+ ctx: CanvasContext
13
+ trace: PcbTrace
14
+ realToCanvasMat: Matrix
15
+ colorMap: PcbColorMap
16
+ }
17
+
18
+ // Draws a PCB trace route as lines or a filled polygon when widths vary.
19
+ export function drawPcbTrace(params: DrawPcbTraceParams): void {
20
+ const { ctx, trace, realToCanvasMat, colorMap } = params
21
+
22
+ if (!trace.route || !Array.isArray(trace.route) || trace.route.length < 2) {
23
+ return
24
+ }
25
+
26
+ const segments = collectTraceSegments(trace.route)
27
+
28
+ for (const segment of segments) {
29
+ const layer = segment[0]?.layer
30
+ if (!layer) continue
31
+ const color = layerToColor(layer, colorMap)
32
+
33
+ if (hasVariableWidth(segment)) {
34
+ const polygonPoints = buildTracePolygon(segment)
35
+ drawPolygon({
36
+ ctx,
37
+ points: polygonPoints,
38
+ fill: color,
39
+ realToCanvasMat,
40
+ })
41
+ continue
42
+ }
43
+
44
+ for (let i = 0; i < segment.length - 1; i++) {
45
+ const start = segment[i]
46
+ const end = segment[i + 1]
47
+ if (!start || !end) continue
48
+
49
+ drawLine({
50
+ ctx,
51
+ start: { x: start.x, y: start.y },
52
+ end: { x: end.x, y: end.y },
53
+ strokeWidth: start.width,
54
+ stroke: color,
55
+ realToCanvasMat,
56
+ lineCap: "round",
57
+ })
58
+ }
59
+ }
60
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "circuit-to-canvas",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.59",
4
+ "version": "0.0.60",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -1,59 +0,0 @@
1
- import type { PCBTrace } from "circuit-json"
2
- import type { Matrix } from "transformation-matrix"
3
- import type { PcbColorMap, CanvasContext } from "../types"
4
- import { drawLine } from "../shapes/line"
5
-
6
- export interface DrawPcbTraceParams {
7
- ctx: CanvasContext
8
- trace: PCBTrace
9
- realToCanvasMat: Matrix
10
- colorMap: PcbColorMap
11
- }
12
-
13
- function layerToColor(layer: string, colorMap: PcbColorMap): string {
14
- return (
15
- colorMap.copper[layer as keyof typeof colorMap.copper] ??
16
- colorMap.copper.top
17
- )
18
- }
19
-
20
- export function drawPcbTrace(params: DrawPcbTraceParams): void {
21
- const { ctx, trace, realToCanvasMat, colorMap } = params
22
-
23
- if (!trace.route || !Array.isArray(trace.route) || trace.route.length < 2) {
24
- return
25
- }
26
-
27
- // Draw each segment of the trace
28
- for (let i = 0; i < trace.route.length - 1; i++) {
29
- const start = trace.route[i]
30
- const end = trace.route[i + 1]
31
-
32
- if (!start || !end) continue
33
-
34
- // Get the layer from either point
35
- const layer =
36
- "layer" in start
37
- ? start.layer
38
- : "layer" in end
39
- ? (end as any).layer
40
- : null
41
- if (!layer) continue
42
-
43
- const color = layerToColor(layer, colorMap)
44
-
45
- // Get the trace width from either point
46
- const traceWidth =
47
- "width" in start ? start.width : "width" in end ? (end as any).width : 0.1
48
-
49
- drawLine({
50
- ctx,
51
- start: { x: start.x, y: start.y },
52
- end: { x: end.x, y: end.y },
53
- strokeWidth: traceWidth,
54
- stroke: color,
55
- realToCanvasMat,
56
- lineCap: "round",
57
- })
58
- }
59
- }