feat: plt export
This commit is contained in:
parent
85ec3b9928
commit
0ed95291ba
|
|
@ -2,6 +2,7 @@ import { For, Show } from 'solid-js';
|
||||||
import type { DeckStore } from './hooks/deckStore';
|
import type { DeckStore } from './hooks/deckStore';
|
||||||
import { usePageLayout } from './hooks/usePageLayout';
|
import { usePageLayout } from './hooks/usePageLayout';
|
||||||
import { usePDFExport, type ExportOptions } from './hooks/usePDFExport';
|
import { usePDFExport, type ExportOptions } from './hooks/usePDFExport';
|
||||||
|
import { usePlotterExport } from './hooks/usePlotterExport';
|
||||||
import { getShapeSvgClipPath } from './hooks/shape-styles';
|
import { getShapeSvgClipPath } from './hooks/shape-styles';
|
||||||
import { PrintPreviewHeader } from './PrintPreviewHeader';
|
import { PrintPreviewHeader } from './PrintPreviewHeader';
|
||||||
import { PrintPreviewFooter } from './PrintPreviewFooter';
|
import { PrintPreviewFooter } from './PrintPreviewFooter';
|
||||||
|
|
@ -20,6 +21,7 @@ export function PrintPreview(props: PrintPreviewProps) {
|
||||||
const { store } = props;
|
const { store } = props;
|
||||||
const { getA4Size, pages, cropMarks } = usePageLayout(store);
|
const { getA4Size, pages, cropMarks } = usePageLayout(store);
|
||||||
const { exportToPDF } = usePDFExport(store, props.onClose);
|
const { exportToPDF } = usePDFExport(store, props.onClose);
|
||||||
|
const { exportToPlt } = usePlotterExport(store);
|
||||||
|
|
||||||
const frontVisibleLayers = () => store.state.frontLayerConfigs.filter((l) => l.visible);
|
const frontVisibleLayers = () => store.state.frontLayerConfigs.filter((l) => l.visible);
|
||||||
const backVisibleLayers = () => store.state.backLayerConfigs.filter((l) => l.visible);
|
const backVisibleLayers = () => store.state.backLayerConfigs.filter((l) => l.visible);
|
||||||
|
|
@ -39,6 +41,10 @@ export function PrintPreview(props: PrintPreviewProps) {
|
||||||
await exportToPDF(pages(), cropMarks(), options);
|
await exportToPDF(pages(), cropMarks(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExportPlt = () => {
|
||||||
|
exportToPlt(pages());
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="fixed inset-0 bg-black/50 z-50 overflow-auto">
|
<div class="fixed inset-0 bg-black/50 z-50 overflow-auto">
|
||||||
<div class="min-h-screen py-20 px-4">
|
<div class="min-h-screen py-20 px-4">
|
||||||
|
|
@ -46,6 +52,7 @@ export function PrintPreview(props: PrintPreviewProps) {
|
||||||
store={store}
|
store={store}
|
||||||
pageCount={pages().length}
|
pageCount={pages().length}
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
|
onExportPlt={handleExportPlt}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export interface PrintPreviewHeaderProps {
|
||||||
store: DeckStore;
|
store: DeckStore;
|
||||||
pageCount: number;
|
pageCount: number;
|
||||||
onExport: () => void;
|
onExport: () => void;
|
||||||
|
onExportPlt: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +87,13 @@ export function PrintPreviewHeader(props: PrintPreviewHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={props.onExportPlt}
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<span>📐</span>
|
||||||
|
<span>导出 PLT</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={props.onExport}
|
onClick={props.onExport}
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import type { DeckStore } from './deckStore';
|
||||||
|
import type { PageData } from './usePDFExport';
|
||||||
|
import type { CardShape } from '../types';
|
||||||
|
import { pts2plotter } from '../../../plotcutter';
|
||||||
|
|
||||||
|
export interface UsePlotterExportReturn {
|
||||||
|
exportToPlt: (pages: PageData[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据形状生成卡片轮廓点(单位:mm,相对于卡片左下角)
|
||||||
|
*/
|
||||||
|
function getCardShapePoints(
|
||||||
|
shape: CardShape,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
): [number, number][] {
|
||||||
|
const points: [number, number][] = [];
|
||||||
|
|
||||||
|
switch (shape) {
|
||||||
|
case 'circle': {
|
||||||
|
// 圆形:生成 36 个点近似圆
|
||||||
|
const radius = Math.min(width, height) / 2;
|
||||||
|
const centerX = width / 2;
|
||||||
|
const centerY = height / 2;
|
||||||
|
for (let i = 0; i < 36; i++) {
|
||||||
|
const angle = (i / 36) * Math.PI * 2;
|
||||||
|
points.push([
|
||||||
|
centerX + radius * Math.cos(angle),
|
||||||
|
centerY + radius * Math.sin(angle)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'triangle': {
|
||||||
|
// 正三角形:顶点向上
|
||||||
|
points.push([width / 2, 0]);
|
||||||
|
points.push([0, height]);
|
||||||
|
points.push([width, height]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hexagon': {
|
||||||
|
// 六边形:尖顶向上
|
||||||
|
const halfW = width / 2;
|
||||||
|
const quarterH = height / 4;
|
||||||
|
points.push([halfW, 0]);
|
||||||
|
points.push([width, quarterH]);
|
||||||
|
points.push([width, height - quarterH]);
|
||||||
|
points.push([halfW, height]);
|
||||||
|
points.push([0, height - quarterH]);
|
||||||
|
points.push([0, quarterH]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'rectangle':
|
||||||
|
default: {
|
||||||
|
// 矩形:顺时针方向
|
||||||
|
points.push([0, 0]);
|
||||||
|
points.push([width, 0]);
|
||||||
|
points.push([width, height]);
|
||||||
|
points.push([0, height]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PLT 导出 hook - 生成 HPGL 格式文件并下载
|
||||||
|
*/
|
||||||
|
export function usePlotterExport(store: DeckStore): UsePlotterExportReturn {
|
||||||
|
const exportToPlt = (pages: PageData[]) => {
|
||||||
|
const cardWidth = store.state.dimensions?.cardWidth || 56;
|
||||||
|
const cardHeight = store.state.dimensions?.cardHeight || 88;
|
||||||
|
const shape = store.state.shape;
|
||||||
|
|
||||||
|
// 计算所有页面的总尺寸
|
||||||
|
const a4Width = 297; // 横向 A4
|
||||||
|
const a4Height = 210;
|
||||||
|
|
||||||
|
// 收集所有卡片的轮廓点
|
||||||
|
const allPaths: [number, number][][] = [];
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
for (const card of page.cards) {
|
||||||
|
// 只导出正面
|
||||||
|
if (card.side !== 'front') continue;
|
||||||
|
|
||||||
|
// 获取卡片形状点(相对于卡片原点)
|
||||||
|
const shapePoints = getCardShapePoints(shape, cardWidth, cardHeight);
|
||||||
|
|
||||||
|
// 转换点到页面坐标(Y 轴翻转:SVG Y 向下,plotter Y 向上)
|
||||||
|
const pagePoints = shapePoints.map(([x, y]) => [
|
||||||
|
card.x + x,
|
||||||
|
a4Height - (card.y + y)
|
||||||
|
] as [number, number]);
|
||||||
|
|
||||||
|
allPaths.push(pagePoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allPaths.length === 0) {
|
||||||
|
alert('没有可导出的卡片');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 HPGL 指令
|
||||||
|
const plotterCode = pts2plotter(allPaths, a4Width, a4Height, 1);
|
||||||
|
|
||||||
|
// 创建 Blob 并下载
|
||||||
|
const blob = new Blob([plotterCode], { 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { exportToPlt };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue