Files
FreeCADWasmDev/web/src/techdraw-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

88 lines
5.0 KiB
TypeScript

import {
FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest,
} from "./facade-envelope.js";
export const TECHDRAW_SCHEMA = "freecad-web-techdraw/frontend-drawing/v1" as const;
export const TECHDRAW_COMMAND = "techdraw.createRecompute" as const;
export interface TechDrawBox { length: number; width: number; height: number }
export interface TechDrawPayload {
schema: typeof TECHDRAW_SCHEMA;
module: "TechDraw";
action: "create/recompute";
documentId?: string;
box?: TechDrawBox;
}
export const TECHDRAW_FIXTURE: TechDrawPayload = {
schema: TECHDRAW_SCHEMA, module: "TechDraw", action: "create/recompute",
box: { length: 40, width: 24, height: 18 },
};
export interface TechDrawResult {
schema: typeof TECHDRAW_SCHEMA;
module: "TechDraw";
action: "create/recompute";
source: "freecad-source";
box: TechDrawBox;
volume: number;
renderer: "TechDraw.viewPartAsSvg";
nativeSource: "TechDraw::DrawViewPart";
svg: string;
bounds: [number, number, number, number];
edgeCount: number;
document: { name: string; objectCount: number; keptOpen: true };
page: string;
pageSize: [number, number];
view: string;
}
export interface TechDrawKernel {
ccall(name: string, type: "number" | "string", argTypes?: string[], args?: unknown[]): number | string;
}
export type TechDrawRequest = FacadeRequest<TechDrawPayload>;
export type TechDrawResponse = FacadeResponse<TechDrawResult>;
export function createTechDrawRequest(id: string, payload: TechDrawPayload = TECHDRAW_FIXTURE): TechDrawRequest {
return createRequest(id, TECHDRAW_COMMAND, payload, payload.documentId ?? "TechDrawDrawing");
}
export function isTechDrawRequest(value: unknown): value is TechDrawRequest {
if (!isFacadeRequest(value)) return false;
const request = value as TechDrawRequest;
const p = request.payload;
return request.command === TECHDRAW_COMMAND && p?.schema === TECHDRAW_SCHEMA
&& p.module === "TechDraw" && p.action === "create/recompute"
&& (p.documentId === undefined || typeof p.documentId === "string")
&& (p.box === undefined || (typeof p.box?.length === "number" && typeof p.box?.width === "number" && typeof p.box?.height === "number"));
}
export async function handleTechDraw(p: TechDrawPayload, signal: AbortSignal, source: TechDrawKernel | Promise<TechDrawKernel>): Promise<TechDrawResult> {
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
if (!isTechDrawRequest(createTechDrawRequest("validation", p))
|| (p.box && ![p.box.length, p.box.width, p.box.height].every(value => Number.isFinite(value) && value > 0)))
throw new FacadeHandlerError("invalid_request", "Box dimensions must be positive finite numbers");
const kernel = await source;
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
let value: TechDrawResult & { ok?: boolean; error?: string; code?: string };
try {
value = JSON.parse(String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: TECHDRAW_COMMAND, args: p })])));
} catch {
throw new FacadeHandlerError("native_protocol_error", "TechDraw native dispatcher returned invalid JSON");
}
if (!value || typeof value !== "object") throw new FacadeHandlerError("native_protocol_error", "TechDraw native response must be an object");
if (value.ok !== true) throw new FacadeHandlerError(value.code ?? "native_error", value.error ?? "TechDraw drawing failed");
if (value.schema !== TECHDRAW_SCHEMA || value.module !== "TechDraw" || value.source !== "freecad-source"
|| value.renderer !== "TechDraw.viewPartAsSvg" || value.nativeSource !== "TechDraw::DrawViewPart"
|| value.action !== "create/recompute" || !Number.isFinite(value.volume) || value.volume <= 0
|| !value.box || ![value.box.length, value.box.width, value.box.height].every(n => Number.isFinite(n) && n > 0)
|| (p.box && (value.box.length !== p.box.length || value.box.width !== p.box.width || value.box.height !== p.box.height))
|| value.document?.name !== (p.documentId ?? "TechDrawDrawing") || value.document.keptOpen !== true
|| !Number.isInteger(value.document.objectCount) || value.document.objectCount < 4
|| value.page !== "Page" || value.view !== "View"
|| !Array.isArray(value.pageSize) || value.pageSize.length !== 2 || !value.pageSize.every(n => Number.isFinite(n) && n > 0)
|| typeof value.svg !== "string" || !value.svg.includes("<path")
|| !Array.isArray(value.bounds) || value.bounds.length !== 4 || !value.bounds.every(Number.isFinite)
|| value.bounds[2] <= value.bounds[0] || value.bounds[3] <= value.bounds[1]
|| !Number.isInteger(value.edgeCount) || value.edgeCount < 1)
throw new FacadeHandlerError("native_protocol_error", "TechDraw native response does not match the requested drawing");
return value;
}
export function dispatchTechDraw(queue: FacadeSerialQueue, request: TechDrawRequest, source: TechDrawKernel | Promise<TechDrawKernel>): Promise<TechDrawResponse> {
return queue.dispatch(request, (payload, signal) => handleTechDraw(payload, signal, source));
}