refactor: establish unified viewer workspaces

This commit is contained in:
2026-07-29 09:11:14 +09:00
parent bedb357259
commit 3279ac099e
27 changed files with 591 additions and 4483 deletions
-4304
View File
File diff suppressed because it is too large Load Diff
+5 -13
View File
@@ -1,5 +1,5 @@
{
"name": "hmwebviewer",
"name": "@hmwebviewer/viewer-3d",
"private": true,
"version": "0.1.0",
"type": "module",
@@ -8,21 +8,13 @@
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"postinstall": "node tools/copy-decoders.mjs"
"stage:decoders": "node tools/copy-decoders.mjs"
},
"dependencies": {
"three": "^0.169.0",
"web-ifc": "^0.0.77"
"three": "0.185.1",
"web-ifc": "0.0.77"
},
"devDependencies": {
"@gltf-transform/cli": "^4.4.0",
"@gltf-transform/core": "^4.4.0",
"@gltf-transform/extensions": "^4.4.0",
"@gltf-transform/functions": "^4.4.0",
"@types/three": "^0.169.0",
"draco3d": "^1.5.7",
"puppeteer-core": "^25.1.0",
"typescript": "^5.6.0",
"vite": "^5.4.0"
"puppeteer-core": "^25.1.0"
}
}
-1
View File
@@ -24,7 +24,6 @@ Both are dependencies of `KTX2Loader`:
```js
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( 'examples/jsm/libs/basis/' );
ktx2Loader.detectSupport( renderer );
ktx2Loader.load( 'diffuse.ktx2', function ( texture ) {
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
ply
format ascii 1.0
comment Three.js r185 Float64 normalization fixture
element vertex 3
property double x
property double y
property double z
property uchar red
property uchar green
property uchar blue
element face 1
property list uchar int vertex_indices
end_header
0.0 0.0 0.0 255 0 0
100.0 0.0 0.0 0 255 0
0.0 100.0 0.0 0 0 255
3 0 1 2
View File
View File
+28 -1
View File
@@ -62,6 +62,21 @@ async function getPLY(): Promise<PLYLoaderT> {
return (_ply = new PLYLoader());
}
function normalizePlyAttributes(geometry: THREE.BufferGeometry): void {
for (const name of ['position', 'normal', 'uv', 'color']) {
const attribute = geometry.getAttribute(name);
if (!attribute || !(attribute.array instanceof Float64Array)) continue;
geometry.setAttribute(
name,
new THREE.Float32BufferAttribute(
new Float32Array(attribute.array),
attribute.itemSize,
attribute.normalized,
),
);
}
}
/**
* Load any supported model URL and resolve to a scene-addable Object3D.
* `nameHint` carries the real file name for ext detection when `url` is a
@@ -165,7 +180,18 @@ export async function loadModel(
case 'dae': {
const loader = await getCollada();
return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(url, (result) => resolve(result.scene), onXhr, (err) => reject(err));
loader.load(
url,
(result) => {
if (!result) {
reject(new Error('Collada loader returned no scene'));
return;
}
resolve(result.scene);
},
onXhr,
(err) => reject(err),
);
});
}
case 'ply': {
@@ -174,6 +200,7 @@ export async function loadModel(
loader.load(
url,
(geometry) => {
normalizePlyAttributes(geometry);
const hasColor = geometry.hasAttribute('color');
if (geometry.index) {
// Triangle mesh
+7 -3
View File
@@ -2,14 +2,16 @@
// runtime paths /draco/ and /basis/ resolve (version-matched to installed three).
// Also copies the web-ifc single-thread WASM to public/web-ifc/ for the IFC path.
// Runs automatically via `npm install` (postinstall). Safe to re-run.
import { cpSync, copyFileSync, existsSync, mkdirSync } from 'node:fs';
import { cpSync, copyFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..');
const libs = resolve(root, 'node_modules/three/examples/jsm/libs');
const threeEntry = fileURLToPath(import.meta.resolve('three'));
const threeRoot = resolve(dirname(threeEntry), '..');
const libs = resolve(threeRoot, 'examples/jsm/libs');
const targets = [
[resolve(libs, 'draco/gltf'), resolve(root, 'public/draco')],
[resolve(libs, 'basis'), resolve(root, 'public/basis')],
@@ -25,13 +27,15 @@ for (const [src, dest] of targets) {
console.warn(`[copy-decoders] source missing: ${src}`);
continue;
}
rmSync(dest, { force: true, recursive: true });
mkdirSync(dest, { recursive: true });
cpSync(src, dest, { recursive: true });
console.log(`[copy-decoders] ${src} -> ${dest}`);
}
// web-ifc single-thread WASM (+ MT build if present) for the IFC load path.
const ifcSrcDir = resolve(root, 'node_modules/web-ifc');
const ifcEntry = fileURLToPath(import.meta.resolve('web-ifc'));
const ifcSrcDir = dirname(ifcEntry);
const ifcDest = resolve(root, 'public/web-ifc');
if (existsSync(ifcSrcDir)) {
mkdirSync(ifcDest, { recursive: true });
+3 -1
View File
@@ -7,7 +7,9 @@ export default defineConfig({
sourcemap: true,
rollupOptions: {
output: {
manualChunks: { three: ['three'] },
manualChunks(id) {
if (id.includes('/node_modules/three/')) return 'three';
},
},
},
},