circuit-to-canvas 0.0.32 → 0.0.33
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 +37 -2
- package/dist/index.js +182 -98
- package/lib/drawer/CircuitToCanvasDrawer.ts +11 -0
- package/lib/drawer/elements/index.ts +5 -0
- package/lib/drawer/elements/pcb-fabrication-note-dimension.ts +42 -0
- package/lib/drawer/elements/pcb-note-dimension.ts +16 -179
- package/lib/drawer/shapes/dimension-line.ts +228 -0
- package/lib/drawer/shapes/index.ts +4 -0
- package/package.json +1 -1
- package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-angled-and-vertical.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-basic.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-vertical.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-with-offset.snap.png +0 -0
- package/tests/elements/pcb-fabrication-note-dimension.test.ts +40 -0
- package/tests/shapes/__snapshots__/dimension-line.snap.png +0 -0
- package/tests/shapes/dimension-line.test.ts +46 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
|
|
1
|
+
import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension } from 'circuit-json';
|
|
2
2
|
import { Matrix } from 'transformation-matrix';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -221,6 +221,33 @@ interface DrawArrowParams {
|
|
|
221
221
|
*/
|
|
222
222
|
declare function drawArrow(params: DrawArrowParams): void;
|
|
223
223
|
|
|
224
|
+
interface DrawDimensionLineParams {
|
|
225
|
+
ctx: CanvasContext;
|
|
226
|
+
from: {
|
|
227
|
+
x: number;
|
|
228
|
+
y: number;
|
|
229
|
+
};
|
|
230
|
+
to: {
|
|
231
|
+
x: number;
|
|
232
|
+
y: number;
|
|
233
|
+
};
|
|
234
|
+
realToCanvasMat: Matrix;
|
|
235
|
+
color: string;
|
|
236
|
+
fontSize: number;
|
|
237
|
+
arrowSize?: number;
|
|
238
|
+
strokeWidth?: number;
|
|
239
|
+
text?: string;
|
|
240
|
+
textRotation?: number;
|
|
241
|
+
offset?: {
|
|
242
|
+
distance: number;
|
|
243
|
+
direction: {
|
|
244
|
+
x: number;
|
|
245
|
+
y: number;
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
declare function drawDimensionLine(params: DrawDimensionLineParams): void;
|
|
250
|
+
|
|
224
251
|
interface StrokeAlphabetTextParams {
|
|
225
252
|
ctx: CanvasContext;
|
|
226
253
|
text: string;
|
|
@@ -467,4 +494,12 @@ interface DrawPcbNoteDimensionParams {
|
|
|
467
494
|
}
|
|
468
495
|
declare function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void;
|
|
469
496
|
|
|
470
|
-
|
|
497
|
+
interface DrawPcbFabricationNoteDimensionParams {
|
|
498
|
+
ctx: CanvasContext;
|
|
499
|
+
pcbFabricationNoteDimension: PcbFabricationNoteDimension;
|
|
500
|
+
realToCanvasMat: Matrix;
|
|
501
|
+
colorMap: PcbColorMap;
|
|
502
|
+
}
|
|
503
|
+
declare function drawPcbFabricationNoteDimension(params: DrawPcbFabricationNoteDimensionParams): void;
|
|
504
|
+
|
|
505
|
+
export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawDimensionLineParams, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNoteDimensionParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenOvalParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawDimensionLine, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNoteDimension, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenOval, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawSoldermaskRingForCircle, drawSoldermaskRingForPill, drawSoldermaskRingForRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
|
package/dist/index.js
CHANGED
|
@@ -1618,7 +1618,7 @@ function drawPcbNoteText(params) {
|
|
|
1618
1618
|
});
|
|
1619
1619
|
}
|
|
1620
1620
|
|
|
1621
|
-
// lib/drawer/
|
|
1621
|
+
// lib/drawer/shapes/dimension-line.ts
|
|
1622
1622
|
import { applyToPoint as applyToPoint13 } from "transformation-matrix";
|
|
1623
1623
|
|
|
1624
1624
|
// lib/drawer/shapes/arrow.ts
|
|
@@ -1640,134 +1640,208 @@ function drawArrow(params) {
|
|
|
1640
1640
|
ctx.restore();
|
|
1641
1641
|
}
|
|
1642
1642
|
|
|
1643
|
-
// lib/drawer/
|
|
1644
|
-
var
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1643
|
+
// lib/drawer/shapes/dimension-line.ts
|
|
1644
|
+
var TEXT_OFFSET_MULTIPLIER = 1.5;
|
|
1645
|
+
var CHARACTER_WIDTH_MULTIPLIER = 0.6;
|
|
1646
|
+
var TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3;
|
|
1647
|
+
function normalize(v) {
|
|
1648
|
+
const len = Math.hypot(v.x, v.y) || 1;
|
|
1649
|
+
return { x: v.x / len, y: v.y / len };
|
|
1650
|
+
}
|
|
1651
|
+
function drawDimensionLine(params) {
|
|
1652
|
+
const {
|
|
1653
|
+
ctx,
|
|
1654
|
+
from,
|
|
1655
|
+
to,
|
|
1656
|
+
realToCanvasMat,
|
|
1657
|
+
color,
|
|
1658
|
+
fontSize,
|
|
1659
|
+
arrowSize = 1,
|
|
1660
|
+
strokeWidth: manualStrokeWidth,
|
|
1661
|
+
text,
|
|
1662
|
+
textRotation,
|
|
1663
|
+
offset
|
|
1664
|
+
} = params;
|
|
1665
|
+
const direction = normalize({ x: to.x - from.x, y: to.y - from.y });
|
|
1666
|
+
const perpendicular = { x: -direction.y, y: direction.x };
|
|
1667
|
+
const hasOffsetDirection = offset?.direction && typeof offset.direction.x === "number" && typeof offset.direction.y === "number";
|
|
1668
|
+
const normalizedOffsetDirection = hasOffsetDirection ? normalize(offset.direction) : { x: 0, y: 0 };
|
|
1669
|
+
const offsetMagnitude = offset?.distance ?? 0;
|
|
1670
|
+
const offsetVector = {
|
|
1671
|
+
x: normalizedOffsetDirection.x * offsetMagnitude,
|
|
1672
|
+
y: normalizedOffsetDirection.y * offsetMagnitude
|
|
1673
|
+
};
|
|
1674
|
+
const fromOffset = { x: from.x + offsetVector.x, y: from.y + offsetVector.y };
|
|
1675
|
+
const toOffset = { x: to.x + offsetVector.x, y: to.y + offsetVector.y };
|
|
1676
|
+
const fromBase = {
|
|
1677
|
+
x: fromOffset.x + direction.x * arrowSize,
|
|
1678
|
+
y: fromOffset.y + direction.y * arrowSize
|
|
1679
|
+
};
|
|
1680
|
+
const toBase = {
|
|
1681
|
+
x: toOffset.x - direction.x * arrowSize,
|
|
1682
|
+
y: toOffset.y - direction.y * arrowSize
|
|
1683
|
+
};
|
|
1684
|
+
const scaleValue = Math.abs(realToCanvasMat.a);
|
|
1685
|
+
const strokeWidth = manualStrokeWidth ?? arrowSize / 5;
|
|
1686
|
+
const lineColor = color || "rgba(255,255,255,0.5)";
|
|
1687
|
+
const extensionDirection = hasOffsetDirection && (Math.abs(normalizedOffsetDirection.x) > Number.EPSILON || Math.abs(normalizedOffsetDirection.y) > Number.EPSILON) ? normalizedOffsetDirection : perpendicular;
|
|
1688
|
+
const extensionLength = offsetMagnitude + 0.5;
|
|
1689
|
+
const drawExtension = (anchor) => {
|
|
1690
|
+
const endPoint = {
|
|
1691
|
+
x: anchor.x + extensionDirection.x * extensionLength,
|
|
1692
|
+
y: anchor.y + extensionDirection.y * extensionLength
|
|
1693
|
+
};
|
|
1690
1694
|
drawLine({
|
|
1691
1695
|
ctx,
|
|
1692
|
-
start:
|
|
1693
|
-
end:
|
|
1696
|
+
start: anchor,
|
|
1697
|
+
end: endPoint,
|
|
1694
1698
|
strokeWidth,
|
|
1695
|
-
stroke:
|
|
1699
|
+
stroke: lineColor,
|
|
1696
1700
|
realToCanvasMat
|
|
1697
1701
|
});
|
|
1698
|
-
}
|
|
1702
|
+
};
|
|
1703
|
+
drawExtension(from);
|
|
1704
|
+
drawExtension(to);
|
|
1699
1705
|
drawLine({
|
|
1700
1706
|
ctx,
|
|
1701
|
-
start:
|
|
1702
|
-
end:
|
|
1707
|
+
start: fromBase,
|
|
1708
|
+
end: toBase,
|
|
1703
1709
|
strokeWidth,
|
|
1704
|
-
stroke:
|
|
1710
|
+
stroke: lineColor,
|
|
1705
1711
|
realToCanvasMat
|
|
1706
1712
|
});
|
|
1707
1713
|
const [canvasFromX, canvasFromY] = applyToPoint13(realToCanvasMat, [
|
|
1708
|
-
|
|
1709
|
-
|
|
1714
|
+
fromOffset.x,
|
|
1715
|
+
fromOffset.y
|
|
1710
1716
|
]);
|
|
1711
|
-
const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
|
|
1717
|
+
const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [
|
|
1718
|
+
toOffset.x,
|
|
1719
|
+
toOffset.y
|
|
1720
|
+
]);
|
|
1721
|
+
const [canvasToDirX, canvasToDirY] = applyToPoint13(realToCanvasMat, [
|
|
1722
|
+
toOffset.x + direction.x,
|
|
1723
|
+
toOffset.y + direction.y
|
|
1724
|
+
]);
|
|
1725
|
+
const canvasLineAngle = Math.atan2(
|
|
1726
|
+
canvasToDirY - canvasToY,
|
|
1727
|
+
canvasToDirX - canvasToX
|
|
1728
|
+
);
|
|
1718
1729
|
drawArrow({
|
|
1719
1730
|
ctx,
|
|
1720
1731
|
x: canvasFromX,
|
|
1721
1732
|
y: canvasFromY,
|
|
1722
|
-
angle:
|
|
1723
|
-
arrowSize:
|
|
1724
|
-
color,
|
|
1725
|
-
strokeWidth:
|
|
1733
|
+
angle: canvasLineAngle + Math.PI,
|
|
1734
|
+
arrowSize: arrowSize * scaleValue,
|
|
1735
|
+
color: lineColor,
|
|
1736
|
+
strokeWidth: strokeWidth * scaleValue
|
|
1726
1737
|
});
|
|
1727
1738
|
drawArrow({
|
|
1728
1739
|
ctx,
|
|
1729
1740
|
x: canvasToX,
|
|
1730
1741
|
y: canvasToY,
|
|
1731
|
-
angle:
|
|
1732
|
-
arrowSize:
|
|
1733
|
-
color,
|
|
1734
|
-
strokeWidth:
|
|
1742
|
+
angle: canvasLineAngle,
|
|
1743
|
+
arrowSize: arrowSize * scaleValue,
|
|
1744
|
+
color: lineColor,
|
|
1745
|
+
strokeWidth: strokeWidth * scaleValue
|
|
1735
1746
|
});
|
|
1736
|
-
if (
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1747
|
+
if (text) {
|
|
1748
|
+
const midPoint = {
|
|
1749
|
+
x: (from.x + to.x) / 2 + offsetVector.x,
|
|
1750
|
+
y: (from.y + to.y) / 2 + offsetVector.y
|
|
1751
|
+
};
|
|
1752
|
+
const [screenFromX, screenFromY] = applyToPoint13(realToCanvasMat, [
|
|
1753
|
+
fromOffset.x,
|
|
1754
|
+
fromOffset.y
|
|
1755
|
+
]);
|
|
1756
|
+
const [screenToX, screenToY] = applyToPoint13(realToCanvasMat, [
|
|
1757
|
+
toOffset.x,
|
|
1758
|
+
toOffset.y
|
|
1759
|
+
]);
|
|
1760
|
+
const screenDirection = normalize({
|
|
1761
|
+
x: screenToX - screenFromX,
|
|
1762
|
+
y: screenToY - screenFromY
|
|
1763
|
+
});
|
|
1764
|
+
let textAngle = Math.atan2(screenDirection.y, screenDirection.x) * 180 / Math.PI;
|
|
1765
|
+
if (textAngle > 90 || textAngle < -90) {
|
|
1766
|
+
textAngle += 180;
|
|
1748
1767
|
}
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1768
|
+
const finalTextAngle = typeof textRotation === "number" && Number.isFinite(textRotation) ? textAngle - textRotation : textAngle;
|
|
1769
|
+
let additionalOffset = 0;
|
|
1770
|
+
if (text && typeof textRotation === "number" && Number.isFinite(textRotation)) {
|
|
1771
|
+
const textWidth = text.length * fontSize * CHARACTER_WIDTH_MULTIPLIER;
|
|
1772
|
+
const textHeight = fontSize;
|
|
1773
|
+
const rotationRad = textRotation * Math.PI / 180;
|
|
1774
|
+
const sinRot = Math.abs(Math.sin(rotationRad));
|
|
1775
|
+
const cosRot = Math.abs(Math.cos(rotationRad));
|
|
1776
|
+
const halfWidth = textWidth / 2;
|
|
1777
|
+
const halfHeight = textHeight / 2;
|
|
1778
|
+
const maxExtension = halfWidth * sinRot + halfHeight * cosRot;
|
|
1779
|
+
additionalOffset = maxExtension + fontSize * TEXT_INTERSECTION_PADDING_MULTIPLIER;
|
|
1780
|
+
}
|
|
1781
|
+
const textOffset = arrowSize * TEXT_OFFSET_MULTIPLIER + additionalOffset;
|
|
1782
|
+
const textPoint = {
|
|
1783
|
+
x: midPoint.x + perpendicular.x * textOffset,
|
|
1784
|
+
y: midPoint.y + perpendicular.y * textOffset
|
|
1785
|
+
};
|
|
1757
1786
|
drawText({
|
|
1758
1787
|
ctx,
|
|
1759
|
-
text
|
|
1760
|
-
x:
|
|
1761
|
-
y:
|
|
1762
|
-
fontSize
|
|
1763
|
-
color,
|
|
1788
|
+
text,
|
|
1789
|
+
x: textPoint.x,
|
|
1790
|
+
y: textPoint.y,
|
|
1791
|
+
fontSize,
|
|
1792
|
+
color: lineColor,
|
|
1764
1793
|
realToCanvasMat,
|
|
1765
1794
|
anchorAlignment: "center",
|
|
1766
|
-
rotation:
|
|
1795
|
+
rotation: -finalTextAngle
|
|
1796
|
+
// drawText expects CCW rotation in degrees
|
|
1767
1797
|
});
|
|
1768
1798
|
}
|
|
1769
1799
|
}
|
|
1770
1800
|
|
|
1801
|
+
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1802
|
+
var DEFAULT_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
1803
|
+
function drawPcbNoteDimension(params) {
|
|
1804
|
+
const { ctx, pcbNoteDimension, realToCanvasMat } = params;
|
|
1805
|
+
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR;
|
|
1806
|
+
drawDimensionLine({
|
|
1807
|
+
ctx,
|
|
1808
|
+
from: pcbNoteDimension.from,
|
|
1809
|
+
to: pcbNoteDimension.to,
|
|
1810
|
+
realToCanvasMat,
|
|
1811
|
+
color,
|
|
1812
|
+
fontSize: pcbNoteDimension.font_size,
|
|
1813
|
+
arrowSize: pcbNoteDimension.arrow_size,
|
|
1814
|
+
text: pcbNoteDimension.text,
|
|
1815
|
+
textRotation: pcbNoteDimension.text_ccw_rotation,
|
|
1816
|
+
offset: pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction ? {
|
|
1817
|
+
distance: pcbNoteDimension.offset_distance,
|
|
1818
|
+
direction: pcbNoteDimension.offset_direction
|
|
1819
|
+
} : void 0
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// lib/drawer/elements/pcb-fabrication-note-dimension.ts
|
|
1824
|
+
var DEFAULT_FABRICATION_NOTE_COLOR2 = "rgba(255,255,255,0.5)";
|
|
1825
|
+
function drawPcbFabricationNoteDimension(params) {
|
|
1826
|
+
const { ctx, pcbFabricationNoteDimension, realToCanvasMat } = params;
|
|
1827
|
+
const color = pcbFabricationNoteDimension.color ?? DEFAULT_FABRICATION_NOTE_COLOR2;
|
|
1828
|
+
drawDimensionLine({
|
|
1829
|
+
ctx,
|
|
1830
|
+
from: pcbFabricationNoteDimension.from,
|
|
1831
|
+
to: pcbFabricationNoteDimension.to,
|
|
1832
|
+
realToCanvasMat,
|
|
1833
|
+
color,
|
|
1834
|
+
fontSize: pcbFabricationNoteDimension.font_size ?? 1,
|
|
1835
|
+
arrowSize: pcbFabricationNoteDimension.arrow_size ?? 1,
|
|
1836
|
+
text: pcbFabricationNoteDimension.text,
|
|
1837
|
+
textRotation: pcbFabricationNoteDimension.text_ccw_rotation,
|
|
1838
|
+
offset: pcbFabricationNoteDimension.offset_distance && pcbFabricationNoteDimension.offset_direction ? {
|
|
1839
|
+
distance: pcbFabricationNoteDimension.offset_distance,
|
|
1840
|
+
direction: pcbFabricationNoteDimension.offset_direction
|
|
1841
|
+
} : void 0
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1771
1845
|
// lib/drawer/elements/pcb-note-line.ts
|
|
1772
1846
|
import { applyToPoint as applyToPoint14 } from "transformation-matrix";
|
|
1773
1847
|
function drawPcbNoteLine(params) {
|
|
@@ -2127,6 +2201,14 @@ var CircuitToCanvasDrawer = class {
|
|
|
2127
2201
|
colorMap: this.colorMap
|
|
2128
2202
|
});
|
|
2129
2203
|
}
|
|
2204
|
+
if (element.type === "pcb_fabrication_note_dimension") {
|
|
2205
|
+
drawPcbFabricationNoteDimension({
|
|
2206
|
+
ctx: this.ctx,
|
|
2207
|
+
pcbFabricationNoteDimension: element,
|
|
2208
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
2209
|
+
colorMap: this.colorMap
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2130
2212
|
}
|
|
2131
2213
|
};
|
|
2132
2214
|
export {
|
|
@@ -2134,6 +2216,7 @@ export {
|
|
|
2134
2216
|
DEFAULT_PCB_COLOR_MAP,
|
|
2135
2217
|
drawArrow,
|
|
2136
2218
|
drawCircle,
|
|
2219
|
+
drawDimensionLine,
|
|
2137
2220
|
drawLine,
|
|
2138
2221
|
drawOval,
|
|
2139
2222
|
drawPath,
|
|
@@ -2141,6 +2224,7 @@ export {
|
|
|
2141
2224
|
drawPcbCopperPour,
|
|
2142
2225
|
drawPcbCopperText,
|
|
2143
2226
|
drawPcbCutout,
|
|
2227
|
+
drawPcbFabricationNoteDimension,
|
|
2144
2228
|
drawPcbFabricationNotePath,
|
|
2145
2229
|
drawPcbFabricationNoteRect,
|
|
2146
2230
|
drawPcbFabricationNoteText,
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
PcbNotePath,
|
|
24
24
|
PcbNoteText,
|
|
25
25
|
PcbNoteDimension,
|
|
26
|
+
PcbFabricationNoteDimension,
|
|
26
27
|
PcbNoteLine,
|
|
27
28
|
PcbRenderLayer,
|
|
28
29
|
} from "circuit-json"
|
|
@@ -67,6 +68,7 @@ import { drawPcbFabricationNotePath } from "./elements/pcb-fabrication-note-path
|
|
|
67
68
|
import { drawPcbNotePath } from "./elements/pcb-note-path"
|
|
68
69
|
import { drawPcbNoteText } from "./elements/pcb-note-text"
|
|
69
70
|
import { drawPcbNoteDimension } from "./elements/pcb-note-dimension"
|
|
71
|
+
import { drawPcbFabricationNoteDimension } from "./elements/pcb-fabrication-note-dimension"
|
|
70
72
|
import { drawPcbNoteLine } from "./elements/pcb-note-line"
|
|
71
73
|
|
|
72
74
|
export interface DrawElementsOptions {
|
|
@@ -482,5 +484,14 @@ export class CircuitToCanvasDrawer {
|
|
|
482
484
|
colorMap: this.colorMap,
|
|
483
485
|
})
|
|
484
486
|
}
|
|
487
|
+
|
|
488
|
+
if (element.type === "pcb_fabrication_note_dimension") {
|
|
489
|
+
drawPcbFabricationNoteDimension({
|
|
490
|
+
ctx: this.ctx,
|
|
491
|
+
pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
|
|
492
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
493
|
+
colorMap: this.colorMap,
|
|
494
|
+
})
|
|
495
|
+
}
|
|
485
496
|
}
|
|
486
497
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PcbFabricationNoteDimension } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawDimensionLine } from "../shapes/dimension-line"
|
|
5
|
+
|
|
6
|
+
export interface DrawPcbFabricationNoteDimensionParams {
|
|
7
|
+
ctx: CanvasContext
|
|
8
|
+
pcbFabricationNoteDimension: PcbFabricationNoteDimension
|
|
9
|
+
realToCanvasMat: Matrix
|
|
10
|
+
colorMap: PcbColorMap
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULT_FABRICATION_NOTE_COLOR = "rgba(255,255,255,0.5)"
|
|
14
|
+
|
|
15
|
+
export function drawPcbFabricationNoteDimension(
|
|
16
|
+
params: DrawPcbFabricationNoteDimensionParams,
|
|
17
|
+
): void {
|
|
18
|
+
const { ctx, pcbFabricationNoteDimension, realToCanvasMat } = params
|
|
19
|
+
|
|
20
|
+
const color =
|
|
21
|
+
pcbFabricationNoteDimension.color ?? DEFAULT_FABRICATION_NOTE_COLOR
|
|
22
|
+
|
|
23
|
+
drawDimensionLine({
|
|
24
|
+
ctx,
|
|
25
|
+
from: pcbFabricationNoteDimension.from,
|
|
26
|
+
to: pcbFabricationNoteDimension.to,
|
|
27
|
+
realToCanvasMat,
|
|
28
|
+
color,
|
|
29
|
+
fontSize: pcbFabricationNoteDimension.font_size ?? 1,
|
|
30
|
+
arrowSize: pcbFabricationNoteDimension.arrow_size ?? 1,
|
|
31
|
+
text: pcbFabricationNoteDimension.text,
|
|
32
|
+
textRotation: pcbFabricationNoteDimension.text_ccw_rotation,
|
|
33
|
+
offset:
|
|
34
|
+
pcbFabricationNoteDimension.offset_distance &&
|
|
35
|
+
pcbFabricationNoteDimension.offset_direction
|
|
36
|
+
? {
|
|
37
|
+
distance: pcbFabricationNoteDimension.offset_distance,
|
|
38
|
+
direction: pcbFabricationNoteDimension.offset_direction,
|
|
39
|
+
}
|
|
40
|
+
: undefined,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { PcbNoteDimension } from "circuit-json"
|
|
2
2
|
import type { Matrix } from "transformation-matrix"
|
|
3
|
-
import { applyToPoint } from "transformation-matrix"
|
|
4
3
|
import type { PcbColorMap, CanvasContext } from "../types"
|
|
5
|
-
import {
|
|
6
|
-
import { drawText } from "../shapes/text"
|
|
7
|
-
import { drawArrow } from "../shapes/arrow"
|
|
4
|
+
import { drawDimensionLine } from "../shapes/dimension-line"
|
|
8
5
|
|
|
9
6
|
export interface DrawPcbNoteDimensionParams {
|
|
10
7
|
ctx: CanvasContext
|
|
@@ -19,183 +16,23 @@ export function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void {
|
|
|
19
16
|
const { ctx, pcbNoteDimension, realToCanvasMat } = params
|
|
20
17
|
|
|
21
18
|
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR
|
|
22
|
-
const arrowSize = pcbNoteDimension.arrow_size
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
const realFromX = pcbNoteDimension.from.x
|
|
26
|
-
const realFromY = pcbNoteDimension.from.y
|
|
27
|
-
const realToX = pcbNoteDimension.to.x
|
|
28
|
-
const realToY = pcbNoteDimension.to.y
|
|
29
|
-
|
|
30
|
-
// Calculate the dimension line endpoints (real/model coords)
|
|
31
|
-
let fromX = realFromX
|
|
32
|
-
let fromY = realFromY
|
|
33
|
-
let toX = realToX
|
|
34
|
-
let toY = realToY
|
|
35
|
-
|
|
36
|
-
// Track if we have an offset (for drawing extension lines)
|
|
37
|
-
let hasOffset = false
|
|
38
|
-
let offsetX = 0
|
|
39
|
-
let offsetY = 0
|
|
40
|
-
|
|
41
|
-
// Apply offset if provided
|
|
42
|
-
if (pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction) {
|
|
43
|
-
const dirX = pcbNoteDimension.offset_direction.x
|
|
44
|
-
const dirY = pcbNoteDimension.offset_direction.y
|
|
45
|
-
const length = Math.hypot(dirX, dirY)
|
|
46
|
-
if (length > 0) {
|
|
47
|
-
const normX = dirX / length
|
|
48
|
-
const normY = dirY / length
|
|
49
|
-
hasOffset = true
|
|
50
|
-
offsetX = pcbNoteDimension.offset_distance * normX
|
|
51
|
-
offsetY = pcbNoteDimension.offset_distance * normY
|
|
52
|
-
fromX += offsetX
|
|
53
|
-
fromY += offsetY
|
|
54
|
-
toX += offsetX
|
|
55
|
-
toY += offsetY
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Calculate stroke width to match text stroke width
|
|
60
|
-
// Text uses fontSize * STROKE_WIDTH_RATIO (0.13) with minimum 0.35
|
|
61
|
-
const STROKE_WIDTH_RATIO = 0.13
|
|
62
|
-
|
|
63
|
-
const strokeWidth = Math.max(
|
|
64
|
-
pcbNoteDimension.font_size * STROKE_WIDTH_RATIO,
|
|
65
|
-
0.35,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
// Draw extension lines if offset is provided
|
|
69
|
-
if (hasOffset) {
|
|
70
|
-
// Extension line from original 'from' point to offset 'from' point
|
|
71
|
-
drawLine({
|
|
72
|
-
ctx,
|
|
73
|
-
start: { x: realFromX, y: realFromY },
|
|
74
|
-
end: { x: fromX, y: fromY },
|
|
75
|
-
strokeWidth,
|
|
76
|
-
stroke: color,
|
|
77
|
-
realToCanvasMat: realToCanvasMat,
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Extension line from original 'to' point to offset 'to' point
|
|
81
|
-
drawLine({
|
|
82
|
-
ctx,
|
|
83
|
-
start: { x: realToX, y: realToY },
|
|
84
|
-
end: { x: toX, y: toY },
|
|
85
|
-
strokeWidth,
|
|
86
|
-
stroke: color,
|
|
87
|
-
realToCanvasMat: realToCanvasMat,
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Draw the dimension line
|
|
92
|
-
drawLine({
|
|
93
|
-
ctx,
|
|
94
|
-
start: { x: fromX, y: fromY },
|
|
95
|
-
end: { x: toX, y: toY },
|
|
96
|
-
strokeWidth,
|
|
97
|
-
stroke: color,
|
|
98
|
-
realToCanvasMat: realToCanvasMat,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Draw arrows at both ends
|
|
102
|
-
const [canvasFromX, canvasFromY] = applyToPoint(realToCanvasMat, [
|
|
103
|
-
fromX,
|
|
104
|
-
fromY,
|
|
105
|
-
])
|
|
106
|
-
const [canvasToX, canvasToY] = applyToPoint(realToCanvasMat, [toX, toY])
|
|
107
|
-
// Calculate angle for arrows in canvas coordinates
|
|
108
|
-
const canvasDx = canvasToX - canvasFromX
|
|
109
|
-
const canvasDy = canvasToY - canvasFromY
|
|
110
|
-
const lineAngle = Math.atan2(canvasDy, canvasDx)
|
|
111
|
-
const scale = Math.abs(realToCanvasMat.a)
|
|
112
|
-
const scaledArrowSize = arrowSize * scale
|
|
113
|
-
const scaledStrokeWidth = strokeWidth * scale
|
|
114
|
-
|
|
115
|
-
// Arrow at 'from' point (pointing outward, away from the line center)
|
|
116
|
-
// This means pointing in the direction opposite to 'to'
|
|
117
|
-
drawArrow({
|
|
20
|
+
drawDimensionLine({
|
|
118
21
|
ctx,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
arrowSize: scaledArrowSize,
|
|
22
|
+
from: pcbNoteDimension.from,
|
|
23
|
+
to: pcbNoteDimension.to,
|
|
24
|
+
realToCanvasMat,
|
|
123
25
|
color,
|
|
124
|
-
|
|
26
|
+
fontSize: pcbNoteDimension.font_size,
|
|
27
|
+
arrowSize: pcbNoteDimension.arrow_size,
|
|
28
|
+
text: pcbNoteDimension.text,
|
|
29
|
+
textRotation: pcbNoteDimension.text_ccw_rotation,
|
|
30
|
+
offset:
|
|
31
|
+
pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction
|
|
32
|
+
? {
|
|
33
|
+
distance: pcbNoteDimension.offset_distance,
|
|
34
|
+
direction: pcbNoteDimension.offset_direction,
|
|
35
|
+
}
|
|
36
|
+
: undefined,
|
|
125
37
|
})
|
|
126
|
-
|
|
127
|
-
// Arrow at 'to' point (pointing outward, away from the line center)
|
|
128
|
-
// This means pointing in the direction toward 'to' (away from 'from')
|
|
129
|
-
drawArrow({
|
|
130
|
-
ctx,
|
|
131
|
-
x: canvasToX,
|
|
132
|
-
y: canvasToY,
|
|
133
|
-
angle: lineAngle,
|
|
134
|
-
arrowSize: scaledArrowSize,
|
|
135
|
-
color,
|
|
136
|
-
strokeWidth: scaledStrokeWidth,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
// Draw text if provided
|
|
140
|
-
if (pcbNoteDimension.text) {
|
|
141
|
-
// Calculate text position (midpoint of the dimension line)
|
|
142
|
-
// The line endpoints are already offset if offset was provided
|
|
143
|
-
let textX = (fromX + toX) / 2
|
|
144
|
-
let textY = (fromY + toY) / 2
|
|
145
|
-
|
|
146
|
-
// Offset text perpendicular to the dimension line so it appears above/outside
|
|
147
|
-
// Calculate perpendicular vector (rotate line direction by 90 degrees CW)
|
|
148
|
-
// For a line from (fromX, fromY) to (toX, toY), perpendicular is (dy, -dx)
|
|
149
|
-
// This ensures text appears above horizontal lines and to the right of vertical lines
|
|
150
|
-
const perpX = toY - fromY
|
|
151
|
-
const perpY = -(toX - fromX)
|
|
152
|
-
const perpLength = Math.sqrt(perpX * perpX + perpY * perpY)
|
|
153
|
-
|
|
154
|
-
// Normalize and offset by font size (plus a small gap)
|
|
155
|
-
if (perpLength > 0) {
|
|
156
|
-
const offsetDistance = pcbNoteDimension.font_size * 1.5 // Offset by 1.5x font size
|
|
157
|
-
const normalizedPerpX = perpX / perpLength
|
|
158
|
-
const normalizedPerpY = perpY / perpLength
|
|
159
|
-
textX += normalizedPerpX * offsetDistance
|
|
160
|
-
textY += normalizedPerpY * offsetDistance
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Calculate rotation (displayed CCW degrees). If the caller provided
|
|
164
|
-
// `text_ccw_rotation` use that directly; otherwise align with the line
|
|
165
|
-
// angle and keep the text upright by folding into [-90, 90]. `drawText`
|
|
166
|
-
// expects a rotation value that it will negate internally, so we pass
|
|
167
|
-
// `-deg` below.
|
|
168
|
-
// Compute the displayed CCW degrees. Use the explicit `text_ccw_rotation`
|
|
169
|
-
// when provided; otherwise derive from the line angle and fold into
|
|
170
|
-
// [-90, 90] so text stays upright. Finally, `drawText` negates the
|
|
171
|
-
// provided rotation when applying it to the canvas, so pass the
|
|
172
|
-
// negative of the displayed CCW degrees.
|
|
173
|
-
const textRotation = -(() => {
|
|
174
|
-
const raw =
|
|
175
|
-
pcbNoteDimension.text_ccw_rotation ?? (lineAngle * 180) / Math.PI
|
|
176
|
-
|
|
177
|
-
if (pcbNoteDimension.text_ccw_rotation !== undefined) return raw
|
|
178
|
-
|
|
179
|
-
// Normalize to [-180, 180]
|
|
180
|
-
let deg = ((raw + 180) % 360) - 180
|
|
181
|
-
|
|
182
|
-
// Fold into [-90, 90]
|
|
183
|
-
if (deg > 90) deg -= 180
|
|
184
|
-
if (deg < -90) deg += 180
|
|
185
|
-
|
|
186
|
-
return deg
|
|
187
|
-
})()
|
|
188
|
-
|
|
189
|
-
drawText({
|
|
190
|
-
ctx,
|
|
191
|
-
text: pcbNoteDimension.text,
|
|
192
|
-
x: textX,
|
|
193
|
-
y: textY,
|
|
194
|
-
fontSize: pcbNoteDimension.font_size,
|
|
195
|
-
color,
|
|
196
|
-
realToCanvasMat: realToCanvasMat,
|
|
197
|
-
anchorAlignment: "center",
|
|
198
|
-
rotation: textRotation,
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
38
|
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { Matrix } from "transformation-matrix"
|
|
2
|
+
import { applyToPoint } from "transformation-matrix"
|
|
3
|
+
import type { CanvasContext } from "../types"
|
|
4
|
+
import { drawLine } from "./line"
|
|
5
|
+
import { drawText } from "./text"
|
|
6
|
+
import { drawArrow } from "./arrow"
|
|
7
|
+
|
|
8
|
+
export interface DrawDimensionLineParams {
|
|
9
|
+
ctx: CanvasContext
|
|
10
|
+
from: { x: number; y: number }
|
|
11
|
+
to: { x: number; y: number }
|
|
12
|
+
realToCanvasMat: Matrix
|
|
13
|
+
color: string
|
|
14
|
+
fontSize: number
|
|
15
|
+
arrowSize?: number
|
|
16
|
+
strokeWidth?: number
|
|
17
|
+
text?: string
|
|
18
|
+
textRotation?: number // CCW rotation in degrees
|
|
19
|
+
offset?: {
|
|
20
|
+
distance: number
|
|
21
|
+
direction: { x: number; y: number }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const TEXT_OFFSET_MULTIPLIER = 1.5
|
|
26
|
+
const CHARACTER_WIDTH_MULTIPLIER = 0.6
|
|
27
|
+
const TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3
|
|
28
|
+
|
|
29
|
+
function normalize(v: { x: number; y: number }) {
|
|
30
|
+
const len = Math.hypot(v.x, v.y) || 1
|
|
31
|
+
return { x: v.x / len, y: v.y / len }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function drawDimensionLine(params: DrawDimensionLineParams): void {
|
|
35
|
+
const {
|
|
36
|
+
ctx,
|
|
37
|
+
from,
|
|
38
|
+
to,
|
|
39
|
+
realToCanvasMat,
|
|
40
|
+
color,
|
|
41
|
+
fontSize,
|
|
42
|
+
arrowSize = 1,
|
|
43
|
+
strokeWidth: manualStrokeWidth,
|
|
44
|
+
text,
|
|
45
|
+
textRotation,
|
|
46
|
+
offset,
|
|
47
|
+
} = params
|
|
48
|
+
|
|
49
|
+
const direction = normalize({ x: to.x - from.x, y: to.y - from.y })
|
|
50
|
+
const perpendicular = { x: -direction.y, y: direction.x }
|
|
51
|
+
|
|
52
|
+
const hasOffsetDirection =
|
|
53
|
+
offset?.direction &&
|
|
54
|
+
typeof offset.direction.x === "number" &&
|
|
55
|
+
typeof offset.direction.y === "number"
|
|
56
|
+
|
|
57
|
+
const normalizedOffsetDirection = hasOffsetDirection
|
|
58
|
+
? normalize(offset!.direction)
|
|
59
|
+
: { x: 0, y: 0 }
|
|
60
|
+
|
|
61
|
+
const offsetMagnitude = offset?.distance ?? 0
|
|
62
|
+
const offsetVector = {
|
|
63
|
+
x: normalizedOffsetDirection.x * offsetMagnitude,
|
|
64
|
+
y: normalizedOffsetDirection.y * offsetMagnitude,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fromOffset = { x: from.x + offsetVector.x, y: from.y + offsetVector.y }
|
|
68
|
+
const toOffset = { x: to.x + offsetVector.x, y: to.y + offsetVector.y }
|
|
69
|
+
|
|
70
|
+
const fromBase = {
|
|
71
|
+
x: fromOffset.x + direction.x * arrowSize,
|
|
72
|
+
y: fromOffset.y + direction.y * arrowSize,
|
|
73
|
+
}
|
|
74
|
+
const toBase = {
|
|
75
|
+
x: toOffset.x - direction.x * arrowSize,
|
|
76
|
+
y: toOffset.y - direction.y * arrowSize,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const scaleValue = Math.abs(realToCanvasMat.a)
|
|
80
|
+
const strokeWidth = manualStrokeWidth ?? arrowSize / 5
|
|
81
|
+
const lineColor = color || "rgba(255,255,255,0.5)"
|
|
82
|
+
|
|
83
|
+
// Extension lines (ticks)
|
|
84
|
+
const extensionDirection =
|
|
85
|
+
hasOffsetDirection &&
|
|
86
|
+
(Math.abs(normalizedOffsetDirection.x) > Number.EPSILON ||
|
|
87
|
+
Math.abs(normalizedOffsetDirection.y) > Number.EPSILON)
|
|
88
|
+
? normalizedOffsetDirection
|
|
89
|
+
: perpendicular
|
|
90
|
+
|
|
91
|
+
const extensionLength = offsetMagnitude + 0.5
|
|
92
|
+
|
|
93
|
+
const drawExtension = (anchor: { x: number; y: number }) => {
|
|
94
|
+
const endPoint = {
|
|
95
|
+
x: anchor.x + extensionDirection.x * extensionLength,
|
|
96
|
+
y: anchor.y + extensionDirection.y * extensionLength,
|
|
97
|
+
}
|
|
98
|
+
drawLine({
|
|
99
|
+
ctx,
|
|
100
|
+
start: anchor,
|
|
101
|
+
end: endPoint,
|
|
102
|
+
strokeWidth,
|
|
103
|
+
stroke: lineColor,
|
|
104
|
+
realToCanvasMat,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
drawExtension(from)
|
|
109
|
+
drawExtension(to)
|
|
110
|
+
|
|
111
|
+
// Main dimension line
|
|
112
|
+
drawLine({
|
|
113
|
+
ctx,
|
|
114
|
+
start: fromBase,
|
|
115
|
+
end: toBase,
|
|
116
|
+
strokeWidth,
|
|
117
|
+
stroke: lineColor,
|
|
118
|
+
realToCanvasMat,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Arrows (Keep V-shaped but matching size)
|
|
122
|
+
const [canvasFromX, canvasFromY] = applyToPoint(realToCanvasMat, [
|
|
123
|
+
fromOffset.x,
|
|
124
|
+
fromOffset.y,
|
|
125
|
+
])
|
|
126
|
+
const [canvasToX, canvasToY] = applyToPoint(realToCanvasMat, [
|
|
127
|
+
toOffset.x,
|
|
128
|
+
toOffset.y,
|
|
129
|
+
])
|
|
130
|
+
const [canvasToDirX, canvasToDirY] = applyToPoint(realToCanvasMat, [
|
|
131
|
+
toOffset.x + direction.x,
|
|
132
|
+
toOffset.y + direction.y,
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
const canvasLineAngle = Math.atan2(
|
|
136
|
+
canvasToDirY - canvasToY,
|
|
137
|
+
canvasToDirX - canvasToX,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
drawArrow({
|
|
141
|
+
ctx,
|
|
142
|
+
x: canvasFromX,
|
|
143
|
+
y: canvasFromY,
|
|
144
|
+
angle: canvasLineAngle + Math.PI,
|
|
145
|
+
arrowSize: arrowSize * scaleValue,
|
|
146
|
+
color: lineColor,
|
|
147
|
+
strokeWidth: strokeWidth * scaleValue,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
drawArrow({
|
|
151
|
+
ctx,
|
|
152
|
+
x: canvasToX,
|
|
153
|
+
y: canvasToY,
|
|
154
|
+
angle: canvasLineAngle,
|
|
155
|
+
arrowSize: arrowSize * scaleValue,
|
|
156
|
+
color: lineColor,
|
|
157
|
+
strokeWidth: strokeWidth * scaleValue,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Text
|
|
161
|
+
if (text) {
|
|
162
|
+
const midPoint = {
|
|
163
|
+
x: (from.x + to.x) / 2 + offsetVector.x,
|
|
164
|
+
y: (from.y + to.y) / 2 + offsetVector.y,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const [screenFromX, screenFromY] = applyToPoint(realToCanvasMat, [
|
|
168
|
+
fromOffset.x,
|
|
169
|
+
fromOffset.y,
|
|
170
|
+
])
|
|
171
|
+
const [screenToX, screenToY] = applyToPoint(realToCanvasMat, [
|
|
172
|
+
toOffset.x,
|
|
173
|
+
toOffset.y,
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
const screenDirection = normalize({
|
|
177
|
+
x: screenToX - screenFromX,
|
|
178
|
+
y: screenToY - screenFromY,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
let textAngle =
|
|
182
|
+
(Math.atan2(screenDirection.y, screenDirection.x) * 180) / Math.PI
|
|
183
|
+
if (textAngle > 90 || textAngle < -90) {
|
|
184
|
+
textAngle += 180
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const finalTextAngle =
|
|
188
|
+
typeof textRotation === "number" && Number.isFinite(textRotation)
|
|
189
|
+
? textAngle - textRotation
|
|
190
|
+
: textAngle
|
|
191
|
+
|
|
192
|
+
let additionalOffset = 0
|
|
193
|
+
if (
|
|
194
|
+
text &&
|
|
195
|
+
typeof textRotation === "number" &&
|
|
196
|
+
Number.isFinite(textRotation)
|
|
197
|
+
) {
|
|
198
|
+
const textWidth = text.length * fontSize * CHARACTER_WIDTH_MULTIPLIER
|
|
199
|
+
const textHeight = fontSize
|
|
200
|
+
const rotationRad = (textRotation * Math.PI) / 180
|
|
201
|
+
const sinRot = Math.abs(Math.sin(rotationRad))
|
|
202
|
+
const cosRot = Math.abs(Math.cos(rotationRad))
|
|
203
|
+
const halfWidth = textWidth / 2
|
|
204
|
+
const halfHeight = textHeight / 2
|
|
205
|
+
const maxExtension = halfWidth * sinRot + halfHeight * cosRot
|
|
206
|
+
additionalOffset =
|
|
207
|
+
maxExtension + fontSize * TEXT_INTERSECTION_PADDING_MULTIPLIER
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const textOffset = arrowSize * TEXT_OFFSET_MULTIPLIER + additionalOffset
|
|
211
|
+
const textPoint = {
|
|
212
|
+
x: midPoint.x + perpendicular.x * textOffset,
|
|
213
|
+
y: midPoint.y + perpendicular.y * textOffset,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
drawText({
|
|
217
|
+
ctx,
|
|
218
|
+
text,
|
|
219
|
+
x: textPoint.x,
|
|
220
|
+
y: textPoint.y,
|
|
221
|
+
fontSize,
|
|
222
|
+
color: lineColor,
|
|
223
|
+
realToCanvasMat,
|
|
224
|
+
anchorAlignment: "center",
|
|
225
|
+
rotation: -finalTextAngle, // drawText expects CCW rotation in degrees
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -6,6 +6,10 @@ export { drawPolygon, type DrawPolygonParams } from "./polygon"
|
|
|
6
6
|
export { drawLine, type DrawLineParams } from "./line"
|
|
7
7
|
export { drawPath, type DrawPathParams } from "./path"
|
|
8
8
|
export { drawArrow, type DrawArrowParams } from "./arrow"
|
|
9
|
+
export {
|
|
10
|
+
drawDimensionLine,
|
|
11
|
+
type DrawDimensionLineParams,
|
|
12
|
+
} from "./dimension-line"
|
|
9
13
|
export {
|
|
10
14
|
drawText,
|
|
11
15
|
type DrawTextParams,
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type {
|
|
3
|
+
AnyCircuitElement,
|
|
4
|
+
PcbFabricationNoteDimension,
|
|
5
|
+
} from "circuit-json"
|
|
6
|
+
import { getStackedPngSvgComparison } from "../fixtures/getStackedPngSvgComparison"
|
|
7
|
+
|
|
8
|
+
test("draw pcb fabrication note dimension - basic", async () => {
|
|
9
|
+
const circuitJson: AnyCircuitElement[] = [
|
|
10
|
+
{
|
|
11
|
+
type: "pcb_board",
|
|
12
|
+
pcb_board_id: "board1",
|
|
13
|
+
center: { x: 10, y: 5 },
|
|
14
|
+
width: 25,
|
|
15
|
+
height: 15,
|
|
16
|
+
thickness: 1.6,
|
|
17
|
+
num_layers: 2,
|
|
18
|
+
material: "fr4",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: "pcb_fabrication_note_dimension",
|
|
22
|
+
pcb_fabrication_note_dimension_id: "fab_dim_1",
|
|
23
|
+
from: { x: 2, y: 5 },
|
|
24
|
+
to: { x: 18, y: 5 },
|
|
25
|
+
arrow_size: 0.4,
|
|
26
|
+
font_size: 0.6,
|
|
27
|
+
text: "16mm",
|
|
28
|
+
layer: "top",
|
|
29
|
+
pcb_component_id: "comp_1",
|
|
30
|
+
font: "tscircuit2024",
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const stackedPng = await getStackedPngSvgComparison(circuitJson, {
|
|
35
|
+
width: 400,
|
|
36
|
+
height: 800,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
await expect(stackedPng).toMatchPngSnapshot(import.meta.path)
|
|
40
|
+
})
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import { identity } from "transformation-matrix"
|
|
4
|
+
import { drawDimensionLine } from "../../lib/drawer/shapes/dimension-line"
|
|
5
|
+
|
|
6
|
+
test("drawDimensionLine shape", async () => {
|
|
7
|
+
const width = 400
|
|
8
|
+
const height = 200
|
|
9
|
+
const canvas = createCanvas(width, height)
|
|
10
|
+
const ctx = canvas.getContext("2d")
|
|
11
|
+
|
|
12
|
+
ctx.fillStyle = "#1a1a1a"
|
|
13
|
+
ctx.fillRect(0, 0, width, height)
|
|
14
|
+
|
|
15
|
+
// Basic horizontal dimension
|
|
16
|
+
drawDimensionLine({
|
|
17
|
+
ctx,
|
|
18
|
+
from: { x: 50, y: 50 },
|
|
19
|
+
to: { x: 350, y: 50 },
|
|
20
|
+
realToCanvasMat: identity(),
|
|
21
|
+
color: "white",
|
|
22
|
+
fontSize: 12,
|
|
23
|
+
arrowSize: 8,
|
|
24
|
+
text: "300mm",
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Vertical dimension with offset and extension lines
|
|
28
|
+
drawDimensionLine({
|
|
29
|
+
ctx,
|
|
30
|
+
from: { x: 50, y: 80 },
|
|
31
|
+
to: { x: 50, y: 180 },
|
|
32
|
+
realToCanvasMat: identity(),
|
|
33
|
+
color: "cyan",
|
|
34
|
+
fontSize: 10,
|
|
35
|
+
arrowSize: 6,
|
|
36
|
+
text: "100mm",
|
|
37
|
+
offset: {
|
|
38
|
+
distance: 30,
|
|
39
|
+
direction: { x: 1, y: 0 },
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
44
|
+
import.meta.path,
|
|
45
|
+
)
|
|
46
|
+
})
|