feat: close ordered pairs and property codec batches
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-17 04:58:46 -04:00
parent 00ed27b7b8
commit d11566403d
265 changed files with 92107 additions and 809 deletions

View File

@@ -44,6 +44,13 @@ const canonicalSummary = (summary) => ({
area: Number(summary.area.toFixed(7)),
bounds: [...summary.boundingBox.min, ...summary.boundingBox.max].map((value) => Number(value.toFixed(7))),
})
const summariesMatchWithinTolerance = (left, right, tolerance = 1e-6) => (
left?.isValid === right?.isValid
&& ['solids', 'faces', 'edges', 'vertices'].every((key) => left?.[key] === right?.[key])
&& ['volume', 'area'].every((key) => Math.abs(left?.[key] - right?.[key]) <= tolerance)
&& left?.bounds?.length === right?.bounds?.length
&& left.bounds.every((value, index) => Math.abs(value - right.bounds[index]) <= tolerance)
)
const operationSpecs = {
fuse: { builder: 'BRepAlgoAPI_Fuse', nominal: { toolSize: 5, toolOffset: 12 }, edited: { toolSize: 5, toolOffset: 11 }, parameter: 'toolOffsetX', beforeValue: 12, editedValue: 11 },
@@ -128,6 +135,48 @@ const pairSpecOverrides = {
'chamfer->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'chamfer->cut': { ...operationSpecs.cut, nominal: { toolSize: 4, toolOffset: 2 }, edited: { toolSize: 3, toolOffset: 2 }, beforeValue: 4, editedValue: 3 },
'chamfer->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'chamfer->rotate': { ...operationSpecs.rotate },
'chamfer->fillet': { ...operationSpecs.fillet, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'chamfer->chamfer': { ...operationSpecs.chamfer, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'chamfer->draft': { ...operationSpecs.draft, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'chamfer->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
'chamfer->mirrored': { ...operationSpecs.mirrored, nominal: { planeOriginX: 6 }, edited: { planeOriginX: 7 }, beforeValue: 6, editedValue: 7 },
'hole->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'hole->cut': { ...operationSpecs.cut, nominal: { toolSize: 4, toolOffset: 2 }, edited: { toolSize: 3, toolOffset: 2 }, beforeValue: 4, editedValue: 3 },
'hole->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'hole->rotate': { ...operationSpecs.rotate },
'hole->pad': { ...operationSpecs.pad, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'hole->pocket': { ...operationSpecs.pocket },
'hole->loft': { ...operationSpecs.loft, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted' },
'hole->pipe': { ...operationSpecs.pipe, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result' },
'hole->revolution': { ...operationSpecs.revolution, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'hole->groove': { ...operationSpecs.groove },
'hole->fillet': { ...operationSpecs.fillet },
'hole->chamfer': { ...operationSpecs.chamfer },
'hole->hole': { ...operationSpecs.hole, nominal: { radius: 1, depth: 14, position: [2, 2, -2], direction: [0, 0, 1] }, edited: { radius: 1.5 }, beforeValue: 1, editedValue: 1.5 },
'hole->draft': { ...operationSpecs.draft },
'hole->thickness': { ...operationSpecs.thickness },
'hole->mirrored': { ...operationSpecs.mirrored, nominal: { planeOriginX: 6 }, edited: { planeOriginX: 7 }, beforeValue: 6, editedValue: 7 },
'draft->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'draft->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'draft->pipe': { ...operationSpecs.pipe, kernelOutcome: 'accepted' },
'thickness->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'thickness->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'thickness->fillet': { ...operationSpecs.fillet, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'thickness->chamfer': { ...operationSpecs.chamfer, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'thickness->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
'linear-pattern->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 16 }, edited: { toolSize: 5, toolOffset: 15 }, beforeValue: 16, editedValue: 15 },
'linear-pattern->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
'polar-pattern->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'polar-pattern->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'polar-pattern->fillet': { ...operationSpecs.fillet, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'polar-pattern->chamfer': { ...operationSpecs.chamfer, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'polar-pattern->draft': { ...operationSpecs.draft, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'polar-pattern->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
'mirrored->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'mirrored->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'multi-transform->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 16 }, edited: { toolSize: 5, toolOffset: 15 }, beforeValue: 16, editedValue: 15 },
'multi-transform->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
}
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, profileWidth = 2, profileHeight = 3, profileX = 0.5, profileY = 2, profileZ = 0, ruled, spineLength, radius, distance, depth, position, direction, faceIndex, axisOrigin, neutralPlaneDirection, reversed, offset, intersectionJoin, translationX, planeOriginX, multiTranslationX, multiMirrorPlaneX = 5, stage }) => {
@@ -191,6 +240,14 @@ const captureRejectedStage = (module, definition) => {
const spec = operationSpecs[definition.operation]
const wasmAbortSignature = definition.stage.stageId.startsWith('pair:loft-fillet:second')
? 'table index is out of bounds'
: definition.stage.stageId.startsWith('pair:thickness-fillet:second')
? 'null function or function signature mismatch'
: definition.stage.stageId.startsWith('pair:chamfer-fillet:second')
? 'memory access out of bounds'
: definition.stage.stageId.startsWith('pair:chamfer-chamfer:second')
? 'memory access out of bounds'
: definition.stage.stageId.startsWith('pair:polar-pattern-fillet:second')
? 'null function or function signature mismatch'
: definition.stage.stageId.startsWith('pair:groove-chamfer:second')
? 'memory access out of bounds'
: definition.stage.stageId.startsWith('pair:groove-thickness:second')
@@ -245,7 +302,10 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
try {
const outcome = response.summary?.isValid === true ? 'accepted' : 'invalid-result'
const noHistorySuperset = expectedOutcome === 'accepted-no-history' && outcome === 'accepted'
const noOp = expectedOutcome === 'no-op' && outcome === 'accepted' && JSON.stringify(canonicalSummary(response.summary)) === JSON.stringify(definition.inputSummary)
const outputSummary = canonicalSummary(response.summary)
const exactNoOp = expectedOutcome === 'no-op' && outcome === 'accepted' && JSON.stringify(outputSummary) === JSON.stringify(definition.inputSummary)
const toleranceNoOp = expectedOutcome === 'no-op' && outcome === 'accepted' && summariesMatchWithinTolerance(outputSummary, definition.inputSummary)
const noOp = exactNoOp || toleranceNoOp
if (response.provider !== 'occt-native' || (!noHistorySuperset && !noOp && outcome !== expectedOutcome) || !response.resultStep?.startsWith('ISO-10303-21;') || (response.records?.length < 1 && !noHistorySuperset)) fail(`${definition.stage.stageId} OCCT compatibility probe returned an unexpected result: ${JSON.stringify({ provider: response.provider, outcome, expectedOutcome, summary: response.summary, inputSummary: definition.inputSummary, resultStep: Boolean(response.resultStep), records: response.records?.length, result: Boolean(response.result) })}`)
return {
operation: definition.operation,
@@ -260,10 +320,11 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
validShapeProduced: response.summary.isValid === true,
historyProduced: response.records.length > 0,
historyProvider: response.provider,
summary: canonicalSummary(response.summary),
summary: outputSummary,
historyRecords: response.records.length,
historySha256: sha256(JSON.stringify(response.records)),
authority: noOp ? 'kernel-no-op' : noHistorySuperset ? 'kernel-superset-no-history' : outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result',
...(!exactNoOp && toleranceNoOp ? { noOpComparison: 'topology-and-metrics-within-1e-6' } : {}),
}
} finally {
response.result?.delete?.()
@@ -351,7 +412,8 @@ const persistPair = async (pair, step, namingEvidence, decision) => {
if (execution.error || execution.status !== 0 || !payload) fail(`FreeCAD FCStd replay exited with ${execution.status}: ${execution.error?.message || output.trim()}`)
const persistence = JSON.parse(payload)
if (!persistence?.checks || typeof persistence.checks !== 'object') fail(`FreeCAD FCStd replay returned incomplete evidence: ${JSON.stringify(persistence)}`)
const checksPass = Object.values(persistence.checks).every(Boolean) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === true && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => key !== 'nativeFeatureRejected').every(([, value]) => value === true)) || (decision === 'rejected' && persistence.checks.freecadProfileNoOp === true && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => !['nativeFeatureRejected', 'freecadProfileAccepted'].includes(key)).every(([, value]) => value === true)) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === true && Object.entries(persistence.checks).filter(([key]) => key !== 'freecadProfileAccepted').every(([, value]) => value === true))
const noOpSourceBrepReserialized = persistence.checks.freecadProfileNoOp === true && persistence.checks.sourceShapeRestored === false && persistence.checks.sourceShapeSummaryRestored === true && persistence.checks.sourceShapeGeometricallyRestored === true && persistence.rollback?.sourceBrepReserialized === true
const checksPass = Object.values(persistence.checks).every(Boolean) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === true && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => key !== 'nativeFeatureRejected').every(([, value]) => value === true)) || (decision === 'rejected' && persistence.checks.freecadProfileNoOp === true && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => !['nativeFeatureRejected', 'freecadProfileAccepted', ...(noOpSourceBrepReserialized ? ['sourceShapeRestored'] : [])].includes(key)).every(([, value]) => value === true)) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === true && Object.entries(persistence.checks).filter(([key]) => key !== 'freecadProfileAccepted').every(([, value]) => value === true))
if (persistence.status !== 'pass' || persistence.decision !== decision || persistence.freecadVersion !== '1.1.1' || persistence.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !checksPass) fail(`FreeCAD FCStd replay failed: ${JSON.stringify(persistence)}`)
return persistence
} finally {
@@ -469,7 +531,57 @@ const classifyPair = async (fromOperation, toOperation, pairIndex) => {
const operations = ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform']
const orderedPairPrefix = [...operations.map((toOperation) => ['fuse', toOperation]), ['cut', 'fuse'], ['cut', 'cut'], ['cut', 'common'], ['cut', 'pad'], ['cut', 'pocket'], ['cut', 'loft'], ['cut', 'pipe'], ['cut', 'revolution'], ['cut', 'groove'], ['cut', 'fillet'], ['cut', 'chamfer'], ['cut', 'hole'], ['cut', 'draft'], ['cut', 'thickness'], ['cut', 'linear-pattern'], ['cut', 'polar-pattern'], ['cut', 'mirrored'], ['cut', 'multi-transform'], ['common', 'fuse'], ['common', 'cut'], ['common', 'common'], ['common', 'rotate'], ['common', 'pad'], ['common', 'pocket'], ['common', 'loft'], ['common', 'pipe'], ['common', 'revolution'], ['common', 'groove'], ['common', 'fillet'], ['common', 'chamfer'], ['common', 'hole'], ['common', 'draft'], ['common', 'thickness'], ['common', 'linear-pattern'], ['common', 'polar-pattern'], ['common', 'mirrored'], ['common', 'multi-transform'], ['rotate', 'fuse'], ['rotate', 'cut'], ['rotate', 'common'], ['rotate', 'rotate'], ['rotate', 'pad'], ['rotate', 'pocket'], ['rotate', 'loft'], ['rotate', 'pipe'], ['rotate', 'revolution'], ['rotate', 'groove'], ['rotate', 'chamfer'], ['rotate', 'hole'], ['rotate', 'draft'], ['rotate', 'thickness'], ['rotate', 'linear-pattern'], ['rotate', 'polar-pattern'], ['rotate', 'mirrored'], ['rotate', 'multi-transform'], ['pad', 'fuse'], ['pad', 'cut'], ['pad', 'common'], ['pad', 'rotate'], ['pad', 'pad'], ['pad', 'pocket'], ['pad', 'loft'], ['pad', 'pipe'], ['pad', 'revolution'], ['pad', 'groove'], ['pad', 'fillet'], ['pad', 'chamfer'], ['pad', 'hole'], ['pad', 'draft'], ['pad', 'thickness'], ['pad', 'linear-pattern'], ['pad', 'polar-pattern'], ['pad', 'mirrored'], ['pad', 'multi-transform'], ['pocket', 'fuse'], ['pocket', 'cut'], ['pocket', 'common'], ['pocket', 'rotate'], ['pocket', 'pad'], ['pocket', 'pocket'], ['pocket', 'loft'], ['pocket', 'pipe'], ['pocket', 'revolution'], ['pocket', 'groove'], ['pocket', 'fillet'], ['pocket', 'chamfer'], ['pocket', 'hole'], ['pocket', 'draft'], ['pocket', 'thickness'], ['pocket', 'linear-pattern'], ['pocket', 'polar-pattern'], ['pocket', 'mirrored'], ['pocket', 'multi-transform'], ['loft', 'fuse'], ['loft', 'cut'], ['loft', 'common'], ['loft', 'rotate'], ['loft', 'pad'], ['loft', 'pocket'], ['loft', 'loft'], ['loft', 'pipe'], ['loft', 'revolution'], ['loft', 'groove'], ['loft', 'fillet'], ['loft', 'chamfer'], ['loft', 'hole'], ['loft', 'draft'], ['loft', 'thickness'], ['loft', 'linear-pattern'], ['loft', 'polar-pattern'], ['loft', 'mirrored'], ['loft', 'multi-transform'], ['pipe', 'fuse'], ['pipe', 'cut'], ['pipe', 'common'], ['pipe', 'rotate'], ['pipe', 'pad'], ['pipe', 'pocket'], ['pipe', 'loft'], ['pipe', 'pipe'], ['pipe', 'revolution'], ['pipe', 'groove'], ['pipe', 'fillet'], ['pipe', 'chamfer'], ['pipe', 'hole'], ['pipe', 'draft'], ['pipe', 'thickness'], ['pipe', 'linear-pattern'], ['pipe', 'polar-pattern'], ['pipe', 'mirrored'], ['pipe', 'multi-transform'], ['revolution', 'fuse'], ['revolution', 'cut'], ['revolution', 'common'], ['revolution', 'rotate'], ['revolution', 'pad'], ['revolution', 'pocket'], ['revolution', 'loft'], ['revolution', 'pipe'], ['revolution', 'revolution'], ['revolution', 'groove'], ['revolution', 'fillet'], ['revolution', 'chamfer'], ['revolution', 'hole'], ['revolution', 'draft'], ['revolution', 'thickness'], ['revolution', 'linear-pattern'], ['revolution', 'polar-pattern'], ['revolution', 'mirrored'], ['revolution', 'multi-transform'], ['groove', 'fuse'], ['groove', 'cut'], ['groove', 'common'], ['groove', 'rotate'], ['groove', 'pad'], ['groove', 'pocket'], ['groove', 'loft'], ['groove', 'pipe'], ['groove', 'revolution'], ['groove', 'groove'], ['groove', 'fillet'], ['groove', 'chamfer'], ['groove', 'hole'], ['groove', 'draft'], ['groove', 'thickness'], ['groove', 'linear-pattern'], ['groove', 'polar-pattern'], ['groove', 'mirrored']]
orderedPairPrefix.push(['groove', 'multi-transform'], ['fillet', 'fuse'], ['fillet', 'cut'], ['fillet', 'common'], ['fillet', 'rotate'], ['fillet', 'pad'], ['fillet', 'pocket'], ['fillet', 'loft'], ['fillet', 'pipe'], ['fillet', 'revolution'], ['fillet', 'groove'], ['fillet', 'fillet'], ['fillet', 'chamfer'], ['fillet', 'hole'], ['fillet', 'draft'], ['fillet', 'thickness'], ['fillet', 'linear-pattern'], ['fillet', 'polar-pattern'], ['fillet', 'multi-transform'], ['chamfer', 'fuse'], ['chamfer', 'cut'], ['chamfer', 'common'])
orderedPairPrefix.push(['groove', 'multi-transform'], ['fillet', 'fuse'], ['fillet', 'cut'], ['fillet', 'common'], ['fillet', 'rotate'], ['fillet', 'pad'], ['fillet', 'pocket'], ['fillet', 'loft'], ['fillet', 'pipe'], ['fillet', 'revolution'], ['fillet', 'groove'], ['fillet', 'fillet'], ['fillet', 'chamfer'], ['fillet', 'hole'], ['fillet', 'draft'], ['fillet', 'thickness'], ['fillet', 'linear-pattern'], ['fillet', 'polar-pattern'], ['fillet', 'multi-transform'], ['chamfer', 'fuse'], ['chamfer', 'cut'], ['chamfer', 'common'], ['chamfer', 'rotate'], ['chamfer', 'pad'], ['chamfer', 'pocket'], ['chamfer', 'loft'], ['chamfer', 'pipe'], ['chamfer', 'revolution'], ['chamfer', 'groove'], ['chamfer', 'fillet'], ['chamfer', 'chamfer'], ['chamfer', 'hole'], ['chamfer', 'draft'], ['chamfer', 'thickness'], ['chamfer', 'linear-pattern'], ['chamfer', 'polar-pattern'], ['chamfer', 'mirrored'], ['chamfer', 'multi-transform'], ['hole', 'fuse'], ['hole', 'cut'], ['hole', 'common'], ['hole', 'rotate'], ['hole', 'pad'], ['hole', 'pocket'], ['hole', 'loft'], ['hole', 'pipe'], ['hole', 'revolution'], ['hole', 'groove'], ['hole', 'fillet'], ['hole', 'chamfer'], ['hole', 'hole'], ['hole', 'draft'], ['hole', 'thickness'], ['hole', 'linear-pattern'], ['hole', 'polar-pattern'], ['hole', 'mirrored'], ['hole', 'multi-transform'], ['draft', 'fuse'], ['draft', 'cut'], ['draft', 'common'], ['draft', 'rotate'], ['draft', 'pad'], ['draft', 'pocket'], ['draft', 'loft'], ['draft', 'pipe'], ['draft', 'revolution'], ['draft', 'groove'], ['draft', 'fillet'], ['draft', 'chamfer'], ['draft', 'hole'], ['draft', 'draft'], ['draft', 'thickness'], ['draft', 'linear-pattern'], ['draft', 'polar-pattern'], ['draft', 'mirrored'], ['draft', 'multi-transform'], ['thickness', 'fuse'], ['thickness', 'cut'], ['thickness', 'common'], ['thickness', 'rotate'], ['thickness', 'pad'], ['thickness', 'pocket'], ['thickness', 'loft'], ['thickness', 'pipe'], ['thickness', 'revolution'], ['thickness', 'groove'], ['thickness', 'fillet'], ['thickness', 'chamfer'], ['thickness', 'hole'], ['thickness', 'draft'], ['thickness', 'thickness'], ['thickness', 'linear-pattern'], ['thickness', 'polar-pattern'], ['thickness', 'mirrored'], ['thickness', 'multi-transform'], ['linear-pattern', 'fuse'], ['linear-pattern', 'cut'], ['linear-pattern', 'common'], ['linear-pattern', 'rotate'], ['linear-pattern', 'pad'], ['linear-pattern', 'pocket'], ['linear-pattern', 'loft'], ['linear-pattern', 'pipe'], ['linear-pattern', 'revolution'], ['linear-pattern', 'groove'], ['linear-pattern', 'fillet'], ['linear-pattern', 'chamfer'], ['linear-pattern', 'hole'], ['linear-pattern', 'draft'], ['linear-pattern', 'thickness'], ['linear-pattern', 'linear-pattern'], ['linear-pattern', 'polar-pattern'], ['linear-pattern', 'mirrored'], ['linear-pattern', 'multi-transform'], ['polar-pattern', 'fuse'], ['polar-pattern', 'cut'], ['polar-pattern', 'common'], ['polar-pattern', 'rotate'], ['polar-pattern', 'pad'], ['polar-pattern', 'pocket'])
orderedPairPrefix.push(['polar-pattern', 'loft'])
orderedPairPrefix.push(['polar-pattern', 'pipe'])
orderedPairPrefix.push(['polar-pattern', 'revolution'])
orderedPairPrefix.push(['polar-pattern', 'groove'])
orderedPairPrefix.push(['polar-pattern', 'fillet'])
orderedPairPrefix.push(['polar-pattern', 'chamfer'])
orderedPairPrefix.push(['polar-pattern', 'hole'])
orderedPairPrefix.push(['polar-pattern', 'draft'])
orderedPairPrefix.push(['polar-pattern', 'thickness'])
orderedPairPrefix.push(['polar-pattern', 'linear-pattern'])
orderedPairPrefix.push(['polar-pattern', 'polar-pattern'])
orderedPairPrefix.push(['polar-pattern', 'mirrored'])
orderedPairPrefix.push(['polar-pattern', 'multi-transform'])
orderedPairPrefix.push(['mirrored', 'fuse'])
orderedPairPrefix.push(['mirrored', 'cut'])
orderedPairPrefix.push(['mirrored', 'common'])
orderedPairPrefix.push(['mirrored', 'rotate'])
orderedPairPrefix.push(['mirrored', 'pad'])
orderedPairPrefix.push(['mirrored', 'pocket'])
orderedPairPrefix.push(['mirrored', 'loft'])
orderedPairPrefix.push(['mirrored', 'pipe'])
orderedPairPrefix.push(['mirrored', 'revolution'])
orderedPairPrefix.push(['mirrored', 'groove'])
orderedPairPrefix.push(['mirrored', 'fillet'])
orderedPairPrefix.push(['mirrored', 'chamfer'])
orderedPairPrefix.push(['mirrored', 'hole'])
orderedPairPrefix.push(['mirrored', 'draft'])
orderedPairPrefix.push(['mirrored', 'thickness'])
orderedPairPrefix.push(['mirrored', 'polar-pattern'])
orderedPairPrefix.push(['mirrored', 'mirrored'])
orderedPairPrefix.push(['mirrored', 'multi-transform'])
orderedPairPrefix.push(['multi-transform', 'fuse'])
orderedPairPrefix.push(['multi-transform', 'cut'])
orderedPairPrefix.push(['multi-transform', 'common'])
orderedPairPrefix.push(['multi-transform', 'rotate'])
orderedPairPrefix.push(['multi-transform', 'pad'])
orderedPairPrefix.push(['multi-transform', 'pocket'])
orderedPairPrefix.push(['multi-transform', 'loft'])
orderedPairPrefix.push(['multi-transform', 'pipe'])
orderedPairPrefix.push(['multi-transform', 'revolution'])
orderedPairPrefix.push(['multi-transform', 'groove'])
orderedPairPrefix.push(['multi-transform', 'fillet'])
orderedPairPrefix.push(['multi-transform', 'chamfer'])
orderedPairPrefix.push(['multi-transform', 'hole'])
orderedPairPrefix.push(['multi-transform', 'draft'])
orderedPairPrefix.push(['multi-transform', 'thickness'])
orderedPairPrefix.push(['multi-transform', 'linear-pattern'])
orderedPairPrefix.push(['multi-transform', 'polar-pattern'])
orderedPairPrefix.push(['multi-transform', 'mirrored'])
orderedPairPrefix.push(['multi-transform', 'multi-transform'])
const classifications = []
const selectedPairs = process.env.FREECAD_ONLY_PAIR ? orderedPairPrefix.filter(([fromOperation, toOperation]) => `${fromOperation}->${toOperation}` === process.env.FREECAD_ONLY_PAIR) : orderedPairPrefix
if (process.env.FREECAD_ONLY_PAIR && selectedPairs.length !== 1) fail(`focused pair ${process.env.FREECAD_ONLY_PAIR} is not in the classified prefix.`)