#!/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 }));