feat: propagate feature suppression in recompute
This commit is contained in:
@@ -8,7 +8,7 @@ import { DependencyGraph } from '../src/facade/dependencyGraph'
|
||||
import { evaluateQuantityExpression, quantityFromNumber } from '../src/facade/units'
|
||||
import { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from '../src/facade/topologyNaming'
|
||||
import { createSketch, solveSketch } from '../src/facade/sketcher'
|
||||
import { createFacadeGeometryRecomputeExecutor, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
|
||||
import { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
|
||||
import { inspectFcstdArchive } from '../src/facade/fcstd'
|
||||
import { assertShapeHandleIntegrity, normalizeBitbybitMesh, validateBooleanUnionInput, validateBoxInput, validateChamferInput, validateConeInput, validateCylinderInput, validateFilletInput, validatePadInput, validatePlacementInput, validatePlanarProfile, validateRevolutionInput, validateSphereInput } from '../src/facade/geometryRuntime'
|
||||
import type { DocumentSnapshot, ShapeHandle } from '../src/facade/types'
|
||||
@@ -348,6 +348,25 @@ test('recompute failure marks dependent objects as upstream-failed', async () =>
|
||||
assert.ok(result.errors.some((error) => error.code === 'UPSTREAM_FAILED'))
|
||||
})
|
||||
|
||||
test('recompute suppression skips dependent nodes without reporting a failure', async () => {
|
||||
const document = recomputeDocumentFixture([{ sourceId: 'child', targetId: 'root', relation: 'link' }])
|
||||
document.objects[0].properties.push({ name: 'Suppressed', label: 'Suppressed', group: 'Feature state', scope: 'data', type: 'App::PropertyBool', value: true, recompute: true })
|
||||
const executed: string[] = []
|
||||
const coordinator = new RecomputeCoordinator(async (object, source, context) => {
|
||||
executed.push(object.id)
|
||||
return executeFacadeRecomputeNode(object, source, context)
|
||||
}, () => 1)
|
||||
const result = await coordinator.run(document)
|
||||
assert.equal(result.status, 'completed')
|
||||
assert.deepEqual(executed, ['root'])
|
||||
assert.deepEqual(result.suppressed, ['root'])
|
||||
assert.deepEqual(result.skipped, ['root', 'child'])
|
||||
assert.equal(result.objectStates.root, 'suppressed')
|
||||
assert.equal(result.objectStates.child, 'upstream-suppressed')
|
||||
assert.deepEqual(result.dirtyObjects, [])
|
||||
assert.deepEqual(result.errors, [])
|
||||
})
|
||||
|
||||
test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains the last valid Shape', async () => {
|
||||
const shape = (id: string): ShapeHandle => ({ id, kernel: 'bitbybit-occt', kind: 'solid', documentId: 'doc-feature', documentVersion: 2 })
|
||||
const calls: string[] = []
|
||||
@@ -417,6 +436,12 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
|
||||
assert.equal(revolutionResult.status, 'success')
|
||||
assert.equal(shapes.get('revolution')?.id, 'revolution-shape')
|
||||
assert.ok(calls.includes('revolution:270:-1'))
|
||||
|
||||
const suppressedPad = { ...pad, properties: [...pad.properties, { name: 'Suppressed', label: 'Suppressed', group: 'Feature state', scope: 'data' as const, type: 'App::PropertyBool' as const, value: true }] }
|
||||
const suppressedResult = await executor(suppressedPad, document, context)
|
||||
assert.equal(suppressedResult.status, 'suppressed')
|
||||
assert.equal(shapes.has('pad'), false)
|
||||
assert.ok(calls.includes('release:pad-shape'))
|
||||
})
|
||||
|
||||
test('Part primitive and boolean nodes execute through the same Shape cache', async () => {
|
||||
@@ -475,6 +500,28 @@ test('facade async recompute commits only an accepted generation', async () => {
|
||||
assert.equal(facade.app.document.getActive().recompute?.dirtyObjects.length, 0)
|
||||
})
|
||||
|
||||
test('feature suppression persists and unsuppress recomputes only its dependent closure', async () => {
|
||||
const facade = createMockFacade()
|
||||
facade.app.document.setProperty({ objectId: 'pocket', propertyName: 'Suppressed', value: true })
|
||||
const suppressed = facade.app.document.recompute()
|
||||
assert.equal(suppressed.status, 'completed')
|
||||
assert.deepEqual(suppressed.order, ['pocket', 'fillet', 'body'])
|
||||
assert.deepEqual(new Set(suppressed.affected), new Set(['pocket', 'fillet', 'body']))
|
||||
assert.equal(facade.app.document.getActive().recompute?.objectStates.pocket, 'suppressed')
|
||||
assert.equal(facade.app.document.getActive().recompute?.objectStates.fillet, 'upstream-suppressed')
|
||||
|
||||
await facade.project.save()
|
||||
assert.equal((await facade.project.load('doc-pump-housing'))?.objects.find((object) => object.id === 'pocket')?.properties.find((property) => property.name === 'Suppressed')?.value, true)
|
||||
|
||||
facade.app.document.setProperty({ objectId: 'pocket', propertyName: 'Suppressed', value: false })
|
||||
const restored = await facade.app.document.recomputeAsync()
|
||||
assert.equal(restored.status, 'completed')
|
||||
assert.deepEqual(restored.order, ['pocket', 'fillet', 'body'])
|
||||
assert.deepEqual(new Set(restored.affected), new Set(['pocket', 'fillet', 'body']))
|
||||
assert.equal(facade.app.document.getActive().recompute?.objectStates.pocket, 'up-to-date')
|
||||
assert.equal(facade.app.document.getActive().recompute?.objectStates.fillet, 'up-to-date')
|
||||
})
|
||||
|
||||
test('persistence writes are serialized and continue after a failed write', async () => {
|
||||
const queue = new PersistenceWriteQueue()
|
||||
let inFlight = 0
|
||||
|
||||
Reference in New Issue
Block a user