28 lines
11 KiB
TypeScript
28 lines
11 KiB
TypeScript
import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
|
|
|
|
export const PARTDESIGN_PYTHON_FAMILY_SCHEMA = "freecad-web-partdesign/python-family-batch/v1" as const;
|
|
export const PARTDESIGN_PYTHON_FAMILY_COMMAND = "partdesign.pythonFamilyBatch" as const;
|
|
export const PARTDESIGN_DESKTOP_COMMANDS = ["PartDesign_CoordinateSystem", "PartDesign_Draft", "PartDesign_Fillet", "PartDesign_Groove", "PartDesign_Hole", "PartDesign_Line", "PartDesign_LinearPattern", "PartDesign_Mirrored", "PartDesign_AdditiveHelix", "PartDesign_AdditiveLoft", "PartDesign_AdditivePipe", "PartDesign_Boolean", "PartDesign_Chamfer", "PartDesign_Clone", "PartDesign_CompDatums", "PartDesign_CompSketches", "PartDesign_CompPrimitiveAdditive", "PartDesign_CompPrimitiveSubtractive", "PartDesign_MultiTransform", "PartDesign_NewSketch", "PartDesign_Pad", "PartDesign_Plane", "PartDesign_Pocket", "PartDesign_Point", "PartDesign_PolarPattern", "PartDesign_Revolution", "PartDesign_Scaled", "PartDesign_ShapeBinder", "PartDesign_SubShapeBinder", "PartDesign_SubtractiveHelix", "PartDesign_SubtractiveLoft", "PartDesign_SubtractivePipe", "PartDesign_Thickness", "PartDesign_InvoluteGear", "PartDesign_Sprocket", "PartDesign_Body", "PartDesign_DuplicateSelection", "PartDesign_Migrate", "PartDesign_MoveFeature", "PartDesign_MoveFeatureInTree", "PartDesign_MoveTip", "Sketcher_EditSketch", "Sketcher_MapSketch"] as const;
|
|
export type PartDesignDesktopCommand = typeof PARTDESIGN_DESKTOP_COMMANDS[number];
|
|
export interface PartDesignDesktopPayload { module: "PartDesign"; command: PartDesignDesktopCommand; args: Record<string, unknown>; }
|
|
export type PartDesignDesktopRequest = FacadeRequest<PartDesignDesktopPayload>;
|
|
export interface PartDesignDesktopResult { schema: "freecad-web-partdesign/desktop-command/v1"; module: "PartDesign"; cmd: PartDesignDesktopCommand; status: string; code: string; message: string; native: true; resourcesReleased: boolean; [key: string]: unknown; }
|
|
export type PartDesignDesktopResponse = FacadeResponse<PartDesignDesktopResult>;
|
|
export type PartDesignPythonFamilyDisposition = "exposed" | "restricted" | "nonportable" | "not_implemented" | "planned";
|
|
export interface PartDesignPythonFamilyRecord { apiId: string; name: string; family: string; sourcePath?: string; disposition?: PartDesignPythonFamilyDisposition; }
|
|
export interface PartDesignPythonFamilyNativeRecord extends PartDesignPythonFamilyRecord { status: Exclude<PartDesignPythonFamilyDisposition, "planned">; code: string; message: string; native: true; resourcesReleased: true; sourceEntry: string; sourceRevision: string; }
|
|
export type PartDesignPythonFamilyTier = "tier-1-readonly" | "tier-2-pure" | "tier-3-stateful" | "object-contract";
|
|
export interface PartDesignPythonFamilyPayload { schema: typeof PARTDESIGN_PYTHON_FAMILY_SCHEMA; module: "PartDesign"; tier: PartDesignPythonFamilyTier; records: PartDesignPythonFamilyRecord[]; }
|
|
export interface PartDesignPythonFamilyResult { schema: typeof PARTDESIGN_PYTHON_FAMILY_SCHEMA; module: "PartDesign"; tier: PartDesignPythonFamilyTier; source: "freecad-source"; records: PartDesignPythonFamilyNativeRecord[]; count: number; resourcesReleased: true; }
|
|
export type PartDesignPythonFamilyRequest = FacadeRequest<PartDesignPythonFamilyPayload>;
|
|
export type PartDesignPythonFamilyResponse = FacadeResponse<PartDesignPythonFamilyResult>;
|
|
export interface PartDesignPythonFamilyKernel { ccall(name: string, type: "number" | "string", argTypes?: string[], args?: unknown[]): number | string; }
|
|
export function createPartDesignPythonFamilyRequest(id: string, records: PartDesignPythonFamilyRecord[], tier: PartDesignPythonFamilyTier = "tier-1-readonly"): PartDesignPythonFamilyRequest { return createRequest(id, PARTDESIGN_PYTHON_FAMILY_COMMAND, { schema: PARTDESIGN_PYTHON_FAMILY_SCHEMA, module: "PartDesign", tier, records }, "partdesign-python-family"); }
|
|
export function isPartDesignPythonFamilyRequest(value: unknown): value is PartDesignPythonFamilyRequest { if (!isFacadeRequest(value)) return false; const request = value as PartDesignPythonFamilyRequest, payload = request.payload; return request.command === PARTDESIGN_PYTHON_FAMILY_COMMAND && payload?.schema === PARTDESIGN_PYTHON_FAMILY_SCHEMA && payload?.module === "PartDesign" && (payload?.tier === "tier-1-readonly" || payload?.tier === "tier-2-pure" || payload?.tier === "tier-3-stateful" || payload?.tier === "object-contract") && Array.isArray(payload.records) && payload.records.length > 0 && payload.records.every((record) => record && typeof record.apiId === "string" && typeof record.name === "string" && typeof record.family === "string" && (record.sourcePath === undefined || typeof record.sourcePath === "string") && (record.disposition === undefined || ["exposed", "restricted", "nonportable", "not_implemented", "planned"].includes(record.disposition))); }
|
|
export async function handlePartDesignPythonFamily(payload: PartDesignPythonFamilyPayload, signal: AbortSignal, kernelSource: PartDesignPythonFamilyKernel | Promise<PartDesignPythonFamilyKernel>): Promise<PartDesignPythonFamilyResult> { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); if (!isPartDesignPythonFamilyRequest(createPartDesignPythonFamilyRequest("validation", payload.records, payload.tier))) throw new FacadeHandlerError("invalid_fixture", "PartDesign Python family payload is invalid"); const kernel = await kernelSource; if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); let raw: string; try { raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: PARTDESIGN_PYTHON_FAMILY_COMMAND, args: { tier: payload.tier, records: payload.records } })])); } catch (error) { throw new FacadeHandlerError("native_dispatch_failed", error instanceof Error ? error.message : "PartDesign native dispatch failed"); } let reply: unknown; try { reply = JSON.parse(raw); } catch { throw new FacadeHandlerError("native_protocol_error", "PartDesign native dispatcher returned invalid JSON"); } if (!reply || typeof reply !== "object") throw new FacadeHandlerError("native_protocol_error", "PartDesign 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 || "PartDesign Python family dispatch failed")); if (result.schema !== PARTDESIGN_PYTHON_FAMILY_SCHEMA || result.module !== "PartDesign" || result.tier !== payload.tier || 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", "PartDesign native dispatcher returned an invalid batch schema"); return result as unknown as PartDesignPythonFamilyResult; }
|
|
export function dispatchPartDesignPythonFamily(queue: FacadeSerialQueue, request: PartDesignPythonFamilyRequest, kernelSource: PartDesignPythonFamilyKernel | Promise<PartDesignPythonFamilyKernel>): Promise<PartDesignPythonFamilyResponse> { return queue.dispatch(request, (payload, signal) => handlePartDesignPythonFamily(payload, signal, kernelSource)); }
|
|
export function createPartDesignDesktopRequest(id: string, command: PartDesignDesktopCommand, args: Record<string, unknown> = {}): PartDesignDesktopRequest { return createRequest(id, command, { module: "PartDesign", command, args }, "partdesign-desktop-command"); }
|
|
export function isPartDesignDesktopRequest(value: unknown): value is PartDesignDesktopRequest { if (!isFacadeRequest(value)) return false; const request = value as PartDesignDesktopRequest; return PARTDESIGN_DESKTOP_COMMANDS.includes(request.command as PartDesignDesktopCommand) && request.payload?.module === "PartDesign" && request.payload?.command === request.command && !!request.payload?.args && typeof request.payload.args === "object"; }
|
|
export async function handlePartDesignDesktop(payload: PartDesignDesktopPayload, signal: AbortSignal, kernelSource: PartDesignPythonFamilyKernel | Promise<PartDesignPythonFamilyKernel>): Promise<PartDesignDesktopResult> { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); const kernel = await kernelSource; let raw: string; try { raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: payload.command, args: payload.args })])); } catch (error) { throw new FacadeHandlerError("native_dispatch_failed", error instanceof Error ? error.message : "PartDesign desktop dispatch failed"); } let result: unknown; try { result = JSON.parse(raw); } catch { throw new FacadeHandlerError("native_protocol_error", "PartDesign desktop dispatcher returned invalid JSON"); } if (!result || typeof result !== "object") throw new FacadeHandlerError("native_protocol_error", "PartDesign desktop dispatcher returned a non-object result"); const native = result as Record<string, unknown>; if (native.ok !== true) throw new FacadeHandlerError(String(native.code || "native_error"), String(native.message || native.error || "PartDesign desktop dispatch failed")); const nonShapeCommand = payload.command === "PartDesign_CompDatums" || payload.command === "PartDesign_CompSketches" || payload.command === "PartDesign_NewSketch" || payload.command === "PartDesign_Plane" || payload.command === "PartDesign_Point" || payload.command === "PartDesign_Body" || payload.command === "PartDesign_Migrate" || payload.command === "PartDesign_MoveFeature" || payload.command === "PartDesign_MoveFeatureInTree" || payload.command === "PartDesign_MoveTip" || payload.command === "Sketcher_EditSketch" || payload.command === "Sketcher_MapSketch"; const interaction = native.interaction as Record<string, unknown> | undefined; const invalidSketchEditor = payload.command === "Sketcher_EditSketch" && (!interaction || interaction.kind !== "sketch-editor" || interaction.editMode !== true || typeof interaction.geometryCount !== "number" || !Array.isArray(interaction.points) || typeof interaction.selection !== "string"); const invalidSketchMapper = payload.command === "Sketcher_MapSketch" && (!interaction || interaction.kind !== "sketch-mapper" || interaction.editMode !== true || typeof interaction.geometryCount !== "number" || !Array.isArray(interaction.points) || typeof interaction.mapMode !== "string" || typeof interaction.supportReference !== "string" || typeof interaction.selection !== "string"); if (native.cmd !== payload.command || native.schema !== "freecad-web-partdesign/desktop-command/v1" || native.module !== "PartDesign" || native.source !== "freecad-source" || native.status !== "exposed" || native.native !== true || native.resourcesReleased !== true || typeof native.documentHandle !== "string" || typeof native.objectHandle !== "string" || (!nonShapeCommand && (typeof native.brep !== "string" || native.brep.length === 0)) || invalidSketchEditor || invalidSketchMapper) throw new FacadeHandlerError("native_protocol_error", `${payload.command} returned an incomplete native command result`); return native as unknown as PartDesignDesktopResult; }
|
|
export function dispatchPartDesignDesktop(queue: FacadeSerialQueue, request: PartDesignDesktopRequest, kernelSource: PartDesignPythonFamilyKernel | Promise<PartDesignPythonFamilyKernel>): Promise<PartDesignDesktopResponse> { return queue.dispatch(request, (payload, signal) => handlePartDesignDesktop(payload, signal, kernelSource)); }
|