Files
FreeCADWasmDev/tools/verify_ifc_semantic_export.mjs

76 lines
4.6 KiB
JavaScript

import fs from 'node:fs';
import { loadWasmProbe } from './wasm-probe-runtime.mjs';
import { writeBusinessReport } from './business-report.mjs';
const cases=[];
let kernel;
const record=(name,input,actual,passed)=>cases.push({input,actual,assertions:[{name,passed:Boolean(passed)}]});
try {
({kernel}=await loadWasmProbe('web/public/kernel','FreeCADWebBimRuntimeProbe'));
const python=source=>{
const value=JSON.parse(kernel.ccall('fc_web_bim_test_python','string',['string'],[
'import FreeCAD as App, Part, json, IfcOpenShellNative\n'+source]));
if(value.ok===false) throw Error(value.error);
return value;
};
const fixture=fs.readFileSync(new URL('../freecad-source/build-web/ifcopenshell-native/fixtures/ColumnPSetsOfSets.ifc',import.meta.url));
const original=python(`
import base64
with open('/tmp/semantic-independent.ifc','wb') as stream:
stream.write(base64.b64decode(${JSON.stringify(fixture.toString('base64'))}))
d=App.newDocument('SemanticSource')
IfcOpenShellNative.insert('/tmp/semantic-independent.ifc',d.Name)
def snapshot(document):
return [{'guid':o.IfcGlobalId,'type':o.IfcType,'label':o.Label,
'properties':json.loads(o.IfcPropertySets) if 'IfcPropertySets' in o.PropertiesList else [],
'volume':o.Shape.Volume if o.isDerivedFrom('Part::Feature') else None,
'children':sorted(c.IfcGlobalId for c in o.Group if 'IfcGlobalId' in c.PropertiesList) if 'Group' in o.PropertiesList else []}
for o in document.Objects if 'IfcGlobalId' in o.PropertiesList]
result=json.dumps({'document':d.Name,'objects':snapshot(d)})
`);
const exports=[];
for(let attempt=0;attempt<2;attempt++) {
const value=python(`
d=App.getDocument(${JSON.stringify(original.document)})
IfcOpenShellNative.exportDocument(d.Name,'/tmp/semantic-independent-output.ifc')
r=App.newDocument('SemanticRead${attempt}')
IfcOpenShellNative.insert('/tmp/semantic-independent-output.ifc',r.Name)
result=json.dumps({'document':r.Name,'objects':[{'guid':o.IfcGlobalId,'type':o.IfcType,'label':o.Label,
'properties':json.loads(o.IfcPropertySets) if 'IfcPropertySets' in o.PropertiesList else [],
'volume':o.Shape.Volume if o.isDerivedFrom('Part::Feature') else None,
'children':sorted(c.IfcGlobalId for c in o.Group if 'IfcGlobalId' in c.PropertiesList) if 'Group' in o.PropertiesList else []}
for o in r.Objects if 'IfcGlobalId' in o.PropertiesList]})
App.closeDocument(r.Name)
`);
exports.push(value);
}
const source=original.objects.find(o=>o.volume>0);
for(const [attempt,value] of exports.entries()) {
const output=value.objects.find(o=>o.volume>0);
record('Export preserves the real source IFC class',{attempt,type:source.type},output?.type,output?.type===source.type);
record('Export preserves the source GlobalId across repeated exports',{attempt,guid:source.guid},output?.guid,output?.guid===source.guid);
record('Export preserves typed IFC property sets',{attempt,properties:source.properties},output?.properties,
source.properties.length>0&&JSON.stringify(output?.properties)===JSON.stringify(source.properties));
const originalGroups=original.objects.filter(o=>o.volume===null).sort((a,b)=>a.guid.localeCompare(b.guid));
const outputGroups=value.objects.filter(o=>o.volume===null).sort((a,b)=>a.guid.localeCompare(b.guid));
const hierarchy=rows=>rows.map(({guid,type,label,children})=>({guid,type,label,children}));
record('Export preserves spatial hierarchy identities and relationships',{attempt,hierarchy:hierarchy(originalGroups)},hierarchy(outputGroups),
originalGroups.length>0&&JSON.stringify(hierarchy(originalGroups))===JSON.stringify(hierarchy(outputGroups)));
record('Semantic export preserves native volume and units',{attempt,volume:source.volume},output?.volume,
Math.abs((output?.volume??NaN)-source.volume)<1);
}
python(`
import os
for d in list(App.listDocuments()): App.closeDocument(d)
for path in ['/tmp/semantic-independent.ifc','/tmp/semantic-independent-output.ifc']:
if os.path.exists(path): os.remove(path)
result=json.dumps({'documents':list(App.listDocuments())})
`);
} catch(error) {
record('Independent native semantic verification completes',{fixture:'ColumnPSetsOfSets.ifc'},String(error.stack??error),false);
} finally {kernel?.PThread?.terminateAllThreads?.();}
const passed=cases.length>=10&&cases.every(c=>c.assertions.every(a=>a.passed));
await writeBusinessReport({schema:'freecad-web-independent-semantic-verification/v1',status:passed?'passed':'failed',cases,
scope:'Independent source identity, hierarchy, typed properties and geometry checks. Full semantic acceptance also requires every declared proof area.'});
process.exit(passed?0:1);