react-os-shell 0.2.44 → 0.2.46
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/{Calendar-4UQDQ3NT.js → Calendar-PKRZ5MBU.js} +153 -54
- package/dist/Calendar-PKRZ5MBU.js.map +1 -0
- package/dist/{CurrencyConverter-TXBFDFG2.js → CurrencyConverter-5N44NZ6Z.js} +3 -3
- package/dist/{CurrencyConverter-TXBFDFG2.js.map → CurrencyConverter-5N44NZ6Z.js.map} +1 -1
- package/dist/{Email-HRBZUWPY.js → Email-CR6XS2AD.js} +4 -4
- package/dist/{Email-HRBZUWPY.js.map → Email-CR6XS2AD.js.map} +1 -1
- package/dist/{Files-ITIKVHIE.js → Files-CEID4TC3.js} +4 -4
- package/dist/{Files-ITIKVHIE.js.map → Files-CEID4TC3.js.map} +1 -1
- package/dist/{GeminiChat-ITU46EH4.js → GeminiChat-XTEBZIVK.js} +3 -3
- package/dist/{GeminiChat-ITU46EH4.js.map → GeminiChat-XTEBZIVK.js.map} +1 -1
- package/dist/{PomodoroTimer-PRP5CZ3S.js → PomodoroTimer-FHSOLF3O.js} +49 -49
- package/dist/PomodoroTimer-FHSOLF3O.js.map +1 -0
- package/dist/Preview-B5DUW2AR.js +7 -0
- package/dist/{Preview-4MBQI66Q.js.map → Preview-B5DUW2AR.js.map} +1 -1
- package/dist/TodoList-7JZ2SLDI.js +494 -0
- package/dist/TodoList-7JZ2SLDI.js.map +1 -0
- package/dist/{WorldClock-QO5PVJQQ.js → WorldClock-XHM7WAUV.js} +43 -97
- package/dist/WorldClock-XHM7WAUV.js.map +1 -0
- package/dist/apps/index.d.ts +4 -1
- package/dist/apps/index.js +14 -12
- package/dist/apps/index.js.map +1 -1
- package/dist/chunk-25L4DIKH.js +90 -0
- package/dist/chunk-25L4DIKH.js.map +1 -0
- package/dist/{chunk-62MVMTBT.js → chunk-5VXRBUEH.js} +20 -3
- package/dist/chunk-5VXRBUEH.js.map +1 -0
- package/dist/{chunk-MTLVXT2C.js → chunk-6AJUSDEM.js} +3 -3
- package/dist/{chunk-MTLVXT2C.js.map → chunk-6AJUSDEM.js.map} +1 -1
- package/dist/{chunk-46LICZUM.js → chunk-MVWEL34Y.js} +3 -2
- package/dist/chunk-MVWEL34Y.js.map +1 -0
- package/dist/{chunk-DUUANLLE.js → chunk-QBH7KERS.js} +482 -19
- package/dist/chunk-QBH7KERS.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/styles.css +6 -4
- package/package.json +1 -1
- package/dist/Calendar-4UQDQ3NT.js.map +0 -1
- package/dist/PomodoroTimer-PRP5CZ3S.js.map +0 -1
- package/dist/Preview-4MBQI66Q.js +0 -7
- package/dist/WorldClock-QO5PVJQQ.js.map +0 -1
- package/dist/chunk-46LICZUM.js.map +0 -1
- package/dist/chunk-62MVMTBT.js.map +0 -1
- package/dist/chunk-DUUANLLE.js.map +0 -1
|
@@ -664,6 +664,11 @@ function hexToRgb(OV, hex) {
|
|
|
664
664
|
const n = m ? parseInt(m[1], 16) : 0;
|
|
665
665
|
return new OV.RGBColor(n >> 16 & 255, n >> 8 & 255, n & 255);
|
|
666
666
|
}
|
|
667
|
+
function formatMeasureDistance(mm) {
|
|
668
|
+
if (mm >= 1e3) return `${(mm / 1e3).toFixed(2)} m`;
|
|
669
|
+
if (mm >= 10) return `${mm.toFixed(1)} mm`;
|
|
670
|
+
return `${mm.toFixed(2)} mm`;
|
|
671
|
+
}
|
|
667
672
|
function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
668
673
|
const containerRef = useRef(null);
|
|
669
674
|
const viewerRef = useRef(null);
|
|
@@ -680,15 +685,21 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
680
685
|
const [edgeThreshold, setEdgeThreshold] = useState(1);
|
|
681
686
|
const [showMeshes, setShowMeshes] = useState(false);
|
|
682
687
|
const [showSettings, setShowSettings] = useState(false);
|
|
683
|
-
const [perspective, setPerspective] = useState(
|
|
688
|
+
const [perspective, setPerspective] = useState(false);
|
|
684
689
|
const [sectionEnabled, setSectionEnabled] = useState(false);
|
|
685
|
-
const [sectionAxis, setSectionAxis] = useState("
|
|
690
|
+
const [sectionAxis, setSectionAxis] = useState("x");
|
|
686
691
|
const [sectionFlip, setSectionFlip] = useState(false);
|
|
687
692
|
const [sectionPosition, setSectionPosition] = useState(0.5);
|
|
693
|
+
const [sectionAngle, setSectionAngle] = useState(0);
|
|
694
|
+
const [measureEnabled, setMeasureEnabled] = useState(false);
|
|
695
|
+
const [measureMode, setMeasureMode] = useState("point");
|
|
696
|
+
const [measureDistance, setMeasureDistance] = useState(null);
|
|
697
|
+
const measureRef = useRef(null);
|
|
688
698
|
const sectionRef = useRef(null);
|
|
689
699
|
useEffect(() => {
|
|
690
700
|
let cancelled = false;
|
|
691
701
|
let viewer = null;
|
|
702
|
+
let resizeObserver = null;
|
|
692
703
|
setLoading(true);
|
|
693
704
|
setError(null);
|
|
694
705
|
setTree(null);
|
|
@@ -751,6 +762,18 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
751
762
|
viewerRef.current = viewer;
|
|
752
763
|
const inputFile = new OV.InputFile(filename, OV.FileSource.Url, url);
|
|
753
764
|
viewer.LoadModelFromInputFiles([inputFile]);
|
|
765
|
+
if (containerRef.current && typeof ResizeObserver !== "undefined") {
|
|
766
|
+
resizeObserver = new ResizeObserver(() => {
|
|
767
|
+
try {
|
|
768
|
+
const v = viewerRef.current;
|
|
769
|
+
if (v?.Resize) v.Resize();
|
|
770
|
+
else if (v?.viewer?.Resize) v.viewer.Resize();
|
|
771
|
+
v?.viewer?.Render?.();
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
resizeObserver.observe(containerRef.current);
|
|
776
|
+
}
|
|
754
777
|
} catch (e) {
|
|
755
778
|
if (!cancelled) {
|
|
756
779
|
setError(e?.message || "Failed to load 3D model.");
|
|
@@ -760,6 +783,10 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
760
783
|
})();
|
|
761
784
|
return () => {
|
|
762
785
|
cancelled = true;
|
|
786
|
+
try {
|
|
787
|
+
resizeObserver?.disconnect();
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
763
790
|
try {
|
|
764
791
|
viewer?.Destroy?.();
|
|
765
792
|
} catch {
|
|
@@ -1003,19 +1030,35 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1003
1030
|
const axisIdx = sectionAxis === "x" ? 0 : sectionAxis === "y" ? 1 : 2;
|
|
1004
1031
|
const min = [bbox.min.x, bbox.min.y, bbox.min.z][axisIdx];
|
|
1005
1032
|
const max = [bbox.max.x, bbox.max.y, bbox.max.z][axisIdx];
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1033
|
+
const t = min + (max - min) * sectionPosition;
|
|
1034
|
+
const \u03B8 = sectionAngle * Math.PI / 180;
|
|
1035
|
+
const cos\u03B8 = Math.cos(\u03B8), sin\u03B8 = Math.sin(\u03B8);
|
|
1036
|
+
const sign = sectionFlip ? -1 : 1;
|
|
1037
|
+
let nx = 0, ny = 0, nz = 0;
|
|
1038
|
+
if (sectionAxis === "x") {
|
|
1039
|
+
nx = sign * cos\u03B8;
|
|
1040
|
+
nz = sign * sin\u03B8;
|
|
1041
|
+
} else if (sectionAxis === "y") {
|
|
1042
|
+
ny = sign * cos\u03B8;
|
|
1043
|
+
nz = sign * sin\u03B8;
|
|
1044
|
+
} else {
|
|
1045
|
+
nx = sign * sin\u03B8;
|
|
1046
|
+
nz = sign * cos\u03B8;
|
|
1047
|
+
}
|
|
1048
|
+
const center = {
|
|
1049
|
+
x: (bbox.min.x + bbox.max.x) / 2,
|
|
1050
|
+
y: (bbox.min.y + bbox.max.y) / 2,
|
|
1051
|
+
z: (bbox.min.z + bbox.max.z) / 2
|
|
1052
|
+
};
|
|
1053
|
+
const Px = sectionAxis === "x" ? t : center.x;
|
|
1054
|
+
const Py = sectionAxis === "y" ? t : center.y;
|
|
1055
|
+
const Pz = sectionAxis === "z" ? t : center.z;
|
|
1011
1056
|
s.plane.normal.x = nx;
|
|
1012
1057
|
s.plane.normal.y = ny;
|
|
1013
1058
|
s.plane.normal.z = nz;
|
|
1014
|
-
s.plane.constant = -
|
|
1059
|
+
s.plane.constant = -(nx * Px + ny * Py + nz * Pz);
|
|
1015
1060
|
if (s.capMesh) {
|
|
1016
|
-
const cx =
|
|
1017
|
-
const cy = (bbox.min.y + bbox.max.y) / 2;
|
|
1018
|
-
const cz = (bbox.min.z + bbox.max.z) / 2;
|
|
1061
|
+
const cx = center.x, cy = center.y, cz = center.z;
|
|
1019
1062
|
const dist = nx * cx + ny * cy + nz * cz + s.plane.constant;
|
|
1020
1063
|
const px = cx - nx * dist;
|
|
1021
1064
|
const py = cy - ny * dist;
|
|
@@ -1027,7 +1070,367 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1027
1070
|
} catch (err) {
|
|
1028
1071
|
console.warn("[Preview] section update failed", err);
|
|
1029
1072
|
}
|
|
1030
|
-
}, [sectionEnabled, sectionAxis, sectionFlip, sectionPosition]);
|
|
1073
|
+
}, [sectionEnabled, sectionAxis, sectionFlip, sectionPosition, sectionAngle]);
|
|
1074
|
+
useEffect(() => {
|
|
1075
|
+
const v = viewerRef.current;
|
|
1076
|
+
if (!v?.viewer || loading || !containerRef.current) return;
|
|
1077
|
+
const renderer = v.viewer.renderer;
|
|
1078
|
+
const scene = v.viewer.scene;
|
|
1079
|
+
const camera = v.viewer.camera;
|
|
1080
|
+
const canvas = renderer?.domElement;
|
|
1081
|
+
if (!renderer || !scene || !camera || !canvas) return;
|
|
1082
|
+
let sampleMesh = null;
|
|
1083
|
+
v.viewer.mainModel?.EnumerateMeshes?.((m) => {
|
|
1084
|
+
if (!sampleMesh && !m.userData?.__sectionHelper && !m.userData?.__measureHelper) sampleMesh = m;
|
|
1085
|
+
});
|
|
1086
|
+
if (!sampleMesh) return;
|
|
1087
|
+
const Vector3Ctor = sampleMesh.position.constructor;
|
|
1088
|
+
const MeshCtor = sampleMesh.constructor;
|
|
1089
|
+
const MaterialCtor = (Array.isArray(sampleMesh.material) ? sampleMesh.material[0] : sampleMesh.material)?.constructor;
|
|
1090
|
+
const GeometryCtor = sampleMesh.geometry.constructor;
|
|
1091
|
+
const BufferAttrCtor = sampleMesh.geometry.attributes?.position?.constructor;
|
|
1092
|
+
if (!Vector3Ctor || !MeshCtor || !MaterialCtor || !GeometryCtor || !BufferAttrCtor) return;
|
|
1093
|
+
const teardown = () => {
|
|
1094
|
+
const s = measureRef.current;
|
|
1095
|
+
if (!s) return;
|
|
1096
|
+
if (s.rafId !== null) cancelAnimationFrame(s.rafId);
|
|
1097
|
+
for (const m of s.markers) {
|
|
1098
|
+
scene.remove(m);
|
|
1099
|
+
m.geometry?.dispose?.();
|
|
1100
|
+
m.material?.dispose?.();
|
|
1101
|
+
}
|
|
1102
|
+
if (s.line) {
|
|
1103
|
+
scene.remove(s.line);
|
|
1104
|
+
s.line.geometry?.dispose?.();
|
|
1105
|
+
s.line.material?.dispose?.();
|
|
1106
|
+
}
|
|
1107
|
+
if (s.label && s.label.parentElement) s.label.parentElement.removeChild(s.label);
|
|
1108
|
+
measureRef.current = null;
|
|
1109
|
+
v.viewer.Render?.();
|
|
1110
|
+
};
|
|
1111
|
+
if (!measureEnabled) {
|
|
1112
|
+
teardown();
|
|
1113
|
+
setMeasureDistance(null);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
measureRef.current = { points: [], normals: [], markers: [], line: null, label: null, rafId: null };
|
|
1117
|
+
let THREE = null;
|
|
1118
|
+
let raycaster = null;
|
|
1119
|
+
(async () => {
|
|
1120
|
+
try {
|
|
1121
|
+
THREE = await import(
|
|
1122
|
+
/* @vite-ignore */
|
|
1123
|
+
'three'
|
|
1124
|
+
);
|
|
1125
|
+
raycaster = new THREE.Raycaster();
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
console.warn("[Preview] measure: failed to load three for raycaster", err);
|
|
1128
|
+
}
|
|
1129
|
+
})();
|
|
1130
|
+
const bbox = v.viewer.GetBoundingBox?.(() => true);
|
|
1131
|
+
const diag = bbox ? Math.sqrt(
|
|
1132
|
+
(bbox.max.x - bbox.min.x) ** 2 + (bbox.max.y - bbox.min.y) ** 2 + (bbox.max.z - bbox.min.z) ** 2
|
|
1133
|
+
) : 100;
|
|
1134
|
+
const markerRadius = Math.max(diag * 5e-3, 0.2);
|
|
1135
|
+
const ndcFromEvent = (ev) => {
|
|
1136
|
+
const rect = canvas.getBoundingClientRect();
|
|
1137
|
+
return {
|
|
1138
|
+
x: (ev.clientX - rect.left) / rect.width * 2 - 1,
|
|
1139
|
+
y: -((ev.clientY - rect.top) / rect.height * 2 - 1)
|
|
1140
|
+
};
|
|
1141
|
+
};
|
|
1142
|
+
const collectTargets = () => {
|
|
1143
|
+
const out = [];
|
|
1144
|
+
v.viewer.mainModel?.EnumerateMeshes?.((m) => {
|
|
1145
|
+
if (m.userData?.__sectionHelper || m.userData?.__measureHelper) return;
|
|
1146
|
+
if (m.visible === false) return;
|
|
1147
|
+
out.push(m);
|
|
1148
|
+
});
|
|
1149
|
+
return out;
|
|
1150
|
+
};
|
|
1151
|
+
const makeMarker = (point) => {
|
|
1152
|
+
const widthSegs = 16, heightSegs = 12;
|
|
1153
|
+
const positions = [];
|
|
1154
|
+
const indices = [];
|
|
1155
|
+
for (let iy = 0; iy <= heightSegs; iy++) {
|
|
1156
|
+
const v2 = iy / heightSegs;
|
|
1157
|
+
const phi = v2 * Math.PI;
|
|
1158
|
+
for (let ix = 0; ix <= widthSegs; ix++) {
|
|
1159
|
+
const u = ix / widthSegs;
|
|
1160
|
+
const theta = u * Math.PI * 2;
|
|
1161
|
+
positions.push(
|
|
1162
|
+
markerRadius * Math.sin(phi) * Math.cos(theta),
|
|
1163
|
+
markerRadius * Math.cos(phi),
|
|
1164
|
+
markerRadius * Math.sin(phi) * Math.sin(theta)
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
for (let iy = 0; iy < heightSegs; iy++) {
|
|
1169
|
+
for (let ix = 0; ix < widthSegs; ix++) {
|
|
1170
|
+
const a = iy * (widthSegs + 1) + ix;
|
|
1171
|
+
const b = a + widthSegs + 1;
|
|
1172
|
+
indices.push(a, b, a + 1, b, b + 1, a + 1);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const geom = new GeometryCtor();
|
|
1176
|
+
geom.setAttribute("position", new BufferAttrCtor(new Float32Array(positions), 3));
|
|
1177
|
+
geom.setIndex(indices);
|
|
1178
|
+
geom.computeVertexNormals?.();
|
|
1179
|
+
const mat = new MaterialCtor();
|
|
1180
|
+
mat.color?.setHex?.(16746496);
|
|
1181
|
+
mat.depthTest = false;
|
|
1182
|
+
mat.depthWrite = false;
|
|
1183
|
+
mat.transparent = true;
|
|
1184
|
+
mat.opacity = 0.95;
|
|
1185
|
+
const mesh = new MeshCtor(geom, mat);
|
|
1186
|
+
mesh.position.copy(point);
|
|
1187
|
+
mesh.renderOrder = 9999;
|
|
1188
|
+
mesh.userData.__measureHelper = true;
|
|
1189
|
+
scene.add(mesh);
|
|
1190
|
+
return mesh;
|
|
1191
|
+
};
|
|
1192
|
+
const buildFaceHighlight = (mesh, hit) => {
|
|
1193
|
+
const geom = mesh?.geometry;
|
|
1194
|
+
const posAttr = geom?.attributes?.position;
|
|
1195
|
+
if (!geom || !posAttr || !hit?.face?.normal) return null;
|
|
1196
|
+
const positions = posAttr.array;
|
|
1197
|
+
const indices = geom.index ? geom.index.array : null;
|
|
1198
|
+
const localPoint = new Vector3Ctor(hit.point.x, hit.point.y, hit.point.z);
|
|
1199
|
+
mesh.worldToLocal?.(localPoint);
|
|
1200
|
+
const ln = hit.face.normal;
|
|
1201
|
+
const planeC = -(ln.x * localPoint.x + ln.y * localPoint.y + ln.z * localPoint.z);
|
|
1202
|
+
geom.computeBoundingBox?.();
|
|
1203
|
+
const bb = geom.boundingBox;
|
|
1204
|
+
const diag2 = bb ? Math.sqrt((bb.max.x - bb.min.x) ** 2 + (bb.max.y - bb.min.y) ** 2 + (bb.max.z - bb.min.z) ** 2) : 100;
|
|
1205
|
+
const PLANE_TOL = Math.max(diag2 * 1e-3, 5e-3);
|
|
1206
|
+
const NORMAL_TOL = 0.9995;
|
|
1207
|
+
const triCount = indices ? indices.length / 3 : posAttr.count / 3;
|
|
1208
|
+
const matched = [];
|
|
1209
|
+
const tmpV = new Vector3Ctor();
|
|
1210
|
+
for (let t = 0; t < triCount; t++) {
|
|
1211
|
+
const ia = indices ? indices[t * 3] : t * 3;
|
|
1212
|
+
const ib = indices ? indices[t * 3 + 1] : t * 3 + 1;
|
|
1213
|
+
const ic = indices ? indices[t * 3 + 2] : t * 3 + 2;
|
|
1214
|
+
const ax = positions[ia * 3], ay = positions[ia * 3 + 1], az = positions[ia * 3 + 2];
|
|
1215
|
+
const bx = positions[ib * 3], by = positions[ib * 3 + 1], bz = positions[ib * 3 + 2];
|
|
1216
|
+
const cx = positions[ic * 3], cy = positions[ic * 3 + 1], cz = positions[ic * 3 + 2];
|
|
1217
|
+
const ux = bx - ax, uy = by - ay, uz = bz - az;
|
|
1218
|
+
const vx = cx - ax, vy = cy - ay, vz = cz - az;
|
|
1219
|
+
let nx = uy * vz - uz * vy;
|
|
1220
|
+
let ny = uz * vx - ux * vz;
|
|
1221
|
+
let nz = ux * vy - uy * vx;
|
|
1222
|
+
const nlen = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
1223
|
+
if (nlen === 0) continue;
|
|
1224
|
+
nx /= nlen;
|
|
1225
|
+
ny /= nlen;
|
|
1226
|
+
nz /= nlen;
|
|
1227
|
+
const ndot = nx * ln.x + ny * ln.y + nz * ln.z;
|
|
1228
|
+
if (ndot < NORMAL_TOL) continue;
|
|
1229
|
+
const ccx = (ax + bx + cx) / 3, ccy = (ay + by + cy) / 3, ccz = (az + bz + cz) / 3;
|
|
1230
|
+
const dist = Math.abs(ln.x * ccx + ln.y * ccy + ln.z * ccz + planeC);
|
|
1231
|
+
if (dist > PLANE_TOL) continue;
|
|
1232
|
+
for (const [vx_, vy_, vz_] of [[ax, ay, az], [bx, by, bz], [cx, cy, cz]]) {
|
|
1233
|
+
tmpV.set?.(vx_, vy_, vz_);
|
|
1234
|
+
mesh.localToWorld?.(tmpV);
|
|
1235
|
+
matched.push(tmpV.x, tmpV.y, tmpV.z);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (matched.length === 0) return null;
|
|
1239
|
+
const hgeom = new GeometryCtor();
|
|
1240
|
+
hgeom.setAttribute("position", new BufferAttrCtor(new Float32Array(matched), 3));
|
|
1241
|
+
hgeom.computeVertexNormals?.();
|
|
1242
|
+
const hmat = new MaterialCtor();
|
|
1243
|
+
hmat.color?.setHex?.(16746496);
|
|
1244
|
+
hmat.transparent = true;
|
|
1245
|
+
hmat.opacity = 0.45;
|
|
1246
|
+
hmat.depthWrite = false;
|
|
1247
|
+
hmat.polygonOffset = true;
|
|
1248
|
+
hmat.polygonOffsetFactor = -2;
|
|
1249
|
+
hmat.polygonOffsetUnits = -2;
|
|
1250
|
+
const highlight = new MeshCtor(hgeom, hmat);
|
|
1251
|
+
highlight.renderOrder = 9998;
|
|
1252
|
+
highlight.userData.__measureHelper = true;
|
|
1253
|
+
scene.add(highlight);
|
|
1254
|
+
return highlight;
|
|
1255
|
+
};
|
|
1256
|
+
const drawLine = (a, b) => {
|
|
1257
|
+
const geom = new GeometryCtor();
|
|
1258
|
+
geom.setAttribute("position", new BufferAttrCtor(new Float32Array([a.x, a.y, a.z, b.x, b.y, b.z]), 3));
|
|
1259
|
+
const LineCtor = THREE?.Line ?? MeshCtor;
|
|
1260
|
+
const LineMatCtor = THREE?.LineBasicMaterial ?? MaterialCtor;
|
|
1261
|
+
const mat = new LineMatCtor({ color: 16746496, depthTest: false, depthWrite: false, transparent: true, opacity: 0.95 });
|
|
1262
|
+
const line = new LineCtor(geom, mat);
|
|
1263
|
+
line.renderOrder = 9998;
|
|
1264
|
+
line.userData.__measureHelper = true;
|
|
1265
|
+
scene.add(line);
|
|
1266
|
+
return line;
|
|
1267
|
+
};
|
|
1268
|
+
const ensureLabel = () => {
|
|
1269
|
+
const s = measureRef.current;
|
|
1270
|
+
if (s.label) return s.label;
|
|
1271
|
+
const el = document.createElement("div");
|
|
1272
|
+
el.style.position = "absolute";
|
|
1273
|
+
el.style.transform = "translate(-50%, -50%)";
|
|
1274
|
+
el.style.padding = "2px 6px";
|
|
1275
|
+
el.style.fontSize = "11px";
|
|
1276
|
+
el.style.fontWeight = "600";
|
|
1277
|
+
el.style.fontFamily = "system-ui, -apple-system, sans-serif";
|
|
1278
|
+
el.style.background = "rgba(255, 136, 0, 0.95)";
|
|
1279
|
+
el.style.color = "#fff";
|
|
1280
|
+
el.style.borderRadius = "4px";
|
|
1281
|
+
el.style.pointerEvents = "none";
|
|
1282
|
+
el.style.whiteSpace = "nowrap";
|
|
1283
|
+
el.style.boxShadow = "0 1px 4px rgba(0,0,0,0.25)";
|
|
1284
|
+
el.style.zIndex = "5";
|
|
1285
|
+
containerRef.current.appendChild(el);
|
|
1286
|
+
s.label = el;
|
|
1287
|
+
return el;
|
|
1288
|
+
};
|
|
1289
|
+
const updateLabel = () => {
|
|
1290
|
+
const s = measureRef.current;
|
|
1291
|
+
if (!s || s.points.length < 2 || !s.label) return;
|
|
1292
|
+
const a = s.points[0], b = s.points[1];
|
|
1293
|
+
const mid = new Vector3Ctor((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2);
|
|
1294
|
+
const projected = mid.clone();
|
|
1295
|
+
projected.project?.(camera);
|
|
1296
|
+
const rect = canvas.getBoundingClientRect();
|
|
1297
|
+
const x = (projected.x + 1) / 2 * rect.width;
|
|
1298
|
+
const y = (-projected.y + 1) / 2 * rect.height;
|
|
1299
|
+
s.label.style.left = `${x}px`;
|
|
1300
|
+
s.label.style.top = `${y}px`;
|
|
1301
|
+
s.label.style.opacity = projected.z > 1 ? "0" : "1";
|
|
1302
|
+
};
|
|
1303
|
+
const tick = () => {
|
|
1304
|
+
const s = measureRef.current;
|
|
1305
|
+
if (!s) return;
|
|
1306
|
+
updateLabel();
|
|
1307
|
+
s.rafId = requestAnimationFrame(tick);
|
|
1308
|
+
};
|
|
1309
|
+
measureRef.current.rafId = requestAnimationFrame(tick);
|
|
1310
|
+
const DRAG_TOL = 4;
|
|
1311
|
+
const DRAG_TIME = 350;
|
|
1312
|
+
let downX = 0, downY = 0, downTime = 0, dragging = false, downActive = false;
|
|
1313
|
+
const worldNormalFromHit = (hit) => {
|
|
1314
|
+
const fn = hit?.face?.normal;
|
|
1315
|
+
const obj = hit?.object;
|
|
1316
|
+
if (!fn || !obj) return null;
|
|
1317
|
+
try {
|
|
1318
|
+
const local = new Vector3Ctor(fn.x, fn.y, fn.z);
|
|
1319
|
+
local.transformDirection?.(obj.matrixWorld);
|
|
1320
|
+
return { x: local.x, y: local.y, z: local.z };
|
|
1321
|
+
} catch {
|
|
1322
|
+
return { x: fn.x, y: fn.y, z: fn.z };
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
const doMeasurePick = (ev) => {
|
|
1326
|
+
const targets = collectTargets();
|
|
1327
|
+
if (!targets.length || !raycaster) return;
|
|
1328
|
+
const ndc = ndcFromEvent(ev);
|
|
1329
|
+
raycaster.setFromCamera(ndc, camera);
|
|
1330
|
+
const hits = raycaster.intersectObjects(targets, false);
|
|
1331
|
+
if (!hits.length) return;
|
|
1332
|
+
const s = measureRef.current;
|
|
1333
|
+
const point = new Vector3Ctor(hits[0].point.x, hits[0].point.y, hits[0].point.z);
|
|
1334
|
+
const normal = worldNormalFromHit(hits[0]);
|
|
1335
|
+
if (s.points.length === 2) {
|
|
1336
|
+
for (const m of s.markers) {
|
|
1337
|
+
scene.remove(m);
|
|
1338
|
+
m.geometry?.dispose?.();
|
|
1339
|
+
m.material?.dispose?.();
|
|
1340
|
+
}
|
|
1341
|
+
if (s.line) {
|
|
1342
|
+
scene.remove(s.line);
|
|
1343
|
+
s.line.geometry?.dispose?.();
|
|
1344
|
+
s.line.material?.dispose?.();
|
|
1345
|
+
}
|
|
1346
|
+
if (s.label) s.label.style.opacity = "0";
|
|
1347
|
+
s.points = [];
|
|
1348
|
+
s.markers = [];
|
|
1349
|
+
s.line = null;
|
|
1350
|
+
s.normals = [];
|
|
1351
|
+
setMeasureDistance(null);
|
|
1352
|
+
}
|
|
1353
|
+
s.points.push(point);
|
|
1354
|
+
s.normals.push(normal);
|
|
1355
|
+
if (measureMode === "perp") {
|
|
1356
|
+
const highlight = buildFaceHighlight(hits[0].object, hits[0]);
|
|
1357
|
+
s.markers.push(highlight ?? makeMarker(point));
|
|
1358
|
+
} else {
|
|
1359
|
+
s.markers.push(makeMarker(point));
|
|
1360
|
+
}
|
|
1361
|
+
if (s.points.length === 2) {
|
|
1362
|
+
const a = s.points[0];
|
|
1363
|
+
let b = s.points[1];
|
|
1364
|
+
let dist;
|
|
1365
|
+
let suffix = "";
|
|
1366
|
+
if (measureMode === "perp") {
|
|
1367
|
+
const refN = s.normals[0] ?? s.normals[1];
|
|
1368
|
+
if (refN) {
|
|
1369
|
+
const len = Math.sqrt(refN.x * refN.x + refN.y * refN.y + refN.z * refN.z) || 1;
|
|
1370
|
+
const nx = refN.x / len, ny = refN.y / len, nz = refN.z / len;
|
|
1371
|
+
const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
|
|
1372
|
+
const proj = dx * nx + dy * ny + dz * nz;
|
|
1373
|
+
const projectedB = new Vector3Ctor(a.x + nx * proj, a.y + ny * proj, a.z + nz * proj);
|
|
1374
|
+
s.points[1] = projectedB;
|
|
1375
|
+
b = projectedB;
|
|
1376
|
+
dist = Math.abs(proj);
|
|
1377
|
+
suffix = " \u22A5";
|
|
1378
|
+
} else {
|
|
1379
|
+
const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
|
|
1380
|
+
dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
|
|
1384
|
+
dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1385
|
+
}
|
|
1386
|
+
s.line = drawLine(a, b);
|
|
1387
|
+
setMeasureDistance(dist);
|
|
1388
|
+
const label = ensureLabel();
|
|
1389
|
+
label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
|
|
1390
|
+
}
|
|
1391
|
+
v.viewer.Render?.();
|
|
1392
|
+
};
|
|
1393
|
+
const onPointerDown = (ev) => {
|
|
1394
|
+
if (ev.button !== 0) return;
|
|
1395
|
+
downX = ev.clientX;
|
|
1396
|
+
downY = ev.clientY;
|
|
1397
|
+
downTime = performance.now();
|
|
1398
|
+
dragging = false;
|
|
1399
|
+
downActive = true;
|
|
1400
|
+
};
|
|
1401
|
+
const onPointerMove = (ev) => {
|
|
1402
|
+
if (!downActive) return;
|
|
1403
|
+
const dx = ev.clientX - downX, dy = ev.clientY - downY;
|
|
1404
|
+
if (dx * dx + dy * dy > DRAG_TOL * DRAG_TOL) dragging = true;
|
|
1405
|
+
};
|
|
1406
|
+
const onPointerUp = (ev) => {
|
|
1407
|
+
if (ev.button !== 0 || !downActive) return;
|
|
1408
|
+
const elapsed = performance.now() - downTime;
|
|
1409
|
+
const wasClick = !dragging && elapsed < DRAG_TIME;
|
|
1410
|
+
downActive = false;
|
|
1411
|
+
if (!wasClick) return;
|
|
1412
|
+
doMeasurePick(ev);
|
|
1413
|
+
};
|
|
1414
|
+
const onKeyDown = (ev) => {
|
|
1415
|
+
if (ev.key === "Escape") setMeasureEnabled(false);
|
|
1416
|
+
};
|
|
1417
|
+
canvas.style.cursor = "crosshair";
|
|
1418
|
+
canvas.addEventListener("pointerdown", onPointerDown);
|
|
1419
|
+
canvas.addEventListener("pointermove", onPointerMove);
|
|
1420
|
+
canvas.addEventListener("pointerup", onPointerUp);
|
|
1421
|
+
canvas.addEventListener("pointercancel", onPointerUp);
|
|
1422
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1423
|
+
return () => {
|
|
1424
|
+
canvas.style.cursor = "";
|
|
1425
|
+
canvas.removeEventListener("pointerdown", onPointerDown);
|
|
1426
|
+
canvas.removeEventListener("pointermove", onPointerMove);
|
|
1427
|
+
canvas.removeEventListener("pointerup", onPointerUp);
|
|
1428
|
+
canvas.removeEventListener("pointercancel", onPointerUp);
|
|
1429
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
1430
|
+
teardown();
|
|
1431
|
+
setMeasureDistance(null);
|
|
1432
|
+
};
|
|
1433
|
+
}, [measureEnabled, measureMode, loading, tree]);
|
|
1031
1434
|
useEffect(() => {
|
|
1032
1435
|
if (!showHint || loading) return;
|
|
1033
1436
|
const t = setTimeout(() => setShowHint(false), 5e3);
|
|
@@ -1130,9 +1533,10 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1130
1533
|
setEdgeColor("#000000");
|
|
1131
1534
|
setEdgeThreshold(1);
|
|
1132
1535
|
setSectionEnabled(false);
|
|
1133
|
-
setSectionAxis("
|
|
1536
|
+
setSectionAxis("x");
|
|
1134
1537
|
setSectionFlip(false);
|
|
1135
1538
|
setSectionPosition(0.5);
|
|
1539
|
+
setSectionAngle(0);
|
|
1136
1540
|
};
|
|
1137
1541
|
const handleDefaultDownload = () => {
|
|
1138
1542
|
const a = document.createElement("a");
|
|
@@ -1191,6 +1595,8 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1191
1595
|
};
|
|
1192
1596
|
const tBtn = "h-8 w-8 shrink-0 flex items-center justify-center rounded text-gray-600 hover:bg-gray-200 hover:text-gray-900 transition-colors";
|
|
1193
1597
|
const tBtnActive = "h-8 w-8 shrink-0 flex items-center justify-center rounded bg-gray-200 text-gray-900";
|
|
1598
|
+
const tBtnWide = "h-8 shrink-0 flex items-center justify-center px-2 rounded text-gray-600 hover:bg-gray-200 hover:text-gray-900 transition-colors";
|
|
1599
|
+
const tBtnWideActive = "h-8 shrink-0 flex items-center justify-center px-2 rounded bg-gray-200 text-gray-900";
|
|
1194
1600
|
const tBtnSep = "h-5 w-px bg-gray-300 mx-1";
|
|
1195
1601
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full bg-white", children: [
|
|
1196
1602
|
/* @__PURE__ */ jsxs(PanelActions, { children: [
|
|
@@ -1200,6 +1606,15 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1200
1606
|
/* @__PURE__ */ jsx("button", { onClick: () => setCameraPreset("top"), className: tBtn, title: "Top view", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold", children: "TOP" }) }),
|
|
1201
1607
|
/* @__PURE__ */ jsx("button", { onClick: () => setCameraPreset("front"), className: tBtn, title: "Front view", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold", children: "FRT" }) }),
|
|
1202
1608
|
/* @__PURE__ */ jsx("button", { onClick: () => setCameraPreset("side"), className: tBtn, title: "Side view", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold", children: "SDE" }) }),
|
|
1609
|
+
/* @__PURE__ */ jsx(
|
|
1610
|
+
"button",
|
|
1611
|
+
{
|
|
1612
|
+
onClick: () => setPerspective((p) => !p),
|
|
1613
|
+
className: perspective ? tBtnWideActive : tBtnWide,
|
|
1614
|
+
title: perspective ? "Switch to orthographic view" : "Switch to perspective view",
|
|
1615
|
+
children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold whitespace-nowrap", children: perspective ? "Perspective" : "Orthographic" })
|
|
1616
|
+
}
|
|
1617
|
+
),
|
|
1203
1618
|
/* @__PURE__ */ jsx("div", { className: tBtnSep }),
|
|
1204
1619
|
/* @__PURE__ */ jsx("button", { onClick: handleSnapshot, className: tBtn, title: "Save snapshot as PNG", children: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
|
|
1205
1620
|
/* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z" }),
|
|
@@ -1209,12 +1624,39 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1209
1624
|
/* @__PURE__ */ jsx(
|
|
1210
1625
|
"button",
|
|
1211
1626
|
{
|
|
1212
|
-
onClick: () =>
|
|
1213
|
-
className:
|
|
1214
|
-
title:
|
|
1215
|
-
children: /* @__PURE__ */
|
|
1627
|
+
onClick: () => setMeasureEnabled((m) => !m),
|
|
1628
|
+
className: measureEnabled ? tBtnActive : tBtn,
|
|
1629
|
+
title: measureEnabled ? "Stop measuring (Esc)" : "Measure distance \u2014 click two points on the model",
|
|
1630
|
+
children: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
|
|
1631
|
+
/* @__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" }),
|
|
1632
|
+
/* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 18.75h19.5" })
|
|
1633
|
+
] })
|
|
1216
1634
|
}
|
|
1217
1635
|
),
|
|
1636
|
+
measureEnabled && /* @__PURE__ */ jsxs("div", { className: "flex items-stretch h-8 rounded border border-gray-200 overflow-hidden", children: [
|
|
1637
|
+
/* @__PURE__ */ jsx(
|
|
1638
|
+
"button",
|
|
1639
|
+
{
|
|
1640
|
+
onClick: () => setMeasureMode("point"),
|
|
1641
|
+
className: `px-2 text-[11px] font-semibold transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
|
|
1642
|
+
title: "Point \u2014 measure straight-line distance between two picked points",
|
|
1643
|
+
children: "Point"
|
|
1644
|
+
}
|
|
1645
|
+
),
|
|
1646
|
+
/* @__PURE__ */ jsx(
|
|
1647
|
+
"button",
|
|
1648
|
+
{
|
|
1649
|
+
onClick: () => setMeasureMode("perp"),
|
|
1650
|
+
className: `px-2 text-[12px] font-semibold transition-colors ${measureMode === "perp" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
|
|
1651
|
+
title: "Perpendicular \u2014 pick two surfaces and measure the perpendicular gap between them",
|
|
1652
|
+
children: "\u22A5"
|
|
1653
|
+
}
|
|
1654
|
+
)
|
|
1655
|
+
] }),
|
|
1656
|
+
measureEnabled && measureDistance !== null && /* @__PURE__ */ jsxs("div", { className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap", title: measureMode === "perp" ? "Perpendicular distance between the two picked surfaces" : "Straight-line distance between the two picked points", children: [
|
|
1657
|
+
formatMeasureDistance(measureDistance),
|
|
1658
|
+
measureMode === "perp" ? " \u22A5" : ""
|
|
1659
|
+
] }),
|
|
1218
1660
|
/* @__PURE__ */ jsx("div", { className: tBtnSep }),
|
|
1219
1661
|
/* @__PURE__ */ jsx("button", { onClick: () => setShowMeshes((s) => !s), className: showMeshes ? tBtnActive : tBtn, title: "Toggle meshes panel", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" }) }) }),
|
|
1220
1662
|
/* @__PURE__ */ jsx("button", { onClick: () => setShowSettings((s) => !s), className: showSettings ? tBtnActive : tBtn, title: "Toggle display panel", children: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
|
|
@@ -1322,6 +1764,27 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
|
|
|
1322
1764
|
)
|
|
1323
1765
|
] }),
|
|
1324
1766
|
/* @__PURE__ */ jsxs("div", { className: sectionEnabled ? "mt-2 space-y-2" : "mt-2 space-y-2 opacity-40 pointer-events-none", children: [
|
|
1767
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1768
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mb-1", children: [
|
|
1769
|
+
/* @__PURE__ */ jsx("span", { children: "Angle" }),
|
|
1770
|
+
/* @__PURE__ */ jsxs("span", { className: "text-gray-500 tabular-nums", children: [
|
|
1771
|
+
sectionAngle,
|
|
1772
|
+
"\xB0"
|
|
1773
|
+
] })
|
|
1774
|
+
] }),
|
|
1775
|
+
/* @__PURE__ */ jsx(
|
|
1776
|
+
"input",
|
|
1777
|
+
{
|
|
1778
|
+
type: "range",
|
|
1779
|
+
min: 0,
|
|
1780
|
+
max: 180,
|
|
1781
|
+
step: 1,
|
|
1782
|
+
value: sectionAngle,
|
|
1783
|
+
onChange: (e) => setSectionAngle(Number(e.target.value)),
|
|
1784
|
+
className: "w-full accent-blue-500"
|
|
1785
|
+
}
|
|
1786
|
+
)
|
|
1787
|
+
] }),
|
|
1325
1788
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1326
1789
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mb-1", children: [
|
|
1327
1790
|
/* @__PURE__ */ jsx("span", { children: "Axis" }),
|
|
@@ -1446,5 +1909,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
|
|
|
1446
1909
|
}
|
|
1447
1910
|
|
|
1448
1911
|
export { Preview, setPdfPreview };
|
|
1449
|
-
//# sourceMappingURL=chunk-
|
|
1450
|
-
//# sourceMappingURL=chunk-
|
|
1912
|
+
//# sourceMappingURL=chunk-QBH7KERS.js.map
|
|
1913
|
+
//# sourceMappingURL=chunk-QBH7KERS.js.map
|