Files
trilium_share_gruvbox/scripts/3d_model_viewer.js
T

190 lines
8.3 KiB
JavaScript

<script>
(function () {
var THREE_JS = "api/notes/__THREE_ID__/download";
var ORBIT_JS = "api/notes/__THREE_ORBIT_ID__/download";
var STL_JS = "api/notes/__THREE_STL_ID__/download";
var OCCT_JS = "api/notes/__OCCT_JS_ID__/download";
var OCCT_WASM = "api/notes/__OCCT_WASM_ID__/download";
var MODEL_RE = /\.(stl|step|stp)$/i;
function makeViewer(name) {
var box = document.createElement("div");
box.className = "model-viewer";
var wrap = document.createElement("div");
wrap.className = "model-viewer-canvas";
var status = document.createElement("div");
status.className = "model-viewer-status";
status.textContent = "Loading 3D model\u2026";
wrap.appendChild(status);
box.appendChild(wrap);
if (name) {
var cap = document.createElement("div");
cap.className = "model-viewer-caption";
cap.textContent = name;
box.appendChild(cap);
}
return box;
}
function loadScript(src, cb) {
var s = document.createElement("script");
s.src = src;
s.onload = cb;
document.head.appendChild(s);
}
function initViewer(el, url, ext) {
var isStep = /\.(step|stp)$/i.test(ext);
function loadThree(cb) {
if (window.THREE && window.THREE.OrbitControls) { cb(); return; }
loadScript(THREE_JS, function () {
loadScript(ORBIT_JS, function () {
if (isStep) { cb(); return; }
if (window.THREE.STLLoader) { cb(); return; }
loadScript(STL_JS, cb);
});
});
}
function status(text) {
var s = el.querySelector(".model-viewer-status");
if (s) s.textContent = text;
}
function fit(group) {
var box = new THREE.Box3().setFromObject(group);
if (box.isEmpty()) return;
var size = box.getSize(new THREE.Vector3()).length() || 1;
var center = box.getCenter(new THREE.Vector3());
var d = size * 2.4;
camera.position.set(center.x + d * 0.9, center.y + d * 0.6, center.z + d);
controls.target.copy(center);
controls.update();
}
function buildScene(cb) {
var statusEl = el.querySelector(".model-viewer-status");
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)));
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(w, h);
renderer.setClearColor(0x1d2021, 1);
wrap.innerHTML = "";
wrap.appendChild(renderer.domElement);
var hint = document.createElement("div");
hint.className = "model-viewer-hint";
hint.textContent = "Drag to rotate \u00b7 scroll to zoom";
wrap.appendChild(hint);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, w / 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);
var back = new THREE.DirectionalLight(0xffffff, 0.35);
back.position.set(-1, -0.5, -1);
scene.add(back);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
var group = new THREE.Group();
scene.add(group);
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
cb({ group: group, scene: scene, camera: camera, controls: controls, statusEl: statusEl });
}
function onError(msg) {
var s = el.querySelector(".model-viewer-status");
if (s) { s.textContent = msg || "Could not load 3D model."; s.classList.add("error"); }
}
loadThree(function () {
buildScene(function (ctx) {
fetch(url).then(function (r) {
if (!r.ok) throw new Error("fetch " + r.status);
return r.arrayBuffer();
}).then(function (buf) {
var data = new Uint8Array(buf);
if (isStep) {
ctx.statusEl.textContent = "Loading STEP parser\u2026";
function runOcct() {
ctx.statusEl.textContent = "Parsing STEP\u2026";
window.occtimportjs({ locateFile: function () { return OCCT_WASM; } }).then(function (occt) {
var result = occt.ReadStepFile(data, null);
if (!result || !result.success) { onError("Could not parse STEP file."); return; }
(result.meshes || []).forEach(function (mesh) {
if (!mesh.attributes || !mesh.attributes.position) return;
var geo = new THREE.BufferGeometry();
geo.setAttribute("position", new THREE.Float32BufferAttribute(mesh.attributes.position.array, 3));
if (mesh.attributes.normal && mesh.attributes.normal.array) {
geo.setAttribute("normal", new THREE.Float32BufferAttribute(mesh.attributes.normal.array, 3));
}
if (mesh.attributes.index && mesh.attributes.index.array) {
geo.setIndex(mesh.attributes.index.array);
}
var color = mesh.color && mesh.color.length === 3
? new THREE.Color(mesh.color[0], mesh.color[1], mesh.color[2]) : null;
var mat = new THREE.MeshStandardMaterial({
color: color || 0xb8bb26,
metalness: 0.25,
roughness: 0.55,
side: THREE.DoubleSide
});
ctx.group.add(new THREE.Mesh(geo, mat));
});
fit(ctx.group);
}).catch(function (e) { onError("Could not load STEP parser."); });
}
if (window.occtimportjs) { runOcct(); } else { loadScript(OCCT_JS, runOcct); }
} else {
var geo = new THREE.STLLoader().parse(data);
geo.computeVertexNormals();
var mat = new THREE.MeshStandardMaterial({ color: 0xb8bb26, metalness: 0.25, roughness: 0.55 });
ctx.group.add(new THREE.Mesh(geo, mat));
fit(ctx.group);
}
}).catch(function (e) { onError("Could not load 3D model."); });
});
});
}
var type = (document.body.className.match(/type-([\w-]+)/) || [])[1];
if (type === "file") {
var title = ((document.getElementById("title") || {}).textContent || document.title || "").trim();
var m = title.match(MODEL_RE);
if (m) {
var content = document.getElementById("content");
if (content) {
var noteId = document.body.getAttribute("data-note-id");
var el = makeViewer(title.trim());
content.innerHTML = "";
content.appendChild(el);
initViewer(el, "api/notes/" + noteId + "/download", m[0]);
}
}
}
document.querySelectorAll("#content a[href*='api/attachments/']").forEach(function (a) {
var name = (a.textContent || "").trim();
var m = name.match(MODEL_RE);
if (!m) return;
var href = a.getAttribute("href");
var el = makeViewer(name);
a.replaceWith(el);
initViewer(el, href, m[0]);
});
})();
</script>