feat: add experimental linear pattern feature
This commit is contained in:
@@ -377,7 +377,8 @@ 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'),
|
||||
union: async () => shape('union-shape'),
|
||||
applyPlacement: async (input) => { calls.push(`placement:${input.placement.translation.join(',')}`); 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'),
|
||||
pad: async (input) => { calls.push(`pad:${input.profile.outer.length}`); if (padShouldFail) throw new Error('OCCT pad failed'); return shape('pad-shape') },
|
||||
@@ -437,6 +438,26 @@ test('OCCT feature executor builds a Sketch to Pad to Pocket chain and retains t
|
||||
assert.equal(shapes.get('revolution')?.id, 'revolution-shape')
|
||||
assert.ok(calls.includes('revolution:270:-1'))
|
||||
|
||||
const linearPattern = { id: 'linear-pattern', typeId: 'PartDesign::LinearPattern', 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: 'Length', label: 'Length', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyLength' as const, value: 20, unit: 'mm' },
|
||||
{ name: 'Direction', label: 'Direction', group: 'Pattern', scope: 'data' as const, type: 'App::PropertyEnumeration' as const, value: 'Vertical', options: ['Horizontal', 'Vertical', 'Normal'] },
|
||||
] }
|
||||
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('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'))
|
||||
|
||||
const invalidPattern = { ...linearPattern, properties: linearPattern.properties.map((property) => property.name === 'Occurrences' ? { ...property, value: 1 } : property) }
|
||||
const invalidPatternResult = await executor(invalidPattern, document, context)
|
||||
assert.equal(invalidPatternResult.status, 'failed')
|
||||
assert.equal(invalidPatternResult.errors?.[0].code, 'PATTERN_OCCURRENCES_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')
|
||||
@@ -453,6 +474,7 @@ test('Part primitive and boolean nodes execute through the same Shape cache', as
|
||||
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('placed-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'),
|
||||
@@ -620,10 +642,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('linear-pattern')
|
||||
const state = facade.gui.command.getState('polar-pattern')
|
||||
assert.equal(state.status, 'disabled')
|
||||
assert.match(state.reason || '', /business executor/)
|
||||
facade.gui.command.execute({ commandId: 'linear-pattern' })
|
||||
facade.gui.command.execute({ commandId: 'polar-pattern' })
|
||||
assert.equal(facade.getState().diagnostics.at(-1)?.code, 'COMMAND_UNIMPLEMENTED')
|
||||
assert.equal(facade.getState().document.version, before)
|
||||
})
|
||||
@@ -878,6 +900,25 @@ test('Part Design feature tasks commit a document object and remain undoable', (
|
||||
assert.equal(revolution?.properties.find((property) => property.name === 'Angle')?.value, 360)
|
||||
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)
|
||||
|
||||
facade.gui.command.execute({ commandId: 'linear-pattern' })
|
||||
assert.equal(facade.task.getActive()?.commandId, 'linear-pattern')
|
||||
facade.task.update({ occurrences: 4, length: 30, direction: 'Vertical' })
|
||||
facade.task.apply()
|
||||
const pattern = facade.app.document.getObject('linear-pattern')
|
||||
assert.equal(pattern?.typeId, 'PartDesign::LinearPattern')
|
||||
assert.equal(pattern?.properties.find((property) => property.name === 'Base')?.value, 'revolution')
|
||||
assert.equal(pattern?.properties.find((property) => property.name === 'Occurrences')?.value, 4)
|
||||
assert.equal(pattern?.properties.find((property) => property.name === 'Length')?.value, 30)
|
||||
assert.equal(pattern?.properties.find((property) => property.name === 'Direction')?.value, 'Vertical')
|
||||
assert.equal(facade.app.document.getObject('body')?.properties.find((property) => property.name === 'Tip')?.value, 'linear-pattern')
|
||||
assert.throws(() => facade.app.document.setProperty({ objectId: 'linear-pattern', propertyName: 'Occurrences', value: 1 }), /between 2 and 100/)
|
||||
|
||||
facade.gui.command.execute({ commandId: 'linear-pattern' })
|
||||
facade.task.update({ occurrences: 1, length: 0 })
|
||||
assert.throws(() => facade.task.apply(), /between 2 and 100/)
|
||||
assert.equal(facade.app.document.getObject('linear-pattern001'), null)
|
||||
facade.task.cancel()
|
||||
})
|
||||
|
||||
test('Part workbench commands create primitive and boolean document objects', () => {
|
||||
|
||||
Reference in New Issue
Block a user