import { createHash } from 'node:crypto' import { readFile, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const runtimePath = resolve(root, '.cache/freecad/reference-desktop.json') const sourcePaths = [ '.cache/freecad/FreeCAD/src/App/PropertyGeo.h', '.cache/freecad/FreeCAD/src/App/PropertyGeo.cpp', '.cache/freecad/FreeCAD/src/Gui/propertyeditor/PropertyItem.h', '.cache/freecad/FreeCAD/src/Gui/propertyeditor/PropertyItem.cpp', '.cache/freecad/FreeCAD/src/Mod/Part/App/FeatureMirroring.h', '.cache/freecad/FreeCAD/src/Mod/Part/App/FeatureMirroring.cpp', '.cache/freecad/FreeCAD/src/Mod/Part/App/FeatureProjectOnSurface.h', '.cache/freecad/FreeCAD/src/Mod/Part/App/FeatureProjectOnSurface.cpp', ] const outputPath = resolve(root, 'config/freecad-property-direction-inventory.json') const runtimeContent = await readFile(runtimePath) const runtime = JSON.parse(runtimeContent) const sourceContents = await Promise.all(sourcePaths.map((path) => readFile(resolve(root, path)))) const fail = (message) => { throw new Error(`FreeCAD PropertyDirection inventory generation failed: ${message}`) } const vector = (value) => { const match = /^Vector \(([-+0-9.eE]+), ([-+0-9.eE]+), ([-+0-9.eE]+)\)$/.exec(value) if (!match) fail(`unexpected native vector value: ${value}`) return match.slice(1).map(Number) } if (runtime.schemaVersion !== 1 || runtime.freecadVersion !== '1.1.1' || runtime.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('desktop oracle is not the locked FreeCAD baseline') const hostContracts = { 'Part::Mirroring.Normal': { dependencies: [ { propertyName: 'Source', role: 'shape-source', requiredForExecution: true }, { propertyName: 'Base', role: 'plane-origin', requiredForExecution: true }, { propertyName: 'MirrorPlane', role: 'optional-plane-override', requiredForExecution: false, overrides: ['Base', 'Normal'], overriddenStatus: 'ReadOnly' }, ], consumer: 'gp_Ax2(gp_Pnt(Base), gp_Dir(Normal))', }, 'Part::ProjectOnSurface.Direction': { dependencies: [ { propertyName: 'SupportFace', role: 'single-support-face', requiredForExecution: true }, { propertyName: 'Projection', role: 'projected-shape-list', requiredForExecution: true }, ], consumer: 'gp_Dir(Direction)', }, } const records = [] for (const objectType of runtime.runtimeObjects?.types ?? []) { for (const property of objectType.properties ?? []) { if (property.typeId !== 'App::PropertyDirection') continue const contract = hostContracts[`${objectType.typeId}.${property.name}`] if (!contract) fail(`missing host contract for ${objectType.typeId}.${property.name}`) records.push({ objectTypeId: objectType.typeId, objectAvailable: objectType.available === true, probeStatus: objectType.probeStatus, propertyName: property.name, group: property.group, status: property.status, defaultDisplayValue: property.default, defaultValue: vector(property.default), valueModel: { kind: 'three-component-direction-vector', representation: 'Base::Vector3d', normalizationAtPropertyBoundary: 'none', unit: 'Length', writable: property.status.length === 0, }, inputs: { accepted: ['Base.Vector', 'tuple-of-three-float-or-integer-components'], listAccepted: false, componentPaths: ['x', 'y', 'z'], }, editor: 'Gui::PropertyEditor::PropertyDirectionItem', dependencies: contract.dependencies, applicability: { requiredObjectTypeId: objectType.typeId, source: 'Document.supportedTypes runtime inventory', propertyWriteRequiresShape: false, hostExecutionRequiresShapeInputs: true, nonZeroRequiredByConsumer: true, consumer: contract.consumer, }, }) } } records.sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`)) if (records.length !== 2) fail(`expected two native App::PropertyDirection records, found ${records.length}`) const report = { schemaVersion: 1, status: 'pass', baseline: { freecadVersion: runtime.freecadVersion, commit: runtime.gitCommit }, propertyType: 'App::PropertyDirection', recordCount: records.length, records, storage: { inheritedFrom: 'App::PropertyVector', xmlElement: 'PropertyVector', attributes: ['valueX', 'valueY', 'valueZ'], scalarEncoding: 'decimal-double', externalResource: false, }, provenance: { runtime: { path: '.cache/freecad/reference-desktop.json', bytes: runtimeContent.length, sha256: createHash('sha256').update(runtimeContent).digest('hex') }, sources: sourcePaths.map((path, index) => ({ path, bytes: sourceContents[index].length, sha256: createHash('sha256').update(sourceContents[index]).digest('hex') })), }, classification: 'opaque-fcstd-proxy', nextPhases: ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], } await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify({ status: 'freecad-property-direction-inventory-generated', output: 'config/freecad-property-direction-inventory.json', propertyType: report.propertyType, recordCount: report.recordCount }, null, 2))