feat: add experimental polar pattern feature

This commit is contained in:
2026-08-02 23:20:02 -04:00
parent 8995b90917
commit ce3b81c3c7
7 changed files with 116 additions and 18 deletions

View File

@@ -377,7 +377,7 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
createCylinder: async () => shape('cylinder-shape'),
createSphere: async () => shape('sphere-shape'),
createCone: async () => shape('cone-shape'),
applyPlacement: async (input) => { calls.push(`placement:${input.placement.translation.join(',')}`); return shape(`pattern-copy-${calls.filter((call) => call.startsWith('placement:')).length}`) },
applyPlacement: async (input) => { calls.push(`placement:${input.placement.translation.join(',')}:${input.placement.rotationAxis.join(',')}:${input.placement.rotationAngle}`); return shape(`pattern-copy-${calls.filter((call) => call.startsWith('placement:')).length}`) },
union: async (input) => { calls.push(`union:${input.shapes.map((entry) => entry.id).join(',')}`); return shape('union-shape') },
cut: async () => shape('cut-shape'),
intersection: async () => shape('intersection-shape'),
@@ -447,8 +447,8 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
const patternResult = await executor(linearPattern, document, context)
assert.equal(patternResult.status, 'success')
assert.equal(shapes.get('linear-pattern')?.id, 'union-shape')
assert.ok(calls.includes('placement:0,10,0'))
assert.ok(calls.includes('placement:0,20,0'))
assert.ok(calls.includes('placement:0,10,0:0,0,1:0'))
assert.ok(calls.includes('placement:0,20,0:0,0,1:0'))
assert.ok(calls.includes('union:pad-shape,pattern-copy-1,pattern-copy-2'))
assert.ok(calls.includes('release:pattern-copy-1'))
assert.ok(calls.includes('release:pattern-copy-2'))
@@ -458,6 +458,24 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
assert.equal(invalidPatternResult.status, 'failed')
assert.equal(invalidPatternResult.errors?.[0].code, 'PATTERN_OCCURRENCES_INVALID')
const polarPattern = { id: 'polar-pattern', typeId: 'PartDesign::PolarPattern', properties: [
{ name: 'Base', label: 'Base', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyLink' as const, value: 'pad' },
{ name: 'Occurrences', label: 'Occurrences', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyInteger' as const, value: 3 },
{ name: 'Angle', label: 'Angle', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyAngle' as const, value: 360, unit: 'deg' },
{ name: 'Axis', label: 'Axis', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyEnumeration' as const, value: 'Normal', options: ['Normal', 'Horizontal', 'Vertical'] },
] }
const polarResult = await executor(polarPattern, document, context)
assert.equal(polarResult.status, 'success')
assert.ok(calls.includes('placement:0,0,0:0,0,1:120'))
assert.ok(calls.includes('placement:0,0,0:0,0,1:240'))
assert.ok(calls.includes('union:pad-shape,pattern-copy-3,pattern-copy-4'))
const partialPolar = { ...polarPattern, id: 'polar-pattern-partial', properties: polarPattern.properties.map((property) => property.name === 'Angle' ? { ...property, value: 180 } : property) }
const partialPolarResult = await executor(partialPolar, document, context)
assert.equal(partialPolarResult.status, 'success')
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 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')
@@ -642,10 +660,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('polar-pattern')
const state = facade.gui.command.getState('hole')
assert.equal(state.status, 'disabled')
assert.match(state.reason || '', /business executor/)
facade.gui.command.execute({ commandId: 'polar-pattern' })
facade.gui.command.execute({ commandId: 'hole' })
assert.equal(facade.getState().diagnostics.at(-1)?.code, 'COMMAND_UNIMPLEMENTED')
assert.equal(facade.getState().document.version, before)
})
@@ -919,6 +937,17 @@ test('Part Design feature tasks commit a document object and remain undoable', (
assert.throws(() => facade.task.apply(), /between 2 and 100/)
assert.equal(facade.app.document.getObject('linear-pattern001'), null)
facade.task.cancel()
facade.gui.command.execute({ commandId: 'polar-pattern' })
facade.task.update({ occurrences: 4, angle: 180, axis: 'Normal' })
facade.task.apply()
const polarPattern = facade.app.document.getObject('polar-pattern')
assert.equal(polarPattern?.typeId, 'PartDesign::PolarPattern')
assert.equal(polarPattern?.properties.find((property) => property.name === 'Base')?.value, 'linear-pattern')
assert.equal(polarPattern?.properties.find((property) => property.name === 'Occurrences')?.value, 4)
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')
})
test('Part workbench commands create primitive and boolean document objects', () => {