Files
Web_FreeCAD_Bitbybit/tests/assembly.test.ts

76 lines
4.8 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { createAssembly } from '../src/facade/assembly'
const connector = (id: string, componentId: string, x = 0) => ({ id, componentId, origin: { x, y: 0, z: 0 }, axis: { x: 1, y: 0, z: 0 } })
test('assembly solves grounded coincident and distance joints deterministically', () => {
const assembly = createAssembly('motor', 'Motor assembly')
assembly.addComponent({ id: 'base', sourceObjectId: 'box-base', label: 'Base', grounded: true, placement: { x: 0 } })
assembly.addComponent({ id: 'shaft', sourceObjectId: 'box-shaft', label: 'Shaft', grounded: false, placement: { x: 9 } })
assembly.addConnector(connector('base-origin', 'base'))
assembly.addConnector(connector('shaft-origin', 'shaft'))
assembly.addJoint({ id: 'coincident', kind: 'coincident', first: 'base-origin', second: 'shaft-origin' })
assembly.addJoint({ id: 'distance', kind: 'distance', first: 'base-origin', second: 'shaft-origin', value: 5 })
const solved = assembly.solve()
assert.equal(solved.solver.status, 'solved')
assert.equal(solved.joints.every((joint) => joint.status === 'solved'), true)
assert.equal(solved.components.find((component) => component.id === 'shaft')?.placement.x, 5)
assert.equal(solved.solver.iterations, 2)
})
test('assembly rejects invalid connectors and reports grounded conflicts', () => {
const assembly = createAssembly()
assembly.addComponent({ id: 'a', sourceObjectId: 'shape-a', label: 'A', grounded: true })
assembly.addComponent({ id: 'b', sourceObjectId: 'shape-b', label: 'B', grounded: true, placement: { x: 2 } })
assert.throws(() => assembly.addConnector({ id: 'bad', componentId: 'a', origin: { x: 0, y: 0, z: 0 }, axis: { x: 0, y: 0, z: 0 } }), /must be non-zero/)
assembly.addConnector(connector('a-origin', 'a'))
assembly.addConnector(connector('b-origin', 'b'))
assembly.addJoint({ id: 'fixed', kind: 'fixed', first: 'a-origin', second: 'b-origin' })
const solved = assembly.solve()
assert.equal(solved.solver.status, 'conflicting')
assert.equal(solved.diagnostics[0]?.code, 'GROUND_CONFLICT')
})
test('assembly angle joint validates its range and snapshots are isolated', () => {
const assembly = createAssembly()
assembly.addComponent({ id: 'a', sourceObjectId: 'shape-a', label: 'A', grounded: true })
assembly.addComponent({ id: 'b', sourceObjectId: 'shape-b', label: 'B', grounded: true })
assembly.addConnector(connector('a-origin', 'a'))
assembly.addConnector(connector('b-origin', 'b'))
assert.throws(() => assembly.addJoint({ id: 'invalid', kind: 'angle', first: 'a-origin', second: 'b-origin', value: Math.PI * 2 }), /outside the supported range/)
assembly.addJoint({ id: 'angle', kind: 'angle', first: 'a-origin', second: 'b-origin', value: 0 })
const snapshot = assembly.snapshot()
snapshot.components[0].placement.x = 99
assert.equal(assembly.snapshot().components[0]?.placement.x, 0)
})
test('assembly tools provide deterministic BOM, collision, exploded and variant views', () => {
const assembly = createAssembly()
assembly.addComponent({ id: 'a', sourceObjectId: 'plate', label: 'Plate', placement: { x: 0 }, bounds: { min: { x: -1, y: -1, z: -1 }, max: { x: 1, y: 1, z: 1 } } })
assembly.addComponent({ id: 'b', sourceObjectId: 'plate', label: 'Plate', placement: { x: 0.5 } })
assembly.addComponent({ id: 'c', sourceObjectId: 'bolt', label: 'Bolt', placement: { x: 10 } })
assert.deepEqual(assembly.bom().map((row) => [row.sourceObjectId, row.quantity]), [['bolt', 1], ['plate', 2]])
assert.equal(assembly.collisions().length, 1)
assert.ok(assembly.exploded(2).some((component) => component.placement.x !== assembly.snapshot().components.find((entry) => entry.id === component.id)?.placement.x))
assert.equal(assembly.variant('open', { c: { x: 12 } }).placements.find((component) => component.id === 'c')?.placement.x, 12)
const motion = assembly.motion('c', { x: 14, yaw: Math.PI / 2 }, 4)
assert.equal(motion.length, 5)
assert.equal(motion[0].placement.x, 10)
assert.equal(motion.at(-1)?.placement.x, 14)
assert.equal(motion.at(-1)?.placement.yaw, Math.PI / 2)
assert.throws(() => assembly.motion('c', { x: 1 }, 0), /between 1 and 10000/)
})
test('assembly solves a movable angle joint', () => {
const assembly = createAssembly()
assembly.addComponent({ id: 'base', sourceObjectId: 'base-shape', label: 'Base', grounded: true })
assembly.addComponent({ id: 'arm', sourceObjectId: 'arm-shape', label: 'Arm', grounded: false })
assembly.addConnector(connector('base-axis', 'base'))
assembly.addConnector(connector('arm-axis', 'arm'))
assembly.addJoint({ id: 'right-angle', kind: 'angle', first: 'base-axis', second: 'arm-axis', value: Math.PI / 2 })
const solved = assembly.solve()
assert.equal(solved.solver.status, 'solved')
assert.equal(solved.components.find((component) => component.id === 'arm')?.placement.yaw, Math.PI / 2)
})