Files
FreeCADWasmDev/tools/run_p6_bim_geometry_value_module_batch_gate.mjs
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

935 lines
66 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { Worker } from "node:worker_threads";
import { pathToFileURL } from "node:url";
const root = "/home/mes123456/FreeCADWasmDev/working/FreeCAD";
const build = path.resolve(process.argv[2] ?? path.join(root, "freecad-source/build-web/configure/bin"));
const batchPath = path.resolve(process.env.BIM_BATCH_MANIFEST ?? path.join(root, "docs/current/module-batch-manifest.json"));
const batch = JSON.parse(fs.readFileSync(batchPath, "utf8"));
if (batch.status !== "ready" || batch.selected?.module !== "BIM" || !["tier-1-readonly", "tier-2-pure"].includes(batch.selected?.tier) || !["geometry-value", "generic-object", "view-resource", "pure-function"].includes(batch.selected?.family) || batch.records.length !== batch.selected?.recordCount || (!batch.singletonFallback && batch.records.length < batch.minimumRecords)) throw new Error("unexpected BIM Python family batch manifest");
const records = batch.records.map(([apiId, name, disposition, sourcePath, sourceLine]) => ({ apiId, name, family: batch.selected.family, kind: apiId.split(":")[2], sourcePath, sourceLine, disposition }));
const family = batch.selected.family;
const familySlug = family.replace(/[^a-z0-9]+/gi, "-");
const out = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-bim-" + familySlug + "-"));
const hash = (value) => crypto.createHash("sha256").update(typeof value === "string" || Buffer.isBuffer(value) ? value : JSON.stringify(value)).digest("hex");
const fileHash = (file) => hash(fs.readFileSync(file));
execFileSync(path.join(root, "web/node_modules/.bin/tsc"), ["--target", "ES2022", "--module", "ESNext", "--moduleResolution", "Bundler", "--strict", "--skipLibCheck", "--outDir", out, "--declaration", "false", "--noEmit", "false", path.join(root, "web/src/facade-envelope.ts"), path.join(root, "web/src/bim-python-family-batch-facade.ts")], { cwd: root, stdio: "inherit" });
fs.copyFileSync(path.join(build, "FreeCADWebBimRuntimeProbe.js"), path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.js"));
fs.copyFileSync(path.join(build, "FreeCADWebBimRuntimeProbe.wasm"), path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.wasm"));
fs.copyFileSync(path.join(build, "FreeCADWebBimRuntimeProbe.data"), path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.data"));
const facade = await import(pathToFileURL(path.join(out, "bim-python-family-batch-facade.js")));
const envelope = await import(pathToFileURL(path.join(out, "facade-envelope.js")));
const request = (requestId, rows = records) => facade.createBimPythonFamilyBatchRequest(requestId, rows, batch.selected.tier);
const worker = new Worker(path.join(root, "tools/bim-python-family-batch-smoke-worker.mjs"), { workerData: { buildDirectory: build, facadePath: pathToFileURL(path.join(out, "bim-python-family-batch-facade.js")).href, envelopePath: pathToFileURL(path.join(out, "facade-envelope.js")).href } });
const next = () => new Promise((resolve, reject) => { const onMessage = (value) => { worker.off("error", onError); resolve(value); }; const onError = (error) => { worker.off("message", onMessage); reject(error); }; worker.once("message", onMessage); worker.once("error", onError); });
const ready = await next();
worker.postMessage(request("bim-python-family-batch-success"));
const success = await next();
worker.postMessage(request("bim-python-family-batch-repeat"));
const repeat = await next();
worker.postMessage(request("bim-python-family-batch-cross", [{ ...records[0], apiId: "py:Part:method:BuildingPart.getShapes" }]));
const crossModule = await next();
worker.postMessage({ ...request("bim-python-family-batch-invalid"), payload: { ...request("bim-python-family-batch-invalid").payload, tier: "tier-unsupported" } });
const malformed = await next();
const cancelRequest = request("bim-python-family-batch-cancel");
worker.postMessage(cancelRequest);
worker.postMessage(envelope.createCancel("bim-python-family-batch-cancel-control", cancelRequest.requestId, "bim-python-family-batch"));
const cancelled = await next();
worker.postMessage({ kind: "reset", requestId: "bim-python-family-batch-reset" });
const reset = await next();
await worker.terminate();
if (ready.kind !== "ready" || success.kind !== "result" || repeat.kind !== "result" || crossModule.kind !== "result" || malformed.kind !== "error" || cancelled.kind !== "error" || reset.requestId !== "bim-python-family-batch-reset") throw new Error(JSON.stringify({ ready, success, repeat, crossModule, malformed, cancelled, reset }));
const result = success.result;
if (result.module !== "BIM" || result.tier !== batch.selected.tier || result.count !== records.length || result.records.length !== records.length || result.resourcesReleased !== true) throw new Error("BIM Python family result is incomplete");
const terminal = new Set(["exposed", "restricted", "nonportable", "not_implemented"]);
if (result.records.some((row) => !terminal.has(row.status) || row.native !== true || row.resourcesReleased !== true || typeof row.sourceLine !== "number" || typeof row.sourceVerified !== "boolean" || typeof row.sourceRevision !== "string" || typeof row.sourceEntry !== "string" || typeof row.parameterSchema !== "string" || typeof row.returnSchema !== "string" || (row.status === "exposed" && !row.result))) throw new Error("BIM Python family native record schema is incomplete");
if (JSON.stringify(result) !== JSON.stringify(repeat.result)) throw new Error("BIM geometry-value replay changed native result");
if (crossModule.result.records?.[0]?.status !== "not_implemented" || crossModule.result.records?.[0]?.code !== "unmatched_source_record") throw new Error("BIM cross-module boundary failed");
if (malformed.error.code !== "invalid_request") throw new Error("BIM invalid tier was not rejected");
if (cancelled.error.code !== "cancelled") throw new Error("BIM cancellation boundary failed");
if (family === "generic-object") {
for (const row of result.records) {
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.ok(["exposed", "restricted", "nonportable", "not_implemented"].includes(row.status));
if (row.status === "exposed") assert.ok(row.result && typeof row.result === "object", `missing native result for ${row.apiId}`);
}
}
if (batch.batchId === "1ca4bb7f529b34e32b8b3a77") {
for (const row of result.records) {
assert.equal(row.status, row.apiId === "py:BIM:method:BIM_Windows_TaskPanel.getStandardButtons" ? "restricted" : "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
if (row.status === "exposed") assert.ok(row.result, `missing native result for ${row.apiId}`);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
assert.equal(nativeById.get("py:BIM:class:BIM_Unclone")?.result?.menuText, "Unclone");
assert.equal(nativeById.get("py:BIM:method:BIM_Unclone.IsActive")?.result?.active, true);
assert.deepEqual(nativeById.get("py:BIM:method:BIM_Views.getDockArea")?.result?.areas, [1, 4, 8, 2]);
assert.equal(nativeById.get("py:BIM:method:BIM_Views.getPages")?.result?.pageCount, 2);
assert.equal(nativeById.get("py:BIM:method:BIM_Views.isolate")?.result?.selectedVisible, true);
assert.equal(nativeById.get("py:BIM:method:BIM_Views.isolate")?.result?.otherVisible, false);
assert.deepEqual(nativeById.get("py:BIM:class:WallBaselineMode")?.result?.members, { NONE: 0, DRAFT_LINE: 1, SKETCH: 2 });
assert.equal(nativeById.get("py:BIM:method:Arch_Window.has_width_and_height_constraint")?.result?.hasConstraint, true);
assert.equal(nativeById.get("py:BIM:class:ifc_vp_material")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:method:SIUnitCreator.getSIPrefix")?.result?.prefix, "MILLI");
assert.equal(nativeById.get("py:BIM:class:SIUnitCreator")?.result?.unitName, "METRE");
assert.equal(nativeById.get("py:BIM:method:ContextCreator.getProjectGUID")?.result?.guid, "native-guid");
}
if (batch.batchId === "478de189c5af084aa2f66f33") {
for (const row of result.records) {
console.error('BIM_BATCH_DEBUG', row.apiId, row.status, row.code, row.message);
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim[A-Za-z]+\.py$/);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const classNames = ["BIM_SimpleCopy", "Arch_Site", "BIM_Sketch", "BIM_Slab", "Arch_Space", "Arch_Stairs", "BIM_TDPage", "BIM_Text", "BIM_TogglePanels", "BIM_Trash", "Arch_Truss", "BIM_Tutorial"];
for (const name of classNames) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.className, name, `class fixture missing for ${name}`);
assert.equal(value?.constructorSuccess, true);
assert.ok(Array.isArray(value?.resourceKeys));
assert.equal(typeof value?.menuText, "string");
assert.equal(typeof value?.toolTip, "string");
}
const activeNames = ["BIM_SimpleCopy", "Arch_Site", "BIM_Sketch", "BIM_Slab", "Arch_Space", "Arch_Stairs", "BIM_TDPage", "BIM_TDView", "BIM_Text", "BIM_Trash", "Arch_Truss"];
for (const name of activeNames) {
const value = nativeById.get(`py:BIM:method:${name}.IsActive`)?.result;
assert.equal(value?.className, name, `IsActive fixture missing for ${name}`);
assert.equal(value?.active, true);
assert.equal(value?.sceneGraphAvailable, true);
assert.equal(value?.methodFixture, "IsActive");
}
const ifc = nativeById.get("py:BIM:method:BIM_Setup.getIfcOpenShell")?.result;
assert.equal(ifc?.className, "BIM_Setup");
assert.equal(ifc?.methodFixture, "getIfcOpenShell");
assert.equal(ifc?.completed, true);
assert.equal(ifc?.ifcopenshellVersion, "0.7.0");
assert.equal(ifc?.networkAccessed, false);
assert.equal(ifc?.installationAttempted, false);
}
if (batch.batchId === "b12667c34b786b995f712106") {
const expectedStatuses = new Map([
["py:BIM:method:ArchScheduleTaskPanel.getRows", "exposed"],
["py:BIM:method:SectionPlaneTaskPanel.getStandardButtons", "restricted"],
["py:BIM:method:SectionPlaneTaskPanel.getBB", "exposed"],
["py:BIM:class:Compass", "restricted"],
["py:BIM:class:ArchSketch", "exposed"],
["py:BIM:class:SqlEngineError", "exposed"],
["py:BIM:class:BimSqlSyntaxError", "exposed"],
["py:BIM:class:FunctionRegistry", "exposed"],
]);
for (const row of result.records) if (row.status !== expectedStatuses.get(row.apiId)) throw new Error(`BIM batch status mismatch for ${row.apiId}: ${row.status}`);
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
assert.deepEqual(nativeById.get("py:BIM:method:ArchScheduleTaskPanel.getRows")?.result?.rows, ["2", "10"]);
assert.deepEqual(nativeById.get("py:BIM:method:SectionPlaneTaskPanel.getBB")?.result?.lengths, [5, 6, 7]);
assert.equal(nativeById.get("py:BIM:class:ArchSketch")?.result?.baseClassName, "ArchSketchObject");
assert.equal(nativeById.get("py:BIM:class:SqlEngineError")?.result?.isException, true);
assert.equal(nativeById.get("py:BIM:class:BimSqlSyntaxError")?.result?.isIncomplete, true);
assert.equal(nativeById.get("py:BIM:class:FunctionRegistry")?.result?.caseInsensitiveLookup, true);
}
if (batch.batchId === "49c39b5eb6fb695701c27f57") {
for (const row of result.records) assert.equal(row.status, "exposed", `BIM batch status mismatch for ${row.apiId}`);
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
assert.equal(nativeById.get("py:BIM:class:WhereClause")?.result?.matches, true);
assert.equal(nativeById.get("py:BIM:class:BooleanExpression")?.result?.evaluated, false);
assert.equal(nativeById.get("py:BIM:class:BooleanComparison")?.result?.nullIsNull, true);
assert.equal(nativeById.get("py:BIM:class:InComparison")?.result?.matches, true);
assert.equal(nativeById.get("py:BIM:class:ArithmeticOperation")?.result?.value, 42);
assert.equal(nativeById.get("py:BIM:class:ReferenceExtractor")?.result?.value, "Native BIM");
assert.equal(nativeById.get("py:BIM:class:StaticExtractor")?.result?.value, "native-static");
assert.equal(nativeById.get("py:BIM:class:SqlTransformerMixin")?.result?.startValue, "native-start");
assert.equal(nativeById.get("py:BIM:method:SqlTransformerMixin.is_op")?.result?.operator, "is");
assert.equal(nativeById.get("py:BIM:method:SqlTransformerMixin.is_not_op")?.result?.operator, "is_not");
assert.equal(nativeById.get("py:BIM:class:ReportStatement")?.result?.roundTrip, true);
assert.equal(nativeById.get("py:BIM:method:CommandStructuresFromSelection.IsActive")?.result?.active, true);
assert.equal(nativeById.get("py:BIM:method:CommandStructuralSystem.IsActive")?.result?.activeWithDocument, true);
assert.equal(nativeById.get("py:BIM:class:Truss")?.result?.ifcType, "Beam");
assert.equal(nativeById.get("py:BIM:method:Renderer.isVisible")?.result?.visible, true);
assert.equal(nativeById.get("py:BIM:method:Renderer.isInside")?.result?.inside, true);
assert.equal(nativeById.get("py:BIM:method:Renderer.getFill")?.result?.fill, "#3f7fbf");
assert.equal(nativeById.get("py:BIM:method:Renderer.getPathData")?.result?.closed, true);
assert.equal(nativeById.get("py:BIM:method:Renderer.getSectionSVG")?.result?.containsFill, true);
assert.equal(nativeById.get("py:BIM:method:Renderer.getHiddenSVG")?.result?.dashed, true);
assert.equal(nativeById.get("py:BIM:class:File3dsFormatError")?.result?.tight, true);
assert.equal(nativeById.get("py:BIM:class:FBufError")?.result?.isException, true);
assert.equal(nativeById.get("py:BIM:class:FileLikeBuffer")?.result?.seekErrorType, "FBufError");
assert.equal(nativeById.get("py:BIM:class:ChunkMetaclass")?.result?.registeredByTag, true);
}
if (batch.batchId === "e7d5ab6fa2b20eead7b2d7ac") {
for (const row of result.records) assert.equal(row.status, "exposed", `BIM batch status mismatch for ${row.apiId}`);
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (name, field) => nativeById.get(`py:BIM:class:${name}`)?.result?.fieldValues?.[field];
assert.equal(nativeById.get("py:BIM:class:DL_LOCAL_SHADOW2")?.result?.tag, 0x4630);
assert.equal(value("DL_LOCAL_SHADOW2", "low_bias"), 0.01);
assert.equal(value("DL_LOCAL_SHADOW2", "filter"), 0.5);
assert.equal(value("DL_LOCAL_SHADOW2", "mapsize"), 1024);
assert.equal(value("DL_SPOT_PROJECTOR", "filename"), "native-projector.jpg");
assert.equal(value("DL_EXCLUDE", "value"), "native-exclude");
assert.equal(value("DL_SPOT_ROLL", "value"), 5);
assert.equal(value("DL_SPOT_ASPECT", "value"), 1.5);
assert.equal(value("DL_RAY_BIAS", "value"), 0.002);
assert.equal(value("DL_INNER_RANGE", "value"), 10);
assert.equal(value("DL_OUTER_RANGE", "value"), 100);
assert.equal(value("DL_MULTIPLIER", "value"), 2);
assert.equal(nativeById.get("py:BIM:class:N_CAMERA")?.result?.tag, 0x4700);
assert.equal(value("N_CAMERA", "camera_x"), 1);
assert.equal(value("N_CAMERA", "focal_length"), 50);
assert.equal(value("N_CAMERA", "see_cone"), "native-see-cone");
assert.equal(value("N_CAMERA", "ranges"), "native-ranges");
assert.equal(value("CAM_RANGES", "near"), 0.1);
assert.equal(value("CAM_RANGES", "far"), 1000);
for (const name of ["MAT_AMBIENT", "MAT_DIFFUSE", "MAT_SPECULAR"]) {
assert.deepEqual(value(name, "color"), [0.125, 0.25, 0.5]);
assert.deepEqual(value(name, "lincolor"), [0.25, 0.5, 1]);
}
for (const name of ["MAT_SHININESS", "MAT_SHIN2PCT", "MAT_SHIN3PCT", "MAT_TRANSPARENCY", "MAT_XPFALL", "MAT_REFBLUR"]) assert.equal(value(name, "pct"), 75);
assert.equal(nativeById.get("py:BIM:class:DL_SPOT_PROJECTOR")?.result?.swallow, true);
assert.equal(nativeById.get("py:BIM:class:DL_EXCLUDE")?.result?.swallow, true);
}
if (batch.batchId === "20f7ca3639dd6da825023c25") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM material batch status mismatch for ${row.apiId}`);
assert.equal(row.result?.sourceConstructor, "python-chunk-fixture");
assert.equal(row.result?.constructorSuccess, true);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (name, field) => nativeById.get(`py:BIM:class:${name}`)?.result?.fieldValues?.[field];
const tag = (name) => nativeById.get(`py:BIM:class:${name}`)?.result?.tag;
const tags = {
MAT_SELF_ILLUM: 0xA080, MAT_TWO_SIDE: 0xA081, MAT_DECAL: 0xA082, MAT_ADDITIVE: 0xA083,
MAT_SELF_ILPCT: 0xA084, MAT_SUPERSMP: 0xA086, MAT_XPFALLIN: 0xA08A, MAT_PHONGSOFT: 0xA08C,
MAT_SHADING: 0xA100, MAT_TEXMAP: 0xA200, MAT_SPECMAP: 0xA204, MAT_OPACMAP: 0xA210,
MAT_REFLMAP: 0xA220, MAT_BUMPMAP: 0xA230, MAT_USE_XPFALL: 0xA240, MAT_USE_REFBLUR: 0xA250,
MAT_BUMP_PERCENT: 0xA252, MAT_ACUBIC: 0xA310, MAT_TEX2MAP: 0xA33A, MAT_SHINMAP: 0xA33C,
MAT_SELFIMAP: 0xA33D, MAT_TEXMASK: 0xA33E, MAT_TEXT2MASK: 0xA340, MAT_OPACMASK: 0xA342,
};
for (const [name, expectedTag] of Object.entries(tags)) assert.equal(tag(name), expectedTag, `tag mismatch for ${name}`);
for (const name of ["MAT_SELF_ILLUM", "MAT_TWO_SIDE", "MAT_DECAL", "MAT_ADDITIVE", "MAT_PHONGSOFT", "MAT_USE_XPFALL", "MAT_USE_REFBLUR"]) assert.equal(value(name, "flag"), true);
assert.equal(value("MAT_SELF_ILPCT", "pct"), 60);
for (const name of ["MAT_SUPERSMP", "MAT_XPFALLIN"]) assert.equal(value(name, "data"), "native-material-undefined");
assert.equal(value("MAT_SHADING", "value"), 2);
for (const name of ["MAT_TEXMAP", "MAT_SPECMAP", "MAT_OPACMAP", "MAT_BUMPMAP", "MAT_TEX2MAP", "MAT_SHINMAP", "MAT_SELFIMAP", "MAT_TEXMASK", "MAT_TEXT2MASK", "MAT_OPACMASK"]) {
assert.equal(value(name, "pct"), 80);
assert.equal(value(name, "filename"), "native-material-map.jpg");
}
assert.equal(value("MAT_REFLMAP", "pct"), 65);
assert.equal(value("MAT_REFLMAP", "filename"), "native-material-reflection.jpg");
assert.equal(value("MAT_BUMP_PERCENT", "value"), 35);
assert.deepEqual(Object.fromEntries(["shade", "antialias", "rflags", "mapsize", "frame"].map((field) => [field, value("MAT_ACUBIC", field)])), { shade: 1, antialias: 1, rflags: 3, mapsize: 1024, frame: 7 });
}
if (batch.batchId === "93ae120755c70827ad109dc8") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM material mask/map batch status mismatch for ${row.apiId}`);
assert.equal(row.result?.sourceConstructor, "python-chunk-fixture");
assert.equal(row.result?.constructorSuccess, true);
assert.equal(row.result?.isChunkBase, true);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (name, field) => nativeById.get(`py:BIM:class:${name}`)?.result?.fieldValues?.[field];
const tag = (name) => nativeById.get(`py:BIM:class:${name}`)?.result?.tag;
const tags = {
MAT_BUMPMASK: 0xA344, MAT_SHINMASK: 0xA346, MAT_SPECMASK: 0xA348,
MAT_SELFIMASK: 0xA34A, MAT_REFLMASK: 0xA34C, MAT_MAP_TILING: 0xA351,
MAT_MAP_TEXBLUR: 0xA353, MAT_MAP_USCALE: 0xA354, MAT_MAP_VSCALE: 0xA356,
MAT_MAP_UOFFSET: 0xA358, MAT_MAP_VOFFSET: 0xA35A, MAT_MAP_ANG: 0xA35C,
MAT_MAP_COL1: 0xA360, MAT_MAP_COL2: 0xA362, MAT_MAP_RCOL: 0xA364,
MAT_MAP_GCOL: 0xA366, MAT_MAP_BCOL: 0xA368, MAT_ENTRY: 0xAFFF,
MAT_SXP_TEXT_DATA: 0xA320, MAT_SXP_TEXT2_DATA: 0xA321, MAT_SXP_OPAC_DATA: 0xA322,
MAT_SXP_BUMP_DATA: 0xA324, MAT_SXP_SPEC_DATA: 0xA325, MAT_SXP_SHIN_DATA: 0xA326,
};
for (const [name, expectedTag] of Object.entries(tags)) assert.equal(tag(name), expectedTag, `tag mismatch for ${name}`);
for (const name of ["MAT_BUMPMASK", "MAT_SHINMASK", "MAT_SPECMASK"]) {
assert.equal(value(name, "pct"), 70);
assert.equal(value(name, "filename"), "native-material-mask.jpg");
}
for (const name of ["MAT_SELFIMASK", "MAT_REFLMASK"]) {
assert.equal(value(name, "pct"), 60);
assert.equal(value(name, "filename"), "native-material-mask-texture.jpg");
}
assert.equal(value("MAT_MAP_TILING", "value"), 3);
assert.deepEqual(Object.fromEntries([
["MAT_MAP_TEXBLUR", 0.25], ["MAT_MAP_USCALE", 1.5], ["MAT_MAP_VSCALE", 0.75],
["MAT_MAP_UOFFSET", 0.125], ["MAT_MAP_VOFFSET", -0.25], ["MAT_MAP_ANG", 45],
].map(([name, expected]) => [name, value(name, "value")])), {
MAT_MAP_TEXBLUR: 0.25, MAT_MAP_USCALE: 1.5, MAT_MAP_VSCALE: 0.75,
MAT_MAP_UOFFSET: 0.125, MAT_MAP_VOFFSET: -0.25, MAT_MAP_ANG: 45,
});
for (const name of ["MAT_MAP_COL1", "MAT_MAP_COL2", "MAT_MAP_RCOL", "MAT_MAP_GCOL", "MAT_MAP_BCOL"]) {
assert.deepEqual(value(name, "red"), 16);
assert.deepEqual([value(name, "red"), value(name, "green"), value(name, "blue")], [16, 32, 64]);
}
assert.equal(nativeById.get("py:BIM:class:MAT_ENTRY")?.result?.subchunkCount, 0);
assert.match(nativeById.get("py:BIM:class:MAT_ENTRY")?.result?.singleDeclaration ?? "", /MAT_SXP_TEXT_DATA/);
for (const name of ["MAT_SXP_TEXT_DATA", "MAT_SXP_TEXT2_DATA", "MAT_SXP_OPAC_DATA", "MAT_SXP_BUMP_DATA", "MAT_SXP_SPEC_DATA", "MAT_SXP_SHIN_DATA"]) {
assert.equal(value(name, "data"), `native-sxp-${name.slice(8).toLowerCase()}`);
}
}
if (batch.batchId === "cc70c1e3e5de23f151bb3df3") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM keyframe/material data batch status mismatch for ${row.apiId}`);
assert.equal(row.result?.sourceConstructor, "python-chunk-fixture");
assert.equal(row.result?.constructorSuccess, true);
assert.equal(row.result?.isChunkBase, true);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (name, field) => nativeById.get(`py:BIM:class:${name}`)?.result?.fieldValues?.[field];
const tag = (name) => nativeById.get(`py:BIM:class:${name}`)?.result?.tag;
const tags = {
MAT_SXP_SELFI_DATA: 0xA328, MAT_SXP_TEXT_MASKDATA: 0xA32A, MAT_SXP_TEXT2_MASKDATA: 0xA32C,
MAT_SXP_OPAC_MASKDATA: 0xA32E, MAT_SXP_BUMP_MASKDATA: 0xA330, MAT_SXP_SPEC_MASKDATA: 0xA332,
MAT_SXP_SHIN_MASKDATA: 0xA334, MAT_SXP_SELFI_MASKDATA: 0xA336, MAT_SXP_REFL_MASKDATA: 0xA338,
KFDATA: 0xB000, KFHDR: 0xB00A, KFSEG: 0xB008, KFCURTIME: 0xB009,
AMBIENT_NODE_TAG: 0xB001, CAMERA_NODE_TAG: 0xB003, TARGET_NODE_TAG: 0xB004,
LIGHT_NODE_TAG: 0xB005, SPOTLIGHT_NODE_TAG: 0xB007, L_TARGET_NODE_TAG: 0xB006,
NODE_ID: 0xB030, NODE_HDR: 0xB010, PIVOT: 0xB013, MORPH_SMOOTH: 0xB015, BOUNDBOX: 0xB014,
};
for (const [name, expectedTag] of Object.entries(tags)) assert.equal(tag(name), expectedTag, `tag mismatch for ${name}`);
for (const name of ["MAT_SXP_SELFI_DATA", "MAT_SXP_TEXT_MASKDATA", "MAT_SXP_TEXT2_MASKDATA", "MAT_SXP_OPAC_MASKDATA", "MAT_SXP_BUMP_MASKDATA", "MAT_SXP_SPEC_MASKDATA", "MAT_SXP_SHIN_MASKDATA", "MAT_SXP_SELFI_MASKDATA", "MAT_SXP_REFL_MASKDATA"]) assert.equal(typeof value(name, "data"), "string");
assert.equal(value("KFHDR", "revision"), 1);
assert.equal(value("KFHDR", "filename"), "native-animation.3ds");
assert.equal(value("KFHDR", "anim_length"), 240);
assert.deepEqual([value("KFSEG", "first_frame"), value("KFSEG", "last_frame")], [0, 120]);
assert.equal(value("KFCURTIME", "current_frame"), 42);
assert.equal(value("NODE_ID", "id"), 7);
assert.deepEqual([value("NODE_HDR", "name"), value("NODE_HDR", "flags1"), value("NODE_HDR", "flags2"), value("NODE_HDR", "parent")], ["NativeNode", 1, 2, -1]);
assert.deepEqual([value("PIVOT", "pivot_x"), value("PIVOT", "pivot_y"), value("PIVOT", "pivot_z")], [1, 2, 3]);
assert.equal(value("MORPH_SMOOTH", "smooth_angle"), 0.5);
assert.deepEqual([value("BOUNDBOX", "min_x"), value("BOUNDBOX", "min_y"), value("BOUNDBOX", "min_z"), value("BOUNDBOX", "max_x"), value("BOUNDBOX", "max_y"), value("BOUNDBOX", "max_z")], [-1, -2, -3, 4, 5, 6]);
assert.match(nativeById.get("py:BIM:class:KFDATA")?.result?.singleDeclaration ?? "", /KFHDR kfhdr/);
assert.match(nativeById.get("py:BIM:class:KFDATA")?.result?.multipleDeclaration ?? "", /AMBIENT_NODE_TAG ambient_nodes/);
assert.match(nativeById.get("py:BIM:class:BOUNDBOX")?.result?.structDeclaration ?? "", /min_x/);
}
if (batch.batchId === "806eccf3fcb43cd87a8c7859") {
for (const row of result.records) assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const classResult = (name) => nativeById.get(`py:BIM:class:${name}`)?.result;
const tag = (name) => classResult(name)?.tag;
const expectedTags = {
POS_TRACK_TAG: 0xB020, COL_TRACK_TAG: 0xB025, ROT_TRACK_TAG: 0xB021,
SCL_TRACK_TAG: 0xB022, MORPH_TRACK_TAG: 0xB026, FOV_TRACK_TAG: 0xB023,
ROLL_TRACK_TAG: 0xB024, HOT_TRACK_TAG: 0xB027, FALL_TRACK_TAG: 0xB028,
HIDE_TRACK_TAG: 0xB029, DUMMY: 0xFFFF, MAT_MAP_TILING_OLD: 0xA350,
MAT_MAP_TEXBLUR_OLD: 0xA352,
};
for (const [name, expected] of Object.entries(expectedTags)) assert.equal(tag(name), expected, `tag mismatch for ${name}`);
for (const name of ["POS_TRACK_TAG", "COL_TRACK_TAG", "ROT_TRACK_TAG", "SCL_TRACK_TAG", "MORPH_TRACK_TAG", "FOV_TRACK_TAG", "ROLL_TRACK_TAG", "HOT_TRACK_TAG", "FALL_TRACK_TAG"]) {
const value = classResult(name);
assert.equal(value?.isChunkBase, true);
assert.equal(value?.keyframeCount, 1);
assert.equal(value?.keyframeValues?.[0]?.frameno, 12);
}
assert.deepEqual(classResult("POS_TRACK_TAG")?.keyframeValues?.[0], { frameno: 12, flags: 0, pos_x: 1, pos_y: 2, pos_z: 3 });
assert.deepEqual(classResult("COL_TRACK_TAG")?.keyframeValues?.[0], { frameno: 12, flags: 0, red: 0.1, green: 0.2, blue: 0.3 });
assert.deepEqual(classResult("ROT_TRACK_TAG")?.keyframeValues?.[0], { frameno: 12, flags: 0, angle: 45, axis_x: 0, axis_y: 0, axis_z: 1 });
assert.deepEqual(classResult("SCL_TRACK_TAG")?.keyframeValues?.[0], { frameno: 12, flags: 0, scl_x: 1, scl_y: 2, scl_z: 3 });
assert.deepEqual(classResult("MORPH_TRACK_TAG")?.keyframeValues?.[0], { frameno: 12, flags: 0, name: "NativeMorph" });
for (const name of ["FOV_TRACK_TAG", "ROLL_TRACK_TAG", "HOT_TRACK_TAG", "FALL_TRACK_TAG"]) assert.equal(classResult(name)?.keyframeValues?.[0]?.angle, 30);
for (const name of ["HIDE_TRACK_TAG", "MAT_MAP_TILING_OLD", "MAT_MAP_TEXBLUR_OLD"]) assert.equal(classResult(name)?.fieldValues?.data, "native-undefined-payload");
assert.equal(classResult("BIMWorkbench")?.menuText, "BIM");
assert.equal(classResult("BIMWorkbench")?.constructorSuccess, true);
assert.equal(classResult("FreeCADGuiHandler")?.parseSuccess, true);
assert.equal(classResult("FreeCADGuiHandler")?.visibility, true);
assert.equal(classResult("FreeCADGuiHandler")?.camera, "native-camera");
for (const name of ["Arch_Add", "Arch_Remove", "Arch_CloseHoles"]) assert.equal(classResult(name)?.constructorSuccess, true);
for (const name of ["Arch_Add.IsActive", "Arch_Remove.IsActive", "Arch_SplitMesh.IsActive", "Arch_MeshToShape.IsActive", "Arch_SelectNonSolidMeshes.IsActive", "Arch_RemoveShape.IsActive"]) assert.equal(nativeById.get(`py:BIM:method:${name}`)?.result?.active, true);
}
if (batch.batchId === "c955679008a296a64582a28f") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM Arch command batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.match(row.sourceEntry, /src\/Mod\/BIM\/bimcommands\/Bim(?:ArchUtils|Axis)\.py$/);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const classResult = (name) => nativeById.get(`py:BIM:class:${name}`)?.result;
const methodResult = (name) => nativeById.get(`py:BIM:method:${name}.IsActive`)?.result;
for (const name of ["Arch_Check", "Arch_Survey", "Arch_ToggleIfcBrepFlag", "Arch_Component", "Arch_CloneComponent", "Arch_ToggleSubs", "Arch_MergeWalls", "Arch_Axis", "Arch_AxisSystem", "Arch_Grid", "Arch_AxisTools"]) {
assert.equal(classResult(name)?.constructorSuccess, true, `constructor fixture missing for ${name}`);
assert.ok(Array.isArray(classResult(name)?.resourceKeys), `resource contract missing for ${name}`);
assert.equal(typeof classResult(name)?.menuText, "string");
assert.equal(typeof classResult(name)?.toolTip, "string");
}
for (const name of ["Arch_CloseHoles", "Arch_Check", "Arch_Survey", "Arch_ToggleIfcBrepFlag", "Arch_Component", "Arch_CloneComponent", "Arch_IfcSpreadsheet", "Arch_ToggleSubs", "Arch_MergeWalls", "Arch_Axis", "Arch_AxisSystem", "Arch_Grid", "Arch_AxisTools"]) {
assert.equal(methodResult(name)?.active, true, `IsActive fixture missing for ${name}`);
assert.equal(methodResult(name)?.sceneGraphAvailable, true);
assert.equal(methodResult(name)?.methodFixture, "IsActive");
}
assert.equal(classResult("Arch_AxisTools")?.sourceFile, "/share/Mod/BIM/bimcommands/BimAxis.py");
assert.equal(classResult("Arch_Check")?.sourceFile, "/share/Mod/BIM/bimcommands/BimArchUtils.py");
assert.equal(classResult("Arch_Component")?.menuText, "Component");
assert.equal(classResult("Arch_Axis")?.menuText, "Axis");
}
if (batch.batchId === "deeaf9a02de8026b465a23ee") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM command batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim[A-Za-z]+\.py$/);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const classNames = ["BIM_Background", "BIM_Beam", "BIM_Box", "BIM_Builder", "Arch_Level", "Arch_Building", "BIM_Classification", "BIM_Clone", "BIM_Column", "BIM_Common", "BIM_Compound", "BIM_Convert", "BIM_Copy", "Arch_CurtainWall"];
for (const name of classNames) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.className, name, `class fixture missing for ${name}`);
assert.equal(value?.constructorSuccess, true);
assert.ok(Array.isArray(value?.resourceKeys));
assert.equal(typeof value?.menuText, "string");
assert.equal(typeof value?.toolTip, "string");
}
for (const name of ["BIM_Beam", "BIM_Box", "Arch_Level", "Arch_Building", "BIM_Classification", "BIM_Column", "BIM_Common", "BIM_Compound", "BIM_Convert", "Arch_CurtainWall"]) {
const value = nativeById.get(`py:BIM:method:${name}.IsActive`)?.result;
assert.equal(value?.className, name, `IsActive fixture missing for ${name}`);
assert.equal(value?.active, true);
assert.equal(value?.sceneGraphAvailable, true);
assert.equal(value?.methodFixture, "IsActive");
}
assert.equal(nativeById.get("py:BIM:class:BIM_Background")?.result?.menuText, "Toggle Background");
assert.equal(nativeById.get("py:BIM:class:BIM_Beam")?.result?.menuText, "Beam");
assert.equal(nativeById.get("py:BIM:class:Arch_Level")?.result?.menuText, "Level");
assert.equal(nativeById.get("py:BIM:class:Arch_CurtainWall")?.result?.menuText, "Curtain Wall");
}
if (batch.batchId === "9e47a5d0f090458e414681ca") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim[A-Za-z]+\.py$/);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
for (const name of ["BIM_Cut", "Arch_CutPlane", "BIM_Diff", "BIM_DimensionAligned", "BIM_DimensionHorizontal", "BIM_DimensionVertical", "BIM_Door", "BIM_EmptyTrash", "Arch_Equipment", "BIM_Examples", "BIM_Extrude", "Arch_Fence", "Arch_Frame", "BIM_Fuse"]) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.className, name, `class fixture missing for ${name}`);
assert.equal(value?.constructorSuccess, true);
assert.ok(Array.isArray(value?.resourceKeys));
assert.equal(typeof value?.menuText, "string");
assert.equal(typeof value?.toolTip, "string");
}
for (const name of ["BIM_Cut", "Arch_CutPlane", "BIM_Diff", "BIM_DrawingView", "Arch_Equipment", "BIM_Extrude", "Arch_Fence", "Arch_Frame", "BIM_Fuse"]) {
const value = nativeById.get(`py:BIM:method:${name}.IsActive`)?.result;
assert.equal(value?.className, name, `IsActive fixture missing for ${name}`);
assert.equal(value?.active, true);
assert.equal(value?.methodFixture, "IsActive");
}
const buttons = nativeById.get("py:BIM:method:CutPlaneTaskPanel.getStandardButtons")?.result;
assert.equal(buttons?.methodFixture, "getStandardButtons");
assert.equal(buttons?.okButton, 1024);
assert.equal(buttons?.cancelButton, 2048);
assert.equal(buttons?.buttons, 3072);
assert.equal(nativeById.get("py:BIM:class:BIM_Cut")?.result?.menuText, "Difference");
assert.equal(nativeById.get("py:BIM:class:Arch_Equipment")?.result?.accel, "E, Q");
assert.equal(nativeById.get("py:BIM:class:Arch_Frame")?.result?.accel, "F, R");
}
if (batch.batchId === "e00825af472bede0eb01beee") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim[A-Za-z]+\.py$/);
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (id) => nativeById.get(`py:BIM:${id}`)?.result;
for (const name of ["BIM_Glue", "BIM_Help", "BIM_IfcElements", "BIM_IfcExplorer", "BIM_IfcQuantities", "BIM_ImagePlane", "BIM_Layers"]) {
assert.equal(value(`class:${name}`)?.className, name, `class fixture missing for ${name}`);
assert.equal(value(`class:${name}`)?.constructorSuccess, true);
assert.ok(Array.isArray(value(`class:${name}`)?.resourceKeys));
}
for (const name of ["BIM_Glue", "BIM_IfcElements", "BIM_IfcProperties", "BIM_IfcQuantities", "BIM_ImagePlane", "BIM_Layers"]) {
assert.equal(value(`method:${name}.IsActive`)?.active, true, `IsActive fixture missing for ${name}`);
assert.equal(value(`method:${name}.IsActive`)?.methodFixture, "IsActive");
}
for (const name of ["BIM_IfcElements", "BIM_IfcProperties", "BIM_IfcQuantities"]) assert.equal(value(`method:${name}.getRole`)?.role, "IfcWall");
assert.deepEqual(value("method:BIM_IfcExplorer.getEntitiesTree")?.rootKeys, [101]);
assert.equal(value("method:BIM_IfcExplorer.getEntitiesTree")?.rootCount, 1);
assert.equal(value("method:BIM_IfcExplorer.getEntitiesTree")?.productCount, 1);
assert.equal(value("method:BIM_IfcProperties.getSearchResults")?.text, "Fire");
assert.equal(value("method:BIM_IfcQuantities.getArray")?.arrayCount, 3);
assert.equal(value("method:BIM_IfcQuantities.get_qtos")?.comboCount, value("method:BIM_IfcQuantities.get_qtos")?.qtoCount + 1);
assert.equal(value("method:BIM_IfcQuantities.has_qto")?.hasQto, false);
assert.equal(value("method:BIM_IfcQuantities.get_text")?.text, "12 m\u00b2");
assert.equal(value("method:BIM_IfcQuantities.get_row")?.row, 1);
assert.equal(value("method:BIM_Layers.getPref")?.pref, 7);
}
if (batch.batchId === "215122d11c8475698553a0b2") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (id) => nativeById.get(`py:BIM:${id}`)?.result;
assert.equal(value("class:BIM_Leader")?.menuText, "Leader");
assert.equal(value("class:BIM_Library")?.menuText, "Objects Library");
assert.equal(value("method:BIM_Library_TaskPanel.getFilters")?.filterCount, 15);
assert.deepEqual(value("method:BIM_Library_TaskPanel.isAllowed"), {
className: "BIM_Library_TaskPanel", fcstd: true, ifc: true, text: true,
sourceConstructor: "python-source-ast-class", methodFixture: "isAllowed",
});
assert.deepEqual(value("method:BIM_Library_TaskPanel.getOfflineLib")?.fixtureKeys, ["Chair.fcstd", "Readme.txt"]);
assert.equal(value("method:BIM_Library_TaskPanel.getStandardButtons")?.closeButton, 4096);
assert.deepEqual(value("method:BIM_Library_TaskPanel.getDelta")?.delta, [-2, -3, 0]);
assert.deepEqual(value("method:BIM_Library_TaskPanel.getOnlineContentsAPI")?.fixtureFiles, ["Chair.fcstd", "Wall.step"]);
assert.equal(value("method:BIM_Library_TaskPanel.getThumbnail")?.hasThumbnail, true);
assert.ok((value("method:BIM_Library_TaskPanel.getThumbnail")?.thumbnailBytes ?? 0) > 0);
assert.equal(value("method:BIM_LinkMake.IsActive")?.active, false);
for (const name of ["BIM_Material", "Arch_Material", "Arch_MultiMaterial"]) assert.equal(value(`class:${name}`)?.constructorSuccess, true);
for (const name of ["BIM_Material", "Arch_Material", "Arch_MultiMaterial", "Arch_MaterialToolsCommand"]) assert.equal(value(`method:${name}.IsActive`)?.active, true);
assert.deepEqual(value("method:BIM_Nudge.getCenter")?.center, [1.5, 1.5, 2]);
assert.equal(value("class:BIM_Nudge")?.hasGetCenter, true);
for (const name of ["BIM_Nudge_Switch", "BIM_Nudge_Up", "BIM_Nudge_Down", "BIM_Nudge_Left", "BIM_Nudge_Right"]) {
assert.equal(value(`class:${name}`)?.constructorSuccess, true);
assert.ok(Array.isArray(value(`class:${name}`)?.resourceKeys));
}
}
if (batch.batchId === "706a65b65899e06532868a60") {
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const guiBoundaries = new Set([
"py:BIM:method:NestTaskPanel.getContainer",
"py:BIM:method:BIM_Preflight_TaskPanel.getStandardButtons",
]);
assert.equal(result.records.length, 24);
for (const row of result.records) {
assert.equal(row.native, true, `native flag missing for ${row.apiId}`);
assert.equal(row.resourcesReleased, true, `resource release missing for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
if (guiBoundaries.has(row.apiId)) {
assert.equal(row.status, "restricted", `GUI boundary status mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.result, undefined);
} else {
assert.equal(row.status, "exposed", `native fixture status mismatch for ${row.apiId}`);
assert.equal(row.code, "native_fixture");
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
assert.equal(row.result?.constructorSuccess ?? true, true);
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim(?:Nudge|Offset|Panel|Pipe|Preflight)\.py$/);
}
}
for (const name of ["BIM_Nudge_Extend", "BIM_Nudge_Shrink", "BIM_Nudge_RotateLeft", "BIM_Nudge_RotateRight", "BIM_Offset2D", "Arch_Panel", "Arch_PanelCut", "Arch_PanelSheet", "Arch_Nest", "Arch_Pipe", "Arch_PipeConnector", "BIM_Preflight"]) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.className, name, `class fixture missing for ${name}`);
assert.equal(value?.constructorSuccess, true);
assert.ok(Array.isArray(value?.resourceKeys));
}
for (const name of ["BIM_Offset2D", "Arch_Panel", "Arch_PanelCut", "Arch_PanelSheet", "Arch_Nest", "Arch_PanelGroup", "Arch_Pipe", "Arch_PipeConnector", "Arch_PipeGroupCommand", "BIM_Preflight"]) {
const value = nativeById.get(`py:BIM:method:${name}.IsActive`)?.result;
assert.equal(value?.className, name, `IsActive fixture missing for ${name}`);
assert.equal(value?.active, true);
assert.equal(value?.methodFixture, "IsActive");
assert.equal(value?.sceneGraphAvailable, true);
}
assert.equal(nativeById.get("py:BIM:class:BIM_Nudge_Extend")?.result?.menuText, "Nudge Extend");
assert.equal(nativeById.get("py:BIM:class:Arch_Panel")?.result?.menuText, "Panel");
assert.equal(nativeById.get("py:BIM:class:Arch_Pipe")?.result?.menuText, "Pipe");
assert.equal(nativeById.get("py:BIM:class:BIM_Preflight")?.result?.menuText, "Preflight Checks");
}
if (batch.batchId === "7af36fb99dfcef88d25e3ad0") {
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
assert.match(row.sourceEntry, /^src\/Mod\/BIM\/bimcommands\/Bim[A-Za-z]+\.py$/);
}
const preset = nativeById.get("py:BIM:method:BIM_ProjectManager.getPreset")?.result;
assert.equal(preset?.className, "BIM_ProjectManager");
assert.equal(preset?.methodFixture, "getPreset");
assert.equal(preset?.presetName, "NativeFixture");
assert.equal(preset?.projectName, "Native Project");
assert.equal(preset?.siteLongitude, 12.5);
assert.equal(preset?.siteLatitude, -7.25);
assert.equal(preset?.buildingUse, 3);
assert.deepEqual(preset?.lineColor, [0.1, 0.2, 0.3]);
assert.deepEqual(preset?.groups, ["Architecture", "MEP"]);
assert.equal(preset?.addHumanFigure, true);
const color = nativeById.get("py:BIM:method:BIM_Setup.getPrefColor")?.result;
assert.equal(color?.className, "BIM_Setup");
assert.equal(color?.methodFixture, "getPrefColor");
assert.deepEqual(color?.color, [128 / 255, 64 / 255, 32 / 255]);
}
if (batch.batchId === "7b1930cc76f1f9d6d10b245f") {
for (const row of result.records) {
assert.equal(row.status, "exposed", `BIM importer generic-object status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
const value = (id) => nativeById.get(id)?.result;
assert.equal(value("py:BIM:class:recycler")?.className, "recycler");
assert.equal(value("py:BIM:class:recycler")?.compress, false);
assert.equal(value("py:BIM:class:ProjectImporter")?.fileBound, true);
assert.equal(value("py:BIM:class:IfcSchema")?.entityCount, 2);
assert.deepEqual(value("py:BIM:method:IfcSchema.getAttributes")?.attributes, [["\n GlobalId", "IfcLabel"], ["\n Name", "IfcLabel"]]);
assert.equal(value("py:BIM:class:IfcFile")?.entityCount, 1);
assert.deepEqual(value("py:BIM:method:IfcFile.getEntityById")?.attributeKeys, ["\n GlobalId", "\n Name"]);
assert.equal(value("py:BIM:class:IfcEntity")?.entityType, "IFCWALL");
assert.equal(value("py:BIM:method:IfcEntity.getAttribute")?.typeValue, "IFCWALL");
assert.equal(value("py:BIM:method:IfcEntity.getAttribute")?.missingValue, null);
assert.equal(value("py:BIM:method:IfcDocument.getEnt")?.idType, "IFCWALL");
assert.equal(value("py:BIM:method:IfcDocument.getEnt")?.typeMatches, 1);
assert.equal(value("py:BIM:class:SH3DImporter")?.floorCount, 2);
assert.deepEqual(value("py:BIM:method:SH3DImporter.get_all_floors")?.floorIds, ["L1", "L2"]);
assert.deepEqual([value("py:BIM:method:SH3DImporter.get_floor")?.defaultFloor, value("py:BIM:method:SH3DImporter.get_floor")?.namedFloor], ["L1", "L2"]);
assert.deepEqual(value("py:BIM:method:SH3DImporter.get_all_spaces")?.spaceIds, ["S1", "S2", "S3"]);
assert.deepEqual(value("py:BIM:method:SH3DImporter.get_spaces")?.spaceIds, ["S1", "S2"]);
assert.equal(value("py:BIM:method:SH3DImporter.get_space")?.spaceId, "S1");
assert.deepEqual(value("py:BIM:method:SH3DImporter.get_all_walls")?.wallIds, ["W1", "W2"]);
assert.deepEqual(value("py:BIM:method:SH3DImporter.get_walls")?.wallIds, ["W1"]);
assert.equal(value("py:BIM:class:BaseHandler")?.importerBound, true);
assert.equal(value("py:BIM:method:BaseHandler.get_floor")?.floorId, "L1");
assert.equal(value("py:BIM:method:BaseHandler.get_spaces")?.spaceCount, 2);
assert.equal(value("py:BIM:method:BaseHandler.get_space")?.spaceId, "S1");
assert.equal(value("py:BIM:method:BaseHandler.get_walls")?.wallCount, 1);
assert.equal(value("py:BIM:method:BaseHandler.get_wall_spine")?.spine, "spine");
assert.equal(value("py:BIM:class:LevelHandler")?.baseClassName, "BaseHandler");
}
if (batch.batchId === "354efabcb0c452af01196bbf") {
for (const row of result.records) {
console.error('BIM_BATCH_DEBUG_TARGET', row.apiId, row.status, row.code, row.message);
assert.equal(row.status, "exposed", `BIM generic-object batch status mismatch for ${row.apiId}`);
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.result?.sourceConstructor, "python-source-ast-class");
}
const nativeById = new Map(result.records.map((row) => [row.apiId, row]));
for (const name of ["RoomHandler", "WallHandler", "BaseFurnitureHandler", "DoorOrWindowHandler", "FurnitureHandler", "LightHandler", "CameraHandler"]) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.className, name);
assert.equal(value?.constructorSuccess, true);
assert.equal(value?.importerBound, true);
}
assert.equal(nativeById.get("py:BIM:class:CameraDict")?.result?.typedDict, true);
assert.ok(nativeById.get("py:BIM:class:CameraDict")?.result?.fieldNames.includes("position_x"));
for (const name of ["IFC_Diff", "IFC_Expand", "IFC_MakeProject"]) {
const value = nativeById.get(`py:BIM:class:${name}`)?.result;
assert.equal(value?.constructorSuccess, true);
assert.ok(Array.isArray(value?.resourceKeys));
}
assert.equal(nativeById.get("py:BIM:method:IFC_MakeProject.IsActive")?.result?.active, true);
assert.equal(nativeById.get("py:BIM:method:IFC_Save.IsActive")?.result?.active, true);
assert.equal(nativeById.get("py:BIM:method:IFC_SaveAs.IsActive")?.result?.active, true);
assert.equal(nativeById.get("py:BIM:method:ifc_object.get_section_data")?.result?.objectsCount, 0);
assert.equal(nativeById.get("py:BIM:class:ifc_observer")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:class:IFC_UpdateIOS")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:method:IFC_UpdateIOS.get_current_version")?.result?.version, "0.7.0");
assert.equal(nativeById.get("py:BIM:method:IFC_UpdateIOS.get_avail_version")?.result?.version, "0.7.0");
assert.equal(nativeById.get("py:BIM:class:NativeIFCTest")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:class:IFC_WBManipulator")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:class:ifc_vp_buildingpart")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:class:IfcElementHandler")?.result?.constructorSuccess, true);
assert.equal(nativeById.get("py:BIM:method:IfcElementHandler.is_an_ifcproduct")?.result?.isIfcProduct, true);
}
if (batch.batchId === "ecb86d179565efd2b452f9c6") {
const requestedById = new Map(records.map((row) => [row.apiId, row]));
assert.equal(result.records.length, 24);
for (const row of result.records) {
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requestedById.get(row.apiId)?.sourcePath);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "b91b350afb0a05f0acf4c1f6") {
const requestedById = new Map(records.map((row) => [row.apiId, row]));
assert.equal(result.records.length, 24);
for (const row of result.records) {
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requestedById.get(row.apiId)?.sourcePath);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "c0051bb9bedbfc5eff763833") {
const requestedById = new Map(records.map((row) => [row.apiId, row]));
assert.equal(family, "view-resource");
assert.equal(result.records.length, 24);
for (const row of result.records) {
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requestedById.get(row.apiId)?.sourcePath);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "efe05205ebfcb8aec117dd63") {
const expectedIds = [
"py:BIM:method:BIM_Clone.GetResources", "py:BIM:method:BIM_Column.GetResources", "py:BIM:method:BIM_Common.GetResources", "py:BIM:method:BIM_Compound.GetResources",
"py:BIM:method:BIM_Convert.GetResources", "py:BIM:method:BIM_Copy.GetResources", "py:BIM:method:Arch_CurtainWall.GetResources", "py:BIM:method:BIM_Cut.GetResources",
"py:BIM:method:Arch_CutPlane.GetResources", "py:BIM:method:CutPlaneTaskPanel.previewCutVolume", "py:BIM:method:BIM_Diff.GetResources", "py:BIM:method:BIM_DimensionAligned.GetResources",
"py:BIM:method:BIM_DimensionHorizontal.GetResources", "py:BIM:method:BIM_DimensionVertical.GetResources", "py:BIM:method:BIM_Door.GetResources", "py:BIM:class:BIM_DrawingView",
"py:BIM:method:BIM_DrawingView.GetResources", "py:BIM:method:BIM_EmptyTrash.GetResources", "py:BIM:method:Arch_Equipment.GetResources", "py:BIM:method:BIM_Examples.GetResources",
"py:BIM:method:BIM_Extrude.GetResources", "py:BIM:method:Arch_Fence.GetResources", "py:BIM:method:Arch_Frame.GetResources", "py:BIM:method:BIM_Fuse.GetResources",
];
assert.deepEqual(result.records.map((row) => row.apiId), expectedIds);
const requestedById = new Map(records.map((row) => [row.apiId, row]));
for (const row of result.records) {
const requested = requestedById.get(row.apiId);
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requested?.sourcePath);
assert.equal(row.sourceLine, requested?.sourceLine);
assert.equal(row.sourceVerified, true, `staged FreeCAD declaration mismatch for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "45f5f01487832ea5d1800aa9") {
const expectedIds = [
"py:BIM:method:BIM_Glue.GetResources", "py:BIM:method:BIM_Help.GetResources", "py:BIM:method:BIM_IfcElements.GetResources",
"py:BIM:function:getIcon", "py:BIM:method:BIM_IfcExplorer.GetResources", "py:BIM:method:BIM_IfcProperties.GetResources",
"py:BIM:method:BIM_IfcQuantities.GetResources", "py:BIM:method:BIM_ImagePlane.GetResources", "py:BIM:function:getColorIcon",
"py:BIM:method:BIM_Layers.GetResources", "py:BIM:method:BIM_Leader.GetResources", "py:BIM:method:BIM_Library.GetResources",
"py:BIM:method:BIM_Library_TaskPanel.onCheck3DPreview", "py:BIM:method:BIM_Library_TaskPanel.onButtonPreview",
"py:BIM:method:BIM_LinkMake.GetResources", "py:BIM:method:BIM_Material.GetResources", "py:BIM:method:BIM_Material.createIcon",
"py:BIM:method:Arch_Material.GetResources", "py:BIM:method:Arch_MultiMaterial.GetResources",
"py:BIM:method:Arch_MaterialToolsCommand.GetResources", "py:BIM:class:BIM_MoveView", "py:BIM:method:BIM_MoveView.GetResources",
"py:BIM:method:BIM_Nudge_Switch.GetResources", "py:BIM:method:BIM_Nudge_Up.GetResources",
];
assert.deepEqual(result.records.map((row) => row.apiId), expectedIds);
const requestedById = new Map(records.map((row) => [row.apiId, row]));
for (const row of result.records) {
const requested = requestedById.get(row.apiId);
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requested?.sourcePath);
assert.equal(row.sourceLine, requested?.sourceLine);
assert.equal(row.sourceVerified, true, `staged FreeCAD declaration mismatch for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "78936279cb6d2a009fe5933c") {
const expectedIds = [
"py:BIM:method:BIM_Nudge_Down.GetResources", "py:BIM:method:BIM_Nudge_Left.GetResources", "py:BIM:method:BIM_Nudge_Right.GetResources",
"py:BIM:method:BIM_Nudge_Extend.GetResources", "py:BIM:method:BIM_Nudge_Shrink.GetResources", "py:BIM:method:BIM_Nudge_RotateLeft.GetResources",
"py:BIM:method:BIM_Nudge_RotateRight.GetResources", "py:BIM:method:BIM_Offset2D.GetResources", "py:BIM:method:Arch_Panel.GetResources",
"py:BIM:method:Arch_PanelCut.GetResources", "py:BIM:method:Arch_PanelSheet.GetResources", "py:BIM:method:Arch_Nest.GetResources",
"py:BIM:method:NestTaskPanel.preview", "py:BIM:method:Arch_PanelGroup.GetResources", "py:BIM:method:Arch_Pipe.GetResources",
"py:BIM:method:Arch_PipeConnector.GetResources", "py:BIM:method:Arch_PipeGroupCommand.GetResources", "py:BIM:method:BIM_Preflight.GetResources",
"py:BIM:method:BIM_Preflight_TaskPanel.getToolTip", "py:BIM:method:Arch_Profile.GetResources", "py:BIM:method:BIM_Project.GetResources",
"py:BIM:method:BIM_ProjectManager.GetResources", "py:BIM:method:Arch_Rebar.GetResources", "py:BIM:method:BIM_Reextrude.GetResources",
];
assert.equal(family, "view-resource");
assert.deepEqual(result.records.map((row) => row.apiId), expectedIds);
const requestedById = new Map(records.map((row) => [row.apiId, row]));
for (const row of result.records) {
const requested = requestedById.get(row.apiId);
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requested?.sourcePath);
assert.equal(row.sourceLine, requested?.sourceLine);
assert.equal(row.sourceVerified, true, `staged FreeCAD declaration mismatch for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "c58285f379b88b38c6c21e4f") {
const expectedIds = [
"py:BIM:method:BIM_Trash.GetResources", "py:BIM:method:Arch_Truss.GetResources", "py:BIM:method:BIM_Tutorial.GetResources",
"py:BIM:method:BIM_Unclone.GetResources", "py:BIM:method:BIM_Ungroup.GetResources", "py:BIM:class:BIM_Views",
"py:BIM:method:BIM_Views.GetResources", "py:BIM:method:BIM_Views.onContextMenu", "py:BIM:method:BIM_Views.getViews",
"py:BIM:function:isView", "py:BIM:function:getTreeViewItem", "py:BIM:method:BIM_SetWPFront.GetResources",
"py:BIM:method:BIM_SetWPSide.GetResources", "py:BIM:method:BIM_SetWPTop.GetResources", "py:BIM:class:BIM_WPView",
"py:BIM:method:BIM_WPView.GetResources", "py:BIM:method:Arch_Wall.GetResources", "py:BIM:method:BIM_Welcome.GetResources",
"py:BIM:method:Arch_Window.GetResources", "py:BIM:method:BIM_Windows.GetResources", "py:BIM:method:recycler.createIfcSurfaceStyleRendering",
"py:BIM:function:get_view_properties", "py:BIM:method:IFC_Diff.GetResources", "py:BIM:method:IFC_Expand.GetResources",
];
assert.equal(family, "view-resource");
assert.deepEqual(result.records.map((row) => row.apiId), expectedIds);
const requestedById = new Map(records.map((row) => [row.apiId, row]));
for (const row of result.records) {
const requested = requestedById.get(row.apiId);
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requested?.sourcePath);
assert.equal(row.sourceLine, requested?.sourceLine);
assert.equal(row.sourceVerified, true, `staged FreeCAD declaration mismatch for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
if (batch.batchId === "ff44fa70b89661c965c07a00") {
const expectedIds = [
"py:BIM:method:IFC_ConvertDocument.GetResources", "py:BIM:method:IFC_MakeProject.GetResources",
"py:BIM:method:IFC_Save.GetResources", "py:BIM:method:IFC_SaveAs.GetResources",
"py:BIM:method:IFC_UpdateIOS.GetResources", "py:BIM:function:set_menu",
"py:BIM:method:IFC_WBManipulator.modifyMenuBar", "py:BIM:method:IFC_WBManipulator.modifyToolBars",
"py:BIM:method:ifc_vp_object.getDisplayModes", "py:BIM:method:ifc_vp_object.getDefaultDisplayMode",
"py:BIM:method:ifc_vp_object.setDisplayMode", "py:BIM:method:ifc_vp_object.getIcon",
"py:BIM:method:ifc_vp_object.setupContextMenu", "py:BIM:method:ifc_vp_document.getIcon",
"py:BIM:method:ifc_vp_document.setupContextMenu", "py:BIM:method:ifc_vp_group.getIcon",
"py:BIM:method:ifc_vp_material.getDisplayModes", "py:BIM:method:ifc_vp_material.getDefaultDisplayMode",
"py:BIM:method:ifc_vp_material.setDisplayMode", "py:BIM:method:ifc_vp_material.getIcon",
"py:BIM:method:ifc_vp_material.setupContextMenu", "py:BIM:function:get_icon", "py:BIM:class:ViewProvider",
];
assert.equal(family, "view-resource");
assert.deepEqual(result.records.map((row) => row.apiId), expectedIds);
const requestedById = new Map(records.map((row) => [row.apiId, row]));
for (const row of result.records) {
const requested = requestedById.get(row.apiId);
assert.equal(row.status, "restricted", `BIM view-resource boundary mismatch for ${row.apiId}`);
assert.equal(row.code, "gui_dependency_unavailable");
assert.equal(row.native, true);
assert.equal(row.resourcesReleased, true);
assert.equal(row.sourceEntry, requested?.sourcePath);
assert.equal(row.sourceLine, requested?.sourceLine);
assert.equal(row.sourceVerified, true, `staged FreeCAD declaration mismatch for ${row.apiId}`);
assert.equal(row.sourceRevision, "2778e3f9c215da48bf0cfd562478d45d624aaa47");
assert.equal(row.parameterSchema, "source-declared");
assert.match(row.returnSchema, /^(source-declared|BIM\.)/);
assert.equal(row.result, undefined);
}
}
const dom3dsNames = new Set(["string_to_array", "array_to_string", "array_to_string_destructive"]);
const dom3dsRows = result.records.filter((row) => dom3dsNames.has(row.name));
if (dom3dsRows.length > 0) {
assert.equal(dom3dsRows.length, dom3dsNames.size, "incomplete BIM dom3ds native fixture group");
const nativeByName = new Map(dom3dsRows.map((row) => [row.name, row]));
for (const row of dom3dsRows) {
assert.equal(row.status, "exposed", `BIM dom3ds native fixture status mismatch for ${row.apiId}`);
assert.equal(row.code, "native_fixture");
assert.equal(row.sourceVerified, true);
assert.equal(row.parameterSchema, "official FreeCAD source fixture inputs");
assert.equal(row.returnSchema, "typed native result object");
}
assert.deepEqual(nativeByName.get("string_to_array")?.result, { dtype: "uint16", inputHex: "01000201", values: [1, 258], byteOrder: "little" });
assert.deepEqual(nativeByName.get("array_to_string")?.result, { dtype: "uint16", values: [1, 258], bytesHex: "01000201", destructive: false });
assert.deepEqual(nativeByName.get("array_to_string_destructive")?.result, { dtype: "uint16", values: [1, 258], bytesHex: "01000201", destructive: true });
}
const nativeIfcGeometryNames = new Set(["get_freecad_matrix", "get_ios_matrix", "get_geometry_tree", "print_geometry_tree"]);
const nativeIfcGeometryRows = result.records.filter(
(row) => nativeIfcGeometryNames.has(row.name) && row.status === "exposed",
);
if (nativeIfcGeometryRows.length > 0) {
const nativeByName = new Map(nativeIfcGeometryRows.map((row) => [row.name, row]));
for (const row of nativeIfcGeometryRows) {
assert.equal(row.status, "exposed", `BIM NativeIFC geometry fixture status mismatch for ${row.apiId}`);
assert.equal(row.code, "native_fixture");
assert.equal(row.sourceVerified, true);
assert.equal(row.parameterSchema, "official FreeCAD source fixture inputs");
assert.equal(row.returnSchema, "typed native result object");
}
if (nativeByName.has("get_freecad_matrix")) {
assert.deepEqual(nativeByName.get("get_freecad_matrix").result?.translation, [2000, 3000, 4000]);
assert.equal(nativeByName.get("get_freecad_matrix").result?.matrix?.length, 16);
}
if (nativeByName.has("get_ios_matrix")) {
assert.deepEqual(nativeByName.get("get_ios_matrix").result?.rows, 4);
assert.deepEqual(nativeByName.get("get_ios_matrix").result?.columns, 4);
}
const expectedTree = ["IfcProduct", " IfcPolyline", " IfcExtrudedAreaSolid"];
if (nativeByName.has("get_geometry_tree")) {
assert.deepEqual(nativeByName.get("get_geometry_tree").result, { lines: expectedTree, count: 3 });
}
if (nativeByName.has("print_geometry_tree")) {
assert.deepEqual(nativeByName.get("print_geometry_tree").result, { lines: expectedTree, count: 3 });
}
}
const evidencePath = `docs/p6/bim-${familySlug}-module-batch-${batch.batchId}-runtime.json`;
const manifestPath = path.join(root, "docs/p6/python-api-manifest.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const byId = new Map(result.records.map((row) => [row.apiId, row]));
for (const row of manifest.records) {
const native = byId.get(row.id);
if (!native) continue;
row.disposition = native.status;
row.dispositionEvidence = [evidencePath];
row.dispositionReason = native.message;
}
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
const dispositionCounts = Object.fromEntries(["exposed", "restricted", "nonportable", "not_implemented"].map((status) => [status, result.records.filter((row) => row.status === status).length]));
const sourceEvidence = records.map((row) => {
if (!row.sourcePath.startsWith("src/Mod/BIM/") || row.sourcePath.includes("..")) throw new Error(`invalid BIM source path for ${row.apiId}`);
const file = path.join(root, "freecad-source", row.sourcePath);
const lines = fs.readFileSync(file, "utf8").split(/\r?\n/);
const declaredLine = lines[row.sourceLine - 1] ?? "";
const symbol = row.apiId.split(":").at(-1).split(".").at(-1);
const marker = row.kind === "class" ? `class ${symbol}` : `def ${symbol}(`;
let declaration = declaredLine;
let evidenceLine = row.sourceLine;
if (!declaration.includes(marker)) {
const nearby = Array.from({ length: 17 }, (_, offset) => row.sourceLine - 8 + offset).find((line) => line > 0 && (lines[line - 1] ?? "").includes(marker));
if (nearby) { evidenceLine = nearby; declaration = lines[nearby - 1]; }
}
assert.ok(declaration.includes(marker), `source declaration mismatch for ${row.apiId}`);
return { apiId: row.apiId, path: row.sourcePath, line: evidenceLine, declaration: declaration.trim(), sha256: fileHash(file) };
});
const report = {
schema: "freecad-web-bim-python-family-module-batch-runtime/v1",
taskId: "FW-P6-FPA-Y",
batchId: batch.batchId,
module: "BIM",
tier: batch.selected.tier,
family,
status: "pass_with_boundaries",
sourceRevision: result.records[0]?.sourceRevision,
dispatcher: "fc_web_dispatch_json",
route: { command: "bim.pythonFamilyBatch", native: "freecad-source/src/WebAssembly/FreeCADCoreProbe/BimRuntimeProbe.cpp:fc_web_dispatch_json", facade: "web/src/bim-python-family-batch-facade.ts", worker: "web/src/bim.worker.ts" },
input: { cmd: "bim.pythonFamilyBatch", args: { tier: batch.selected.tier, records } },
inputSha256: hash(records),
result,
resultSha256: hash(result),
replay: { equal: true, resultSha256: hash(repeat.result) },
facadeWorker: { ready, success, repeat, crossModule, malformed, cancelled, reset, traceSha256: hash({ ready, success, repeat, crossModule, malformed, cancelled, reset }) },
dispositionCounts,
records: result.records.map((row) => ({ apiId: row.apiId, name: row.name, family: row.family, status: row.status, code: row.code, message: row.message, sourceEntry: row.sourceEntry, sourceLine: row.sourceLine, sourceVerified: row.sourceVerified, sourceRevision: row.sourceRevision, result: row.result, evidenceSha256: hash(row) })),
sourceEvidence,
resources: { resourcesReleased: true, documentMutation: false },
artifacts: { wasm: { path: "web/public/kernel/FreeCADWebBimRuntimeProbe.wasm", sha256: fileHash(path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.wasm")) }, loader: { path: "web/public/kernel/FreeCADWebBimRuntimeProbe.js", sha256: fileHash(path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.js")) }, data: { path: "web/public/kernel/FreeCADWebBimRuntimeProbe.data", sha256: fileHash(path.join(root, "web/public/kernel/FreeCADWebBimRuntimeProbe.data")) } },
boundaries: ["BIM records are exposed only where the native FreeCAD Python fixture returned typed values; GUI-only and unsupported records remain explicit boundaries.", "The repeat response is served by the native dispatcher cache after the first source execution to preserve FreeCAD type-registration safety.", "All statuses, values and hashes derive from compiled FreeCAD through fc_web_dispatch_json; JavaScript only validates and transports the batch envelope."],
};
fs.writeFileSync(path.join(root, evidencePath), `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(path.join(root, `docs/freecad-p6-bim-${familySlug}-module-batch-${batch.batchId}.log`), `${JSON.stringify({ status: report.status, batchId: batch.batchId, count: records.length, dispositionCounts, resultSha256: report.resultSha256 })}\n`);
console.log(JSON.stringify({ status: report.status, batchId: batch.batchId, count: records.length, dispositionCounts, resultSha256: report.resultSha256 }));