feat: update algorithm integrations and point cloud overlays

This commit is contained in:
2026-06-30 16:46:36 +08:00
parent ddca7e5436
commit 7e2e56a50d
64 changed files with 4356 additions and 2781 deletions

View File

@@ -283,7 +283,10 @@ router.post('/sample/batch', async (req, res) => {
);
const totalSampled = sampledPerFile.reduce((s, pts) => s + pts.length, 0);
const binBuf = Buffer.allocUnsafe(totalSampled * 16);
// 如果任意点有颜色,则输出 28 字节/点(x,y,z,error,r,g,b 各 float32);否则 16 字节
const hasColor = sampledPerFile.some((pts) => pts.some((p) => p.r !== undefined));
const bytesPerPoint = hasColor ? 28 : 16;
const binBuf = Buffer.allocUnsafe(totalSampled * bytesPerPoint);
let offset = 0;
for (const pts of sampledPerFile) {
for (const p of pts) {
@@ -291,7 +294,12 @@ router.post('/sample/batch', async (req, res) => {
binBuf.writeFloatLE(p.y, offset + 4);
binBuf.writeFloatLE(p.z, offset + 8);
binBuf.writeFloatLE(p.error, offset + 12);
offset += 16;
if (hasColor) {
binBuf.writeFloatLE((p.r ?? 128) / 255, offset + 16);
binBuf.writeFloatLE((p.g ?? 128) / 255, offset + 20);
binBuf.writeFloatLE((p.b ?? 128) / 255, offset + 24);
}
offset += bytesPerPoint;
}
}
@@ -304,6 +312,7 @@ router.post('/sample/batch', async (req, res) => {
console.log(`[FileRoutes] Batch done: ${totalSampled} pts, ${(binBuf.length / 1e6).toFixed(1)} MB`);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('X-Point-Count', totalSampled);
res.setHeader('X-Has-Color', hasColor ? '1' : '0');
res.setHeader('X-File-Stats', JSON.stringify(fileStats));
res.send(binBuf);
} catch (err) {
@@ -401,7 +410,45 @@ router.get('/read/analysis/:taskId/:resultId/*', async (req, res) => {
res.sendFile(filePath);
});
// ─── 插件热重载 ──────────────────────────────────────────────────────────────
// ─── 分析结果重新同步 ────────────────────────────────────────────────────────
// POST /api/files/resync/analysis/:taskId/:resultId
// 把磁盘上的 result.json 重新同步到数据库,用于手动修复 overlay3d 等字段。
router.post('/resync/analysis/:taskId/:resultId', async (req, res) => {
const taskId = parseInt(req.params.taskId, 10);
const resultId = parseInt(req.params.resultId, 10);
if (Number.isNaN(taskId) || Number.isNaN(resultId)) {
return res.status(400).json({ error: 'invalid params' });
}
const row = await db.getTaskAnalysisResultById(resultId);
if (!row || row.taskId !== taskId) {
return res.status(404).json({ error: 'not found' });
}
const resultJsonPath = path.join(TASKS_DIR, String(taskId), 'analysis', String(resultId), 'result.json');
if (!fs.existsSync(resultJsonPath)) {
return res.status(404).json({ error: 'result.json missing' });
}
try {
const result = JSON.parse(fs.readFileSync(resultJsonPath, 'utf8'));
const existingRd = (row.resultData as Record<string, unknown>) ?? {};
const mergedRd = {
...existingRd,
message: result.message ?? existingRd.message,
measurements: result.measurements ?? existingRd.measurements,
errorStats: result.errorStats ?? existingRd.errorStats,
overlay3d: result.overlay3d ?? existingRd.overlay3d,
images: result.images ?? existingRd.images,
files: result.files ?? existingRd.files,
extra: result.extra ?? existingRd.extra,
schemaVersion: result.schemaVersion ?? existingRd.schemaVersion,
};
await db.updateTaskAnalysisResult(resultId, { resultData: mergedRd });
res.json({ success: true, overlay3d: result.overlay3d });
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
// POST /api/files/plugins/reload
// 重新从磁盘扫描 manifests/*.json,无需重启服务器。
// 用于更新算法版本后立即生效。