127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import type { DeckStore } from './deckStore';
|
|
import { calculateSinglePageLayout, generateTravelPaths } from '../../../plotcutter/layout';
|
|
import { pts2plotter } from '../../../plotcutter/plotter';
|
|
|
|
export interface PltExportData {
|
|
/** 单页满排时的 PLT 代码 */
|
|
pltCode: string;
|
|
/** 排版区域宽度 (mm) */
|
|
frameWidth: number;
|
|
/** 排版区域高度 (mm) */
|
|
frameHeight: number;
|
|
/** 每页卡片数 */
|
|
cardsPerPage: number;
|
|
}
|
|
|
|
export interface UsePlotterExportReturn {
|
|
/** 生成单页满排时的 PLT 数据 */
|
|
generatePltData: () => PltExportData | null;
|
|
/** 下载 PLT 文件 */
|
|
downloadPltFile: (pltCode: string) => void;
|
|
/** 直接导出 PLT(打开下载) */
|
|
exportToPlt: () => void;
|
|
}
|
|
|
|
/**
|
|
* PLT 导出 hook - 生成单页满排时的 HPGL 格式文件
|
|
*
|
|
* 刀路只关心单页排满的情况,不考虑实际牌组的张数。
|
|
* PLT 坐标系统以 frameBoundsWithMargin 左上角为原点 (0,0)。
|
|
*/
|
|
export function usePlotterExport(store: DeckStore): UsePlotterExportReturn {
|
|
const bleed = () => store.state.bleed || 1;
|
|
const cornerRadius = () => store.state.cornerRadius ?? 3;
|
|
const cardWidth = () => store.state.dimensions?.cardWidth || 56;
|
|
const cardHeight = () => store.state.dimensions?.cardHeight || 88;
|
|
const shape = () => store.state.shape;
|
|
const orientation = () => store.state.printOrientation || 'landscape';
|
|
const doubleCut = () => store.state.pltDoubleCut;
|
|
|
|
/**
|
|
* 生成单页满排时的 PLT 数据
|
|
*/
|
|
const generatePltData = (): PltExportData | null => {
|
|
const layout = calculateSinglePageLayout({
|
|
cardWidth: cardWidth(),
|
|
cardHeight: cardHeight(),
|
|
shape: shape(),
|
|
bleed: bleed(),
|
|
cornerRadius: cornerRadius(),
|
|
orientation: orientation()
|
|
});
|
|
|
|
if (layout.cardPaths.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// 使用 frameBoundsWithMargin 作为 PLT 的坐标系统
|
|
const frameBounds = layout.frameBoundsWithMargin;
|
|
const pltWidth = frameBounds.width;
|
|
const pltHeight = frameBounds.height;
|
|
|
|
// 将卡片路径转换为相对于 frameBoundsWithMargin 的坐标
|
|
// 原点在 frameBoundsWithMargin 的左上角
|
|
const relativePaths = layout.cardPaths.flatMap(cardPath => {
|
|
const relativePoints = cardPath.points.map(([x, y]) => {
|
|
// 转换为相对于 frameBounds 左上角的坐标
|
|
const relativeX = x - frameBounds.x;
|
|
const relativeY = y - frameBounds.y;
|
|
// 翻转 Y 轴(plotter 坐标 Y 向上,SVG 坐标 Y 向下)
|
|
// 在 frameBounds 坐标系中,原点在左上,Y 向下为正
|
|
// 在 plotter 坐标系中,原点在左下,Y 向上为正
|
|
// 所以 plotterY = pltHeight - relativeY
|
|
return [relativeX, pltHeight - relativeY] as [number, number];
|
|
});
|
|
if (doubleCut()) {
|
|
// 重复一次:某些切割机需要每张卡牌切两次
|
|
return [relativePoints, relativePoints];
|
|
}
|
|
return [relativePoints];
|
|
});
|
|
|
|
// 起点和终点都在 frameBoundsWithMargin 的左上角 (0, pltHeight)
|
|
// 在 plotter 坐标系中,左上角是 (0, height)
|
|
const startPoint: [number, number] = [0, pltHeight];
|
|
const endPoint: [number, number] = [0, pltHeight];
|
|
|
|
// 生成 HPGL 代码
|
|
const plotterCode = pts2plotter(relativePaths, pltWidth, pltHeight, 1, startPoint, endPoint);
|
|
|
|
return {
|
|
pltCode: plotterCode,
|
|
frameWidth: pltWidth,
|
|
frameHeight: pltHeight,
|
|
cardsPerPage: layout.cardsPerPage
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 下载 PLT 文件
|
|
*/
|
|
const downloadPltFile = (pltCode: string) => {
|
|
const blob = new Blob([pltCode], { type: 'application/vnd.hp-HPGL' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `deck-plt-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.plt`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
/**
|
|
* 直接导出 PLT(打开下载)
|
|
*/
|
|
const exportToPlt = () => {
|
|
const data = generatePltData();
|
|
if (!data) {
|
|
alert('没有可导出的卡片');
|
|
return;
|
|
}
|
|
downloadPltFile(data.pltCode);
|
|
};
|
|
|
|
return { generatePltData, downloadPltFile, exportToPlt };
|
|
}
|