feat: add experimental basic hole feature

This commit is contained in:
2026-08-02 23:25:10 -04:00
parent ce3b81c3c7
commit 1ed35273a5
7 changed files with 74 additions and 16 deletions

View File

@@ -382,7 +382,7 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
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') },
pocket: async (input) => { calls.push(`pocket:${input.base.id}:${String(input.throughAll)}:${input.profile.outer.length}:${input.length}`); return shape('pocket-shape') },
revolution: async (input) => { calls.push(`revolution:${input.angle}:${input.axisDirection?.[1]}`); return shape('revolution-shape') },
fillet: async () => shape('fillet-shape'),
chamfer: async () => shape('chamfer-shape'),
@@ -414,7 +414,7 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
const context = { documentId: document.id, documentVersion: document.version, generation: 1, signal: new AbortController().signal }
const result = await new RecomputeCoordinator(executor, () => 2).run(document)
assert.equal(result.status, 'completed')
assert.deepEqual(calls.slice(0, 2), ['pad:3', 'pocket:pad-shape:true'])
assert.deepEqual(calls.slice(0, 2), ['pad:3', 'pocket:pad-shape:true:3:5'])
assert.equal(shapes.get('pad')?.id, 'pad-shape')
assert.equal(shapes.get('pocket')?.id, 'pocket-shape')
@@ -476,6 +476,21 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
assert.ok(calls.includes('placement:0,0,0:0,0,1:90'))
assert.ok(calls.includes('placement:0,0,0:0,0,1:180'))
const hole = { id: 'hole', typeId: 'PartDesign::Hole', properties: [
{ name: 'Base', label: 'Base', group: 'Hole', scope: 'data' as const, type: 'App::PropertyLink' as const, value: 'pad' },
{ name: 'Diameter', label: 'Diameter', group: 'Hole', scope: 'data' as const, type: 'App::PropertyLength' as const, value: 6, unit: 'mm' },
{ name: 'Depth', label: 'Depth', group: 'Hole', scope: 'data' as const, type: 'App::PropertyLength' as const, value: 8, unit: 'mm' },
{ name: 'Type', label: 'Type', group: 'Hole', scope: 'data' as const, type: 'App::PropertyEnumeration' as const, value: 'Dimension', options: ['Dimension', 'Through all'] },
] }
const holeResult = await executor(hole, document, context)
assert.equal(holeResult.status, 'success')
assert.equal(shapes.get('hole')?.id, 'pocket-shape')
assert.ok(calls.includes('pocket:pad-shape:false:32:8'))
const invalidHole = { ...hole, properties: hole.properties.map((property) => property.name === 'Diameter' ? { ...property, value: 0 } : property) }
const invalidHoleResult = await executor(invalidHole, document, context)
assert.equal(invalidHoleResult.status, 'failed')
assert.equal(invalidHoleResult.errors?.[0].code, 'HOLE_DIAMETER_INVALID')
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')
@@ -660,10 +675,10 @@ test('disabled commands return a reason instead of mutating the document', () =>
test('manifest commands without a Bitbybit executor are explicitly unsupported', () => {
const facade = createMockFacade()
const before = facade.getState().document.version
const state = facade.gui.command.getState('hole')
const state = facade.gui.command.getState('mirrored')
assert.equal(state.status, 'disabled')
assert.match(state.reason || '', /business executor/)
facade.gui.command.execute({ commandId: 'hole' })
facade.gui.command.execute({ commandId: 'mirrored' })
assert.equal(facade.getState().diagnostics.at(-1)?.code, 'COMMAND_UNIMPLEMENTED')
assert.equal(facade.getState().document.version, before)
})
@@ -948,6 +963,17 @@ test('Part Design feature tasks commit a document object and remain undoable', (
assert.equal(polarPattern?.properties.find((property) => property.name === 'Angle')?.value, 180)
assert.equal(polarPattern?.properties.find((property) => property.name === 'Axis')?.value, 'Normal')
assert.equal(facade.app.document.getObject('body')?.properties.find((property) => property.name === 'Tip')?.value, 'polar-pattern')
facade.gui.command.execute({ commandId: 'hole' })
facade.task.update({ diameter: 6, depth: 12, type: 'Through all' })
facade.task.apply()
const hole = facade.app.document.getObject('hole')
assert.equal(hole?.typeId, 'PartDesign::Hole')
assert.equal(hole?.properties.find((property) => property.name === 'Base')?.value, 'polar-pattern')
assert.equal(hole?.properties.find((property) => property.name === 'Diameter')?.value, 6)
assert.equal(hole?.properties.find((property) => property.name === 'Depth')?.value, 12)
assert.equal(hole?.properties.find((property) => property.name === 'Type')?.value, 'Through all')
assert.equal(facade.app.document.getObject('body')?.properties.find((property) => property.name === 'Tip')?.value, 'hole')
})
test('Part workbench commands create primitive and boolean document objects', () => {