From 57d2a7ed46b17d4296ee3e1c02a10cf16d8165e6 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 15 Mar 2026 10:14:14 +0800 Subject: [PATCH] refactor: sizing --- .../md-deck/hooks/usePlotterExport.ts | 49 +++++++++++++------ src/plotcutter/layout.ts | 14 +++++- src/plotcutter/types.ts | 24 +++++++++ 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/components/md-deck/hooks/usePlotterExport.ts b/src/components/md-deck/hooks/usePlotterExport.ts index 2fcca49..3e56993 100644 --- a/src/components/md-deck/hooks/usePlotterExport.ts +++ b/src/components/md-deck/hooks/usePlotterExport.ts @@ -5,10 +5,10 @@ import { pts2plotter } from '../../../plotcutter/plotter'; export interface PltExportData { /** 单页满排时的 PLT 代码 */ pltCode: string; - /** A4 宽度 (mm) */ - a4Width: number; - /** A4 高度 (mm) */ - a4Height: number; + /** 排版区域宽度 (mm) */ + frameWidth: number; + /** 排版区域高度 (mm) */ + frameHeight: number; /** 每页卡片数 */ cardsPerPage: number; } @@ -24,8 +24,9 @@ export interface UsePlotterExportReturn { /** * PLT 导出 hook - 生成单页满排时的 HPGL 格式文件 - * + * * 刀路只关心单页排满的情况,不考虑实际牌组的张数。 + * PLT 坐标系统以 frameBoundsWithMargin 左上角为原点 (0,0)。 */ export function usePlotterExport(store: DeckStore): UsePlotterExportReturn { const bleed = () => store.state.bleed || 1; @@ -52,19 +53,39 @@ export function usePlotterExport(store: DeckStore): UsePlotterExportReturn { return null; } - // 生成空走路径 - const travelPaths = generateTravelPaths(layout.cardPaths, layout.a4Height); + // 使用 frameBoundsWithMargin 作为 PLT 的坐标系统 + const frameBounds = layout.frameBoundsWithMargin; + const pltWidth = frameBounds.width; + const pltHeight = frameBounds.height; - // 生成 HPGL 代码(包含空走路径,从左上角出发并返回) - const allPaths = layout.cardPaths.map(p => p.points); - const startPoint: [number, number] = [0, layout.a4Height]; - const endPoint: [number, number] = [0, layout.a4Height]; - const plotterCode = pts2plotter(allPaths, layout.a4Width, layout.a4Height, 1, startPoint, endPoint); + // 将卡片路径转换为相对于 frameBoundsWithMargin 的坐标 + // 原点在 frameBoundsWithMargin 的左上角 + const relativePaths = layout.cardPaths.map(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]; + }); + 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, - a4Width: layout.a4Width, - a4Height: layout.a4Height, + frameWidth: pltWidth, + frameHeight: pltHeight, cardsPerPage: layout.cardsPerPage }; }; diff --git a/src/plotcutter/layout.ts b/src/plotcutter/layout.ts index 9089b2d..6a8d56a 100644 --- a/src/plotcutter/layout.ts +++ b/src/plotcutter/layout.ts @@ -178,7 +178,19 @@ export function calculateSinglePageLayout(options: LayoutOptions): SinglePageLay a4Height, cardsPerRow, rowsPerPage, - cardsPerPage + cardsPerPage, + frameBounds: { + minX: offsetX, + minY: offsetY, + maxX: offsetX + maxGridWidth, + maxY: offsetY + maxGridHeight + }, + frameBoundsWithMargin: { + x: offsetX - 1, + y: offsetY - 1, + width: maxGridWidth + 2, + height: maxGridHeight + 2 + } }; } diff --git a/src/plotcutter/types.ts b/src/plotcutter/types.ts index a9b4ee3..669e2a8 100644 --- a/src/plotcutter/types.ts +++ b/src/plotcutter/types.ts @@ -1,3 +1,23 @@ +/** + * 边界框 + */ +export interface Bounds { + minX: number; + minY: number; + maxX: number; + maxY: number; +} + +/** + * 带边距的边界框(用于裁切标记) + */ +export interface BoundsWithMargin { + x: number; + y: number; + width: number; + height: number; +} + /** * 卡片形状类型 */ @@ -53,6 +73,10 @@ export interface SinglePageLayout { rowsPerPage: number; /** 每页总卡片数 */ cardsPerPage: number; + /** 排版区域边界框 */ + frameBounds: Bounds; + /** 带边距的边界框(用于裁切标记) */ + frameBoundsWithMargin: BoundsWithMargin; } /**