feat: extend ordered pair evidence through common
This commit is contained in:
@@ -22,16 +22,66 @@ const expectedPairs = [
|
||||
{ pair: 'fuse->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
|
||||
{ pair: 'fuse->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [10, 9, 10] },
|
||||
{ pair: 'fuse->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
|
||||
{ pair: 'fuse->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad' },
|
||||
{ pair: 'fuse->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
|
||||
{ pair: 'fuse->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
|
||||
{ pair: 'fuse->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
|
||||
{ pair: 'fuse->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
|
||||
{ pair: 'fuse->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
|
||||
{ pair: 'fuse->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
|
||||
{ pair: 'fuse->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'fuse->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'fuse->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
|
||||
{ pair: 'fuse->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
|
||||
{ pair: 'fuse->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
|
||||
{ pair: 'fuse->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
|
||||
{ pair: 'fuse->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
|
||||
{ pair: 'fuse->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
|
||||
{ pair: 'fuse->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
|
||||
{ pair: 'cut->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 9, 8] },
|
||||
{ pair: 'cut->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [3, 4, 3] },
|
||||
{ pair: 'cut->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [4, 5, 4] },
|
||||
{ pair: 'cut->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
|
||||
{ pair: 'cut->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
|
||||
{ pair: 'cut->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
|
||||
{ pair: 'cut->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
|
||||
{ pair: 'cut->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
|
||||
{ pair: 'cut->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
|
||||
{ pair: 'cut->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'cut->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'cut->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
|
||||
{ pair: 'cut->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
|
||||
{ pair: 'cut->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
|
||||
{ pair: 'cut->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
|
||||
{ pair: 'cut->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
|
||||
{ pair: 'cut->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
|
||||
{ pair: 'cut->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
|
||||
{ pair: 'common->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
|
||||
{ pair: 'common->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
|
||||
{ pair: 'common->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [7, 6, 7] },
|
||||
{ pair: 'common->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
|
||||
{ pair: 'common->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
|
||||
{ pair: 'common->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
|
||||
{ pair: 'common->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
|
||||
{ pair: 'common->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
|
||||
{ pair: 'common->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
|
||||
{ pair: 'common->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
|
||||
{ pair: 'common->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'common->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
|
||||
{ pair: 'common->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
|
||||
{ pair: 'common->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
|
||||
{ pair: 'common->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
|
||||
{ pair: 'common->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [4, 3, 4] },
|
||||
{ pair: 'common->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
|
||||
{ pair: 'common->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
|
||||
{ pair: 'common->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepAlgoAPI_Common', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [4, 3, 4] },
|
||||
]
|
||||
if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.')
|
||||
for (const [index, entry] of report.classifications.entries()) {
|
||||
const expected = expectedPairs[index]
|
||||
const secondOperation = expected.pair.split('->')[1]
|
||||
if (entry.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== expected.decision || entry.reasonCode !== expected.reasonCode || entry.nativeDecision !== expected.decision || entry.first?.operation !== 'fuse' || entry.first.builder !== 'BRepAlgoAPI_Fuse' || entry.first.inputCount !== 2 || entry.first.historyProvider !== 'occt-native' || entry.first.summary?.isValid !== true || entry.first.historyRecords < 1) fail(`${expected.pair} decision contract is invalid.`)
|
||||
const [firstOperation, secondOperation] = expected.pair.split('->')
|
||||
const firstBuilder = expected.firstBuilder ?? 'BRepAlgoAPI_Fuse'
|
||||
const firstInputCount = expected.firstInputCount ?? 2
|
||||
if (entry.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== expected.decision || entry.reasonCode !== expected.reasonCode || entry.nativeDecision !== expected.decision || entry.first?.operation !== firstOperation || entry.first.builder !== firstBuilder || entry.first.inputCount !== firstInputCount || entry.first.historyProvider !== 'occt-native' || entry.first.summary?.isValid !== true || entry.first.historyRecords < 1) fail(`${expected.pair} decision contract is invalid.`)
|
||||
if (expected.decision === 'rejected') {
|
||||
if ('second' in entry || 'mutation' in entry || 'naming' in entry) fail(`${expected.pair} fabricates evidence for a result that FreeCAD rejected.`)
|
||||
const rejection = entry.rejection
|
||||
@@ -50,10 +100,10 @@ for (const [index, entry] of report.classifications.entries()) {
|
||||
const diagnostic = persistence?.diagnostic
|
||||
if (persistence?.status !== 'pass' || persistence.decision !== 'rejected' || persistence.pair !== entry.pair || persistence.operation !== secondOperation || persistence.nativeTypeId !== expected.nativeTypeId || persistence.freecadVersion !== '1.1.1' || !Object.values(persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd rejection evidence is incomplete.`)
|
||||
if (diagnostic?.nativeTypeId !== expected.nativeTypeId || diagnostic.parameter?.name !== expected.mutationParameter || diagnostic.parameter.value !== expected.mutationTrajectory[0] || diagnostic.inputShape?.solids !== 1 || diagnostic.recomputeResult !== true || diagnostic.shapeNull !== true || diagnostic.shapeValid !== false || !diagnostic.state?.includes('Invalid') || diagnostic.statusString === 'Valid' || !diagnostic.objects?.some(({ name, typeId }) => name === 'PairResult' && typeId === expected.nativeTypeId)) fail(`${expected.pair} did not capture a real rejected FreeCAD recompute.`)
|
||||
if (expected.rejectionAuthority === 'freecad-document' && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
|
||||
if (expected.freeCadDiagnostic && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
|
||||
if (persistence.source?.shape?.solids !== 1 || !persistence.source.shape.brepSha256 || JSON.stringify(persistence.rollback?.before) !== JSON.stringify(persistence.rollback?.after)) fail(`${expected.pair} abort did not restore the source object set and Shape.`)
|
||||
const phases = persistence.phases
|
||||
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`)
|
||||
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft' || typeId === 'Part::Revolution')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`)
|
||||
continue
|
||||
}
|
||||
if (entry.second?.operation !== secondOperation || entry.second.builder !== expected.secondBuilder || entry.second.inputCount !== expected.secondInputCount || entry.second.historyProvider !== 'occt-native') fail(`${expected.pair} accepted second operation is invalid.`)
|
||||
@@ -66,6 +116,6 @@ for (const [index, entry] of report.classifications.entries()) {
|
||||
const phases = entry.persistence.phases
|
||||
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.namingEvidenceSha256 !== entry.second.namingEvidenceSha256) fail(`${expected.pair} FCStd phases changed Shape or naming evidence.`)
|
||||
}
|
||||
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 8, accepted: 5, rejected: 3, unknown: 353 }
|
||||
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 56, accepted: 44, rejected: 12, unknown: 305 }
|
||||
if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.')
|
||||
console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-pass', completedTasks: report.classifications.map(({ taskId }) => taskId), pairs: report.classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: report.classifications.length * 4, fcstdPhases: report.classifications.length * 3, remainingPairs: report.summary.unknown }, null, 2))
|
||||
|
||||
@@ -205,6 +205,15 @@ def collect_rejected():
|
||||
"sections": [{"name": item.Name, "typeId": item.TypeId} for item in feature.Sections],
|
||||
"spine": link_snapshot(feature.Spine),
|
||||
}
|
||||
elif operation == "revolution":
|
||||
feature = document.addObject("Part::Revolution", "PairResult")
|
||||
feature.Source = source
|
||||
feature.Axis = App.Vector(0.0, 1.0, 0.0)
|
||||
feature.Base = App.Vector(-1.0, 0.0, 0.0)
|
||||
feature.Angle = 360.0
|
||||
feature.Solid = True
|
||||
diagnostic["parameter"] = {"name": "angle", "value": round(float(feature.Angle), 7)}
|
||||
diagnostic["input"] = link_snapshot(feature.Source)
|
||||
else:
|
||||
raise RuntimeError("Unsupported rejected ordered-pair operation: " + operation)
|
||||
diagnostic["nativeTypeId"] = feature.TypeId
|
||||
@@ -248,8 +257,8 @@ def collect_rejected():
|
||||
or "Error" in diagnostic["state"]
|
||||
or "Invalid" in diagnostic["state"]
|
||||
)
|
||||
expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep"}[operation]
|
||||
input_names = [diagnostic.get("input", {}).get("name")] if operation == "pad" else [item["name"] for item in diagnostic.get("input", {}).get("sections", [])]
|
||||
expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep", "revolution": "Part::Revolution"}[operation]
|
||||
input_names = [diagnostic.get("input", {}).get("name")] if operation in ("pad", "revolution") else [item["name"] for item in diagnostic.get("input", {}).get("sections", [])]
|
||||
checks = {
|
||||
"nativeFeatureConstructed": diagnostic.get("nativeTypeId") == expected_type_id,
|
||||
"fuseSolidUsedAsInput": source_name in input_names and diagnostic.get("inputShape", {}).get("solids") == 1,
|
||||
|
||||
@@ -54,12 +54,55 @@ const operationSpecs = {
|
||||
pocket: { builder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', nominal: { length: 5 }, edited: { length: 4 }, parameter: 'length', beforeValue: 5, editedValue: 4 },
|
||||
loft: { builder: 'BRepOffsetAPI_ThruSections', nominal: { ruled: false }, edited: { ruled: true }, parameter: 'ruled', beforeValue: false, editedValue: true, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted' },
|
||||
pipe: { builder: 'BRepOffsetAPI_MakePipe', nominal: { spineLength: 15 }, edited: { spineLength: 12 }, parameter: 'spineLength', beforeValue: 15, editedValue: 12, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result' },
|
||||
revolution: { builder: 'BRepPrimAPI_MakeRevol', nominal: { angle: 360 }, edited: { angle: 270 }, parameter: 'angle', beforeValue: 360, editedValue: 270, decision: 'rejected' },
|
||||
groove: { builder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', nominal: { angle: 360 }, edited: { angle: 180 }, parameter: 'angle', beforeValue: 360, editedValue: 180 },
|
||||
fillet: { builder: 'BRepFilletAPI_MakeFillet', nominal: { radius: 0.4 }, edited: { radius: 0.6 }, parameter: 'radius', beforeValue: 0.4, editedValue: 0.6 },
|
||||
chamfer: { builder: 'BRepFilletAPI_MakeChamfer', nominal: { distance: 0.4 }, edited: { distance: 0.6 }, parameter: 'distance', beforeValue: 0.4, editedValue: 0.6 },
|
||||
hole: { builder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', nominal: { radius: 1, depth: 14, position: [5, 5, -2], direction: [0, 0, 1] }, edited: { radius: 1.5 }, parameter: 'radius', beforeValue: 1, editedValue: 1.5 },
|
||||
draft: { builder: 'BRepOffsetAPI_DraftAngle', nominal: { faceIndex: 0, angle: 5, direction: [0, 0, 1], axisOrigin: [0, 0, 0], neutralPlaneDirection: [0, 0, 1], reversed: false }, edited: { angle: 8 }, parameter: 'angle', beforeValue: 5, editedValue: 8 },
|
||||
thickness: { builder: 'BRepOffsetAPI_MakeThickSolid', nominal: { faceIndex: 1, offset: -0.4, intersectionJoin: false }, edited: { offset: -0.6 }, parameter: 'offset', beforeValue: -0.4, editedValue: -0.6 },
|
||||
'linear-pattern': { builder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', nominal: { translationX: 8 }, edited: { translationX: 9 }, parameter: 'translationX', beforeValue: 8, editedValue: 9 },
|
||||
'polar-pattern': { builder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', nominal: { angle: 30, axisOrigin: [0, 0, 0], direction: [0, 0, 1] }, edited: { angle: 45 }, parameter: 'angle', beforeValue: 30, editedValue: 45 },
|
||||
mirrored: { builder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', nominal: { planeOriginX: 5 }, edited: { planeOriginX: 6 }, parameter: 'planeOriginX', beforeValue: 5, editedValue: 6 },
|
||||
'multi-transform': { builder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', nominal: { multiTranslationX: 8 }, edited: { multiTranslationX: 9 }, parameter: 'multiTranslationX', beforeValue: 8, editedValue: 9 },
|
||||
}
|
||||
const pairSpecOverrides = {
|
||||
'cut->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 9 }, beforeValue: 8, editedValue: 9 },
|
||||
'cut->cut': { ...operationSpecs.cut, nominal: { toolSize: 3, toolOffset: 6 }, edited: { toolSize: 4, toolOffset: 6 }, beforeValue: 3, editedValue: 4 },
|
||||
'cut->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 4 }, edited: { toolSize: 5, toolOffset: 5 }, beforeValue: 4, editedValue: 5 },
|
||||
'common->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
|
||||
'common->common': { ...operationSpecs.common, nominal: { toolSize: 4, toolOffset: 7 }, edited: { toolSize: 4, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
|
||||
'common->linear-pattern': { ...operationSpecs['linear-pattern'], nominal: { translationX: 4 }, edited: { translationX: 3 }, beforeValue: 4, editedValue: 3 },
|
||||
'common->multi-transform': { ...operationSpecs['multi-transform'], nominal: { multiTranslationX: 4 }, edited: { multiTranslationX: 3 }, beforeValue: 4, editedValue: 3 },
|
||||
}
|
||||
|
||||
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, ruled, spineLength, stage }) => {
|
||||
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, ruled, spineLength, radius, distance, depth, position, direction, faceIndex, axisOrigin, neutralPlaneDirection, reversed, offset, intersectionJoin, translationX, planeOriginX, multiTranslationX, stage }) => {
|
||||
const baseStep = objectStep ?? shapeStep(module, 'makeBox', 10, 10, 10)
|
||||
const objectInput = input('object', 'object', stage.objectId, stage.objectTag, baseStep, stage.namingEvidence)
|
||||
if (operation === 'rotate') return { response: module.rotateHistoryFromStep(baseStep, 0, 0, 0, 0, 0, 1, angle), inputs: [objectInput] }
|
||||
if (operation === 'revolution') return { response: module.revolutionHistoryFromStep(baseStep, -1, 0, 0, 0, 1, 0, angle), inputs: [objectInput] }
|
||||
if (operation === 'fillet') return { response: module.filletHistoryFromStep(baseStep, radius), inputs: [objectInput] }
|
||||
if (operation === 'chamfer') return { response: module.chamferHistoryFromStep(baseStep, distance), inputs: [objectInput] }
|
||||
if (operation === 'hole') return { response: module.holeHistoryFromStep(baseStep, radius, depth, ...position, ...direction), inputs: [objectInput] }
|
||||
if (operation === 'draft') return { response: module.draftHistoryFromStep(baseStep, faceIndex, angle, ...direction, ...axisOrigin, ...neutralPlaneDirection, reversed), inputs: [objectInput] }
|
||||
if (operation === 'thickness') return { response: module.thicknessHistoryFromStep(baseStep, faceIndex, offset, intersectionJoin), inputs: [objectInput] }
|
||||
if (operation === 'linear-pattern') return { response: module.linearPatternHistoryFromStep(baseStep, translationX, 0, 0), inputs: [objectInput] }
|
||||
if (operation === 'polar-pattern') return { response: module.polarPatternHistoryFromStep(baseStep, ...axisOrigin, ...direction, angle), inputs: [objectInput] }
|
||||
if (operation === 'mirrored') return { response: module.mirroredHistoryFromStep(baseStep, planeOriginX, 0, 0, 1, 0, 0), inputs: [objectInput] }
|
||||
if (operation === 'multi-transform') return {
|
||||
response: module.multiTransformHistoryFromStep(baseStep, [
|
||||
{ type: 'linear', direction: [multiTranslationX, 0, 0] },
|
||||
{ type: 'mirrored', axisOrigin: [5, 0, 0], direction: [1, 0, 0] },
|
||||
]),
|
||||
inputs: [objectInput],
|
||||
}
|
||||
if (operation === 'groove') {
|
||||
const profileStep = shapeStep(module, 'makeRectangleFacePlaced', 1, 3, 0.5, 2, 0)
|
||||
return {
|
||||
response: module.grooveHistoryFromStep(baseStep, profileStep, 0, 0, 0, 0, 1, 0, angle),
|
||||
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:profile`, stage.objectTag + 1, profileStep)],
|
||||
}
|
||||
}
|
||||
if (operation === 'pad') return { response: module.prismHistoryFromStep(baseStep, 0, 0, length), inputs: [objectInput] }
|
||||
if (operation === 'pocket') {
|
||||
const profileStep = shapeStep(module, 'makeRectangleFace', 2, 3)
|
||||
@@ -91,16 +134,17 @@ const createOperation = (module, { operation, objectStep, toolSize, toolOffset,
|
||||
}
|
||||
|
||||
const captureRejectedStage = (module, definition) => {
|
||||
const spec = operationSpecs[definition.operation]
|
||||
let capture
|
||||
try {
|
||||
capture = createOperation(module, definition)
|
||||
} catch (error) {
|
||||
return {
|
||||
operation: definition.operation,
|
||||
builder: operationSpecs[definition.operation].builder,
|
||||
builder: spec.builder,
|
||||
inputCount: 1,
|
||||
parameter: operationSpecs[definition.operation].parameter,
|
||||
parameterValue: definition.length,
|
||||
parameter: spec.parameter,
|
||||
parameterValue: definition[spec.parameter],
|
||||
runtime: 'occt-native',
|
||||
outcome: 'rejected',
|
||||
resultProduced: false,
|
||||
@@ -232,14 +276,27 @@ const persistPair = async (pair, step, namingEvidence, decision) => {
|
||||
}
|
||||
}
|
||||
|
||||
const classifyFusePair = async (toOperation, pairIndex) => {
|
||||
const pair = `fuse->${toOperation}`
|
||||
const classifyPair = async (fromOperation, toOperation, pairIndex) => {
|
||||
const pair = `${fromOperation}->${toOperation}`
|
||||
const pairId = pair.replace('->', '-')
|
||||
const firstParameters = pair === 'cut->thickness'
|
||||
? { toolSize: 10, toolOffset: 5 }
|
||||
: ['common->pocket', 'common->groove'].includes(pair)
|
||||
? { toolSize: 5, toolOffset: 0 }
|
||||
: fromOperation === 'fuse'
|
||||
? toOperation === 'thickness'
|
||||
? { toolSize: 10, toolOffset: 0 }
|
||||
: ['fillet', 'chamfer'].includes(toOperation)
|
||||
? { toolSize: 10, toolOffset: 5 }
|
||||
: { toolSize: 5, toolOffset: 8 }
|
||||
: fromOperation === 'common'
|
||||
? { toolSize: 5, toolOffset: 4 }
|
||||
: operationSpecs[fromOperation].nominal
|
||||
const first = captureStage(module, {
|
||||
operation: 'fuse', toolSize: 5, toolOffset: 8,
|
||||
operation: fromOperation, ...firstParameters,
|
||||
stage: { stageId: `pair:${pairId}:first`, ordinal: 0, objectId: `pair:${pairId}:base`, objectTag: 1001 + pairIndex * 100, resultObjectId: `pair:${pairId}:first`, resultObjectTag: 1101 + pairIndex * 100 },
|
||||
})
|
||||
const spec = operationSpecs[toOperation]
|
||||
const spec = pairSpecOverrides[pair] ?? operationSpecs[toOperation]
|
||||
const secondDefinition = {
|
||||
operation: toOperation,
|
||||
objectStep: first.response.resultStep,
|
||||
@@ -255,7 +312,7 @@ const classifyFusePair = async (toOperation, pairIndex) => {
|
||||
if (!kernelRestoredExactly) fail(`${pair} OCCT diagnostic probe did not restore exactly.`)
|
||||
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
|
||||
return {
|
||||
taskId: `TSN-PAIR-fuse-${toOperation}`,
|
||||
taskId: `TSN-PAIR-${pairId}`,
|
||||
pair,
|
||||
classification: 'rejected',
|
||||
reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave',
|
||||
@@ -264,12 +321,12 @@ const classifyFusePair = async (toOperation, pairIndex) => {
|
||||
rejection: { scope: 'second-operation-only', authority: 'freecad-document', parameter: spec.parameter, trajectory, kernelAttempts, kernelRestoredExactly, persistence },
|
||||
}
|
||||
}
|
||||
const attempts = trajectory.map((length) => captureRejectedStage(module, { ...secondDefinition, length }))
|
||||
const attempts = trajectory.map((value) => captureRejectedStage(module, { ...secondDefinition, [spec.parameter]: value }))
|
||||
const stableRejection = attempts.every((attempt) => rejectionSignature(attempt) === rejectionSignature(attempts[0]))
|
||||
if (!stableRejection) fail(`${pair} rejection changed across its parameter trajectory.`)
|
||||
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
|
||||
return {
|
||||
taskId: `TSN-PAIR-fuse-${toOperation}`,
|
||||
taskId: `TSN-PAIR-${pairId}`,
|
||||
pair,
|
||||
classification: 'rejected',
|
||||
reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave',
|
||||
@@ -290,7 +347,7 @@ const classifyFusePair = async (toOperation, pairIndex) => {
|
||||
if (!mutationChanged || !restoredExactly) fail(`${pair} mutation did not change and restore native geometry/history.`)
|
||||
const persistence = await persistPair(pair, second.response.resultStep, second.namingEvidence, 'accepted')
|
||||
return {
|
||||
taskId: `TSN-PAIR-fuse-${toOperation}`,
|
||||
taskId: `TSN-PAIR-${pairId}`,
|
||||
pair,
|
||||
classification: 'accepted',
|
||||
reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass',
|
||||
@@ -306,8 +363,10 @@ const classifyFusePair = async (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']]
|
||||
const classifications = []
|
||||
for (const [index, operation] of ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe'].entries()) classifications.push(await classifyFusePair(operation, index))
|
||||
for (const [index, [fromOperation, toOperation]] of orderedPairPrefix.entries()) classifications.push(await classifyPair(fromOperation, toOperation, index))
|
||||
|
||||
const [artifacts, executorHarness, matrixHarness, resaveHarness] = await Promise.all([
|
||||
Promise.all(artifactNames.map(async (name) => {
|
||||
|
||||
Reference in New Issue
Block a user