package layout import ( "printer.backend/internal/model" ) // Footprint returns the physical width and height one item occupies on the plate (mm). // Includes bleed on all sides and the item's margin as a safe zone. func Footprint(spec model.ItemSpec) (width, height float64) { w := spec.SizeMM + 2*spec.BleedMM + 2*spec.MarginMM return w, w } // CellPitch returns width and height of one grid cell (footprint + spacing between items). func CellPitch(spec model.ItemSpec, spacingMM float64) (width, height float64) { fw, fh := Footprint(spec) return fw + spacingMM, fh + spacingMM } // Pack computes the maximum grid of items that fit on the plate. func Pack(plate model.Plate, spec model.ItemSpec, spacingMM float64) model.LayoutPreview { pw := plate.PrintableWidth() ph := plate.PrintableHeight() fw, fh := Footprint(spec) cw, ch := CellPitch(spec, spacingMM) cols, rows := gridCount(pw, ph, cw, ch) count := cols * rows ox := plate.MarginLeft oy := plate.MarginTop positions := make([]model.LayoutPosition, 0, count) for row := 0; row < rows; row++ { for col := 0; col < cols; col++ { positions = append(positions, model.LayoutPosition{ XMM: ox + float64(col)*cw, YMM: oy + float64(row)*ch, }) } } return model.LayoutPreview{ PlateWidthMM: plate.WidthMM, PlateHeightMM: plate.HeightMM, PrintableXMM: ox, PrintableYMM: oy, PrintableWMM: pw, PrintableHMM: ph, CellWidthMM: cw, CellHeightMM: ch, FootprintWMM: fw, FootprintHMM: fh, SpacingMM: spacingMM, Columns: cols, Rows: rows, Count: count, Positions: positions, } } func gridCount(printableW, printableH, cellW, cellH float64) (cols, rows int) { if printableW <= 0 || printableH <= 0 || cellW <= 0 || cellH <= 0 { return 0, 0 } cols = int(printableW / cellW) rows = int(printableH / cellH) if cols < 0 { cols = 0 } if rows < 0 { rows = 0 } return cols, rows }