Save verified native IFC import and user validation evidence

Real IFC2X3/IFC4 import tested. Full BIM authoring/export remains out of scope; 349-second building import performance remains to optimize.
This commit is contained in:
2026-09-16 04:27:37 +08:00
parent 9a7c4f0335
commit 72152ef356
120 changed files with 3020296 additions and 114 deletions

View File

@@ -83,6 +83,7 @@ export async function executeOpenDocument(router: WebModuleRouter, args: Record<
}, value.activeDocument);
if (scene.kind === "error") throw new Error(scene.error.message);
if ((scene.result as DocumentViewResult).documentId !== value.activeDocument) throw new Error("Opened document scene identity mismatch");
value.scene = scene.result as DocumentViewResult;
window.dispatchEvent(new CustomEvent("freecad-document-view", { detail: { owner, documentId: value.activeDocument, value: scene.result } }));
} catch (error) { value.sceneError = String(error); }
}

View File

@@ -1,3 +1,4 @@
import type { DocumentViewResult } from "./part-view-facade.js";
export interface OpenFileEntry { filename: string; fileData: string; module?: string }
export interface OpenResult {
command: "Std_Open"; documentId: string; operation: "list" | "open"; activeDocument: string | null;
@@ -7,6 +8,7 @@ export interface OpenResult {
errors: Array<{ filename: string; message: string }>;
storedFiles?: Array<{ key: string; filename: string }>;
sceneError?: string;
scene?: DocumentViewResult;
}
export function validOpenResult(value: unknown, documentId: string): value is OpenResult {
const r = value as OpenResult | null;

View File

@@ -13,10 +13,11 @@ type ViewResult = {
annotations?: NativeAnnotation[];
};
export function WebViewTask({ router, command, documentId, selection, onSelection, runtimeOwner, disabled = false }: {
export function WebViewTask({ router, command, documentId, selection, onSelection, runtimeOwner, initialScene, disabled = false }: {
router: WebModuleRouter; command: string; documentId: string; selection: Selection[];
onSelection: (documentId: string, selection: Selection[]) => void;
runtimeOwner?: ViewOwner; disabled?: boolean;
initialScene?: ViewResult;
}) {
const host = useRef<HTMLDivElement>(null);
const projection = useRef<number[]>([]);
@@ -55,9 +56,9 @@ export function WebViewTask({ router, command, documentId, selection, onSelectio
return () => window.removeEventListener("freecad-document-view", receive);
}, [documentId, owner]);
useEffect(() => { current.current = `${owner}:${documentId}`; setValue(undefined); setError(""); projection.current = []; setDrag(undefined);
useEffect(() => { current.current = `${owner}:${documentId}`; setValue(initialScene?.documentId === documentId ? initialScene : undefined); setError(""); projection.current = []; setDrag(undefined);
return () => { current.current = ""; };
}, [documentId, owner]);
}, [documentId, owner, initialScene]);
async function execute(name: string, args: Record<string, unknown> = {}) {
if (busy || disabled) return;
@@ -78,8 +79,8 @@ export function WebViewTask({ router, command, documentId, selection, onSelectio
}
useEffect(() => {
if (command === "Std_SceneInspector" && !disabled) void execute("Std_DrawStyle", { action: "read" });
}, [command, documentId, owner, disabled]);
if (command === "Std_SceneInspector" && !disabled && initialScene?.documentId !== documentId) void execute("Std_DrawStyle", { action: "read" });
}, [command, documentId, owner, disabled, initialScene]);
useEffect(() => {
const node = host.current;
@@ -222,7 +223,8 @@ export function WebViewTask({ router, command, documentId, selection, onSelectio
</>}
</div>
{error && <pre role="alert" style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{error}</pre>}
<div ref={host} style={{ position: "relative", width: "100%", height: 420, maxHeight: "65vh", touchAction: "none" }}
<div ref={host} data-native-mesh-count={value?.meshes.length ?? 0}
style={{ position: "relative", width: "100%", height: 420, maxHeight: "65vh", touchAction: "none" }}
onPointerDown={event => {
if (!isBox || busy || disabled || !value || event.button !== 0) return;
const rect = event.currentTarget.getBoundingClientRect();

View File

@@ -1155,6 +1155,16 @@ function App() {
<style>{css}</style>
{(module === "Web" || module === "Std") && command?.replace(/^(Web|Std):desktop:/, "") === "Std_Open" &&
<WebOpenTask response={response} busy={busy} execute={values => { void run(undefined, values); }} />}
{(module === "Web" || module === "Std") && command?.replace(/^(Web|Std):desktop:/, "") === "Std_Open"
&& response?.kind === "result" && (() => {
const batch = response.result as { owner?: "PartBox" | "DocumentProbe"; records?: Array<{ name?: string; result?: { activeDocument?: string; opened?: unknown[]; scene?: import("./part-view-facade.js").DocumentViewResult } }> };
const opened = batch.records?.find(row => row.name === "Std_Open")?.result;
return opened?.activeDocument && opened.opened?.length && WebViewTask ? <WebViewTask router={router}
command="Std_SceneInspector" documentId={opened.activeDocument} runtimeOwner={batch.owner ?? "PartBox"}
initialScene={opened.scene}
disabled={busy} selection={documentSelection.selection}
onSelection={(documentId, selection) => setDocumentSelection({ documentId, selection })} /> : null;
})()}
{module === "BIM" && command?.replace(/^BIM:desktop:/, "") === "BIM_Windows" &&
<BimWindowsTask key={documentSelection.documentId} documentId={documentSelection.documentId}
response={response} busy={busy} execute={values => { void run(undefined, values); }}