88 lines
5.0 KiB
TypeScript
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));
|
|
}
|