Three.js viewer supporting glb/gltf/obj/fbx/dae/ifc/ply via both server (SSR) and drag&drop (CSR) paths. - Streaming OBJ parser (src/viewer/objStream.ts) for files past the V8 max string length (>~1GB text) that OBJLoader can't handle; indexed geometry, per-vertex color from MTL Kd, float64 recenter baked in. - In-viewer float64 recenter (objRecenter.ts) for huge CAD/survey coordinates (~1e8) so float32 vertex buffers keep precision (no cracked faces). - Z-up right-handed world; Y-up formats rotated on load. - OBJ+MTL+texture drag&drop (LoadingManager URL-modifier maps dropped images). - OrbitControls ground-plane panning (road/rail alignment workflow). - UI: Zoom Fit, perspective/orthographic toggle, feature-edge outline. - DoubleSide for CAD OBJ; PLY mesh + point-cloud support. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
3.0 KiB
JavaScript
79 lines
3.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Diagnostic: load a huge OBJ (+mtl) through the REAL drag&drop path in headless
|
|
* Chrome, capture console ([recenter]/[hmw] bbox) and a screenshot. Confirms
|
|
* whether the in-viewer recenter ran and whether faces crack.
|
|
*
|
|
* USAGE: node tools/girder-smoke.mjs [--url http://localhost:3333] [obj] [mtl]
|
|
*/
|
|
import puppeteer from 'puppeteer-core';
|
|
import { existsSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
|
|
const CHROME = [
|
|
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
|
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
|
].find((p) => existsSync(p));
|
|
|
|
const args = process.argv.slice(2);
|
|
let url = 'http://localhost:3333';
|
|
const files = [];
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--url') url = args[++i];
|
|
else files.push(args[i]);
|
|
}
|
|
if (files.length === 0) {
|
|
files.push('samples/GirderObjs/part01.obj', 'samples/GirderObjs/part01.mtl');
|
|
}
|
|
const paths = files.map((f) => resolve(f));
|
|
for (const p of paths) if (!existsSync(p)) { console.error('missing', p); process.exit(1); }
|
|
|
|
const browser = await puppeteer.launch({
|
|
executablePath: CHROME,
|
|
headless: 'new',
|
|
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist',
|
|
'--disable-dev-shm-usage', '--window-size=1400,900'],
|
|
});
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1400, height: 900 });
|
|
page.on('console', (m) => console.log(' [browser]', m.text()));
|
|
page.on('pageerror', (e) => console.log(' [pageerror]', e.message));
|
|
|
|
await page.evaluateOnNewDocument(() => {
|
|
window.__hmwReady = null;
|
|
window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
|
|
});
|
|
|
|
console.log('goto', url);
|
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
await page.waitForSelector('#file-input', { timeout: 10000 });
|
|
|
|
const input = await page.$('#file-input');
|
|
console.log('uploading', paths.join(', '));
|
|
await input.uploadFile(...paths);
|
|
await page.evaluate(() => document.getElementById('file-input')
|
|
.dispatchEvent(new Event('change', { bubbles: true })));
|
|
|
|
console.log('waiting for hmw:ready (up to 240s)...');
|
|
const t0 = Date.now();
|
|
try {
|
|
await page.waitForFunction('window.__hmwReady != null', { timeout: 240000, polling: 1000 });
|
|
console.log('READY in', ((Date.now() - t0) / 1000).toFixed(1) + 's');
|
|
} catch {
|
|
console.log('TIMEOUT after', ((Date.now() - t0) / 1000).toFixed(1) + 's — capturing anyway');
|
|
}
|
|
|
|
await new Promise((r) => setTimeout(r, 1500));
|
|
await page.screenshot({ path: resolve('girder-smoke.png') });
|
|
console.log('screenshot -> girder-smoke.png (full)');
|
|
|
|
// Medium zoom (reproduce user's close inspection) via OrbitControls wheel.
|
|
await page.mouse.move(700, 450);
|
|
for (let i = 0; i < 14; i++) { await page.mouse.wheel({ deltaY: -200 }); await new Promise((r) => setTimeout(r, 20)); }
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
await page.screenshot({ path: resolve('girder-zoom.png') });
|
|
console.log('screenshot -> girder-zoom.png (zoomed in)');
|
|
|
|
await browser.close();
|