Files
FreeCADWasmDev/web/src/measure-python-family-facade.ts
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

22 lines
5.6 KiB
TypeScript

import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
export const MEASURE_PYTHON_FAMILY_SCHEMA = "freecad-web-measure/python-family-batch/v1" as const;
export const MEASURE_PYTHON_FAMILY_COMMAND = "measure.pythonFamilyBatch" as const;
export const MEASURE_DESKTOP_COMMANDS = ["Std_Measure", "Std_MassProperties"] as const;
/** Commands exposed to React command palettes; execution remains native. */
export const MEASURE_DESKTOP_COMMAND_ENTRIES = MEASURE_DESKTOP_COMMANDS.map((command) => ({
command,
apiId: `Measure:desktop:${command}`,
family: "desktop-command-command" as const,
}));
export type MeasureDesktopCommand = typeof MEASURE_DESKTOP_COMMANDS[number];
export interface MeasurePythonFamilyRecord { apiId: string; name: string; family: string; args?: Record<string, unknown>; }
export interface MeasurePythonFamilyPayload { schema: typeof MEASURE_PYTHON_FAMILY_SCHEMA; module: "Measure"; tier: "tier-1-readonly"; records: MeasurePythonFamilyRecord[]; }
export interface MeasurePythonFamilyResult { schema: typeof MEASURE_PYTHON_FAMILY_SCHEMA; module: "Measure"; tier: "tier-1-readonly"; source: "freecad-source"; records: Array<MeasurePythonFamilyRecord & { status: "exposed" | "restricted" | "nonportable" | "not_implemented"; code: string; message: string; native: true; resourcesReleased: true; desktopCommandRoute?: string; documentHandle?: string; objectHandle?: string; value?: Record<string, unknown> }>; count: number; resourcesReleased: true; }
export type MeasurePythonFamilyRequest = FacadeRequest<MeasurePythonFamilyPayload>; export type MeasurePythonFamilyResponse = FacadeResponse<MeasurePythonFamilyResult>;
export interface MeasurePythonFamilyKernel { ccall(name: string, returnType: "number" | "string", argTypes?: string[], args?: unknown[]): number | string; }
export function createMeasurePythonFamilyRequest(requestId: string, records: MeasurePythonFamilyRecord[]): MeasurePythonFamilyRequest { return createRequest(requestId, MEASURE_PYTHON_FAMILY_COMMAND, { schema: MEASURE_PYTHON_FAMILY_SCHEMA, module: "Measure", tier: "tier-1-readonly", records }, "measure-python-family"); }
export function createMeasureDesktopCommandRequest(requestId: string, command: MeasureDesktopCommand, args: Record<string, unknown>): MeasurePythonFamilyRequest { return createMeasurePythonFamilyRequest(requestId, [{ apiId: `Measure:desktop:${command}`, name: command, family: "desktop-command-command", args }]); }
export function isMeasurePythonFamilyRequest(value: unknown): value is MeasurePythonFamilyRequest { if (!isFacadeRequest(value)) return false; const request = value as MeasurePythonFamilyRequest; const p = request.payload; return request.command === MEASURE_PYTHON_FAMILY_COMMAND && p?.schema === MEASURE_PYTHON_FAMILY_SCHEMA && p?.module === "Measure" && p?.tier === "tier-1-readonly" && Array.isArray(p.records) && p.records.length > 0 && p.records.every((r) => r && typeof r.apiId === "string" && typeof r.name === "string" && typeof r.family === "string" && (r.args === undefined || (r.args && typeof r.args === "object" && !Array.isArray(r.args)))); }
export async function handleMeasurePythonFamily(payload: MeasurePythonFamilyPayload, signal: AbortSignal, source: MeasurePythonFamilyKernel | Promise<MeasurePythonFamilyKernel>): Promise<MeasurePythonFamilyResult> { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); if (!isMeasurePythonFamilyRequest(createMeasurePythonFamilyRequest("validation", payload.records))) throw new FacadeHandlerError("invalid_fixture", "Measure Python family payload is invalid"); const kernel = await source; if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: MEASURE_PYTHON_FAMILY_COMMAND, args: { tier: payload.tier, records: payload.records } })])); let reply: unknown; try { reply = JSON.parse(raw); } catch { throw new FacadeHandlerError("native_protocol_error", "Measure native dispatcher returned invalid JSON"); } if (!reply || typeof reply !== "object") throw new FacadeHandlerError("native_protocol_error", "Measure native dispatcher returned a non-object result"); const result = reply as Record<string, unknown>; if (result.ok !== true) throw new FacadeHandlerError(String(result.code || "native_error"), String(result.error || "Measure Python family dispatch failed")); if (result.schema !== MEASURE_PYTHON_FAMILY_SCHEMA || result.module !== "Measure" || result.tier !== "tier-1-readonly" || result.source !== "freecad-source" || result.count !== payload.records.length || result.resourcesReleased !== true || !Array.isArray(result.records) || result.records.length !== payload.records.length) throw new FacadeHandlerError("native_protocol_error", "Measure native dispatcher returned an invalid batch schema"); const desktopRecords = (result.records as Array<Record<string, unknown>>).filter((r) => typeof r.apiId === "string" && r.apiId.startsWith("Measure:desktop:")); if (desktopRecords.some((r) => r.status !== "exposed" || r.desktopCommandRoute !== r.apiId)) throw new FacadeHandlerError("native_protocol_error", "Measure desktop command route was not executed natively"); return result as unknown as MeasurePythonFamilyResult; }
export function dispatchMeasurePythonFamily(queue: FacadeSerialQueue, request: MeasurePythonFamilyRequest, source: MeasurePythonFamilyKernel | Promise<MeasurePythonFamilyKernel>): Promise<MeasurePythonFamilyResponse> { return queue.dispatch(request, (payload, signal) => handleMeasurePythonFamily(payload, signal, source)); }