feat: initial commit - massive-cloud point cloud processing system

This commit is contained in:
2026-05-27 09:51:42 +00:00
commit 16811100d1
391 changed files with 78338 additions and 0 deletions

125
server/smoothHeatmapJob.ts Normal file
View File

@@ -0,0 +1,125 @@
import * as fs from 'fs';
import { fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import * as db from './db.js';
import {
computeCadShellHeatmapFromObj,
computeCadShellHeatmapFromStl,
} from './cadHeatmapCompute.js';
import { pickCadFileForHeatmap } from './cadFilePick.js';
import { sampleTaskPointclouds } from './sampleTaskPointclouds.js';
const DATA_DIR = process.env.DATA_DIR || '/data';
const TASKS_DIR = path.join(DATA_DIR, 'tasks');
async function reportSmoothHeatmapProgress(
resultId: number,
progress: number,
stage: string,
): Promise<void> {
const p = Math.min(1, Math.max(0, progress));
await db.updateTaskAnalysisResult(resultId, {
resultData: {
kind: 'smooth_heatmap',
progress: p,
stage,
},
});
}
function resolveSmoothHeatmapRunnerEntry(): string {
const dir = path.dirname(fileURLToPath(import.meta.url));
if (process.env.NODE_ENV === 'production') {
return path.join(dir, 'smoothHeatmapJobRunner.js');
}
return path.join(dir, 'smoothHeatmapJobRunner.ts');
}
/**
* 在子进程中执行平滑热力(STL/OBJ 解析与顶点着色为 CPU 密集同步计算,会长时间阻塞事件循环)。
* 主进程只负责 fork,HTTP 与页面刷新可继续响应。
*/
export function enqueueSmoothHeatmapJob(resultId: number, taskId: number, totalPoints: number): void {
const script = resolveSmoothHeatmapRunnerEntry();
const isProd = process.env.NODE_ENV === 'production';
const child = fork(script, [String(resultId), String(taskId), String(totalPoints)], {
env: { ...process.env },
// fork 必须保留 IPC:自定义 stdio 时须显式加入 'ipc',否则 Node 会抛错
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
execArgv: isProd ? [] : ['--import', 'tsx'],
});
child.on('error', (err) => {
console.error('[SmoothHeatmapJob] fork 失败:', err);
});
child.on('exit', (code, signal) => {
if (code !== 0 && code != null) {
console.error(`[SmoothHeatmapJob] 子进程退出 code=${code} signal=${signal ?? ''}`);
}
});
}
/** 供子进程入口调用;也可在测试中直接 await。 */
export async function runSmoothHeatmapJob(resultId: number, taskId: number, totalPoints: number): Promise<void> {
try {
await reportSmoothHeatmapProgress(resultId, 0.06, '加载任务与 CAD…');
const task = await db.getInspectionTaskById(taskId);
if (!task) throw new Error('task not found');
const cadFiles = await db.listModelCadFiles(task.modelId);
const picked = pickCadFileForHeatmap(cadFiles);
if (!picked) {
throw new Error(
'当前车型下未找到 STL 或 OBJ 的 CAD。请在与该检测任务相同的车型下上传 CAD,或确认文件扩展名为 .stl / .obj',
);
}
if (!fs.existsSync(picked.file.filePath)) throw new Error('CAD 文件不存在');
await reportSmoothHeatmapProgress(resultId, 0.14, '点云采样中…');
const allPoints = await sampleTaskPointclouds(taskId, totalPoints);
if (allPoints.length === 0) throw new Error('任务下无可用点云采样');
await reportSmoothHeatmapProgress(resultId, 0.36, '读取 CAD 文件…');
const cadBuf = fs.readFileSync(picked.file.filePath);
await reportSmoothHeatmapProgress(resultId, 0.42, '解析网格并计算顶点热力(耗时较长)…');
const computed =
picked.ext === 'stl'
? computeCadShellHeatmapFromStl(cadBuf, allPoints)
: computeCadShellHeatmapFromObj(cadBuf, allPoints);
if (!computed) throw new Error('热力计算失败');
await reportSmoothHeatmapProgress(resultId, 0.88, '写入热力缓存文件…');
const outDir = path.join(TASKS_DIR, String(taskId), 'analysis');
fs.mkdirSync(outDir, { recursive: true });
const binName = `heatmap_${resultId}.bin`;
const binPath = path.join(outDir, binName);
fs.writeFileSync(
binPath,
Buffer.from(computed.colors.buffer, computed.colors.byteOffset, computed.colors.byteLength),
);
await db.updateTaskAnalysisResult(resultId, {
status: 'completed',
resultData: {
kind: 'smooth_heatmap',
vertexCount: computed.vertexCount,
dispMin: computed.dispMin,
dispMax: computed.dispMax,
binName,
},
});
console.log(
`[SmoothHeatmapJob] ok resultId=${resultId} taskId=${taskId} verts=${computed.vertexCount}`,
);
} catch (err) {
console.error('[SmoothHeatmapJob]', err);
await db.updateTaskAnalysisResult(resultId, {
status: 'error',
resultData: {
kind: 'smooth_heatmap',
error: err instanceof Error ? err.message : String(err),
},
});
}
}