feat: connect Part primitives and booleans
This commit is contained in:
@@ -351,6 +351,13 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
|
||||
let padShouldFail = false
|
||||
const runtime: RecomputeGeometryRuntime = {
|
||||
capabilities: () => ({ status: 'ready' }),
|
||||
createBox: async () => shape('box-shape'),
|
||||
createCylinder: async () => shape('cylinder-shape'),
|
||||
createSphere: async () => shape('sphere-shape'),
|
||||
createCone: async () => shape('cone-shape'),
|
||||
union: async () => shape('union-shape'),
|
||||
cut: async () => shape('cut-shape'),
|
||||
intersection: async () => shape('intersection-shape'),
|
||||
pad: async (input) => { calls.push(`pad:${input.profile.outer.length}`); if (padShouldFail) throw new Error('OCCT pad failed'); return shape('pad-shape') },
|
||||
pocket: async (input) => { calls.push(`pocket:${input.base.id}:${String(input.throughAll)}`); return shape('pocket-shape') },
|
||||
revolution: async (input) => { calls.push(`revolution:${input.angle}:${input.axisDirection?.[1]}`); return shape('revolution-shape') },
|
||||
@@ -409,6 +416,53 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
|
||||
assert.ok(calls.includes('revolution:270:-1'))
|
||||
})
|
||||
|
||||
test('Part primitive and boolean nodes execute through the same Shape cache', async () => {
|
||||
const shape = (id: string): ShapeHandle => ({ id, kernel: 'bitbybit-occt', kind: 'solid', documentId: 'doc-part', documentVersion: 2 })
|
||||
const calls: string[] = []
|
||||
const runtime: RecomputeGeometryRuntime = {
|
||||
capabilities: () => ({ status: 'ready' }),
|
||||
createBox: async (input) => { calls.push(`box:${input.width}:${input.length}:${input.height}`); return shape('box-shape') },
|
||||
createCylinder: async () => shape('cylinder-shape'),
|
||||
createSphere: async () => shape('sphere-shape'),
|
||||
createCone: async () => shape('cone-shape'),
|
||||
union: async (input) => { calls.push(`union:${input.shapes.map((entry) => entry.id).join(',')}`); return shape('fuse-shape') },
|
||||
cut: async () => shape('cut-shape'),
|
||||
intersection: async () => shape('common-shape'),
|
||||
pad: async () => shape('pad-shape'),
|
||||
pocket: async () => shape('pocket-shape'),
|
||||
revolution: async () => shape('revolution-shape'),
|
||||
fillet: async () => shape('fillet-shape'),
|
||||
chamfer: async () => shape('chamfer-shape'),
|
||||
release: async () => undefined,
|
||||
}
|
||||
const boxProperties = (width: number) => [
|
||||
{ name: 'Width', label: 'Width', group: 'Box', scope: 'data' as const, type: 'App::PropertyLength' as const, value: width },
|
||||
{ name: 'Length', label: 'Length', group: 'Box', scope: 'data' as const, type: 'App::PropertyLength' as const, value: 4 },
|
||||
{ name: 'Height', label: 'Height', group: 'Box', scope: 'data' as const, type: 'App::PropertyLength' as const, value: 5 },
|
||||
]
|
||||
const document: DocumentSnapshot = {
|
||||
id: 'doc-part', label: 'Part fixture', version: 2, dirty: true, readOnly: false, units: 'mm',
|
||||
tree: [{ id: 'boxA', label: 'Box', type: 'feature', state: 'dirty' }, { id: 'boxB', label: 'Box001', type: 'feature', state: 'dirty' }, { id: 'fuse', label: 'Union', type: 'feature', state: 'dirty' }],
|
||||
objects: [
|
||||
{ id: 'boxA', typeId: 'Part::Box', properties: boxProperties(2) },
|
||||
{ id: 'boxB', typeId: 'Part::Box', properties: boxProperties(3) },
|
||||
{ id: 'fuse', typeId: 'Part::Fuse', properties: [
|
||||
{ name: 'Base', label: 'Base', group: 'Boolean', scope: 'data', type: 'App::PropertyLink' as const, value: 'boxA' },
|
||||
{ name: 'Tool', label: 'Tool', group: 'Boolean', scope: 'data', type: 'App::PropertyLink' as const, value: 'boxB' },
|
||||
] },
|
||||
],
|
||||
dependencies: [{ sourceId: 'fuse', targetId: 'boxA', relation: 'link' }, { sourceId: 'fuse', targetId: 'boxB', relation: 'link' }],
|
||||
recompute: { generation: 0, status: 'idle', objectStates: { boxA: 'touched', boxB: 'touched', fuse: 'touched' }, dirtyObjects: ['boxA', 'boxB'], order: [], errors: [] },
|
||||
}
|
||||
const shapes = new Map<string, ShapeHandle>()
|
||||
const result = await new RecomputeCoordinator(createFacadeGeometryRecomputeExecutor(runtime, shapes), () => 2).run(document)
|
||||
assert.equal(result.status, 'completed')
|
||||
assert.equal(shapes.get('boxA')?.id, 'box-shape')
|
||||
assert.equal(shapes.get('fuse')?.id, 'fuse-shape')
|
||||
assert.ok(calls.includes('box:2:4:5'))
|
||||
assert.ok(calls.includes('union:box-shape,box-shape'))
|
||||
})
|
||||
|
||||
test('facade async recompute commits only an accepted generation', async () => {
|
||||
const facade = createMockFacade()
|
||||
facade.app.document.setProperty({ objectId: 'pad', propertyName: 'Length', value: 51 })
|
||||
@@ -728,3 +782,23 @@ test('Part Design feature tasks commit a document object and remain undoable', (
|
||||
facade.app.document.setProperty({ objectId: 'revolution', propertyName: 'Angle', value: 180 })
|
||||
assert.equal(facade.app.document.getObject('revolution')?.properties.find((property) => property.name === 'Angle')?.value, 180)
|
||||
})
|
||||
|
||||
test('Part workbench commands create primitive and boolean document objects', () => {
|
||||
const facade = createMockFacade()
|
||||
facade.gui.workbench.setActive('Part')
|
||||
assert.equal(facade.gui.command.getState('primitive').status, 'enabled')
|
||||
facade.gui.command.execute({ commandId: 'primitive' })
|
||||
facade.task.apply()
|
||||
assert.equal(facade.app.document.getObject('box')?.typeId, 'Part::Box')
|
||||
facade.gui.command.execute({ commandId: 'primitive' })
|
||||
facade.task.apply()
|
||||
assert.equal(facade.app.document.getObject('box001')?.typeId, 'Part::Box')
|
||||
|
||||
assert.equal(facade.gui.command.getState('union').status, 'enabled')
|
||||
facade.gui.command.execute({ commandId: 'union' })
|
||||
facade.task.apply()
|
||||
assert.equal(facade.app.document.getObject('union')?.typeId, 'Part::Fuse')
|
||||
facade.app.document.setProperty({ objectId: 'union', propertyName: 'Base', value: 'box' })
|
||||
facade.app.document.setProperty({ objectId: 'union', propertyName: 'Tool', value: 'box001' })
|
||||
assert.deepEqual(facade.app.document.getDependencies().filter((edge) => edge.sourceId === 'union').map((edge) => edge.targetId).sort(), ['box', 'box001'])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user