3D viewer overhaul: full-bleed + responsive, Web Worker STEP parse, progress bar, caption stats, inline models in text notes, STL fix, app widget; weekday dates (dateText); fix shields badge escaping; docs + licenses
This commit is contained in:
+58
-14
@@ -11,7 +11,7 @@
|
||||
|
||||
var MODEL_RE = /\.(stl|step|stp)$/i;
|
||||
|
||||
var STEP_PARAMS = { linearDeflection: 0.0005, angularDeflection: 0.2 };
|
||||
var STEP_PARAMS = { linearDeflection: 0.0002, angularDeflection: 0.1 };
|
||||
|
||||
var WORKER_SRC = [
|
||||
"self.onmessage = function (e) {",
|
||||
@@ -153,11 +153,21 @@
|
||||
}
|
||||
function showData(extra) {
|
||||
var d = el.querySelector(".model-viewer-data");
|
||||
if (!d) return;
|
||||
var parts = [];
|
||||
if (fileSize) parts.push(formatBytes(fileSize));
|
||||
if (extra) parts.push(extra);
|
||||
if (parts.length) { d.textContent = parts.join(" \u00b7 "); d.style.display = "block"; }
|
||||
if (d) {
|
||||
var parts = [];
|
||||
if (fileSize) parts.push(formatBytes(fileSize));
|
||||
if (extra) parts.push(extra);
|
||||
if (parts.length) { d.textContent = parts.join(" \u00b7 "); d.style.display = "block"; }
|
||||
}
|
||||
var cap = el.querySelector(".model-viewer-caption");
|
||||
if (cap && extra) {
|
||||
var stats = [];
|
||||
if (fileSize) stats.push(formatBytes(fileSize));
|
||||
stats.push(extra);
|
||||
var name = cap.getAttribute("data-model-name");
|
||||
if (!name) { name = (cap.textContent || "").trim(); cap.setAttribute("data-model-name", name); }
|
||||
cap.textContent = (name ? name + " \u00b7 " : "") + stats.join(" \u00b7 ");
|
||||
}
|
||||
}
|
||||
|
||||
function loadThree(cb) {
|
||||
@@ -176,14 +186,16 @@
|
||||
|
||||
loadThree(function () {
|
||||
var wrap = el.querySelector(".model-viewer-canvas") || el;
|
||||
var w = wrap.clientWidth || el.clientWidth || 400;
|
||||
var h = Math.min(520, Math.max(320, Math.round(w * 0.62)));
|
||||
function canvasSize() {
|
||||
return { w: wrap.clientWidth || el.clientWidth || 400, h: wrap.clientHeight || 420 };
|
||||
}
|
||||
var initSize = canvasSize();
|
||||
var renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(w, h);
|
||||
renderer.setSize(initSize.w, initSize.h);
|
||||
renderer.setClearColor(0x1d2021, 1);
|
||||
|
||||
var scene = new THREE.Scene();
|
||||
var camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 1000000);
|
||||
var camera = new THREE.PerspectiveCamera(45, initSize.w / initSize.h, 0.1, 1000000);
|
||||
camera.position.set(300, 220, 320);
|
||||
scene.add(new THREE.AmbientLight(0xffffff, 0.55));
|
||||
var dir = new THREE.DirectionalLight(0xffffff, 1.0); dir.position.set(1, 1.5, 1); scene.add(dir);
|
||||
@@ -193,6 +205,15 @@
|
||||
var group = new THREE.Group();
|
||||
scene.add(group);
|
||||
|
||||
var resizeObs = new ResizeObserver(function () {
|
||||
var s = canvasSize();
|
||||
if (!s.w || !s.h) return;
|
||||
renderer.setSize(s.w, s.h);
|
||||
camera.aspect = s.w / s.h;
|
||||
camera.updateProjectionMatrix();
|
||||
});
|
||||
resizeObs.observe(wrap);
|
||||
|
||||
function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); }
|
||||
animate();
|
||||
|
||||
@@ -226,6 +247,8 @@
|
||||
fetchProgress(OCCT_WASM_ABS, function () {}).then(function (wasmBuf) {
|
||||
setStatus("Parsing STEP\u2026");
|
||||
setProgress(null);
|
||||
var t0 = Date.now();
|
||||
var parseTimer = setInterval(function () { setStatus("Parsing STEP\u2026 (" + Math.round((Date.now() - t0) / 1000) + "s)"); }, 1000);
|
||||
var blob = new Blob([WORKER_SRC], { type: "application/javascript" });
|
||||
var wurl = URL.createObjectURL(blob);
|
||||
var worker = new Worker(wurl);
|
||||
@@ -234,7 +257,7 @@
|
||||
if (e.data.type === "ready") {
|
||||
worker.postMessage({ type: "parse", data: data, params: STEP_PARAMS }, [data.buffer]);
|
||||
} else if (e.data.type === "done") {
|
||||
settled = true; worker.terminate(); URL.revokeObjectURL(wurl);
|
||||
settled = true; worker.terminate(); URL.revokeObjectURL(wurl); clearInterval(parseTimer);
|
||||
if (!e.data.success) { onError("Could not parse STEP file."); return; }
|
||||
var tris = 0;
|
||||
(e.data.meshes || []).forEach(function (m) {
|
||||
@@ -251,17 +274,17 @@
|
||||
showCanvas();
|
||||
showData(formatCount(tris) + " triangles");
|
||||
} else if (e.data.type === "error") {
|
||||
settled = true; worker.terminate(); URL.revokeObjectURL(wurl);
|
||||
settled = true; worker.terminate(); URL.revokeObjectURL(wurl); clearInterval(parseTimer);
|
||||
onError(e.data.message || "Could not parse STEP file.");
|
||||
}
|
||||
};
|
||||
worker.onerror = function (e) {
|
||||
if (!settled) { settled = true; worker.terminate(); URL.revokeObjectURL(wurl); onError("STEP worker error: " + (e && e.message || "unknown")); }
|
||||
if (!settled) { settled = true; worker.terminate(); URL.revokeObjectURL(wurl); clearInterval(parseTimer); onError("STEP worker error: " + (e && e.message || "unknown")); }
|
||||
};
|
||||
worker.postMessage({ type: "loadLib", libUrl: OCCT_JS_ABS, wasm: new Uint8Array(wasmBuf) }, [wasmBuf]);
|
||||
}).catch(function () { onError("Could not download STEP parser."); });
|
||||
} else {
|
||||
var geo = new THREE.STLLoader().parse(data);
|
||||
var geo = new THREE.STLLoader().parse(data.buffer);
|
||||
geo.computeVertexNormals();
|
||||
var mat = new THREE.MeshStandardMaterial({ color: 0xb8bb26, metalness: 0.25, roughness: 0.55, flatShading: true });
|
||||
group.add(new THREE.Mesh(geo, mat));
|
||||
@@ -299,5 +322,26 @@
|
||||
a.replaceWith(el);
|
||||
initViewer(el, href, m[0]);
|
||||
});
|
||||
|
||||
var blog = window.blogData;
|
||||
var byAlias = {};
|
||||
if (blog && blog.articles) {
|
||||
blog.articles.forEach(function (a) {
|
||||
var al = String(a.url || "").split("/").pop();
|
||||
if (al) byAlias[al] = a;
|
||||
});
|
||||
}
|
||||
document.querySelectorAll("#content a[href]").forEach(function (a) {
|
||||
var m = String(a.getAttribute("href") || "").match(/\.\/([^/?#]+)$/);
|
||||
if (!m) return;
|
||||
var art = byAlias[m[1]];
|
||||
if (!art) return;
|
||||
var t = String(art.title || "").trim();
|
||||
var mm = t.match(MODEL_RE);
|
||||
if (!mm) return;
|
||||
var el = makeViewer(t);
|
||||
a.replaceWith(el);
|
||||
initViewer(el, "api/notes/" + art.id + "/download", mm[0]);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user