feat: integrate SimpleCADAPI 2.0.2 CAD workflows

This commit is contained in:
Jerry
2026-08-03 11:17:05 +08:00
parent b5738e9109
commit c3a0f269b7
481 changed files with 110229 additions and 12826 deletions
+108
View File
@@ -22,6 +22,7 @@ import {
type CadGenerationResult,
} from "@/lib/cad-generator";
import { loadLlmConfig, resolveProviderApiKey, selectedModelId, type LlmConfig } from "@/lib/config";
import { exportRobotDescription } from "@/lib/robot-export";
import { readManifest, taskDir } from "@/lib/task-store";
const execFileAsync = promisify(execFile);
@@ -285,6 +286,41 @@ async function buildTaskContext(selectedTaskId?: string) {
if (sourcePath && (sourcePath.endsWith(".py") || sourcePath.endsWith(".scad") || sourcePath.endsWith(".json"))) {
const absoluteSourcePath = path.join(taskDir(selectedTaskId), sourcePath);
sourceText = await fs.readFile(absoluteSourcePath, "utf8").catch(() => "");
if (sourcePath.endsWith(".json") && sourceText) {
try {
const payload = JSON.parse(sourceText) as Record<string, any>;
const surfaceLayer = payload.surface_layer;
if (
surfaceLayer
&& typeof surfaceLayer === "object"
&& typeof surfaceLayer.data === "string"
) {
payload.surface_layer = {
format: surfaceLayer.format,
encoding: surfaceLayer.encoding,
uncompressed_bytes: surfaceLayer.uncompressed_bytes,
compressed_bytes: surfaceLayer.compressed_bytes,
sha256: surfaceLayer.sha256,
data: "<omitted from LLM context; deterministic CAD runtime only>",
};
} else if (surfaceLayer && typeof surfaceLayer === "object") {
payload.surface_layer = {
storage: "expanded_surfaceir_omitted_from_llm_context",
solid_count: Array.isArray(surfaceLayer.solids)
? surfaceLayer.solids.length
: undefined,
free_shell_count: Array.isArray(surfaceLayer.free_shells)
? surfaceLayer.free_shells.length
: undefined,
surface_vocabulary: surfaceLayer.surface_vocabulary,
curve_vocabulary: surfaceLayer.curve_vocabulary,
};
}
sourceText = JSON.stringify(payload, null, 2);
} catch {
// Non-DesignIR JSON remains available as a bounded text excerpt.
}
}
}
return [
`Current task id: ${selectedTaskId}`,
@@ -333,6 +369,10 @@ function buildSystemPrompt(viewerContext: unknown[], attachments: unknown[], tas
"For selected holes, use the selected reference center/surface/bbox/adjacent selectors to identify the individual feature. If the editable source models a repeated pattern with one shared diameter variable, split out or override the selected instance instead of changing the shared variable.",
"Prefer editing the DesignIR recorded in cad-task.json before regenerating STEP and viewer assets.",
"If the current task is an uploaded-STEP DesignIR 3.0 surface_parametric reconstruction and the requested change matches a validated parameter, call edit_designir3_parameter.",
"After uploaded-STEP reconstruction, list every perturbation-validated editable parameter by name, current value, unit, and validated range. Never report only the parameter count. Explain that unlisted inferred parameters remain in DesignIR but are not safely exposed until an executable binding and perturbation acceptance exist.",
"When the user asks to download or convert the current model to URDF or MJCF, call export_robot_description. Do not fabricate XML. The tool performs lazy conversion from the current DesignIR and returns a portable ZIP containing the robot description and mesh.",
"After export_robot_description succeeds, the UI starts the download and renders the exact package link. Say that the download has started; do not manually rewrite, shorten, or guess the returned URL.",
"A STEP-derived DesignIR without authoritative link/joint/material evidence exports honestly as one fixed base_link with inertial data omitted. Never invent articulated joints, limits, actuators, density, mass, or inertia.",
"If the current task is a text/image DesignIR 3.0 fully_semantic_parametric or hybrid_semantic_surface_parametric model, modify its authoritative semantic_layer and call generate_cad again; preserve unrelated parameters, constraints, and features.",
"For every new text/image CAD generation, author complete DesignIR 3.0 with designir_kind=independent_parametric_cad, reconstruction_mode=fully_semantic_parametric, authoring_mode=semantic_feature_program, and semantic_layer containing reconstruction_status, coordinate_system, datums, parameters, expressions, sketches, constraints, features, patterns, attachments, and construction_stages.",
"For semantic DesignIR 3.0, edit_interface must contain semantic_parameters, surface_parameter_groups=[], modification_levels=[semantic_feature], and preserved_interfaces. validation_contract must contain source_independence=true, geometry_checks, edit_checks, invariants, perturbations, and thresholds.",
@@ -759,6 +799,8 @@ export async function streamAgentResponse({
data: {
taskId: cadGeneration.taskId,
sourcePath: cadGeneration.sourcePath,
sourceUrl: cadGeneration.sourceUrl,
editableParameters: cadGeneration.editableParameters,
artifactPath: cadGeneration.artifactPath,
artifactUrl: cadGeneration.artifactUrl,
previewPath: cadGeneration.previewPath,
@@ -830,6 +872,8 @@ export async function streamAgentResponse({
data: {
taskId: cadGeneration.taskId,
sourcePath: cadGeneration.sourcePath,
sourceUrl: cadGeneration.sourceUrl,
editableParameters: cadGeneration.editableParameters,
artifactPath: cadGeneration.artifactPath,
artifactUrl: cadGeneration.artifactUrl,
previewPath: cadGeneration.previewPath,
@@ -843,6 +887,68 @@ export async function streamAgentResponse({
return cadGeneration;
},
}),
export_robot_description: tool({
description: [
"Lazily export the current DesignIR 3.0 CAD task as a portable URDF or MJCF ZIP package.",
"The package contains the robot description, a high-resolution STL visual/collision mesh, and an export manifest.",
"Use this when the user asks to download, export, or convert the current model to URDF or MJCF.",
].join(" "),
inputSchema: z.object({
format: z.enum(["urdf", "mjcf"]),
taskId: z.string().optional().describe(
"CAD task id. Omit to use the currently selected task.",
),
}),
execute: async ({ format, taskId }) => {
const targetTaskId = String(taskId || selectedTaskId || "").trim();
if (!targetTaskId) {
throw new Error("Select or generate a CAD task before robot export.");
}
writeCadProgress({
step: `export_${format}`,
label: `生成 ${format.toUpperCase()}`,
status: "running",
message: "正在从 DesignIR 独立重建几何并打包机器人描述与网格。",
});
try {
const exported = await exportRobotDescription(
targetTaskId,
format,
);
writeCadProgress({
step: `export_${format}`,
label: `生成 ${format.toUpperCase()}`,
status: "success",
message: exported.status === "cached"
? "已复用当前 DesignIR 的现有导出包。"
: "机器人描述、网格和导出说明已生成。",
});
streamWriter?.write({
type: "data-robot-export",
data: exported,
id: `robot_export_${format}_${targetTaskId}`,
});
completeAgentStream(`${format.toUpperCase()} 按需导出已完成。`);
return {
ok: true,
...exported,
downloadStartedByUi: true,
note: "The UI starts the ZIP download and renders the exact package URL; do not rewrite the URL in prose.",
};
} catch (error) {
const message = error instanceof Error
? error.message
: `${format.toUpperCase()} export failed.`;
writeCadProgress({
step: `export_${format}`,
label: `生成 ${format.toUpperCase()}`,
status: "error",
message,
});
throw error;
}
},
}),
generate_cad: tool({
description: [
"Generate a real CAD model from agent-authored semantic DesignIR 3.0.",
@@ -920,6 +1026,8 @@ export async function streamAgentResponse({
data: {
taskId: cadGeneration.taskId,
sourcePath: cadGeneration.sourcePath,
sourceUrl: cadGeneration.sourceUrl,
editableParameters: cadGeneration.editableParameters,
artifactPath: cadGeneration.artifactPath,
artifactUrl: cadGeneration.artifactUrl,
previewPath: cadGeneration.previewPath,