import { createSignal, For, Show, createMemo } from 'solid-js'; import { parsePlt, extractCutPaths, parsedPltToSvg } from '../../plotcutter/parser'; import { generateTravelPaths, travelPathsToSvg } from '../../plotcutter/layout'; import { pts2plotter } from '../../plotcutter/plotter'; import type { CardPath } from '../../plotcutter'; import type { CardShape } from '../../plotcutter'; import { getCardShapePoints, calculateCenter, contourToSvgPath } from '../../plotcutter'; export interface PltPreviewProps { /** PLT 文件内容 */ pltCode: string; /** 卡片形状(用于生成刀路) */ shape: CardShape; /** 卡片宽度 (mm) */ cardWidth: number; /** 卡片高度 (mm) */ cardHeight: number; /** 出血 (mm) */ bleed: number; /** 圆角半径 (mm) */ cornerRadius: number; /** 打印方向 */ orientation: 'portrait' | 'landscape'; /** 关闭回调 */ onClose: () => void; } /** * 从 PLT 代码解析并生成卡片路径数据 */ function parsePltToCardPaths(pltCode: string, a4Height: number): { cutPaths: [number, number][][]; cardPaths: CardPath[]; } { const parsed = parsePlt(pltCode); const cutPaths = extractCutPaths(parsed, 5); // 5mm 阈值 // 将解析的路径转换为 CardPath 格式用于显示 const cardPaths: CardPath[] = cutPaths.map((points, index) => { const center = calculateCenter(points); const pathD = contourToSvgPath(points); const startPoint = points[0]; const endPoint = points[points.length - 1]; return { pageIndex: 0, cardIndex: index, points, centerX: center.x, centerY: center.y, pathD, startPoint, endPoint }; }); return { cutPaths, cardPaths }; } /** * 生成单页满排时的 PLT 代码(用于预览对比) */ function generateSinglePagePlt( shape: CardShape, cardWidth: number, cardHeight: number, bleed: number, cornerRadius: number, orientation: 'portrait' | 'landscape' ): string { const a4Width = orientation === 'landscape' ? 297 : 210; const a4Height = orientation === 'landscape' ? 210 : 297; const printMargin = 5; const usableWidth = a4Width - printMargin * 2; const usableHeight = a4Height - printMargin * 2; const cardsPerRow = Math.floor(usableWidth / cardWidth); const rowsPerPage = Math.floor(usableHeight / cardHeight); const cardsPerPage = cardsPerRow * rowsPerPage; const maxGridWidth = cardsPerRow * cardWidth; const maxGridHeight = rowsPerPage * cardHeight; const offsetX = (a4Width - maxGridWidth) / 2; const offsetY = (a4Height - maxGridHeight) / 2; const cutWidth = cardWidth - bleed * 2; const cutHeight = cardHeight - bleed * 2; const allPaths: [number, number][][] = []; for (let i = 0; i < cardsPerPage; i++) { const row = Math.floor(i / cardsPerRow); const col = i % cardsPerRow; const x = offsetX + col * cardWidth; const y = offsetY + row * cardHeight; const shapePoints = getCardShapePoints(shape, cutWidth, cutHeight, cornerRadius); const pagePoints = shapePoints.map(([px, py]) => [ x + bleed + px, a4Height - (y + bleed + py) ] as [number, number]); allPaths.push(pagePoints); } if (allPaths.length === 0) return ''; const startPoint: [number, number] = [0, a4Height]; const endPoint: [number, number] = [0, a4Height]; return pts2plotter(allPaths, a4Width, a4Height, 1, startPoint, endPoint); } /** * PLT 预览组件 - 基于 PLT 文本解析显示切割路径预览 */ export function PltPreview(props: PltPreviewProps) { const a4Width = props.orientation === 'landscape' ? 297 : 210; const a4Height = props.orientation === 'landscape' ? 210 : 297; // 使用传入的圆角值,但也允许用户修改 const [cornerRadius, setCornerRadius] = createSignal(props.cornerRadius); // 解析传入的 PLT 代码 const parsedData = createMemo(() => { if (!props.pltCode) { return { cutPaths: [] as [number, number][][], cardPaths: [] as CardPath[] }; } return parsePltToCardPaths(props.pltCode, a4Height); }); // 生成空走路径 const travelPathD = createMemo(() => { const cardPaths = parsedData().cardPaths; if (cardPaths.length === 0) return ''; const travelPaths = generateTravelPaths(cardPaths, a4Height); return travelPathsToSvg(travelPaths); }); // 生成单页满排时的 PLT 代码(用于对比) const singlePagePltCode = createMemo(() => { return generateSinglePagePlt( props.shape, props.cardWidth, props.cardHeight, props.bleed, cornerRadius(), props.orientation ); }); // 生成当前 PLT 的 HPGL 代码(重新生成,确保圆角更新) const currentPltCode = createMemo(() => { const cardPaths = parsedData().cardPaths; if (cardPaths.length === 0) return ''; const allPaths = cardPaths.map(p => p.points); const startPoint: [number, number] = [0, a4Height]; const endPoint: [number, number] = [0, a4Height]; return pts2plotter(allPaths, a4Width, a4Height, 1, startPoint, endPoint); }); const handleDownload = () => { if (!currentPltCode()) { alert('没有可导出的卡片'); return; } const blob = new Blob([currentPltCode()], { type: 'application/vnd.hp-HPGL' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `deck-export-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.plt`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const handleCornerRadiusChange = (e: Event) => { const target = e.target as HTMLInputElement; setCornerRadius(Number(target.value)); }; return (
{/* 头部控制栏 */}

PLT 切割预览

{/* 预览区域 */}
{/* A4 边框 */} {/* 空走路径(虚线) */} {/* 切割路径 */} {(path) => { return ( {/* 切割路径 */} {/* 动画小球 */} {/* 序号标签 */} {path.cardIndex + 1} ); }}
{/* 图例说明 */}
空走路径
切割路径
刀头
); }