test: validate HiCAD hardware production drawings

This commit is contained in:
134 2026-07-27 22:20:27 +08:00
parent 592df97568
commit d6fcd1233a
2 changed files with 131 additions and 1 deletions

View File

@ -9,6 +9,7 @@ import {
detectPanelHoles,
filterManufacturingBodies,
groupBodiesIntoBom,
localizePartName,
type BodyMeasurement,
type CircularEdgeSample,
} from '../../vendor/zoo-modeling-app/manufacturing'
@ -57,6 +58,78 @@ function circularEdge(
}
}
function hardwareAssemblyFixture() {
const holeCenters = [
[-160, -100],
[160, -100],
[-160, 100],
[160, 100],
] as const
const panel: BodyMeasurement = {
id: 'mounting-panel',
name: 'mountingPanel',
center: { x: 0, y: 0, z: 9 },
dimensions: { x: 400, y: 300, z: 18 },
holes: detectPanelHoles(
{
id: 'mounting-panel',
name: 'mountingPanel',
center: { x: 0, y: 0, z: 9 },
dimensions: { x: 400, y: 300, z: 18 },
},
holeCenters.flatMap(([x, y]) => [
circularEdge(x, y, 0, 4),
circularEdge(x, y, 18, 4),
])
),
}
const plate: BodyMeasurement = {
id: 'connector-plate',
name: 'connectorPlate',
center: { x: 0, y: 0, z: 19.5 },
dimensions: { x: 360, y: 260, z: 3 },
holes: detectPanelHoles(
{
id: 'connector-plate',
name: 'connectorPlate',
center: { x: 0, y: 0, z: 19.5 },
dimensions: { x: 360, y: 260, z: 3 },
},
holeCenters.flatMap(([x, y]) => [
circularEdge(x, y, 18, 4),
circularEdge(x, y, 21, 4),
])
),
}
const bolts = holeCenters.map(
([x, y], index): BodyMeasurement => ({
id: `connector-bolt-${index + 1}`,
name: `connectorBolt${index + 1}`,
center: { x, y, z: 11 },
dimensions: { x: 8, y: 8, z: 30 },
})
)
const washers = holeCenters.map(
([x, y], index): BodyMeasurement => ({
id: `washer-${index + 1}`,
name: `washer${index + 1}`,
center: { x, y, z: 22 },
dimensions: { x: 18, y: 18, z: 2 },
holes: [
{
center: { x, y, z: 22 },
diameter: 8,
axis: 'z',
type: 'through-hole',
depth: 2,
},
],
})
)
return [panel, plate, ...bolts, ...washers]
}
test.describe('HiCAD manufacturing extension', () => {
test('keeps the extension isolated behind one official registration point', () => {
const projectRoot = process.cwd()
@ -172,6 +245,62 @@ test.describe('HiCAD manufacturing extension', () => {
expect(rows.some((row) => row.name === 'unitBox')).toBe(false)
})
test('localizes the hardware validation assembly for a Chinese BOM', () => {
expect(localizePartName('mountingPanel')).toBe('安装基板')
expect(localizePartName('connectorPlate')).toBe('连接片')
expect(localizePartName('connectorBolt4')).toBe('连接螺栓')
expect(localizePartName('washer3')).toBe('垫圈')
})
test('builds a production-ready hardware BOM and detailed hole schedule', async ({
page,
}, testInfo) => {
const assembly = hardwareAssemblyFixture()
const rows = groupBodiesIntoBom(assembly)
expect(assembly).toHaveLength(10)
expect(rows).toHaveLength(4)
expect(rows.find((row) => row.name === '安装基板')?.holes).toHaveLength(4)
expect(rows.find((row) => row.name === '连接片')?.holes).toHaveLength(4)
expect(rows.find((row) => row.name === '连接螺栓')?.quantity).toBe(4)
expect(rows.find((row) => row.name === '垫圈')?.quantity).toBe(4)
const html = createEngineeringSheetHtml({
projectName: '五金连接安装板',
generatedAt: '2026-07-27 22:30',
overallDimensions: { x: 400, y: 300, z: 30 },
bodies: assembly,
bomRows: rows,
})
expect(html).toContain('安装基板')
expect(html).toContain('连接片')
expect(html).toContain('连接螺栓')
expect(html).toContain('垫圈')
expect(html).toContain('H1:Ø8 通孔')
expect(html).toContain('横向中心孔距 320 mm')
expect(html).toContain('纵向中心孔距 200 mm')
expect(html.match(/class="part-detail"/g)).toHaveLength(4)
await page.setViewportSize({ width: 1600, height: 900 })
await page.setContent(html)
expect(
await page.locator('.part-detail').evaluateAll((parts) =>
parts.map((part) => part.textContent)
)
).toEqual(
expect.arrayContaining([
expect.stringContaining('安装基板'),
expect.stringContaining('连接片'),
expect.stringContaining('连接螺栓'),
expect.stringContaining('垫圈'),
])
)
await page.screenshot({
path: testInfo.outputPath('hardware-engineering-sheet.png'),
fullPage: true,
})
})
test('normalizes URL-safe engine snapshots for browser image rendering', () => {
expect(createSnapshotDataUrl('iVBORw0KGgo_8A')).toBe(
'data:image/png;base64,iVBORw0KGgo/8A=='

View File

@ -437,6 +437,7 @@ const CHINESE_PART_NAMES: Record<string, string> = {
handle: '拉手',
hinge: '铰链',
metalbracket: '金属连接角码',
mountingpanel: '安装基板',
leftsidepanel: '左侧板',
leg: '支腿',
rightsidepanel: '右侧板',
@ -458,7 +459,7 @@ const CHINESE_PART_NAMES: Record<string, string> = {
}
export function localizePartName(name: string) {
const cleaned = name.trim() || '未命名零件'
const cleaned = normalizePartName(name.trim() || '未命名零件')
if (/[\u3400-\u9fff]/.test(cleaned)) return cleaned
return CHINESE_PART_NAMES[compactPartName(cleaned)] ?? cleaned
}