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) })