190 lines
8.3 KiB
JavaScript
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> |