Files
massive-cloud/server/smoothHeatmapJob.ts

126 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
},
});
}
}