@yumiai/chat-widget 0.1.2 → 0.2.1
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/CHANGELOG.md +100 -0
- package/README.md +119 -22
- package/dist/ExcelCore-DJOIVQMI.js +11 -0
- package/dist/ExcelCore-DJOIVQMI.js.map +1 -0
- package/dist/ExcelViewer-3YLLYYIQ.js +65 -0
- package/dist/ExcelViewer-3YLLYYIQ.js.map +1 -0
- package/dist/GerberViewerA2UI-7CNT7HX4.css +693 -0
- package/dist/GerberViewerA2UI-7CNT7HX4.css.map +1 -0
- package/dist/GerberViewerA2UI-X5FWAD5M.js +57 -0
- package/dist/GerberViewerA2UI-X5FWAD5M.js.map +1 -0
- package/dist/GraphStatsLegend-D5bPeXB_.d.cts +607 -0
- package/dist/GraphStatsLegend-D5bPeXB_.d.ts +607 -0
- package/dist/JsonRenderStandalone-EIZM62JU.js +18 -0
- package/dist/JsonRenderStandalone-EIZM62JU.js.map +1 -0
- package/dist/JsonRenderStandalone-POB4Q3N3.css +2384 -0
- package/dist/JsonRenderStandalone-POB4Q3N3.css.map +1 -0
- package/dist/JsonRenderStandalone-UsTcST4G.d.cts +23 -0
- package/dist/JsonRenderStandalone-UsTcST4G.d.ts +23 -0
- package/dist/KicadViewer-GV6ZC4AQ.js +124 -0
- package/dist/KicadViewer-GV6ZC4AQ.js.map +1 -0
- package/dist/KicadViewerCore-U7BWZHKJ.js +11 -0
- package/dist/KicadViewerCore-U7BWZHKJ.js.map +1 -0
- package/dist/PdfViewer-CHPDRK46.js +51 -0
- package/dist/PdfViewer-CHPDRK46.js.map +1 -0
- package/dist/PdfViewer-LPYGQETK.css +1899 -0
- package/dist/PdfViewer-LPYGQETK.css.map +1 -0
- package/dist/PdfViewerCore-HJPEHSRA.js +364 -0
- package/dist/PdfViewerCore-HJPEHSRA.js.map +1 -0
- package/dist/PowerPointCore-FPDR2BL4.js +11 -0
- package/dist/PowerPointCore-FPDR2BL4.js.map +1 -0
- package/dist/PowerPointViewer-LQTO6UCU.js +61 -0
- package/dist/PowerPointViewer-LQTO6UCU.js.map +1 -0
- package/dist/StepViewerCore-7W3L3R4E.js +285 -0
- package/dist/StepViewerCore-7W3L3R4E.js.map +1 -0
- package/dist/ThreeViewerCore-N3QJD5QI.js +161 -0
- package/dist/ThreeViewerCore-N3QJD5QI.js.map +1 -0
- package/dist/WordCore-JKSXK2XD.js +11 -0
- package/dist/WordCore-JKSXK2XD.js.map +1 -0
- package/dist/WordViewer-ZHCQMHOH.js +61 -0
- package/dist/WordViewer-ZHCQMHOH.js.map +1 -0
- package/dist/chunk-2SKA3F5U.js +88 -0
- package/dist/chunk-2SKA3F5U.js.map +1 -0
- package/dist/chunk-2UC7YLVX.js +318 -0
- package/dist/chunk-2UC7YLVX.js.map +1 -0
- package/dist/chunk-3R6T3LBR.js +24 -0
- package/dist/chunk-3R6T3LBR.js.map +1 -0
- package/dist/chunk-56WRZM3R.js +398 -0
- package/dist/chunk-56WRZM3R.js.map +1 -0
- package/dist/chunk-7A4FY6FK.js +10226 -0
- package/dist/chunk-7A4FY6FK.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-7S67DOHQ.js +436 -0
- package/dist/chunk-7S67DOHQ.js.map +1 -0
- package/dist/chunk-CFKGNAJM.js +14013 -0
- package/dist/chunk-CFKGNAJM.js.map +1 -0
- package/dist/chunk-GAMA3VA7.js +99 -0
- package/dist/chunk-GAMA3VA7.js.map +1 -0
- package/dist/chunk-GYXTSY22.js +639 -0
- package/dist/chunk-GYXTSY22.js.map +1 -0
- package/dist/chunk-K4KGNVL5.js +77 -0
- package/dist/chunk-K4KGNVL5.js.map +1 -0
- package/dist/chunk-KQV7IKET.js +1621 -0
- package/dist/chunk-KQV7IKET.js.map +1 -0
- package/dist/chunk-O3NXUM6C.js +1871 -0
- package/dist/chunk-O3NXUM6C.js.map +1 -0
- package/dist/chunk-PZXSASDY.js +83 -0
- package/dist/chunk-PZXSASDY.js.map +1 -0
- package/dist/chunk-QLVPIM6R.js +595 -0
- package/dist/chunk-QLVPIM6R.js.map +1 -0
- package/dist/chunk-VXJWGLZ7.js +21 -0
- package/dist/chunk-VXJWGLZ7.js.map +1 -0
- package/dist/chunk-XQ562W7I.js +116 -0
- package/dist/chunk-XQ562W7I.js.map +1 -0
- package/dist/components/JsonRender/standalone.cjs +39368 -0
- package/dist/components/JsonRender/standalone.cjs.map +1 -0
- package/dist/components/JsonRender/standalone.css +2384 -0
- package/dist/components/JsonRender/standalone.css.map +1 -0
- package/dist/components/JsonRender/standalone.d.cts +16 -0
- package/dist/components/JsonRender/standalone.d.ts +16 -0
- package/dist/components/JsonRender/standalone.js +38 -0
- package/dist/components/JsonRender/standalone.js.map +1 -0
- package/dist/gerber-2d-entry-OQ4SQRBY.js +3950 -0
- package/dist/gerber-2d-entry-OQ4SQRBY.js.map +1 -0
- package/dist/gerber-3d-entry-DEHDBOO2.js +3679 -0
- package/dist/gerber-3d-entry-DEHDBOO2.js.map +1 -0
- package/dist/gerber-simulation-entry-EBDX72XE.js +1801 -0
- package/dist/gerber-simulation-entry-EBDX72XE.js.map +1 -0
- package/dist/index.cjs +60113 -2970
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +11342 -1708
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +3275 -77
- package/dist/index.d.ts +3275 -77
- package/dist/index.js +18078 -2540
- package/dist/index.js.map +1 -1
- package/dist/provenance/index.cjs +2248 -0
- package/dist/provenance/index.cjs.map +1 -0
- package/dist/provenance/index.css +52 -0
- package/dist/provenance/index.css.map +1 -0
- package/dist/provenance/index.d.cts +12 -0
- package/dist/provenance/index.d.ts +12 -0
- package/dist/provenance/index.js +27 -0
- package/dist/provenance/index.js.map +1 -0
- package/dist/resolveToArrayBuffer-AQIDZHSQ.js +23 -0
- package/dist/resolveToArrayBuffer-AQIDZHSQ.js.map +1 -0
- package/package.json +98 -17
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
injectStyles,
|
|
3
|
+
useResource,
|
|
4
|
+
useResourceContext
|
|
5
|
+
} from "./chunk-GAMA3VA7.js";
|
|
6
|
+
import "./chunk-7D4SUZUM.js";
|
|
7
|
+
|
|
8
|
+
// src/components/JsonRender/domain/PowerPointViewer.tsx
|
|
9
|
+
import React, { useEffect } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var STYLE_ID = "jr-styles-pptx-viewer";
|
|
12
|
+
var SHEET = `
|
|
13
|
+
.jr-pptx-viewer { width: 100%; min-width: 0; max-width: 100%; min-height: 400px; }
|
|
14
|
+
.jr-pptx-viewer--loading, .jr-pptx-viewer--error {
|
|
15
|
+
padding: 1rem; font-family: var(--jr-font, system-ui, sans-serif);
|
|
16
|
+
color: var(--jr-text-muted, #64748b);
|
|
17
|
+
}
|
|
18
|
+
.jr-pptx-viewer--error { color: var(--jr-danger, #ef4444); }
|
|
19
|
+
`;
|
|
20
|
+
var LazyPowerPointCore = React.lazy(() => import("./PowerPointCore-FPDR2BL4.js"));
|
|
21
|
+
var PowerPointViewer = ({ props, emit }) => {
|
|
22
|
+
injectStyles(STYLE_ID, SHEET);
|
|
23
|
+
const res = useResource({ resource_id: props.resource_id, url: props.url });
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (res.loading) return;
|
|
26
|
+
if (res.error) {
|
|
27
|
+
emit?.("viewer:error", { resource_id: props.resource_id, error: res.error });
|
|
28
|
+
} else {
|
|
29
|
+
emit?.("viewer:loaded", { resource_id: props.resource_id, mimeType: res.mimeType, fileName: res.fileName });
|
|
30
|
+
}
|
|
31
|
+
}, [res.loading, res.error]);
|
|
32
|
+
const ctx = useResourceContext();
|
|
33
|
+
if (res.loading) return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer jr-pptx-viewer--loading", children: "Loading\u2026" });
|
|
34
|
+
if (res.error) return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer jr-pptx-viewer--error", children: res.error });
|
|
35
|
+
const src = res.url;
|
|
36
|
+
const isLegacy = res.fileName?.match(/\.ppt$/i) && !res.fileName?.match(/\.pptx$/i);
|
|
37
|
+
if (isLegacy && ctx?.resolvePreviewUrl && props.resource_id) {
|
|
38
|
+
return /* @__PURE__ */ jsx(LegacyFallback, { resourceId: props.resource_id, ctx });
|
|
39
|
+
}
|
|
40
|
+
if (!src) return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer jr-pptx-viewer--error", children: "No presentation source" });
|
|
41
|
+
return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer ycw-ppt-viewer", children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer--loading", children: "Loading PowerPoint viewer\u2026" }), children: /* @__PURE__ */ jsx(LazyPowerPointCore, { src }) }) });
|
|
42
|
+
};
|
|
43
|
+
var LegacyFallback = ({ resourceId, ctx }) => {
|
|
44
|
+
const [previewUrl, setPreviewUrl] = React.useState();
|
|
45
|
+
const [error, setError] = React.useState();
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
ctx.resolvePreviewUrl?.(resourceId, "html").then((url) => setPreviewUrl(url)).catch((err) => setError(String(err?.message ?? err)));
|
|
48
|
+
}, [resourceId, ctx]);
|
|
49
|
+
if (error) return /* @__PURE__ */ jsxs("div", { className: "jr-pptx-viewer jr-pptx-viewer--error", children: [
|
|
50
|
+
".ppt preview failed: ",
|
|
51
|
+
error
|
|
52
|
+
] });
|
|
53
|
+
if (!previewUrl) return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer jr-pptx-viewer--loading", children: "Converting .ppt\u2026" });
|
|
54
|
+
return /* @__PURE__ */ jsx("div", { className: "jr-pptx-viewer", children: /* @__PURE__ */ jsx("iframe", { src: previewUrl, title: "PowerPoint Preview", style: { width: "100%", height: 600, border: "none" } }) });
|
|
55
|
+
};
|
|
56
|
+
var PowerPointViewer_default = PowerPointViewer;
|
|
57
|
+
export {
|
|
58
|
+
PowerPointViewer,
|
|
59
|
+
PowerPointViewer_default as default
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=PowerPointViewer-LQTO6UCU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/JsonRender/domain/PowerPointViewer.tsx"],"sourcesContent":["import React, { useEffect } from 'react'\nimport { useResource } from '../../../hooks/useResource.js'\nimport { useResourceContext } from '../../../contexts/ResourceContext.js'\nimport { injectStyles } from './injectStyles.js'\n\nconst STYLE_ID = 'jr-styles-pptx-viewer'\nconst SHEET = `\n.jr-pptx-viewer { width: 100%; min-width: 0; max-width: 100%; min-height: 400px; }\n.jr-pptx-viewer--loading, .jr-pptx-viewer--error {\n padding: 1rem; font-family: var(--jr-font, system-ui, sans-serif);\n color: var(--jr-text-muted, #64748b);\n}\n.jr-pptx-viewer--error { color: var(--jr-danger, #ef4444); }\n`\n\ninterface Props {\n props: {\n resource_id?: string\n url?: string\n title?: string\n }\n emit?: (event: string, data?: unknown) => void\n}\n\nconst LazyPowerPointCore = React.lazy(() => import('../../ViewerCore/PowerPointCore.js'))\n\nexport const PowerPointViewer: React.FC<Props> = ({ props, emit }) => {\n injectStyles(STYLE_ID, SHEET)\n const res = useResource({ resource_id: props.resource_id, url: props.url })\n\n useEffect(() => {\n if (res.loading) return\n if (res.error) {\n emit?.('viewer:error', { resource_id: props.resource_id, error: res.error })\n } else {\n emit?.('viewer:loaded', { resource_id: props.resource_id, mimeType: res.mimeType, fileName: res.fileName })\n }\n }, [res.loading, res.error])\n\n const ctx = useResourceContext()\n\n if (res.loading) return <div className=\"jr-pptx-viewer jr-pptx-viewer--loading\">Loading…</div>\n if (res.error) return <div className=\"jr-pptx-viewer jr-pptx-viewer--error\">{res.error}</div>\n\n const src = res.url\n const isLegacy = res.fileName?.match(/\\.ppt$/i) && !res.fileName?.match(/\\.pptx$/i)\n\n if (isLegacy && ctx?.resolvePreviewUrl && props.resource_id) {\n return <LegacyFallback resourceId={props.resource_id} ctx={ctx} />\n }\n\n if (!src) return <div className=\"jr-pptx-viewer jr-pptx-viewer--error\">No presentation source</div>\n\n return (\n <div className=\"jr-pptx-viewer ycw-ppt-viewer\">\n <React.Suspense fallback={<div className=\"jr-pptx-viewer--loading\">Loading PowerPoint viewer…</div>}>\n <LazyPowerPointCore src={src} />\n </React.Suspense>\n </div>\n )\n}\n\nconst LegacyFallback: React.FC<{ resourceId: string; ctx: { resolvePreviewUrl?(id: string, fmt: 'html' | 'pdf'): Promise<string> } }> = ({ resourceId, ctx }) => {\n const [previewUrl, setPreviewUrl] = React.useState<string>()\n const [error, setError] = React.useState<string>()\n\n React.useEffect(() => {\n ctx.resolvePreviewUrl?.(resourceId, 'html')\n .then(url => setPreviewUrl(url))\n .catch(err => setError(String(err?.message ?? err)))\n }, [resourceId, ctx])\n\n if (error) return <div className=\"jr-pptx-viewer jr-pptx-viewer--error\">.ppt preview failed: {error}</div>\n if (!previewUrl) return <div className=\"jr-pptx-viewer jr-pptx-viewer--loading\">Converting .ppt…</div>\n\n return (\n <div className=\"jr-pptx-viewer\">\n <iframe src={previewUrl} title=\"PowerPoint Preview\" style={{ width: '100%', height: 600, border: 'none' }} />\n </div>\n )\n}\n\nexport default PowerPointViewer\n"],"mappings":";;;;;;;;AAAA,OAAO,SAAS,iBAAiB;AAyCP,cA+BN,YA/BM;AApC1B,IAAM,WAAW;AACjB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBd,IAAM,qBAAqB,MAAM,KAAK,MAAM,OAAO,8BAAoC,CAAC;AAEjF,IAAM,mBAAoC,CAAC,EAAE,OAAO,KAAK,MAAM;AACpE,eAAa,UAAU,KAAK;AAC5B,QAAM,MAAM,YAAY,EAAE,aAAa,MAAM,aAAa,KAAK,MAAM,IAAI,CAAC;AAE1E,YAAU,MAAM;AACd,QAAI,IAAI,QAAS;AACjB,QAAI,IAAI,OAAO;AACb,aAAO,gBAAgB,EAAE,aAAa,MAAM,aAAa,OAAO,IAAI,MAAM,CAAC;AAAA,IAC7E,OAAO;AACL,aAAO,iBAAiB,EAAE,aAAa,MAAM,aAAa,UAAU,IAAI,UAAU,UAAU,IAAI,SAAS,CAAC;AAAA,IAC5G;AAAA,EACF,GAAG,CAAC,IAAI,SAAS,IAAI,KAAK,CAAC;AAE3B,QAAM,MAAM,mBAAmB;AAE/B,MAAI,IAAI,QAAS,QAAO,oBAAC,SAAI,WAAU,0CAAyC,2BAAQ;AACxF,MAAI,IAAI,MAAO,QAAO,oBAAC,SAAI,WAAU,wCAAwC,cAAI,OAAM;AAEvF,QAAM,MAAM,IAAI;AAChB,QAAM,WAAW,IAAI,UAAU,MAAM,SAAS,KAAK,CAAC,IAAI,UAAU,MAAM,UAAU;AAElF,MAAI,YAAY,KAAK,qBAAqB,MAAM,aAAa;AAC3D,WAAO,oBAAC,kBAAe,YAAY,MAAM,aAAa,KAAU;AAAA,EAClE;AAEA,MAAI,CAAC,IAAK,QAAO,oBAAC,SAAI,WAAU,wCAAuC,oCAAsB;AAE7F,SACE,oBAAC,SAAI,WAAU,iCACb,8BAAC,MAAM,UAAN,EAAe,UAAU,oBAAC,SAAI,WAAU,2BAA0B,6CAA0B,GAC3F,8BAAC,sBAAmB,KAAU,GAChC,GACF;AAEJ;AAEA,IAAM,iBAAkI,CAAC,EAAE,YAAY,IAAI,MAAM;AAC/J,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAiB;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAiB;AAEjD,QAAM,UAAU,MAAM;AACpB,QAAI,oBAAoB,YAAY,MAAM,EACvC,KAAK,SAAO,cAAc,GAAG,CAAC,EAC9B,MAAM,SAAO,SAAS,OAAO,KAAK,WAAW,GAAG,CAAC,CAAC;AAAA,EACvD,GAAG,CAAC,YAAY,GAAG,CAAC;AAEpB,MAAI,MAAO,QAAO,qBAAC,SAAI,WAAU,wCAAuC;AAAA;AAAA,IAAsB;AAAA,KAAM;AACpG,MAAI,CAAC,WAAY,QAAO,oBAAC,SAAI,WAAU,0CAAyC,mCAAgB;AAEhG,SACE,oBAAC,SAAI,WAAU,kBACb,8BAAC,YAAO,KAAK,YAAY,OAAM,sBAAqB,OAAO,EAAE,OAAO,QAAQ,QAAQ,KAAK,QAAQ,OAAO,GAAG,GAC7G;AAEJ;AAEA,IAAO,2BAAQ;","names":[]}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useThreeScene
|
|
3
|
+
} from "./chunk-XQ562W7I.js";
|
|
4
|
+
import {
|
|
5
|
+
useInteractionDispatch
|
|
6
|
+
} from "./chunk-3R6T3LBR.js";
|
|
7
|
+
import {
|
|
8
|
+
useChatWidgetI18n
|
|
9
|
+
} from "./chunk-QLVPIM6R.js";
|
|
10
|
+
import "./chunk-7D4SUZUM.js";
|
|
11
|
+
|
|
12
|
+
// src/components/FileViewer/viewers/StepViewerCore.tsx
|
|
13
|
+
import { useEffect, useState, useCallback } from "react";
|
|
14
|
+
import * as THREE from "three";
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
function isIgesFile(name) {
|
|
17
|
+
const lower = name.toLowerCase();
|
|
18
|
+
return lower.endsWith(".iges") || lower.endsWith(".igs");
|
|
19
|
+
}
|
|
20
|
+
async function fetchFileBuffer(src) {
|
|
21
|
+
const resp = await fetch(src);
|
|
22
|
+
if (!resp.ok) throw new Error(`Fetch failed: ${resp.status}`);
|
|
23
|
+
const ab = await resp.arrayBuffer();
|
|
24
|
+
return new Uint8Array(ab);
|
|
25
|
+
}
|
|
26
|
+
var WASM_MAGIC = [0, 97, 115, 109];
|
|
27
|
+
async function fetchWasmBinary() {
|
|
28
|
+
const candidates = [
|
|
29
|
+
"/node_modules/occt-import-js/dist/occt-import-js.wasm",
|
|
30
|
+
"/occt-import-js.wasm"
|
|
31
|
+
];
|
|
32
|
+
for (const url of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
const resp = await fetch(url);
|
|
35
|
+
if (!resp.ok) continue;
|
|
36
|
+
const buf = await resp.arrayBuffer();
|
|
37
|
+
const header = new Uint8Array(buf, 0, 4);
|
|
38
|
+
if (WASM_MAGIC.every((b, i) => header[i] === b)) return buf;
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
var _occtPromise = null;
|
|
45
|
+
function loadOcct() {
|
|
46
|
+
if (!_occtPromise) {
|
|
47
|
+
_occtPromise = (async () => {
|
|
48
|
+
const occtImportJs = (await import("occt-import-js")).default;
|
|
49
|
+
const wasmBinary = await fetchWasmBinary();
|
|
50
|
+
return await occtImportJs(wasmBinary ? { wasmBinary } : void 0);
|
|
51
|
+
})();
|
|
52
|
+
_occtPromise.catch(() => {
|
|
53
|
+
_occtPromise = null;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return _occtPromise;
|
|
57
|
+
}
|
|
58
|
+
function makeBodyMaterial(color) {
|
|
59
|
+
const base = color ? new THREE.Color(color[0] / 255, color[1] / 255, color[2] / 255) : new THREE.Color(1710622);
|
|
60
|
+
const brightness = base.r * 0.299 + base.g * 0.587 + base.b * 0.114;
|
|
61
|
+
if (brightness < 0.08) base.lerp(new THREE.Color(0.1, 0.1, 0.12), 0.35);
|
|
62
|
+
return new THREE.MeshPhysicalMaterial({
|
|
63
|
+
color: base,
|
|
64
|
+
metalness: 0,
|
|
65
|
+
roughness: 0.75,
|
|
66
|
+
clearcoat: 0.08,
|
|
67
|
+
clearcoatRoughness: 0.6,
|
|
68
|
+
side: THREE.DoubleSide,
|
|
69
|
+
envMapIntensity: 0.25
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function makePinMaterial() {
|
|
73
|
+
return new THREE.MeshPhysicalMaterial({
|
|
74
|
+
color: 14080734,
|
|
75
|
+
metalness: 0.92,
|
|
76
|
+
roughness: 0.15,
|
|
77
|
+
clearcoat: 0.1,
|
|
78
|
+
clearcoatRoughness: 0.05,
|
|
79
|
+
side: THREE.DoubleSide,
|
|
80
|
+
envMapIntensity: 1.8
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function makePadMaterial() {
|
|
84
|
+
return new THREE.MeshPhysicalMaterial({
|
|
85
|
+
color: 12756314,
|
|
86
|
+
metalness: 0.9,
|
|
87
|
+
roughness: 0.2,
|
|
88
|
+
clearcoat: 0.1,
|
|
89
|
+
clearcoatRoughness: 0.1,
|
|
90
|
+
side: THREE.DoubleSide,
|
|
91
|
+
envMapIntensity: 1.6
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function makeCeramicMaterial(color) {
|
|
95
|
+
const base = color ? new THREE.Color(color[0] / 255, color[1] / 255, color[2] / 255) : new THREE.Color(12888186);
|
|
96
|
+
return new THREE.MeshPhysicalMaterial({
|
|
97
|
+
color: base,
|
|
98
|
+
metalness: 0,
|
|
99
|
+
roughness: 0.6,
|
|
100
|
+
clearcoat: 0.15,
|
|
101
|
+
clearcoatRoughness: 0.4,
|
|
102
|
+
side: THREE.DoubleSide,
|
|
103
|
+
envMapIntensity: 0.5
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function makeColoredMaterial(r, g, b) {
|
|
107
|
+
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
108
|
+
const saturation = (Math.max(r, g, b) - Math.min(r, g, b)) / (Math.max(r, g, b) || 1);
|
|
109
|
+
if (brightness > 160 && saturation < 0.15) return makePinMaterial();
|
|
110
|
+
if (brightness > 100 && saturation > 0.2 && r > g && r > b * 1.3) return makePadMaterial();
|
|
111
|
+
if (brightness > 120 && saturation < 0.3) return makeCeramicMaterial([r, g, b]);
|
|
112
|
+
return makeBodyMaterial([r, g, b]);
|
|
113
|
+
}
|
|
114
|
+
function analyzeMeshGeometry(geo) {
|
|
115
|
+
geo.computeBoundingBox();
|
|
116
|
+
const box = geo.boundingBox;
|
|
117
|
+
const s = new THREE.Vector3();
|
|
118
|
+
box.getSize(s);
|
|
119
|
+
const dims = [s.x, s.y, s.z].sort((a, b) => b - a);
|
|
120
|
+
const volume = s.x * s.y * s.z;
|
|
121
|
+
const aspectRatio = dims[0] / (dims[2] || 1e-4);
|
|
122
|
+
return { volume, aspectRatio, thinnestDim: dims[2] };
|
|
123
|
+
}
|
|
124
|
+
function isLikelyEmbossedText(p, bodyThickness, largestVolume) {
|
|
125
|
+
const volumeRatio = p.volume / (largestVolume || 1);
|
|
126
|
+
if (volumeRatio > 0.01) return false;
|
|
127
|
+
if (p.thinnestDim > bodyThickness * 0.08) return false;
|
|
128
|
+
if (p.aspectRatio > 8 && volumeRatio < 5e-3) return true;
|
|
129
|
+
if (p.vertexCount > 30 && volumeRatio < 2e-3 && p.aspectRatio > 4) return true;
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
function classifyByGeometry(p, largestVolume, totalMeshCount) {
|
|
133
|
+
const nameLower = (p.name || "").toLowerCase();
|
|
134
|
+
if (/lead|pin|terminal|leg|gull/i.test(nameLower)) return "pin";
|
|
135
|
+
if (/pad|solder/i.test(nameLower)) return "pad";
|
|
136
|
+
if (/body|case|housing|package|mold|encap/i.test(nameLower)) return "body";
|
|
137
|
+
const volumeRatio = p.volume / (largestVolume || 1);
|
|
138
|
+
if (volumeRatio > 0.4) return "body";
|
|
139
|
+
if (p.aspectRatio > 3 && volumeRatio < 0.3) return "pin";
|
|
140
|
+
if (totalMeshCount > 2 && volumeRatio < 0.15) return "pin";
|
|
141
|
+
return volumeRatio < 0.25 ? "pin" : "body";
|
|
142
|
+
}
|
|
143
|
+
function buildMeshFromResult(result) {
|
|
144
|
+
const group = new THREE.Group();
|
|
145
|
+
if (!result.meshes || result.meshes.length === 0) {
|
|
146
|
+
throw new Error("No geometry found in file");
|
|
147
|
+
}
|
|
148
|
+
const parsed = [];
|
|
149
|
+
for (const meshData of result.meshes) {
|
|
150
|
+
const geometry = new THREE.BufferGeometry();
|
|
151
|
+
const vertices = new Float32Array(meshData.attributes.position.array);
|
|
152
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
|
|
153
|
+
if (meshData.attributes.normal?.array) {
|
|
154
|
+
const normals = new Float32Array(meshData.attributes.normal.array);
|
|
155
|
+
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
|
|
156
|
+
}
|
|
157
|
+
if (meshData.index?.array) {
|
|
158
|
+
const indices = new Uint32Array(meshData.index.array);
|
|
159
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
160
|
+
}
|
|
161
|
+
if (!meshData.attributes.normal?.array) geometry.computeVertexNormals();
|
|
162
|
+
const { volume, aspectRatio, thinnestDim } = analyzeMeshGeometry(geometry);
|
|
163
|
+
parsed.push({
|
|
164
|
+
geometry,
|
|
165
|
+
volume,
|
|
166
|
+
aspectRatio,
|
|
167
|
+
thinnestDim,
|
|
168
|
+
vertexCount: meshData.attributes.position.array.length / 3,
|
|
169
|
+
color: meshData.color ?? void 0,
|
|
170
|
+
name: meshData.name ?? void 0
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const sorted = [...parsed].sort((a, b) => b.volume - a.volume);
|
|
174
|
+
const largestVolume = sorted[0]?.volume || 1;
|
|
175
|
+
const bodyMesh = sorted[0];
|
|
176
|
+
const bodyThickness = bodyMesh?.thinnestDim || 1;
|
|
177
|
+
const colors = parsed.map((p) => p.color).filter(Boolean);
|
|
178
|
+
const allSameColor = colors.length <= 1 || colors.every((c) => {
|
|
179
|
+
const [r0, g0, b0] = colors[0];
|
|
180
|
+
return Math.abs(c[0] - r0) < 20 && Math.abs(c[1] - g0) < 20 && Math.abs(c[2] - b0) < 20;
|
|
181
|
+
});
|
|
182
|
+
const useGeometry = colors.length === 0 || allSameColor;
|
|
183
|
+
for (const p of parsed) {
|
|
184
|
+
if (parsed.length > 3 && isLikelyEmbossedText(p, bodyThickness, largestVolume)) continue;
|
|
185
|
+
let material;
|
|
186
|
+
if (useGeometry && parsed.length > 1) {
|
|
187
|
+
const role = classifyByGeometry(p, largestVolume, parsed.length);
|
|
188
|
+
if (role === "pin") material = makePinMaterial();
|
|
189
|
+
else if (role === "pad") material = makePadMaterial();
|
|
190
|
+
else material = makeBodyMaterial(p.color);
|
|
191
|
+
} else if (!allSameColor && colors.length > 1) {
|
|
192
|
+
material = p.color ? makeColoredMaterial(p.color[0], p.color[1], p.color[2]) : makeBodyMaterial();
|
|
193
|
+
} else {
|
|
194
|
+
material = makeBodyMaterial(p.color);
|
|
195
|
+
}
|
|
196
|
+
group.add(new THREE.Mesh(p.geometry, material));
|
|
197
|
+
}
|
|
198
|
+
return group;
|
|
199
|
+
}
|
|
200
|
+
var StepViewerCore = ({ src, fileName }) => {
|
|
201
|
+
const { t } = useChatWidgetI18n();
|
|
202
|
+
const emitInteraction = useInteractionDispatch();
|
|
203
|
+
const { containerRef, handleRef, resetView, wireframe, toggleWireframe } = useThreeScene();
|
|
204
|
+
const [loading, setLoading] = useState(true);
|
|
205
|
+
const [error, setError] = useState(null);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const h = handleRef.current;
|
|
208
|
+
if (!h || !src) return;
|
|
209
|
+
setLoading(true);
|
|
210
|
+
setError(null);
|
|
211
|
+
let cancelled = false;
|
|
212
|
+
(async () => {
|
|
213
|
+
try {
|
|
214
|
+
const buffer = await fetchFileBuffer(src);
|
|
215
|
+
if (cancelled) return;
|
|
216
|
+
const occt = await loadOcct();
|
|
217
|
+
const result = isIgesFile(fileName) ? occt.ReadIgesFile(buffer, null) : occt.ReadStepFile(buffer, null);
|
|
218
|
+
if (cancelled) return;
|
|
219
|
+
if (!result.success) {
|
|
220
|
+
throw new Error("Failed to parse CAD file");
|
|
221
|
+
}
|
|
222
|
+
const group = buildMeshFromResult(result);
|
|
223
|
+
h.scene.add(group);
|
|
224
|
+
h.fitToScene(group);
|
|
225
|
+
setLoading(false);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
if (!cancelled) {
|
|
228
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
229
|
+
setLoading(false);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
return () => {
|
|
234
|
+
cancelled = true;
|
|
235
|
+
};
|
|
236
|
+
}, [src, fileName, handleRef]);
|
|
237
|
+
const handleDownload = useCallback(() => {
|
|
238
|
+
emitInteraction("file-download", fileName, { source: "step-viewer" });
|
|
239
|
+
const a = document.createElement("a");
|
|
240
|
+
a.href = src;
|
|
241
|
+
a.download = fileName;
|
|
242
|
+
a.click();
|
|
243
|
+
}, [src, fileName, emitInteraction]);
|
|
244
|
+
return /* @__PURE__ */ jsxs("div", { className: "ycw-file-viewer-step", children: [
|
|
245
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-toolbar", children: [
|
|
246
|
+
/* @__PURE__ */ jsx("span", { className: "ycw-3d-filename", title: fileName, children: fileName }),
|
|
247
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-toolbar-actions", children: [
|
|
248
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: `ycw-3d-btn${wireframe ? " active" : ""}`, onClick: toggleWireframe, title: t("viewer.3d.wireframe"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
249
|
+
/* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
|
|
250
|
+
/* @__PURE__ */ jsx("path", { d: "M2 17l10 5 10-5" }),
|
|
251
|
+
/* @__PURE__ */ jsx("path", { d: "M2 12l10 5 10-5" })
|
|
252
|
+
] }) }),
|
|
253
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: resetView, title: t("viewer.3d.resetView"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
254
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
255
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" }),
|
|
256
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3l-7 7" }),
|
|
257
|
+
/* @__PURE__ */ jsx("path", { d: "M3 21l7-7" })
|
|
258
|
+
] }) }),
|
|
259
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: handleDownload, title: t("viewer.fallback.download"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
260
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
261
|
+
/* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
|
|
262
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
263
|
+
] }) })
|
|
264
|
+
] })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-canvas", ref: containerRef, children: [
|
|
267
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "ycw-3d-overlay", children: [
|
|
268
|
+
/* @__PURE__ */ jsx("div", { className: "ycw-3d-spinner" }),
|
|
269
|
+
/* @__PURE__ */ jsx("span", { children: t("viewer.loadingStep") })
|
|
270
|
+
] }),
|
|
271
|
+
error && /* @__PURE__ */ jsxs("div", { className: "ycw-3d-overlay ycw-3d-error", children: [
|
|
272
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
273
|
+
"\u26A0\uFE0F ",
|
|
274
|
+
error
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: handleDownload, children: t("viewer.fallback.download") })
|
|
277
|
+
] })
|
|
278
|
+
] })
|
|
279
|
+
] });
|
|
280
|
+
};
|
|
281
|
+
var StepViewerCore_default = StepViewerCore;
|
|
282
|
+
export {
|
|
283
|
+
StepViewerCore_default as default
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=StepViewerCore-7W3L3R4E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FileViewer/viewers/StepViewerCore.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\nimport * as THREE from 'three'\nimport { useChatWidgetI18n } from '../../../i18n'\nimport { useInteractionDispatch } from '../../../hooks/useInteractionDispatch'\nimport { useThreeScene } from './useThreeScene'\n\ninterface StepViewerCoreProps {\n src: string\n fileName: string\n}\n\nfunction isIgesFile(name: string): boolean {\n const lower = name.toLowerCase()\n return lower.endsWith('.iges') || lower.endsWith('.igs')\n}\n\nasync function fetchFileBuffer(src: string): Promise<Uint8Array> {\n const resp = await fetch(src)\n if (!resp.ok) throw new Error(`Fetch failed: ${resp.status}`)\n const ab = await resp.arrayBuffer()\n return new Uint8Array(ab)\n}\n\nconst WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d]\n\nasync function fetchWasmBinary(): Promise<ArrayBuffer | undefined> {\n const candidates = [\n '/node_modules/occt-import-js/dist/occt-import-js.wasm',\n '/occt-import-js.wasm',\n ]\n for (const url of candidates) {\n try {\n const resp = await fetch(url)\n if (!resp.ok) continue\n const buf = await resp.arrayBuffer()\n const header = new Uint8Array(buf, 0, 4)\n if (WASM_MAGIC.every((b, i) => header[i] === b)) return buf\n } catch { /* try next */ }\n }\n return undefined\n}\n\nlet _occtPromise: Promise<any> | null = null\n\nfunction loadOcct(): Promise<any> {\n if (!_occtPromise) {\n _occtPromise = (async () => {\n const occtImportJs = (await import('occt-import-js')).default\n const wasmBinary = await fetchWasmBinary()\n return await occtImportJs(wasmBinary ? { wasmBinary } : undefined)\n })()\n _occtPromise.catch(() => { _occtPromise = null })\n }\n return _occtPromise\n}\n\nfunction makeBodyMaterial(color?: [number, number, number]): THREE.MeshPhysicalMaterial {\n const base = color\n ? new THREE.Color(color[0] / 255, color[1] / 255, color[2] / 255)\n : new THREE.Color(0x1a1a1e)\n const brightness = base.r * 0.299 + base.g * 0.587 + base.b * 0.114\n if (brightness < 0.08) base.lerp(new THREE.Color(0.10, 0.10, 0.12), 0.35)\n return new THREE.MeshPhysicalMaterial({\n color: base,\n metalness: 0.0,\n roughness: 0.75,\n clearcoat: 0.08,\n clearcoatRoughness: 0.6,\n side: THREE.DoubleSide,\n envMapIntensity: 0.25,\n })\n}\n\nfunction makePinMaterial(): THREE.MeshPhysicalMaterial {\n return new THREE.MeshPhysicalMaterial({\n color: 0xd6dade,\n metalness: 0.92,\n roughness: 0.15,\n clearcoat: 0.1,\n clearcoatRoughness: 0.05,\n side: THREE.DoubleSide,\n envMapIntensity: 1.8,\n })\n}\n\nfunction makePadMaterial(): THREE.MeshPhysicalMaterial {\n return new THREE.MeshPhysicalMaterial({\n color: 0xc2a55a,\n metalness: 0.9,\n roughness: 0.2,\n clearcoat: 0.1,\n clearcoatRoughness: 0.1,\n side: THREE.DoubleSide,\n envMapIntensity: 1.6,\n })\n}\n\nfunction makeCeramicMaterial(color?: [number, number, number]): THREE.MeshPhysicalMaterial {\n const base = color\n ? new THREE.Color(color[0] / 255, color[1] / 255, color[2] / 255)\n : new THREE.Color(0xc4a87a)\n return new THREE.MeshPhysicalMaterial({\n color: base,\n metalness: 0.0,\n roughness: 0.6,\n clearcoat: 0.15,\n clearcoatRoughness: 0.4,\n side: THREE.DoubleSide,\n envMapIntensity: 0.5,\n })\n}\n\nfunction makeColoredMaterial(r: number, g: number, b: number): THREE.MeshPhysicalMaterial {\n const brightness = 0.299 * r + 0.587 * g + 0.114 * b\n const saturation = (Math.max(r, g, b) - Math.min(r, g, b)) / (Math.max(r, g, b) || 1)\n if (brightness > 160 && saturation < 0.15) return makePinMaterial()\n if (brightness > 100 && saturation > 0.2 && r > g && r > b * 1.3) return makePadMaterial()\n if (brightness > 120 && saturation < 0.3) return makeCeramicMaterial([r, g, b])\n return makeBodyMaterial([r, g, b])\n}\n\ninterface ParsedMesh {\n geometry: THREE.BufferGeometry\n volume: number\n aspectRatio: number\n thinnestDim: number\n vertexCount: number\n color?: [number, number, number]\n name?: string\n}\n\nfunction analyzeMeshGeometry(geo: THREE.BufferGeometry): { volume: number; aspectRatio: number; thinnestDim: number } {\n geo.computeBoundingBox()\n const box = geo.boundingBox!\n const s = new THREE.Vector3()\n box.getSize(s)\n const dims = [s.x, s.y, s.z].sort((a, b) => b - a)\n const volume = s.x * s.y * s.z\n const aspectRatio = dims[0] / (dims[2] || 0.0001)\n return { volume, aspectRatio, thinnestDim: dims[2] }\n}\n\nfunction isLikelyEmbossedText(p: ParsedMesh, bodyThickness: number, largestVolume: number): boolean {\n const volumeRatio = p.volume / (largestVolume || 1)\n if (volumeRatio > 0.01) return false\n if (p.thinnestDim > bodyThickness * 0.08) return false\n if (p.aspectRatio > 8 && volumeRatio < 0.005) return true\n if (p.vertexCount > 30 && volumeRatio < 0.002 && p.aspectRatio > 4) return true\n return false\n}\n\nfunction classifyByGeometry(p: ParsedMesh, largestVolume: number, totalMeshCount: number): 'body' | 'pin' | 'pad' {\n const nameLower = (p.name || '').toLowerCase()\n if (/lead|pin|terminal|leg|gull/i.test(nameLower)) return 'pin'\n if (/pad|solder/i.test(nameLower)) return 'pad'\n if (/body|case|housing|package|mold|encap/i.test(nameLower)) return 'body'\n\n const volumeRatio = p.volume / (largestVolume || 1)\n\n if (volumeRatio > 0.4) return 'body'\n\n if (p.aspectRatio > 3 && volumeRatio < 0.3) return 'pin'\n\n if (totalMeshCount > 2 && volumeRatio < 0.15) return 'pin'\n\n return volumeRatio < 0.25 ? 'pin' : 'body'\n}\n\nfunction buildMeshFromResult(result: any): THREE.Group {\n const group = new THREE.Group()\n if (!result.meshes || result.meshes.length === 0) {\n throw new Error('No geometry found in file')\n }\n\n const parsed: ParsedMesh[] = []\n\n for (const meshData of result.meshes) {\n const geometry = new THREE.BufferGeometry()\n const vertices = new Float32Array(meshData.attributes.position.array)\n geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))\n\n if (meshData.attributes.normal?.array) {\n const normals = new Float32Array(meshData.attributes.normal.array)\n geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3))\n }\n if (meshData.index?.array) {\n const indices = new Uint32Array(meshData.index.array)\n geometry.setIndex(new THREE.BufferAttribute(indices, 1))\n }\n if (!meshData.attributes.normal?.array) geometry.computeVertexNormals()\n\n const { volume, aspectRatio, thinnestDim } = analyzeMeshGeometry(geometry)\n parsed.push({\n geometry,\n volume,\n aspectRatio,\n thinnestDim,\n vertexCount: meshData.attributes.position.array.length / 3,\n color: meshData.color ?? undefined,\n name: meshData.name ?? undefined,\n })\n }\n\n const sorted = [...parsed].sort((a, b) => b.volume - a.volume)\n const largestVolume = sorted[0]?.volume || 1\n const bodyMesh = sorted[0]\n const bodyThickness = bodyMesh?.thinnestDim || 1\n\n const colors = parsed.map(p => p.color).filter(Boolean) as [number, number, number][]\n const allSameColor = colors.length <= 1 || colors.every(c => {\n const [r0, g0, b0] = colors[0]\n return Math.abs(c[0] - r0) < 20 && Math.abs(c[1] - g0) < 20 && Math.abs(c[2] - b0) < 20\n })\n const useGeometry = colors.length === 0 || allSameColor\n\n for (const p of parsed) {\n if (parsed.length > 3 && isLikelyEmbossedText(p, bodyThickness, largestVolume)) continue\n\n let material: THREE.MeshPhysicalMaterial\n if (useGeometry && parsed.length > 1) {\n const role = classifyByGeometry(p, largestVolume, parsed.length)\n if (role === 'pin') material = makePinMaterial()\n else if (role === 'pad') material = makePadMaterial()\n else material = makeBodyMaterial(p.color)\n } else if (!allSameColor && colors.length > 1) {\n material = p.color\n ? makeColoredMaterial(p.color[0], p.color[1], p.color[2])\n : makeBodyMaterial()\n } else {\n material = makeBodyMaterial(p.color)\n }\n group.add(new THREE.Mesh(p.geometry, material))\n }\n\n return group\n}\n\nconst StepViewerCore: React.FC<StepViewerCoreProps> = ({ src, fileName }) => {\n const { t } = useChatWidgetI18n()\n const emitInteraction = useInteractionDispatch()\n const { containerRef, handleRef, resetView, wireframe, toggleWireframe } = useThreeScene()\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n const h = handleRef.current\n if (!h || !src) return\n\n setLoading(true)\n setError(null)\n let cancelled = false\n\n ;(async () => {\n try {\n const buffer = await fetchFileBuffer(src)\n if (cancelled) return\n\n const occt = await loadOcct()\n\n const result = isIgesFile(fileName)\n ? occt.ReadIgesFile(buffer, null)\n : occt.ReadStepFile(buffer, null)\n\n if (cancelled) return\n\n if (!result.success) {\n throw new Error('Failed to parse CAD file')\n }\n\n const group = buildMeshFromResult(result)\n h.scene.add(group)\n h.fitToScene(group)\n setLoading(false)\n } catch (e) {\n if (!cancelled) {\n setError(e instanceof Error ? e.message : String(e))\n setLoading(false)\n }\n }\n })()\n\n return () => { cancelled = true }\n }, [src, fileName, handleRef])\n\n const handleDownload = useCallback(() => {\n emitInteraction('file-download', fileName, { source: 'step-viewer' })\n const a = document.createElement('a')\n a.href = src\n a.download = fileName\n a.click()\n }, [src, fileName, emitInteraction])\n\n return (\n <div className=\"ycw-file-viewer-step\">\n <div className=\"ycw-3d-toolbar\">\n <span className=\"ycw-3d-filename\" title={fileName}>{fileName}</span>\n <div className=\"ycw-3d-toolbar-actions\">\n <button type=\"button\" className={`ycw-3d-btn${wireframe ? ' active' : ''}`} onClick={toggleWireframe} title={t('viewer.3d.wireframe')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M12 2L2 7l10 5 10-5-10-5z\"/><path d=\"M2 17l10 5 10-5\"/><path d=\"M2 12l10 5 10-5\"/></svg>\n </button>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={resetView} title={t('viewer.3d.resetView')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M15 3h6v6\"/><path d=\"M9 21H3v-6\"/><path d=\"M21 3l-7 7\"/><path d=\"M3 21l7-7\"/></svg>\n </button>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={handleDownload} title={t('viewer.fallback.download')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n </button>\n </div>\n </div>\n <div className=\"ycw-3d-canvas\" ref={containerRef}>\n {loading && (\n <div className=\"ycw-3d-overlay\">\n <div className=\"ycw-3d-spinner\" />\n <span>{t('viewer.loadingStep')}</span>\n </div>\n )}\n {error && (\n <div className=\"ycw-3d-overlay ycw-3d-error\">\n <span>⚠️ {error}</span>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={handleDownload}>{t('viewer.fallback.download')}</button>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default StepViewerCore\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useThreeScene
|
|
3
|
+
} from "./chunk-XQ562W7I.js";
|
|
4
|
+
import {
|
|
5
|
+
useInteractionDispatch
|
|
6
|
+
} from "./chunk-3R6T3LBR.js";
|
|
7
|
+
import {
|
|
8
|
+
useChatWidgetI18n
|
|
9
|
+
} from "./chunk-QLVPIM6R.js";
|
|
10
|
+
import "./chunk-7D4SUZUM.js";
|
|
11
|
+
|
|
12
|
+
// src/components/FileViewer/viewers/ThreeViewerCore.tsx
|
|
13
|
+
import { useEffect, useState, useCallback } from "react";
|
|
14
|
+
import * as THREE from "three";
|
|
15
|
+
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
|
|
16
|
+
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
17
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
18
|
+
import { ThreeMFLoader } from "three/examples/jsm/loaders/3MFLoader.js";
|
|
19
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
+
var DEFAULT_MATERIAL = new THREE.MeshPhysicalMaterial({
|
|
21
|
+
color: 8952234,
|
|
22
|
+
metalness: 0.15,
|
|
23
|
+
roughness: 0.45,
|
|
24
|
+
side: THREE.DoubleSide,
|
|
25
|
+
envMapIntensity: 0.8
|
|
26
|
+
});
|
|
27
|
+
function getFormatFromName(name) {
|
|
28
|
+
const lower = name.toLowerCase();
|
|
29
|
+
if (lower.endsWith(".stl")) return "stl";
|
|
30
|
+
if (lower.endsWith(".obj")) return "obj";
|
|
31
|
+
if (lower.endsWith(".glb") || lower.endsWith(".gltf")) return "gltf";
|
|
32
|
+
if (lower.endsWith(".3mf")) return "3mf";
|
|
33
|
+
return "unknown";
|
|
34
|
+
}
|
|
35
|
+
var ThreeViewerCore = ({ src, fileName }) => {
|
|
36
|
+
const { t } = useChatWidgetI18n();
|
|
37
|
+
const emitInteraction = useInteractionDispatch();
|
|
38
|
+
const { containerRef, handleRef, resetView, wireframe, toggleWireframe } = useThreeScene();
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
const [error, setError] = useState(null);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const h = handleRef.current;
|
|
43
|
+
if (!h || !src) return;
|
|
44
|
+
setLoading(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
const format = getFormatFromName(fileName);
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
const onLoad = (obj) => {
|
|
49
|
+
if (cancelled) return;
|
|
50
|
+
h.scene.add(obj);
|
|
51
|
+
h.fitToScene(obj);
|
|
52
|
+
setLoading(false);
|
|
53
|
+
};
|
|
54
|
+
const onError = (e) => {
|
|
55
|
+
if (cancelled) return;
|
|
56
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
57
|
+
setLoading(false);
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
switch (format) {
|
|
61
|
+
case "stl": {
|
|
62
|
+
const loader = new STLLoader();
|
|
63
|
+
loader.load(src, (geometry) => {
|
|
64
|
+
if (cancelled) return;
|
|
65
|
+
geometry.computeVertexNormals();
|
|
66
|
+
const mesh = new THREE.Mesh(geometry, DEFAULT_MATERIAL.clone());
|
|
67
|
+
onLoad(mesh);
|
|
68
|
+
}, void 0, onError);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "obj": {
|
|
72
|
+
const loader = new OBJLoader();
|
|
73
|
+
loader.load(src, (group) => {
|
|
74
|
+
group.traverse((child) => {
|
|
75
|
+
if (child instanceof THREE.Mesh && !child.material) {
|
|
76
|
+
child.material = DEFAULT_MATERIAL.clone();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
onLoad(group);
|
|
80
|
+
}, void 0, onError);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "gltf": {
|
|
84
|
+
const loader = new GLTFLoader();
|
|
85
|
+
loader.load(src, (gltf) => {
|
|
86
|
+
onLoad(gltf.scene);
|
|
87
|
+
}, void 0, onError);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "3mf": {
|
|
91
|
+
const loader = new ThreeMFLoader();
|
|
92
|
+
loader.load(src, (group) => {
|
|
93
|
+
group.traverse((child) => {
|
|
94
|
+
if (child instanceof THREE.Mesh && !child.material) {
|
|
95
|
+
child.material = DEFAULT_MATERIAL.clone();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
onLoad(group);
|
|
99
|
+
}, void 0, onError);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
setError(`Unsupported 3D format: ${format}`);
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
onError(e);
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
cancelled = true;
|
|
111
|
+
};
|
|
112
|
+
}, [src, fileName, handleRef]);
|
|
113
|
+
const handleDownload = useCallback(() => {
|
|
114
|
+
emitInteraction("file-download", fileName, { source: "3d-viewer" });
|
|
115
|
+
const a = document.createElement("a");
|
|
116
|
+
a.href = src;
|
|
117
|
+
a.download = fileName;
|
|
118
|
+
a.click();
|
|
119
|
+
}, [src, fileName, emitInteraction]);
|
|
120
|
+
return /* @__PURE__ */ jsxs("div", { className: "ycw-file-viewer-3d", children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-toolbar", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("span", { className: "ycw-3d-filename", title: fileName, children: fileName }),
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-toolbar-actions", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: `ycw-3d-btn${wireframe ? " active" : ""}`, onClick: toggleWireframe, title: t("viewer.3d.wireframe"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
125
|
+
/* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
|
|
126
|
+
/* @__PURE__ */ jsx("path", { d: "M2 17l10 5 10-5" }),
|
|
127
|
+
/* @__PURE__ */ jsx("path", { d: "M2 12l10 5 10-5" })
|
|
128
|
+
] }) }),
|
|
129
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: resetView, title: t("viewer.3d.resetView"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
130
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
131
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" }),
|
|
132
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3l-7 7" }),
|
|
133
|
+
/* @__PURE__ */ jsx("path", { d: "M3 21l7-7" })
|
|
134
|
+
] }) }),
|
|
135
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: handleDownload, title: t("viewer.fallback.download"), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
136
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
137
|
+
/* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
|
|
138
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
139
|
+
] }) })
|
|
140
|
+
] })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "ycw-3d-canvas", ref: containerRef, children: [
|
|
143
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "ycw-3d-overlay", children: [
|
|
144
|
+
/* @__PURE__ */ jsx("div", { className: "ycw-3d-spinner" }),
|
|
145
|
+
/* @__PURE__ */ jsx("span", { children: t("viewer.loading3d") })
|
|
146
|
+
] }),
|
|
147
|
+
error && /* @__PURE__ */ jsxs("div", { className: "ycw-3d-overlay ycw-3d-error", children: [
|
|
148
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
149
|
+
"\u26A0\uFE0F ",
|
|
150
|
+
error
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ycw-3d-btn", onClick: handleDownload, children: t("viewer.fallback.download") })
|
|
153
|
+
] })
|
|
154
|
+
] })
|
|
155
|
+
] });
|
|
156
|
+
};
|
|
157
|
+
var ThreeViewerCore_default = ThreeViewerCore;
|
|
158
|
+
export {
|
|
159
|
+
ThreeViewerCore_default as default
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=ThreeViewerCore-N3QJD5QI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FileViewer/viewers/ThreeViewerCore.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\nimport * as THREE from 'three'\nimport { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js'\nimport { useChatWidgetI18n } from '../../../i18n'\nimport { useInteractionDispatch } from '../../../hooks/useInteractionDispatch'\nimport { useThreeScene } from './useThreeScene'\n\ninterface ThreeViewerCoreProps {\n src: string\n fileName: string\n}\n\nconst DEFAULT_MATERIAL = new THREE.MeshPhysicalMaterial({\n color: 0x8899aa,\n metalness: 0.15,\n roughness: 0.45,\n side: THREE.DoubleSide,\n envMapIntensity: 0.8,\n})\n\nfunction getFormatFromName(name: string): string {\n const lower = name.toLowerCase()\n if (lower.endsWith('.stl')) return 'stl'\n if (lower.endsWith('.obj')) return 'obj'\n if (lower.endsWith('.glb') || lower.endsWith('.gltf')) return 'gltf'\n if (lower.endsWith('.3mf')) return '3mf'\n return 'unknown'\n}\n\nconst ThreeViewerCore: React.FC<ThreeViewerCoreProps> = ({ src, fileName }) => {\n const { t } = useChatWidgetI18n()\n const emitInteraction = useInteractionDispatch()\n const { containerRef, handleRef, resetView, wireframe, toggleWireframe } = useThreeScene()\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n const h = handleRef.current\n if (!h || !src) return\n\n setLoading(true)\n setError(null)\n\n const format = getFormatFromName(fileName)\n let cancelled = false\n\n const onLoad = (obj: THREE.Object3D) => {\n if (cancelled) return\n h.scene.add(obj)\n h.fitToScene(obj)\n setLoading(false)\n }\n\n const onError = (e: unknown) => {\n if (cancelled) return\n setError(e instanceof Error ? e.message : String(e))\n setLoading(false)\n }\n\n try {\n switch (format) {\n case 'stl': {\n const loader = new STLLoader()\n loader.load(src, (geometry) => {\n if (cancelled) return\n geometry.computeVertexNormals()\n const mesh = new THREE.Mesh(geometry, DEFAULT_MATERIAL.clone())\n onLoad(mesh)\n }, undefined, onError)\n break\n }\n case 'obj': {\n const loader = new OBJLoader()\n loader.load(src, (group) => {\n group.traverse(child => {\n if (child instanceof THREE.Mesh && !child.material) {\n child.material = DEFAULT_MATERIAL.clone()\n }\n })\n onLoad(group)\n }, undefined, onError)\n break\n }\n case 'gltf': {\n const loader = new GLTFLoader()\n loader.load(src, (gltf) => {\n onLoad(gltf.scene)\n }, undefined, onError)\n break\n }\n case '3mf': {\n const loader = new ThreeMFLoader()\n loader.load(src, (group) => {\n group.traverse(child => {\n if (child instanceof THREE.Mesh && !child.material) {\n child.material = DEFAULT_MATERIAL.clone()\n }\n })\n onLoad(group)\n }, undefined, onError)\n break\n }\n default:\n setError(`Unsupported 3D format: ${format}`)\n setLoading(false)\n }\n } catch (e) {\n onError(e)\n }\n\n return () => { cancelled = true }\n }, [src, fileName, handleRef])\n\n const handleDownload = useCallback(() => {\n emitInteraction('file-download', fileName, { source: '3d-viewer' })\n const a = document.createElement('a')\n a.href = src\n a.download = fileName\n a.click()\n }, [src, fileName, emitInteraction])\n\n return (\n <div className=\"ycw-file-viewer-3d\">\n <div className=\"ycw-3d-toolbar\">\n <span className=\"ycw-3d-filename\" title={fileName}>{fileName}</span>\n <div className=\"ycw-3d-toolbar-actions\">\n <button type=\"button\" className={`ycw-3d-btn${wireframe ? ' active' : ''}`} onClick={toggleWireframe} title={t('viewer.3d.wireframe')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M12 2L2 7l10 5 10-5-10-5z\"/><path d=\"M2 17l10 5 10-5\"/><path d=\"M2 12l10 5 10-5\"/></svg>\n </button>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={resetView} title={t('viewer.3d.resetView')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M15 3h6v6\"/><path d=\"M9 21H3v-6\"/><path d=\"M21 3l-7 7\"/><path d=\"M3 21l7-7\"/></svg>\n </button>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={handleDownload} title={t('viewer.fallback.download')}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n </button>\n </div>\n </div>\n <div className=\"ycw-3d-canvas\" ref={containerRef}>\n {loading && (\n <div className=\"ycw-3d-overlay\">\n <div className=\"ycw-3d-spinner\" />\n <span>{t('viewer.loading3d')}</span>\n </div>\n )}\n {error && (\n <div className=\"ycw-3d-overlay ycw-3d-error\">\n <span>⚠️ {error}</span>\n <button type=\"button\" className=\"ycw-3d-btn\" onClick={handleDownload}>{t('viewer.fallback.download')}</button>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThreeViewerCore\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|