fix: correct HiCAD exploded previews and BOM templates
This commit is contained in:
parent
46f1d97593
commit
dc7f58d940
@ -5,6 +5,8 @@ import {
|
||||
buildExplodeOffsets,
|
||||
createBomCsv,
|
||||
createEngineeringSheetHtml,
|
||||
createSnapshotDataUrl,
|
||||
filterManufacturingBodies,
|
||||
groupBodiesIntoBom,
|
||||
type BodyMeasurement,
|
||||
} from '../../vendor/zoo-modeling-app/manufacturing'
|
||||
@ -117,6 +119,48 @@ test.describe('HiCAD manufacturing extension', () => {
|
||||
expect(csv).toContain('"生产工艺"')
|
||||
})
|
||||
|
||||
test('filters helper solids and groups directional leg names', () => {
|
||||
const stoolBodies: BodyMeasurement[] = [
|
||||
{
|
||||
id: 'template',
|
||||
name: 'unitBox',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
dimensions: { x: 1, y: 1, z: 1 },
|
||||
},
|
||||
{
|
||||
id: 'seat',
|
||||
name: 'seatBody',
|
||||
center: { x: 0, y: 0, z: 310 },
|
||||
dimensions: { x: 300, y: 220, z: 20 },
|
||||
},
|
||||
...['frontLeftLeg', 'frontRightLeg', 'backLeftLeg', 'backRightLeg'].map(
|
||||
(name, index): BodyMeasurement => ({
|
||||
id: `leg-${index}`,
|
||||
name,
|
||||
center: { x: index % 2 ? 100 : -100, y: index < 2 ? -70 : 70, z: 150 },
|
||||
dimensions: { x: 30, y: 30, z: 300 },
|
||||
})
|
||||
),
|
||||
]
|
||||
|
||||
const filtered = filterManufacturingBodies(stoolBodies)
|
||||
const rows = groupBodiesIntoBom(filtered)
|
||||
|
||||
expect(filtered).toHaveLength(5)
|
||||
expect(rows).toHaveLength(2)
|
||||
expect(rows.find((row) => row.name === 'Leg')?.quantity).toBe(4)
|
||||
expect(rows.some((row) => row.name === 'unitBox')).toBe(false)
|
||||
})
|
||||
|
||||
test('normalizes URL-safe engine snapshots for browser image rendering', () => {
|
||||
expect(createSnapshotDataUrl('iVBORw0KGgo_8A')).toBe(
|
||||
'data:image/png;base64,iVBORw0KGgo/8A=='
|
||||
)
|
||||
expect(
|
||||
createSnapshotDataUrl('data:image/png;base64,iVBORw0KGgo_8A')
|
||||
).toBe('data:image/png;base64,iVBORw0KGgo/8A==')
|
||||
})
|
||||
|
||||
test('builds a printable dimensioned engineering sheet safely', () => {
|
||||
const rows = groupBodiesIntoBom(bodies)
|
||||
const html = createEngineeringSheetHtml({
|
||||
|
||||
@ -8,8 +8,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
buildExplodeOffsets,
|
||||
createSnapshotDataUrl,
|
||||
createBomCsv,
|
||||
createEngineeringSheetHtml,
|
||||
filterManufacturingBodies,
|
||||
getOverallBounds,
|
||||
groupBodiesIntoBom,
|
||||
type BodyMeasurement,
|
||||
@ -128,12 +130,6 @@ function formatNumber(value: number | undefined) {
|
||||
return Math.round(value * 100) / 100
|
||||
}
|
||||
|
||||
function dataUrlFromSnapshot(contents: string) {
|
||||
return contents.startsWith('data:')
|
||||
? contents
|
||||
: `data:image/png;base64,${contents}`
|
||||
}
|
||||
|
||||
export function HicadManufacturingPanel({
|
||||
hideTrigger = false,
|
||||
initialOpen = false,
|
||||
@ -274,7 +270,7 @@ export function HicadManufacturingPanel({
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
const dataUrl = dataUrlFromSnapshot(response.data.contents)
|
||||
const dataUrl = createSnapshotDataUrl(response.data.contents)
|
||||
setSnapshot(dataUrl)
|
||||
return dataUrl
|
||||
}
|
||||
@ -411,7 +407,13 @@ export function HicadManufacturingPanel({
|
||||
)
|
||||
}
|
||||
|
||||
setBodies(measured)
|
||||
const manufacturingBodies = filterManufacturingBodies(measured)
|
||||
if (manufacturingBodies.length < measured.length) {
|
||||
toast(
|
||||
`已忽略 ${measured.length - manufacturingBodies.length} 个建模模板实体。`
|
||||
)
|
||||
}
|
||||
setBodies(manufacturingBodies)
|
||||
await Promise.allSettled([
|
||||
engineViewIsometric({
|
||||
engineCommandManager: kclManager.engineCommandManager,
|
||||
@ -431,7 +433,7 @@ export function HicadManufacturingPanel({
|
||||
error
|
||||
)
|
||||
}
|
||||
return measured
|
||||
return manufacturingBodies
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : '工程数据分析失败'
|
||||
|
||||
62
vendor/zoo-modeling-app/manufacturing.ts
vendored
62
vendor/zoo-modeling-app/manufacturing.ts
vendored
@ -48,6 +48,61 @@ function roundDimension(value: number) {
|
||||
return Math.round(value * 100) / 100
|
||||
}
|
||||
|
||||
function maximumDimension(body: BodyMeasurement) {
|
||||
return Math.max(
|
||||
Math.abs(body.dimensions.x),
|
||||
Math.abs(body.dimensions.y),
|
||||
Math.abs(body.dimensions.z)
|
||||
)
|
||||
}
|
||||
|
||||
function compactPartName(name: string) {
|
||||
return name.trim().replaceAll(/[\s_-]+/g, '').toLowerCase()
|
||||
}
|
||||
|
||||
function isKnownTemplateBody(body: BodyMeasurement) {
|
||||
return /^unit(?:box|cube|cylinder[xyz]?|righttriangle[xyz]?)$/.test(
|
||||
compactPartName(body.name)
|
||||
)
|
||||
}
|
||||
|
||||
export function filterManufacturingBodies(bodies: BodyMeasurement[]) {
|
||||
if (bodies.length <= 1) return bodies
|
||||
|
||||
const hasProductionScaleBody = bodies.some(
|
||||
(body) => maximumDimension(body) >= 20
|
||||
)
|
||||
const filtered = bodies.filter((body) => {
|
||||
if (isKnownTemplateBody(body)) return false
|
||||
return !(hasProductionScaleBody && maximumDimension(body) <= 2)
|
||||
})
|
||||
|
||||
return filtered.length > 0 ? filtered : bodies
|
||||
}
|
||||
|
||||
function normalizeBase64Payload(value: string) {
|
||||
const normalized = value
|
||||
.trim()
|
||||
.replaceAll(/\s+/g, '')
|
||||
.replaceAll('-', '+')
|
||||
.replaceAll('_', '/')
|
||||
return normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=')
|
||||
}
|
||||
|
||||
export function createSnapshotDataUrl(contents: string) {
|
||||
const trimmed = contents.trim()
|
||||
if (trimmed.startsWith('data:')) {
|
||||
const commaIndex = trimmed.indexOf(',')
|
||||
if (commaIndex < 0 || !trimmed.slice(0, commaIndex).includes(';base64')) {
|
||||
return trimmed
|
||||
}
|
||||
return `${trimmed.slice(0, commaIndex + 1)}${normalizeBase64Payload(
|
||||
trimmed.slice(commaIndex + 1)
|
||||
)}`
|
||||
}
|
||||
return `data:image/png;base64,${normalizeBase64Payload(trimmed)}`
|
||||
}
|
||||
|
||||
function vectorLength(point: Point3d) {
|
||||
return Math.hypot(point.x, point.y, point.z)
|
||||
}
|
||||
@ -146,7 +201,12 @@ export function buildExplodeOffsets(
|
||||
|
||||
function normalizePartName(name: string) {
|
||||
const cleaned = name.trim() || '未命名零件'
|
||||
return cleaned.replace(/(?:[_-]?(?:copy|part))?[_-]?\d+$/i, '').trim() || cleaned
|
||||
const withoutCopySuffix =
|
||||
cleaned.replace(/(?:[_-]?(?:copy|part))?[_-]?\d+$/i, '').trim() || cleaned
|
||||
const locationLegMatch = withoutCopySuffix.match(
|
||||
/^(?:front|back|rear)(?:left|right)(leg|foot)$/i
|
||||
)
|
||||
return locationLegMatch?.[1] ?? withoutCopySuffix
|
||||
}
|
||||
|
||||
function dimensionsKey(dimensions: Point3d) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user