feat: add model file library links
This commit is contained in:
@@ -14,6 +14,52 @@ import { stepParseCacheDir } from "./stepParse.js";
|
||||
import { listPlugins, getPlugin } from "./plugins/registry.js";
|
||||
import { enqueueAlgorithmJob, algorithmJobStats, cancelAlgorithmJob, cancelAlgorithmJobsByTask } from "./algorithmJob.js";
|
||||
|
||||
async function validateRegistrationRunInput(input: {
|
||||
taskId: number;
|
||||
pointCloudFileIds?: number[];
|
||||
cadFileIds?: number[];
|
||||
allowDuplicateRegistration?: boolean;
|
||||
}) {
|
||||
const pointCloudFileIds = input.pointCloudFileIds ?? [];
|
||||
const cadFileIds = input.cadFileIds ?? [];
|
||||
|
||||
if (pointCloudFileIds.length !== 1 || cadFileIds.length !== 1) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "整车配准必须恰好选择 1 个点云文件和 1 个 CAD 文件。",
|
||||
});
|
||||
}
|
||||
|
||||
const pointcloudFiles = await db.listTaskPointcloudFiles(input.taskId);
|
||||
const pointcloud = pointcloudFiles.find((row) => row.id === pointCloudFileIds[0]);
|
||||
if (!pointcloud) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "选中的点云文件不存在。" });
|
||||
}
|
||||
if (pointcloud.source === "registered") {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "已配准点云不能再次作为配准输入,请选择原始点云进行配准。",
|
||||
});
|
||||
}
|
||||
|
||||
const cadFiles = await db.listTaskCadFiles(input.taskId);
|
||||
const cad = cadFiles.find((row) => row.id === cadFileIds[0]);
|
||||
if (!cad) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "选中的 CAD 文件不存在。" });
|
||||
}
|
||||
|
||||
const registeredChildren = pointcloudFiles
|
||||
.filter((row) => row.source === "registered" && row.sourcePointcloudFileId === pointcloud.id)
|
||||
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||
if (registeredChildren.length > 0 && !input.allowDuplicateRegistration) {
|
||||
const latest = registeredChildren[0];
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message: `点云「${pointcloud.name}」已有配准产物「${latest.name}」,重复配准需要确认。`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const appRouter = router({
|
||||
system: systemRouter,
|
||||
|
||||
@@ -147,6 +193,23 @@ export const appRouter = router({
|
||||
}),
|
||||
}),
|
||||
|
||||
// ─── 车型点云文件 ─────────────────────────────────────────────────────────
|
||||
|
||||
modelPointcloudFile: router({
|
||||
list: protectedProcedure
|
||||
.input(z.object({ modelId: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
return db.listModelPointcloudFiles(input.modelId);
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
await db.deleteModelPointcloudFile(input.id);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
// ─── 任务 CAD 文件 ────────────────────────────────────────────────────────────
|
||||
|
||||
taskCadFile: router({
|
||||
@@ -163,6 +226,27 @@ export const appRouter = router({
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
linkFromModel: protectedProcedure
|
||||
.input(z.object({
|
||||
taskId: z.number(),
|
||||
modelCadFileIds: z.array(z.number()).min(1),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const files = await db.linkTaskCadFilesFromModel(input.taskId, input.modelCadFileIds);
|
||||
return { success: true, files };
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
if (message.includes("another model")) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "只能关联当前车型的 CAD 库文件" });
|
||||
}
|
||||
if (message.includes("not found")) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "任务或 CAD 库文件不存在" });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}),
|
||||
|
||||
// 检查 CAD 文件是否已有三角化采样缓存(.cad_sample_cache/ 目录下存在对应 PLY)
|
||||
hasSampleCache: protectedProcedure
|
||||
.input(z.object({ cadFileIds: z.array(z.number()) }))
|
||||
@@ -257,6 +341,27 @@ export const appRouter = router({
|
||||
await db.deleteTaskPointcloudFile(input.id);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
linkFromModel: protectedProcedure
|
||||
.input(z.object({
|
||||
taskId: z.number(),
|
||||
modelPointcloudFileIds: z.array(z.number()).min(1),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const files = await db.linkTaskPointcloudFilesFromModel(input.taskId, input.modelPointcloudFileIds);
|
||||
return { success: true, files };
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
if (message.includes("another model")) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "只能关联当前车型的点云库文件" });
|
||||
}
|
||||
if (message.includes("not found")) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "任务或点云库文件不存在" });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}),
|
||||
}),
|
||||
|
||||
// ─── 任务分析结果 ────────────────────────────────────────────────────────────
|
||||
@@ -467,6 +572,8 @@ export const appRouter = router({
|
||||
pointCloudFileIds: z.array(z.number()).optional(),
|
||||
/** 可选:任务级 CAD 文件 ID(空/缺省 = 不传 CAD) */
|
||||
cadFileIds: z.array(z.number()).optional(),
|
||||
/** 配准:允许同一原始点云再次生成配准产物 */
|
||||
allowDuplicateRegistration: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -477,6 +584,9 @@ export const appRouter = router({
|
||||
message: `未注册的算法插件:${input.pluginId}`,
|
||||
});
|
||||
}
|
||||
if (manifest.category === "配准") {
|
||||
await validateRegistrationRunInput(input);
|
||||
}
|
||||
|
||||
try {
|
||||
const record = await db.createTaskAnalysisResult({
|
||||
@@ -529,6 +639,8 @@ export const appRouter = router({
|
||||
pointCloudFileIds: z.array(z.number()).optional(),
|
||||
/** 可选:任务级 CAD 文件 ID(空/缺省 = 不传 CAD) */
|
||||
cadFileIds: z.array(z.number()).optional(),
|
||||
/** 配准:允许同一原始点云再次生成配准产物 */
|
||||
allowDuplicateRegistration: z.boolean().optional(),
|
||||
jobs: z
|
||||
.array(
|
||||
z.object({
|
||||
@@ -542,6 +654,10 @@ export const appRouter = router({
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const created: { id: number; pluginId: string }[] = [];
|
||||
const hasRegistrationJob = input.jobs.some((job) => getPlugin(job.pluginId)?.category === "配准");
|
||||
if (hasRegistrationJob) {
|
||||
await validateRegistrationRunInput(input);
|
||||
}
|
||||
for (const job of input.jobs) {
|
||||
const manifest = getPlugin(job.pluginId);
|
||||
if (!manifest) continue;
|
||||
|
||||
Reference in New Issue
Block a user