react-os-shell 2.4.0 → 2.5.0
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/Browser-PH57GK7S.js +7 -0
- package/dist/{Browser-PNQ5TVRZ.js.map → Browser-PH57GK7S.js.map} +1 -1
- package/dist/{Calculator-XGXSRMF2.js → Calculator-H7HMALL3.js} +4 -4
- package/dist/{Calculator-XGXSRMF2.js.map → Calculator-H7HMALL3.js.map} +1 -1
- package/dist/{CurrencyConverter-SVYA7ZGT.js → CurrencyConverter-576WXG7B.js} +4 -4
- package/dist/{CurrencyConverter-SVYA7ZGT.js.map → CurrencyConverter-576WXG7B.js.map} +1 -1
- package/dist/{Documents-L67KOICK.js → Documents-3SXOEFIQ.js} +4 -4
- package/dist/{Documents-L67KOICK.js.map → Documents-3SXOEFIQ.js.map} +1 -1
- package/dist/Files-YIYWFGHQ.js +13 -0
- package/dist/{Files-4D6XQRZB.js.map → Files-YIYWFGHQ.js.map} +1 -1
- package/dist/{Notepad-L6QXDCAY.js → Notepad-BZBRXG43.js} +4 -4
- package/dist/{Notepad-L6QXDCAY.js.map → Notepad-BZBRXG43.js.map} +1 -1
- package/dist/{PomodoroTimer-L62PEYPP.js → PomodoroTimer-PQ7FXSKY.js} +4 -4
- package/dist/{PomodoroTimer-L62PEYPP.js.map → PomodoroTimer-PQ7FXSKY.js.map} +1 -1
- package/dist/Preview-L5ABFUSO.js +9 -0
- package/dist/{Preview-XWX24RJD.js.map → Preview-L5ABFUSO.js.map} +1 -1
- package/dist/Spreadsheet-WDMPVUDW.js +7 -0
- package/dist/{Spreadsheet-2SWMHJQY.js.map → Spreadsheet-WDMPVUDW.js.map} +1 -1
- package/dist/{Stock-EDXEPZFF.js → Stock-NQFKY52J.js} +4 -4
- package/dist/{Stock-EDXEPZFF.js.map → Stock-NQFKY52J.js.map} +1 -1
- package/dist/{Weather-T2EVJ5NE.js → Weather-KH5A7AZ3.js} +4 -4
- package/dist/{Weather-T2EVJ5NE.js.map → Weather-KH5A7AZ3.js.map} +1 -1
- package/dist/{WorldClock-JN7YJN3B.js → WorldClock-VUMFYV5V.js} +4 -4
- package/dist/{WorldClock-JN7YJN3B.js.map → WorldClock-VUMFYV5V.js.map} +1 -1
- package/dist/apps/index.js +19 -19
- package/dist/{chunk-EH6MIG5E.js → chunk-6YJFK6H3.js} +4 -4
- package/dist/{chunk-EH6MIG5E.js.map → chunk-6YJFK6H3.js.map} +1 -1
- package/dist/{chunk-G4H4FQN3.js → chunk-7CXMEEUA.js} +5 -5
- package/dist/{chunk-G4H4FQN3.js.map → chunk-7CXMEEUA.js.map} +1 -1
- package/dist/{chunk-PXCTWRVI.js → chunk-FPOVTUH2.js} +4 -4
- package/dist/{chunk-PXCTWRVI.js.map → chunk-FPOVTUH2.js.map} +1 -1
- package/dist/{chunk-BZLRZPLP.js → chunk-QMX4QCJG.js} +4 -4
- package/dist/{chunk-BZLRZPLP.js.map → chunk-QMX4QCJG.js.map} +1 -1
- package/dist/{chunk-A7MAQOF3.js → chunk-QQ3K3EMW.js} +6 -6
- package/dist/{chunk-A7MAQOF3.js.map → chunk-QQ3K3EMW.js.map} +1 -1
- package/dist/{chunk-QAHF22KW.js → chunk-TJ6N7SI5.js} +24 -3
- package/dist/chunk-TJ6N7SI5.js.map +1 -0
- package/dist/{chunk-CGSN2MCD.js → chunk-UFTJG6IM.js} +587 -126
- package/dist/chunk-UFTJG6IM.js.map +1 -0
- package/dist/{chunk-5V6OCGI6.js → chunk-YVIW5GPB.js} +3 -3
- package/dist/{chunk-5V6OCGI6.js.map → chunk-YVIW5GPB.js.map} +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.js +25 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/Browser-PNQ5TVRZ.js +0 -7
- package/dist/Files-4D6XQRZB.js +0 -13
- package/dist/Preview-XWX24RJD.js +0 -9
- package/dist/Spreadsheet-2SWMHJQY.js +0 -7
- package/dist/chunk-CGSN2MCD.js.map +0 -1
- package/dist/chunk-QAHF22KW.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ImageAnnotator_default } from './chunk-KUIPWCTJ.js';
|
|
2
2
|
import { toast_default } from './chunk-WIJ45SYD.js';
|
|
3
|
-
import { AboutApp } from './chunk-
|
|
4
|
-
import { WindowTitle, getActiveModalId } from './chunk-
|
|
3
|
+
import { AboutApp } from './chunk-QQ3K3EMW.js';
|
|
4
|
+
import { WindowTitle, registerModalEscapeInterceptor, getActiveModalId } from './chunk-TJ6N7SI5.js';
|
|
5
5
|
import { createContext, useRef, useEffect, useState, useContext } from 'react';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
@@ -604,6 +604,61 @@ var DEFAULT_DXF_FONTS = [
|
|
|
604
604
|
"https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NotoSansDisplay-SemiCondensedLightItalic.ttf",
|
|
605
605
|
"https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NanumGothic-Regular.ttf"
|
|
606
606
|
];
|
|
607
|
+
var DXF_EDIT_COMMANDS = {
|
|
608
|
+
l: "LINE",
|
|
609
|
+
line: "LINE",
|
|
610
|
+
pl: "PLINE",
|
|
611
|
+
pline: "PLINE",
|
|
612
|
+
ex: "EXTEND",
|
|
613
|
+
extend: "EXTEND",
|
|
614
|
+
tr: "TRIM",
|
|
615
|
+
trim: "TRIM",
|
|
616
|
+
e: "ERASE",
|
|
617
|
+
erase: "ERASE",
|
|
618
|
+
m: "MOVE",
|
|
619
|
+
move: "MOVE",
|
|
620
|
+
co: "COPY",
|
|
621
|
+
cp: "COPY",
|
|
622
|
+
copy: "COPY",
|
|
623
|
+
o: "OFFSET",
|
|
624
|
+
offset: "OFFSET",
|
|
625
|
+
f: "FILLET",
|
|
626
|
+
fillet: "FILLET",
|
|
627
|
+
cha: "CHAMFER",
|
|
628
|
+
chamfer: "CHAMFER",
|
|
629
|
+
x: "EXPLODE",
|
|
630
|
+
explode: "EXPLODE",
|
|
631
|
+
mi: "MIRROR",
|
|
632
|
+
mirror: "MIRROR",
|
|
633
|
+
ro: "ROTATE",
|
|
634
|
+
rotate: "ROTATE",
|
|
635
|
+
sc: "SCALE",
|
|
636
|
+
scale: "SCALE",
|
|
637
|
+
s: "STRETCH",
|
|
638
|
+
stretch: "STRETCH",
|
|
639
|
+
ar: "ARRAY",
|
|
640
|
+
array: "ARRAY",
|
|
641
|
+
rec: "RECTANG",
|
|
642
|
+
rectang: "RECTANG",
|
|
643
|
+
rectangle: "RECTANG",
|
|
644
|
+
c: "CIRCLE",
|
|
645
|
+
circle: "CIRCLE",
|
|
646
|
+
a: "ARC",
|
|
647
|
+
arc: "ARC",
|
|
648
|
+
el: "ELLIPSE",
|
|
649
|
+
ellipse: "ELLIPSE",
|
|
650
|
+
t: "MTEXT",
|
|
651
|
+
mt: "MTEXT",
|
|
652
|
+
mtext: "MTEXT",
|
|
653
|
+
text: "TEXT",
|
|
654
|
+
b: "BLOCK",
|
|
655
|
+
block: "BLOCK",
|
|
656
|
+
i: "INSERT",
|
|
657
|
+
insert: "INSERT",
|
|
658
|
+
ha: "HATCH",
|
|
659
|
+
hatch: "HATCH"
|
|
660
|
+
};
|
|
661
|
+
var DXF_CMD_HELP = "Commands: DI distance \xB7 DIM/DLI linear dim \xB7 H / V force axis \xB7 <number> lock \u0394 \xB7 U undo pick \xB7 Z fit \xB7 LA layers \xB7 Esc exit";
|
|
607
662
|
function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
608
663
|
const containerRef = useRef(null);
|
|
609
664
|
const viewerRef = useRef(null);
|
|
@@ -613,8 +668,9 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
613
668
|
const [showLayers, setShowLayers] = useState(false);
|
|
614
669
|
const [showHint, setShowHint] = useState(true);
|
|
615
670
|
const [measureEnabled, setMeasureEnabled] = useState(false);
|
|
616
|
-
const [measureMode, setMeasureMode] = useState("
|
|
671
|
+
const [measureMode, setMeasureMode] = useState("auto");
|
|
617
672
|
const [measureDistance, setMeasureDistance] = useState(null);
|
|
673
|
+
const [measureResolved, setMeasureResolved] = useState(null);
|
|
618
674
|
const [measureFixedDist, setMeasureFixedDist] = useState(null);
|
|
619
675
|
const [measureFixedInput, setMeasureFixedInput] = useState("");
|
|
620
676
|
const measureModeRef = useRef(measureMode);
|
|
@@ -626,6 +682,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
626
682
|
measureFixedDistRef.current = measureFixedDist;
|
|
627
683
|
}, [measureFixedDist]);
|
|
628
684
|
const measureRedrawRef = useRef(null);
|
|
685
|
+
const measureResetRef = useRef(null);
|
|
686
|
+
const measureUndoRef = useRef(null);
|
|
687
|
+
const [cmdValue, setCmdValue] = useState("");
|
|
688
|
+
const [cmdEcho, setCmdEcho] = useState(null);
|
|
689
|
+
const lastCmdRef = useRef("");
|
|
690
|
+
const cmdInputRef = useRef(null);
|
|
691
|
+
const rootRef = useRef(null);
|
|
629
692
|
const measureRef = useRef(null);
|
|
630
693
|
useEffect(() => {
|
|
631
694
|
let cancelled = false;
|
|
@@ -729,6 +792,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
729
792
|
const t = setTimeout(() => setShowHint(false), 5e3);
|
|
730
793
|
return () => clearTimeout(t);
|
|
731
794
|
}, [showHint, loading]);
|
|
795
|
+
const layersKey = layers.map((l) => l.visible ? "1" : "0").join("");
|
|
732
796
|
useEffect(() => {
|
|
733
797
|
const v = viewerRef.current;
|
|
734
798
|
if (loading || error || !v) return;
|
|
@@ -745,6 +809,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
745
809
|
if (!measureEnabled) {
|
|
746
810
|
teardown();
|
|
747
811
|
setMeasureDistance(null);
|
|
812
|
+
setMeasureResolved(null);
|
|
748
813
|
return;
|
|
749
814
|
}
|
|
750
815
|
const overlay = document.createElement("div");
|
|
@@ -768,13 +833,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
768
833
|
defs.appendChild(marker);
|
|
769
834
|
svg.appendChild(defs);
|
|
770
835
|
overlay.appendChild(svg);
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
836
|
+
const mkRefLine = () => {
|
|
837
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
838
|
+
el.setAttribute("stroke", "#ff8800");
|
|
839
|
+
el.setAttribute("stroke-width", "1");
|
|
840
|
+
el.setAttribute("stroke-dasharray", "6,4");
|
|
841
|
+
el.setAttribute("opacity", "0.55");
|
|
842
|
+
el.style.display = "none";
|
|
843
|
+
svg.appendChild(el);
|
|
844
|
+
return el;
|
|
845
|
+
};
|
|
846
|
+
const refLineEl = mkRefLine();
|
|
847
|
+
const refLineVEl = mkRefLine();
|
|
778
848
|
const extLineA = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
779
849
|
extLineA.setAttribute("stroke", "#ff8800");
|
|
780
850
|
extLineA.setAttribute("stroke-width", "1");
|
|
@@ -850,10 +920,44 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
850
920
|
poly.setAttribute("stroke-linejoin", "round");
|
|
851
921
|
g.appendChild(poly);
|
|
852
922
|
});
|
|
923
|
+
const snapGlyphMidpoint = mkSnapGlyph((g) => {
|
|
924
|
+
const poly = document.createElementNS(SVGNS, "polygon");
|
|
925
|
+
poly.setAttribute("points", "11,3 19,18 3,18");
|
|
926
|
+
poly.setAttribute("fill", "rgba(255,255,255,0.9)");
|
|
927
|
+
poly.setAttribute("stroke", "#ff8800");
|
|
928
|
+
poly.setAttribute("stroke-width", "1.8");
|
|
929
|
+
poly.setAttribute("stroke-linejoin", "round");
|
|
930
|
+
g.appendChild(poly);
|
|
931
|
+
});
|
|
932
|
+
const snapGlyphNode = mkSnapGlyph((g) => {
|
|
933
|
+
const c = document.createElementNS(SVGNS, "circle");
|
|
934
|
+
c.setAttribute("cx", "11");
|
|
935
|
+
c.setAttribute("cy", "11");
|
|
936
|
+
c.setAttribute("r", "7");
|
|
937
|
+
c.setAttribute("fill", "rgba(255,255,255,0.9)");
|
|
938
|
+
c.setAttribute("stroke", "#ff8800");
|
|
939
|
+
c.setAttribute("stroke-width", "1.8");
|
|
940
|
+
g.appendChild(c);
|
|
941
|
+
const mkLn = (x1, y1, x2, y2) => {
|
|
942
|
+
const ln = document.createElementNS(SVGNS, "line");
|
|
943
|
+
ln.setAttribute("x1", String(x1));
|
|
944
|
+
ln.setAttribute("y1", String(y1));
|
|
945
|
+
ln.setAttribute("x2", String(x2));
|
|
946
|
+
ln.setAttribute("y2", String(y2));
|
|
947
|
+
ln.setAttribute("stroke", "#ff8800");
|
|
948
|
+
ln.setAttribute("stroke-width", "1.6");
|
|
949
|
+
ln.setAttribute("stroke-linecap", "round");
|
|
950
|
+
return ln;
|
|
951
|
+
};
|
|
952
|
+
g.appendChild(mkLn(6.5, 6.5, 15.5, 15.5));
|
|
953
|
+
g.appendChild(mkLn(15.5, 6.5, 6.5, 15.5));
|
|
954
|
+
});
|
|
853
955
|
const setSnapGlyph = (type) => {
|
|
854
956
|
snapGlyphEndpoint.style.display = type === "endpoint" ? "" : "none";
|
|
855
957
|
snapGlyphIntersection.style.display = type === "intersection" ? "" : "none";
|
|
856
958
|
snapGlyphLine.style.display = type === "line" ? "" : "none";
|
|
959
|
+
snapGlyphMidpoint.style.display = type === "midpoint" ? "" : "none";
|
|
960
|
+
snapGlyphNode.style.display = type === "node" ? "" : "none";
|
|
857
961
|
};
|
|
858
962
|
overlay.appendChild(snapEl);
|
|
859
963
|
measureRef.current = {
|
|
@@ -865,12 +969,14 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
865
969
|
fixedLine: fixedLineEl,
|
|
866
970
|
fixedLabel: null,
|
|
867
971
|
refLine: refLineEl,
|
|
972
|
+
refLineV: refLineVEl,
|
|
868
973
|
extLineA,
|
|
869
974
|
extLineB,
|
|
870
975
|
markers: [],
|
|
871
976
|
label: null,
|
|
872
977
|
snap: snapEl,
|
|
873
|
-
|
|
978
|
+
segs: null,
|
|
979
|
+
nodes: null
|
|
874
980
|
};
|
|
875
981
|
let THREE = null;
|
|
876
982
|
let ready = false;
|
|
@@ -880,28 +986,82 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
880
986
|
const w = canvas.clientWidth, h = canvas.clientHeight;
|
|
881
987
|
return { x: (v3.x + 1) / 2 * w, y: (-v3.y + 1) / 2 * h };
|
|
882
988
|
};
|
|
989
|
+
const MAX_SNAP_SEGS = 4e5;
|
|
883
990
|
(async () => {
|
|
884
991
|
try {
|
|
885
992
|
THREE = await import(
|
|
886
993
|
/* @vite-ignore */
|
|
887
994
|
'three'
|
|
888
995
|
);
|
|
889
|
-
const
|
|
890
|
-
if (!
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
996
|
+
const st = measureRef.current;
|
|
997
|
+
if (!st) return;
|
|
998
|
+
const segXY = [];
|
|
999
|
+
const nodeXY = [];
|
|
1000
|
+
let truncated = false;
|
|
1001
|
+
const instanceXforms = (g) => {
|
|
1002
|
+
const t0 = g?.attributes?.instanceTransform0;
|
|
1003
|
+
const t1 = g?.attributes?.instanceTransform1;
|
|
1004
|
+
if (t0 && t1) {
|
|
1005
|
+
const out = [];
|
|
1006
|
+
const n = Math.min(t0.count, t1.count);
|
|
1007
|
+
for (let k = 0; k < n; k++) {
|
|
1008
|
+
out.push([t0.getX(k), t0.getY(k), t0.getZ(k), t1.getX(k), t1.getY(k), t1.getZ(k)]);
|
|
1009
|
+
}
|
|
1010
|
+
return out;
|
|
1011
|
+
}
|
|
1012
|
+
const tp = g?.attributes?.instanceTransform;
|
|
1013
|
+
if (tp) {
|
|
1014
|
+
const out = [];
|
|
1015
|
+
for (let k = 0; k < tp.count; k++) out.push([1, 0, tp.getX(k), 0, 1, tp.getY(k)]);
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
return [[1, 0, 0, 0, 1, 0]];
|
|
1019
|
+
};
|
|
1020
|
+
const visit = (obj) => {
|
|
1021
|
+
const isSeg = !!obj?.isLineSegments;
|
|
1022
|
+
const isPts = !!obj?.isPoints;
|
|
1023
|
+
if (!isSeg && !isPts) return;
|
|
1024
|
+
const g = obj.geometry;
|
|
1025
|
+
const pos = g?.attributes?.position;
|
|
895
1026
|
if (!pos) return;
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1027
|
+
const xfs = instanceXforms(g);
|
|
1028
|
+
if (isSeg) {
|
|
1029
|
+
const idx = g.index;
|
|
1030
|
+
const pairCount = Math.floor((idx ? idx.count : pos.count) / 2);
|
|
1031
|
+
for (let p = 0; p < pairCount; p++) {
|
|
1032
|
+
const ia = idx ? idx.getX(2 * p) : 2 * p;
|
|
1033
|
+
const ib = idx ? idx.getX(2 * p + 1) : 2 * p + 1;
|
|
1034
|
+
const ax = pos.getX(ia), ay = pos.getY(ia);
|
|
1035
|
+
const bx = pos.getX(ib), by = pos.getY(ib);
|
|
1036
|
+
if (!Number.isFinite(ax + ay + bx + by)) continue;
|
|
1037
|
+
for (const m of xfs) {
|
|
1038
|
+
if (segXY.length >= MAX_SNAP_SEGS * 4) {
|
|
1039
|
+
truncated = true;
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const tax = m[0] * ax + m[1] * ay + m[2], tay = m[3] * ax + m[4] * ay + m[5];
|
|
1043
|
+
const tbx = m[0] * bx + m[1] * by + m[2], tby = m[3] * bx + m[4] * by + m[5];
|
|
1044
|
+
if (tax === tbx && tay === tby) continue;
|
|
1045
|
+
segXY.push(tax, tay, tbx, tby);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
} else {
|
|
1049
|
+
for (let i = 0; i < pos.count; i++) {
|
|
1050
|
+
const x = pos.getX(i), y = pos.getY(i);
|
|
1051
|
+
if (!Number.isFinite(x + y)) continue;
|
|
1052
|
+
for (const m of xfs) {
|
|
1053
|
+
nodeXY.push(m[0] * x + m[1] * y + m[2], m[3] * x + m[4] * y + m[5]);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
903
1056
|
}
|
|
904
|
-
}
|
|
1057
|
+
};
|
|
1058
|
+
if (typeof scene.traverseVisible === "function") scene.traverseVisible(visit);
|
|
1059
|
+
else scene.traverse(visit);
|
|
1060
|
+
st.segs = new Float64Array(segXY);
|
|
1061
|
+
st.nodes = new Float64Array(nodeXY);
|
|
1062
|
+
if (truncated) {
|
|
1063
|
+
console.warn(`[Preview] DXF snap cache truncated at ${MAX_SNAP_SEGS} segments \u2014 snapping may miss some geometry.`);
|
|
1064
|
+
}
|
|
905
1065
|
ready = true;
|
|
906
1066
|
} catch {
|
|
907
1067
|
}
|
|
@@ -918,54 +1078,92 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
918
1078
|
};
|
|
919
1079
|
const findSnap = (cx, cy) => {
|
|
920
1080
|
const s = measureRef.current;
|
|
921
|
-
if (!s || !ready) return null;
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const
|
|
1081
|
+
if (!s || !ready || !s.segs) return null;
|
|
1082
|
+
const o = pxFromScene(0, 0);
|
|
1083
|
+
const ex = pxFromScene(1, 0);
|
|
1084
|
+
const ey = pxFromScene(0, 1);
|
|
1085
|
+
const mxx = ex.x - o.x, mxy = ex.y - o.y;
|
|
1086
|
+
const myx = ey.x - o.x, myy = ey.y - o.y;
|
|
1087
|
+
const det = mxx * myy - myx * mxy;
|
|
1088
|
+
if (!det) return null;
|
|
1089
|
+
const csx = ((cx - o.x) * myy - (cy - o.y) * myx) / det;
|
|
1090
|
+
const csy = ((cy - o.y) * mxx - (cx - o.x) * mxy) / det;
|
|
1091
|
+
const rx = SNAP_PX / (Math.hypot(mxx, mxy) || 1);
|
|
1092
|
+
const ry = SNAP_PX / (Math.hypot(myx, myy) || 1);
|
|
1093
|
+
const minX = csx - rx, maxX = csx + rx, minY = csy - ry, maxY = csy + ry;
|
|
1094
|
+
const T2 = SNAP_PX * SNAP_PX;
|
|
1095
|
+
let bestPt = null, dPt = T2;
|
|
1096
|
+
let bestMid = null, dMid = T2;
|
|
1097
|
+
let bestLine = null, dLine = T2;
|
|
1098
|
+
let bestX = null, dX = T2;
|
|
925
1099
|
const cand = [];
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
const ldx = seg.bx - seg.ax, ldy = seg.by - seg.ay;
|
|
934
|
-
const llen = Math.sqrt(ldx * ldx + ldy * ldy) || 1;
|
|
935
|
-
const segDir = { dx: ldx / llen, dy: ldy / llen };
|
|
1100
|
+
const segs = s.segs;
|
|
1101
|
+
for (let i = 0; i < segs.length; i += 4) {
|
|
1102
|
+
const ax = segs[i], ay = segs[i + 1], bx = segs[i + 2], by = segs[i + 3];
|
|
1103
|
+
if (ax < minX && bx < minX || ax > maxX && bx > maxX || ay < minY && by < minY || ay > maxY && by > maxY) continue;
|
|
1104
|
+
const apx = o.x + mxx * ax + myx * ay, apy = o.y + mxy * ax + myy * ay;
|
|
1105
|
+
const bpx = o.x + mxx * bx + myx * by, bpy = o.y + mxy * bx + myy * by;
|
|
1106
|
+
let near = false;
|
|
936
1107
|
let dx = cx - apx, dy = cy - apy;
|
|
937
|
-
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
|
|
1108
|
+
let d2 = dx * dx + dy * dy;
|
|
1109
|
+
if (d2 < T2) {
|
|
1110
|
+
near = true;
|
|
1111
|
+
if (d2 < dPt) {
|
|
1112
|
+
dPt = d2;
|
|
1113
|
+
bestPt = { sx: ax, sy: ay, type: "endpoint" };
|
|
1114
|
+
}
|
|
941
1115
|
}
|
|
942
1116
|
dx = cx - bpx;
|
|
943
1117
|
dy = cy - bpy;
|
|
944
|
-
|
|
945
|
-
if (
|
|
946
|
-
|
|
947
|
-
|
|
1118
|
+
d2 = dx * dx + dy * dy;
|
|
1119
|
+
if (d2 < T2) {
|
|
1120
|
+
near = true;
|
|
1121
|
+
if (d2 < dPt) {
|
|
1122
|
+
dPt = d2;
|
|
1123
|
+
bestPt = { sx: bx, sy: by, type: "endpoint" };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
dx = cx - (apx + bpx) / 2;
|
|
1127
|
+
dy = cy - (apy + bpy) / 2;
|
|
1128
|
+
d2 = dx * dx + dy * dy;
|
|
1129
|
+
if (d2 < T2) {
|
|
1130
|
+
near = true;
|
|
1131
|
+
if (d2 < dMid) {
|
|
1132
|
+
dMid = d2;
|
|
1133
|
+
bestMid = { sx: (ax + bx) / 2, sy: (ay + by) / 2, type: "midpoint" };
|
|
1134
|
+
}
|
|
948
1135
|
}
|
|
949
1136
|
const sdx = bpx - apx, sdy = bpy - apy;
|
|
950
1137
|
const len2 = sdx * sdx + sdy * sdy;
|
|
951
|
-
let dLine2 = Infinity;
|
|
952
1138
|
if (len2 > 0) {
|
|
953
1139
|
const t = ((cx - apx) * sdx + (cy - apy) * sdy) / len2;
|
|
954
1140
|
if (t > 0 && t < 1) {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1141
|
+
dx = cx - (apx + t * sdx);
|
|
1142
|
+
dy = cy - (apy + t * sdy);
|
|
1143
|
+
d2 = dx * dx + dy * dy;
|
|
1144
|
+
if (d2 < T2) {
|
|
1145
|
+
near = true;
|
|
1146
|
+
if (d2 < dLine) {
|
|
1147
|
+
dLine = d2;
|
|
1148
|
+
bestLine = { sx: ax + t * (bx - ax), sy: ay + t * (by - ay), type: "line" };
|
|
1149
|
+
}
|
|
964
1150
|
}
|
|
965
1151
|
}
|
|
966
1152
|
}
|
|
967
|
-
if (
|
|
968
|
-
|
|
1153
|
+
if (near) cand.push({ ax, ay, bx, by, apx, apy, bpx, bpy });
|
|
1154
|
+
}
|
|
1155
|
+
const nodes = s.nodes;
|
|
1156
|
+
if (nodes) {
|
|
1157
|
+
for (let i = 0; i < nodes.length; i += 2) {
|
|
1158
|
+
const x = nodes[i], y = nodes[i + 1];
|
|
1159
|
+
if (x < minX || x > maxX || y < minY || y > maxY) continue;
|
|
1160
|
+
const dx = cx - (o.x + mxx * x + myx * y);
|
|
1161
|
+
const dy = cy - (o.y + mxy * x + myy * y);
|
|
1162
|
+
const d2 = dx * dx + dy * dy;
|
|
1163
|
+
if (d2 < dPt) {
|
|
1164
|
+
dPt = d2;
|
|
1165
|
+
bestPt = { sx: x, sy: y, type: "node" };
|
|
1166
|
+
}
|
|
969
1167
|
}
|
|
970
1168
|
}
|
|
971
1169
|
for (let i = 0; i < cand.length; i++) {
|
|
@@ -976,16 +1174,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
976
1174
|
if (!ix) continue;
|
|
977
1175
|
const dx = cx - ix.px, dy = cy - ix.py;
|
|
978
1176
|
const d2 = dx * dx + dy * dy;
|
|
979
|
-
if (d2 >=
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const sy = A.seg.ay + ix.t * (A.seg.by - A.seg.ay);
|
|
983
|
-
best = { sx, sy, type: "intersection" };
|
|
984
|
-
bestD2 = d2;
|
|
985
|
-
}
|
|
1177
|
+
if (d2 >= T2 || d2 >= dX) continue;
|
|
1178
|
+
dX = d2;
|
|
1179
|
+
bestX = { sx: A.ax + ix.t * (A.bx - A.ax), sy: A.ay + ix.t * (A.by - A.ay), type: "intersection" };
|
|
986
1180
|
}
|
|
987
1181
|
}
|
|
988
|
-
|
|
1182
|
+
const top = bestX && bestPt ? dX < dPt - 1 ? bestX : bestPt : bestX ?? bestPt;
|
|
1183
|
+
return top ?? bestMid ?? bestLine;
|
|
989
1184
|
};
|
|
990
1185
|
const makeMarker = () => {
|
|
991
1186
|
const el = document.createElement("div");
|
|
@@ -1026,16 +1221,23 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1026
1221
|
s.picks[1] = { x: raw.x, y: raw.y };
|
|
1027
1222
|
}
|
|
1028
1223
|
};
|
|
1224
|
+
const resolveMode = (a, b) => {
|
|
1225
|
+
const mode = measureModeRef.current;
|
|
1226
|
+
if (mode !== "auto") return mode;
|
|
1227
|
+
if (!a || !b) return "horizontal";
|
|
1228
|
+
return Math.abs(b.x - a.x) >= Math.abs(b.y - a.y) ? "horizontal" : "vertical";
|
|
1229
|
+
};
|
|
1029
1230
|
const computeMainDimEnds = () => {
|
|
1030
1231
|
const s = measureRef.current;
|
|
1031
1232
|
const a = s.picks[0];
|
|
1032
1233
|
const b = s.picks[1];
|
|
1033
|
-
const mode =
|
|
1234
|
+
const mode = resolveMode(a, b);
|
|
1235
|
+
const rawMode = measureModeRef.current;
|
|
1034
1236
|
const fixed = measureFixedDistRef.current;
|
|
1035
|
-
if (fixed !== null && Number.isFinite(fixed) &&
|
|
1237
|
+
if (fixed !== null && Number.isFinite(fixed) && rawMode === "horizontal") {
|
|
1036
1238
|
return { from: { x: b.x, y: a.y }, to: { x: b.x, y: b.y } };
|
|
1037
1239
|
}
|
|
1038
|
-
if (fixed !== null && Number.isFinite(fixed) &&
|
|
1240
|
+
if (fixed !== null && Number.isFinite(fixed) && rawMode === "vertical") {
|
|
1039
1241
|
return { from: { x: a.x, y: b.y }, to: { x: b.x, y: b.y } };
|
|
1040
1242
|
}
|
|
1041
1243
|
if (mode === "horizontal") return { from: a, to: { x: b.x, y: a.y } };
|
|
@@ -1046,36 +1248,49 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1046
1248
|
const s = measureRef.current;
|
|
1047
1249
|
if (!s) return;
|
|
1048
1250
|
reconcileLockedPick();
|
|
1049
|
-
const
|
|
1251
|
+
const rawMode = measureModeRef.current;
|
|
1050
1252
|
const fixed = measureFixedDistRef.current;
|
|
1051
|
-
const fixedActive = fixed !== null && Number.isFinite(fixed) && (
|
|
1253
|
+
const fixedActive = fixed !== null && Number.isFinite(fixed) && (rawMode === "horizontal" || rawMode === "vertical");
|
|
1052
1254
|
if (s.markers[0]) positionMarker(s.markers[0], s.picks[0].x, s.picks[0].y);
|
|
1053
1255
|
if (s.markers[1]) positionMarker(s.markers[1], s.picks[1].x, s.picks[1].y);
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1256
|
+
const drawRefLine = (el, dir, visible) => {
|
|
1257
|
+
if (!el) return;
|
|
1258
|
+
if (!visible || s.picks.length < 1) {
|
|
1259
|
+
el.style.display = "none";
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1056
1262
|
const a = s.picks[0];
|
|
1057
1263
|
const w = canvas.clientWidth, h = canvas.clientHeight;
|
|
1058
1264
|
const screenSpan = Math.hypot(w, h) * 4;
|
|
1059
1265
|
const ap = pxFromScene(a.x, a.y);
|
|
1060
|
-
const probe = pxFromScene(a.x +
|
|
1266
|
+
const probe = pxFromScene(a.x + dir.dx, a.y + dir.dy);
|
|
1061
1267
|
const pxLen = Math.hypot(probe.x - ap.x, probe.y - ap.y) || 1;
|
|
1062
1268
|
const sceneStep = screenSpan / pxLen;
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1269
|
+
const p0 = pxFromScene(a.x - dir.dx * sceneStep, a.y - dir.dy * sceneStep);
|
|
1270
|
+
const p1 = pxFromScene(a.x + dir.dx * sceneStep, a.y + dir.dy * sceneStep);
|
|
1271
|
+
el.setAttribute("x1", String(p0.x));
|
|
1272
|
+
el.setAttribute("y1", String(p0.y));
|
|
1273
|
+
el.setAttribute("x2", String(p1.x));
|
|
1274
|
+
el.setAttribute("y2", String(p1.y));
|
|
1275
|
+
el.style.display = "";
|
|
1276
|
+
};
|
|
1277
|
+
let showH = rawMode === "horizontal";
|
|
1278
|
+
let showV = rawMode === "vertical";
|
|
1279
|
+
if (rawMode === "auto") {
|
|
1280
|
+
if (s.picks.length >= 2) {
|
|
1281
|
+
const r = resolveMode(s.picks[0], s.picks[1]);
|
|
1282
|
+
showH = r === "horizontal";
|
|
1283
|
+
showV = r === "vertical";
|
|
1284
|
+
} else {
|
|
1285
|
+
showH = true;
|
|
1286
|
+
showV = true;
|
|
1287
|
+
}
|
|
1076
1288
|
}
|
|
1289
|
+
drawRefLine(s.refLine, { dx: 1, dy: 0 }, showH);
|
|
1290
|
+
drawRefLine(s.refLineV, { dx: 0, dy: 1 }, showV);
|
|
1077
1291
|
if (s.picks.length === 2) {
|
|
1078
1292
|
const a = s.picks[0], b = s.picks[1];
|
|
1293
|
+
const mode = resolveMode(a, b);
|
|
1079
1294
|
const ends = computeMainDimEnds();
|
|
1080
1295
|
const fp = pxFromScene(ends.from.x, ends.from.y);
|
|
1081
1296
|
const tp = pxFromScene(ends.to.x, ends.to.y);
|
|
@@ -1212,28 +1427,46 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1212
1427
|
if (!s || s.picks.length !== 2) return;
|
|
1213
1428
|
reconcileLockedPick();
|
|
1214
1429
|
const a = s.picks[0], b = s.picks[1];
|
|
1215
|
-
const
|
|
1430
|
+
const rawMode = measureModeRef.current;
|
|
1431
|
+
const mode = resolveMode(a, b);
|
|
1216
1432
|
const fixed = measureFixedDistRef.current;
|
|
1217
|
-
const fixedActive = fixed !== null && Number.isFinite(fixed) && (
|
|
1433
|
+
const fixedActive = fixed !== null && Number.isFinite(fixed) && (rawMode === "horizontal" || rawMode === "vertical");
|
|
1218
1434
|
let dist;
|
|
1219
1435
|
let suffix = "";
|
|
1220
|
-
|
|
1221
|
-
|
|
1436
|
+
let echo;
|
|
1437
|
+
let shown = mode;
|
|
1438
|
+
const adx = Math.abs(b.x - a.x), ady = Math.abs(b.y - a.y);
|
|
1439
|
+
if (fixedActive && rawMode === "horizontal") {
|
|
1440
|
+
dist = ady;
|
|
1222
1441
|
suffix = " \u2195";
|
|
1223
|
-
|
|
1224
|
-
|
|
1442
|
+
shown = "vertical";
|
|
1443
|
+
echo = `\u0394Y = ${formatMeasureDistance(ady)} with \u0394X locked to ${formatMeasureDistance(Math.abs(fixed))}`;
|
|
1444
|
+
} else if (fixedActive && rawMode === "vertical") {
|
|
1445
|
+
dist = adx;
|
|
1225
1446
|
suffix = " \u2194";
|
|
1447
|
+
shown = "horizontal";
|
|
1448
|
+
echo = `\u0394X = ${formatMeasureDistance(adx)} with \u0394Y locked to ${formatMeasureDistance(Math.abs(fixed))}`;
|
|
1226
1449
|
} else if (mode === "horizontal") {
|
|
1227
|
-
dist =
|
|
1450
|
+
dist = adx;
|
|
1228
1451
|
suffix = " \u2194";
|
|
1452
|
+
echo = `Linear dimension = ${formatMeasureDistance(adx)} (\u0394X)`;
|
|
1229
1453
|
} else if (mode === "vertical") {
|
|
1230
|
-
dist =
|
|
1454
|
+
dist = ady;
|
|
1231
1455
|
suffix = " \u2195";
|
|
1456
|
+
echo = `Linear dimension = ${formatMeasureDistance(ady)} (\u0394Y)`;
|
|
1232
1457
|
} else {
|
|
1233
|
-
|
|
1234
|
-
|
|
1458
|
+
dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1459
|
+
echo = `Distance = ${formatMeasureDistance(dist)} \u0394X = ${formatMeasureDistance(adx)} \u0394Y = ${formatMeasureDistance(ady)}`;
|
|
1460
|
+
}
|
|
1461
|
+
if (!Number.isFinite(dist)) {
|
|
1462
|
+
setMeasureDistance(null);
|
|
1463
|
+
setMeasureResolved(null);
|
|
1464
|
+
if (s.label) s.label.style.opacity = "0";
|
|
1465
|
+
return;
|
|
1235
1466
|
}
|
|
1236
1467
|
setMeasureDistance(dist);
|
|
1468
|
+
setMeasureResolved(shown);
|
|
1469
|
+
setCmdEcho(echo);
|
|
1237
1470
|
const label = ensureLabel();
|
|
1238
1471
|
label.style.opacity = "1";
|
|
1239
1472
|
label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
|
|
@@ -1242,23 +1475,42 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1242
1475
|
recomputeLabel();
|
|
1243
1476
|
updateOverlay();
|
|
1244
1477
|
};
|
|
1478
|
+
const hideDimVisuals = (s) => {
|
|
1479
|
+
s.line.style.display = "none";
|
|
1480
|
+
if (s.fixedLine) s.fixedLine.style.display = "none";
|
|
1481
|
+
if (s.fixedLabel) s.fixedLabel.style.display = "none";
|
|
1482
|
+
if (s.extLineA) s.extLineA.style.display = "none";
|
|
1483
|
+
if (s.extLineB) s.extLineB.style.display = "none";
|
|
1484
|
+
if (s.label) s.label.style.opacity = "0";
|
|
1485
|
+
setMeasureDistance(null);
|
|
1486
|
+
setMeasureResolved(null);
|
|
1487
|
+
};
|
|
1488
|
+
const resetPicks = () => {
|
|
1489
|
+
const s = measureRef.current;
|
|
1490
|
+
if (!s) return;
|
|
1491
|
+
for (const m of s.markers) m.parentElement?.removeChild(m);
|
|
1492
|
+
s.markers = [];
|
|
1493
|
+
s.picks = [];
|
|
1494
|
+
s.rawSecondClick = null;
|
|
1495
|
+
if (s.refLine) s.refLine.style.display = "none";
|
|
1496
|
+
if (s.refLineV) s.refLineV.style.display = "none";
|
|
1497
|
+
hideDimVisuals(s);
|
|
1498
|
+
};
|
|
1499
|
+
measureResetRef.current = resetPicks;
|
|
1500
|
+
measureUndoRef.current = () => {
|
|
1501
|
+
const s = measureRef.current;
|
|
1502
|
+
if (!s || s.picks.length === 0) return;
|
|
1503
|
+
s.picks.pop();
|
|
1504
|
+
const m = s.markers.pop();
|
|
1505
|
+
m?.parentElement?.removeChild(m);
|
|
1506
|
+
s.rawSecondClick = null;
|
|
1507
|
+
hideDimVisuals(s);
|
|
1508
|
+
updateOverlay();
|
|
1509
|
+
};
|
|
1245
1510
|
const doPick = (p) => {
|
|
1246
1511
|
const s = measureRef.current;
|
|
1247
1512
|
if (!s) return;
|
|
1248
|
-
if (s.picks.length === 2)
|
|
1249
|
-
for (const m of s.markers) m.parentElement?.removeChild(m);
|
|
1250
|
-
s.markers = [];
|
|
1251
|
-
s.picks = [];
|
|
1252
|
-
s.rawSecondClick = null;
|
|
1253
|
-
s.line.style.display = "none";
|
|
1254
|
-
if (s.fixedLine) s.fixedLine.style.display = "none";
|
|
1255
|
-
if (s.fixedLabel) s.fixedLabel.style.display = "none";
|
|
1256
|
-
if (s.refLine) s.refLine.style.display = "none";
|
|
1257
|
-
if (s.extLineA) s.extLineA.style.display = "none";
|
|
1258
|
-
if (s.extLineB) s.extLineB.style.display = "none";
|
|
1259
|
-
if (s.label) s.label.style.opacity = "0";
|
|
1260
|
-
setMeasureDistance(null);
|
|
1261
|
-
}
|
|
1513
|
+
if (s.picks.length === 2) resetPicks();
|
|
1262
1514
|
if (s.picks.length === 0) {
|
|
1263
1515
|
s.picks.push({ x: p.x, y: p.y });
|
|
1264
1516
|
s.markers.push(makeMarker());
|
|
@@ -1305,10 +1557,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1305
1557
|
canvas.removeEventListener("pointermove", handlePointerMove);
|
|
1306
1558
|
window.removeEventListener("keydown", onKeyDown);
|
|
1307
1559
|
measureRedrawRef.current = null;
|
|
1560
|
+
measureResetRef.current = null;
|
|
1561
|
+
measureUndoRef.current = null;
|
|
1308
1562
|
teardown();
|
|
1309
1563
|
setMeasureDistance(null);
|
|
1564
|
+
setMeasureResolved(null);
|
|
1310
1565
|
};
|
|
1311
|
-
}, [measureEnabled, loading, error]);
|
|
1566
|
+
}, [measureEnabled, loading, error, layersKey]);
|
|
1312
1567
|
useEffect(() => {
|
|
1313
1568
|
measureRedrawRef.current?.();
|
|
1314
1569
|
}, [measureMode, measureFixedDist]);
|
|
@@ -1358,12 +1613,170 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1358
1613
|
} catch {
|
|
1359
1614
|
}
|
|
1360
1615
|
};
|
|
1616
|
+
const runCommand = (raw) => {
|
|
1617
|
+
const exec = raw.trim() || lastCmdRef.current;
|
|
1618
|
+
setCmdValue("");
|
|
1619
|
+
if (!exec) return;
|
|
1620
|
+
const cmd = exec.toLowerCase().split(/\s+/)[0];
|
|
1621
|
+
const remember = () => {
|
|
1622
|
+
lastCmdRef.current = exec;
|
|
1623
|
+
};
|
|
1624
|
+
const startMeasure = (mode, echo) => {
|
|
1625
|
+
setMeasureEnabled(true);
|
|
1626
|
+
setMeasureMode(mode);
|
|
1627
|
+
measureResetRef.current?.();
|
|
1628
|
+
setCmdEcho(echo);
|
|
1629
|
+
remember();
|
|
1630
|
+
};
|
|
1631
|
+
if (/^-?(\d+\.?\d*|\.\d+)$/.test(cmd)) {
|
|
1632
|
+
if (measureEnabled && (measureMode === "horizontal" || measureMode === "vertical")) {
|
|
1633
|
+
const n = parseFloat(cmd);
|
|
1634
|
+
if (n) {
|
|
1635
|
+
setMeasureFixedInput(cmd);
|
|
1636
|
+
setMeasureFixedDist(n);
|
|
1637
|
+
setCmdEcho(`\u0394${measureMode === "horizontal" ? "X" : "Y"} locked to ${formatMeasureDistance(Math.abs(n))} \u2014 the label shows the perpendicular distance`);
|
|
1638
|
+
} else {
|
|
1639
|
+
setMeasureFixedInput("");
|
|
1640
|
+
setMeasureFixedDist(null);
|
|
1641
|
+
setCmdEcho("Fixed distance cleared.");
|
|
1642
|
+
}
|
|
1643
|
+
} else {
|
|
1644
|
+
setCmdEcho("Fixed distances apply in H or V mode \u2014 type H or V first.");
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
switch (cmd) {
|
|
1649
|
+
case "di":
|
|
1650
|
+
case "dist":
|
|
1651
|
+
case "mea":
|
|
1652
|
+
case "measuregeom":
|
|
1653
|
+
startMeasure("point", "DIST \u2014 click two points; straight-line distance (Esc to exit)");
|
|
1654
|
+
break;
|
|
1655
|
+
case "dim":
|
|
1656
|
+
case "dli":
|
|
1657
|
+
case "dimlin":
|
|
1658
|
+
case "dimlinear":
|
|
1659
|
+
startMeasure("auto", "DIMLINEAR \u2014 click two points; measures \u0394X or \u0394Y, whichever is larger (H / V to force)");
|
|
1660
|
+
break;
|
|
1661
|
+
// H / V / AUTO switch the axis without dropping existing picks —
|
|
1662
|
+
// same as clicking the pill.
|
|
1663
|
+
case "h":
|
|
1664
|
+
case "hor":
|
|
1665
|
+
case "horizontal":
|
|
1666
|
+
setMeasureEnabled(true);
|
|
1667
|
+
setMeasureMode("horizontal");
|
|
1668
|
+
setCmdEcho("Horizontal \u2014 \u0394X between the two picks");
|
|
1669
|
+
remember();
|
|
1670
|
+
break;
|
|
1671
|
+
case "v":
|
|
1672
|
+
case "ver":
|
|
1673
|
+
case "vertical":
|
|
1674
|
+
setMeasureEnabled(true);
|
|
1675
|
+
setMeasureMode("vertical");
|
|
1676
|
+
setCmdEcho("Vertical \u2014 \u0394Y between the two picks");
|
|
1677
|
+
remember();
|
|
1678
|
+
break;
|
|
1679
|
+
case "au":
|
|
1680
|
+
case "auto":
|
|
1681
|
+
setMeasureEnabled(true);
|
|
1682
|
+
setMeasureMode("auto");
|
|
1683
|
+
setCmdEcho("Auto (DIMLINEAR) \u2014 measures along the dominant axis of the two picks");
|
|
1684
|
+
remember();
|
|
1685
|
+
break;
|
|
1686
|
+
case "measure":
|
|
1687
|
+
setMeasureEnabled(!measureEnabled);
|
|
1688
|
+
setCmdEcho(measureEnabled ? "Measure off." : "Measure on \u2014 click two points.");
|
|
1689
|
+
remember();
|
|
1690
|
+
break;
|
|
1691
|
+
case "u":
|
|
1692
|
+
case "undo":
|
|
1693
|
+
measureUndoRef.current?.();
|
|
1694
|
+
setCmdEcho("Last pick removed.");
|
|
1695
|
+
remember();
|
|
1696
|
+
break;
|
|
1697
|
+
case "z":
|
|
1698
|
+
case "ze":
|
|
1699
|
+
case "zoom":
|
|
1700
|
+
case "fit":
|
|
1701
|
+
case "zoomextents":
|
|
1702
|
+
handleResetView();
|
|
1703
|
+
setCmdEcho("Zoom extents.");
|
|
1704
|
+
remember();
|
|
1705
|
+
break;
|
|
1706
|
+
case "la":
|
|
1707
|
+
case "layer":
|
|
1708
|
+
case "layers":
|
|
1709
|
+
setShowLayers((s) => !s);
|
|
1710
|
+
setCmdEcho("Layer panel toggled.");
|
|
1711
|
+
remember();
|
|
1712
|
+
break;
|
|
1713
|
+
case "off":
|
|
1714
|
+
case "clear":
|
|
1715
|
+
setMeasureFixedDist(null);
|
|
1716
|
+
setMeasureFixedInput("");
|
|
1717
|
+
measureResetRef.current?.();
|
|
1718
|
+
setCmdEcho("Measurement cleared.");
|
|
1719
|
+
remember();
|
|
1720
|
+
break;
|
|
1721
|
+
case "?":
|
|
1722
|
+
case "help":
|
|
1723
|
+
setCmdEcho(DXF_CMD_HELP);
|
|
1724
|
+
break;
|
|
1725
|
+
default: {
|
|
1726
|
+
const editCmd = DXF_EDIT_COMMANDS[cmd];
|
|
1727
|
+
if (editCmd) setCmdEcho(`${editCmd} is a drawing command \u2014 Preview is a read-only viewer. Measuring: DI, DIM, H, V (? for help)`);
|
|
1728
|
+
else setCmdEcho(`Unknown command "${cmd.toUpperCase()}" \u2014 try DI, DIM, H, V, Z, LA (? for help)`);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
useEffect(() => {
|
|
1733
|
+
const onKey = (e) => {
|
|
1734
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1735
|
+
if (e.key.length !== 1 || e.key === " ") return;
|
|
1736
|
+
const t = e.target;
|
|
1737
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
|
|
1738
|
+
const root = rootRef.current;
|
|
1739
|
+
if (!root) return;
|
|
1740
|
+
const myModal = root.closest("[data-modal-id]");
|
|
1741
|
+
if (myModal && getActiveModalId() !== myModal.dataset.modalId) return;
|
|
1742
|
+
cmdInputRef.current?.focus();
|
|
1743
|
+
};
|
|
1744
|
+
window.addEventListener("keydown", onKey);
|
|
1745
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1746
|
+
}, []);
|
|
1747
|
+
const cmdValueRef = useRef(cmdValue);
|
|
1748
|
+
const measureEnabledRef = useRef(measureEnabled);
|
|
1749
|
+
useEffect(() => {
|
|
1750
|
+
cmdValueRef.current = cmdValue;
|
|
1751
|
+
}, [cmdValue]);
|
|
1752
|
+
useEffect(() => {
|
|
1753
|
+
measureEnabledRef.current = measureEnabled;
|
|
1754
|
+
}, [measureEnabled]);
|
|
1755
|
+
useEffect(() => {
|
|
1756
|
+
return registerModalEscapeInterceptor(() => {
|
|
1757
|
+
const root = rootRef.current;
|
|
1758
|
+
if (!root) return false;
|
|
1759
|
+
const myModal = root.closest("[data-modal-id]");
|
|
1760
|
+
if (!myModal || getActiveModalId() !== myModal.dataset.modalId) return false;
|
|
1761
|
+
if (cmdValueRef.current) {
|
|
1762
|
+
setCmdValue("");
|
|
1763
|
+
setCmdEcho("*Cancel*");
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
if (measureEnabledRef.current) {
|
|
1767
|
+
setMeasureEnabled(false);
|
|
1768
|
+
setCmdEcho("Measure off.");
|
|
1769
|
+
return true;
|
|
1770
|
+
}
|
|
1771
|
+
return false;
|
|
1772
|
+
});
|
|
1773
|
+
}, []);
|
|
1361
1774
|
const btn = "px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1";
|
|
1362
1775
|
const colorHex = (n) => {
|
|
1363
1776
|
if (typeof n !== "number") return "#999";
|
|
1364
1777
|
return "#" + n.toString(16).padStart(6, "0");
|
|
1365
1778
|
};
|
|
1366
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
|
|
1779
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "flex flex-col h-full", children: [
|
|
1367
1780
|
/* @__PURE__ */ jsxs(PanelActions, { children: [
|
|
1368
1781
|
/* @__PURE__ */ jsxs(
|
|
1369
1782
|
"button",
|
|
@@ -1391,7 +1804,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1391
1804
|
{
|
|
1392
1805
|
onClick: () => setMeasureEnabled((m) => !m),
|
|
1393
1806
|
className: btn + (measureEnabled ? " bg-gray-200" : ""),
|
|
1394
|
-
title: measureEnabled ? "Stop measuring (Esc)" : "Measure distance \u2014 click two points on the drawing",
|
|
1807
|
+
title: measureEnabled ? "Stop measuring (Esc)" : "Measure distance \u2014 click two points on the drawing, or type DI / DIM below",
|
|
1395
1808
|
children: [
|
|
1396
1809
|
/* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
|
|
1397
1810
|
/* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 14.25l6-6 6 6 4.5-4.5M9.75 8.25v3M12.75 11.25v3M15.75 14.25v3" }),
|
|
@@ -1406,10 +1819,10 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1406
1819
|
/* @__PURE__ */ jsx(
|
|
1407
1820
|
"button",
|
|
1408
1821
|
{
|
|
1409
|
-
onClick: () => setMeasureMode("
|
|
1410
|
-
className: `px-2 transition-colors ${measureMode === "
|
|
1411
|
-
title: "
|
|
1412
|
-
children: "
|
|
1822
|
+
onClick: () => setMeasureMode("auto"),
|
|
1823
|
+
className: `px-2 transition-colors ${measureMode === "auto" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
|
|
1824
|
+
title: "Auto \u2014 AutoCAD DIMLINEAR: measures \u0394X or \u0394Y, whichever is larger between the two picks",
|
|
1825
|
+
children: "Auto"
|
|
1413
1826
|
}
|
|
1414
1827
|
),
|
|
1415
1828
|
/* @__PURE__ */ jsx(
|
|
@@ -1429,6 +1842,15 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1429
1842
|
title: "Vertical \u2014 distance along the Y axis between two picks (AutoCAD DIMLINEAR vertical)",
|
|
1430
1843
|
children: "V"
|
|
1431
1844
|
}
|
|
1845
|
+
),
|
|
1846
|
+
/* @__PURE__ */ jsx(
|
|
1847
|
+
"button",
|
|
1848
|
+
{
|
|
1849
|
+
onClick: () => setMeasureMode("point"),
|
|
1850
|
+
className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
|
|
1851
|
+
title: "Point \u2014 straight-line (Euclidean) distance between two picks (AutoCAD DIST)",
|
|
1852
|
+
children: "Point"
|
|
1853
|
+
}
|
|
1432
1854
|
)
|
|
1433
1855
|
] }),
|
|
1434
1856
|
(measureMode === "horizontal" || measureMode === "vertical") && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -1471,10 +1893,10 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1471
1893
|
"div",
|
|
1472
1894
|
{
|
|
1473
1895
|
className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap",
|
|
1474
|
-
title:
|
|
1896
|
+
title: measureResolved === "horizontal" ? `Horizontal distance (\u0394x) between the two picked points${measureMode === "auto" ? " \u2014 Auto resolved to the X axis" : ""}` : measureResolved === "vertical" ? `Vertical distance (\u0394y) between the two picked points${measureMode === "auto" ? " \u2014 Auto resolved to the Y axis" : ""}` : "Straight-line distance between the two picked points",
|
|
1475
1897
|
children: [
|
|
1476
1898
|
formatMeasureDistance(measureDistance),
|
|
1477
|
-
|
|
1899
|
+
measureResolved === "horizontal" ? " \u2194" : measureResolved === "vertical" ? " \u2195" : ""
|
|
1478
1900
|
]
|
|
1479
1901
|
}
|
|
1480
1902
|
),
|
|
@@ -1530,10 +1952,50 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1530
1952
|
"Scroll to zoom"
|
|
1531
1953
|
] }),
|
|
1532
1954
|
/* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
|
|
1533
|
-
/* @__PURE__ */ jsx("span", { children: "Fit to reset" })
|
|
1955
|
+
/* @__PURE__ */ jsx("span", { children: "Fit to reset" }),
|
|
1956
|
+
/* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
|
|
1957
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1958
|
+
"Type ",
|
|
1959
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: "DI" }),
|
|
1960
|
+
" / ",
|
|
1961
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: "DIM" }),
|
|
1962
|
+
" to measure"
|
|
1963
|
+
] })
|
|
1534
1964
|
] }),
|
|
1535
1965
|
loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 text-sm text-gray-500", children: "Loading drawing\u2026" }),
|
|
1536
1966
|
error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center", children: error })
|
|
1967
|
+
] }),
|
|
1968
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t border-gray-200 bg-gray-50", children: [
|
|
1969
|
+
cmdEcho && /* @__PURE__ */ jsx("div", { className: "px-2.5 pt-1 text-[11px] font-mono text-gray-500 truncate", title: cmdEcho, children: cmdEcho }),
|
|
1970
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2.5 h-7", children: [
|
|
1971
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-mono font-semibold text-gray-400 select-none", children: ">" }),
|
|
1972
|
+
/* @__PURE__ */ jsx(
|
|
1973
|
+
"input",
|
|
1974
|
+
{
|
|
1975
|
+
ref: cmdInputRef,
|
|
1976
|
+
value: cmdValue,
|
|
1977
|
+
onChange: (e) => setCmdValue(e.target.value),
|
|
1978
|
+
onKeyDown: (e) => {
|
|
1979
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1980
|
+
e.preventDefault();
|
|
1981
|
+
runCommand(cmdValue);
|
|
1982
|
+
} else if (e.key === "Escape") {
|
|
1983
|
+
if (cmdValue) {
|
|
1984
|
+
e.stopPropagation();
|
|
1985
|
+
setCmdValue("");
|
|
1986
|
+
setCmdEcho("*Cancel*");
|
|
1987
|
+
} else {
|
|
1988
|
+
e.target.blur();
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1992
|
+
placeholder: "Type a command \u2014 DI, DIM, H, V, Z, LA (? for help)",
|
|
1993
|
+
spellCheck: false,
|
|
1994
|
+
autoComplete: "off",
|
|
1995
|
+
className: "flex-1 min-w-0 bg-transparent text-[11px] font-mono text-gray-700 focus:outline-none placeholder:text-gray-300"
|
|
1996
|
+
}
|
|
1997
|
+
)
|
|
1998
|
+
] })
|
|
1537
1999
|
] })
|
|
1538
2000
|
] });
|
|
1539
2001
|
}
|
|
@@ -1557,8 +2019,7 @@ function hexToRgb(OV, hex) {
|
|
|
1557
2019
|
return new OV.RGBColor(n >> 16 & 255, n >> 8 & 255, n & 255);
|
|
1558
2020
|
}
|
|
1559
2021
|
function formatMeasureDistance(mm) {
|
|
1560
|
-
if (mm >= 1e3) return `${(mm / 1e3).toFixed(
|
|
1561
|
-
if (mm >= 10) return `${mm.toFixed(1)} mm`;
|
|
2022
|
+
if (mm >= 1e3) return `${(mm / 1e3).toFixed(3)} m`;
|
|
1562
2023
|
return `${mm.toFixed(2)} mm`;
|
|
1563
2024
|
}
|
|
1564
2025
|
function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
@@ -2838,5 +3299,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
|
|
|
2838
3299
|
}
|
|
2839
3300
|
|
|
2840
3301
|
export { Preview, setPdfPreview };
|
|
2841
|
-
//# sourceMappingURL=chunk-
|
|
2842
|
-
//# sourceMappingURL=chunk-
|
|
3302
|
+
//# sourceMappingURL=chunk-UFTJG6IM.js.map
|
|
3303
|
+
//# sourceMappingURL=chunk-UFTJG6IM.js.map
|