test: validate HiCAD hardware production drawings
This commit is contained in:
parent
592df97568
commit
d6fcd1233a
@ -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=='
|
||||
|
||||
3
vendor/zoo-modeling-app/manufacturing.ts
vendored
3
vendor/zoo-modeling-app/manufacturing.ts
vendored
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user