Rewrite LayerPacker as skyline packer, add Extreme Points alternative, manual footprint tuning and reordering UI
- Replace the old layer-grid LayerPacker with a skyline (lowest-slot-first) algorithm: grows the footprint only when items truly can't fit within maxBox, lets shorter types stack multiple sub-layers into taller leftover space, cascades unfinished layers to later types, and allows a staircase profile to minimize wasted volume. - Fix ItemDatabaseEditor NumericUpDown crash (Value set before Minimum/Maximum) and ItemDatabase.Upsert doing INSERT instead of UPDATE (was keyed by Name, now by Id). - Add Packer3D as a working Extreme Points algorithm (was dead/commented-out code) behind a shared IPacker interface, selectable in the UI alongside Skyline. - Add manual footprint (Skyline) / box-size (Extreme Points) adjustment controls for the first box, with correct min/max clamping against the configured max box size. - Add a "Poradie" column with up/down buttons to reorder packing items; both packers now honor that manual order instead of re-sorting by volume. - Drop multi-box packing (single box only), add a pre-pack volume warning, and make unpacked items exportable to PDF / inspectable via a result-label tooltip, both grouped by type with dimensions, count and total volume. - Merge the min/max box dimension groups, hide the now-unused box navigation group, and misc UI cleanup (typo fix, group renames, wider item name column). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+114
-10
@@ -1,23 +1,127 @@
|
||||
/*
|
||||
* PÔVODNÝ ALGORITMUS – zakomentovaný, nahradený LayerPacker
|
||||
*
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BoxPacker3D
|
||||
{
|
||||
public class Packer3D
|
||||
/// <summary>
|
||||
/// Extreme Points packing algorithm — an alternative to <see cref="LayerPacker"/> (skyline).
|
||||
/// Unlike LayerPacker, item types are free to mix at any position; each item is placed
|
||||
/// greedily at the best available candidate point. This trades the tidy, layer-by-layer
|
||||
/// look of skyline packing for potentially tighter volume usage.
|
||||
///
|
||||
/// Same input contract as LayerPacker.Pack: items rotate only around the vertical axis
|
||||
/// (W↔D, H preserved), itemPad separates items, wallPad+wallThickness (edge) separates
|
||||
/// items from the walls, the resulting box is tightened to [minBox, maxBox], and an item
|
||||
/// only ends up unpacked if it truly does not fit anywhere within maxBox. Items are
|
||||
/// processed in whatever order allItems is given (manual priority — e.g. table row
|
||||
/// order in the UI), not sorted by volume.
|
||||
/// </summary>
|
||||
public class Packer3D : IPacker
|
||||
{
|
||||
private const double EPS = 0.001;
|
||||
|
||||
public List<PackedItem> Pack(List<Item> items, Box box,
|
||||
double wallPad = 0, double itemPad = 0, Action<int, int>? onProgress = null,
|
||||
bool allowRotation = true, bool preferRotated = false)
|
||||
public (List<PackedItem> packed, Box box, List<Item> unpacked) Pack(
|
||||
List<Item> allItems, Box minBox, Box maxBox,
|
||||
double wallPad, double itemPad, double wallThickness)
|
||||
{
|
||||
// ... (Extreme Points algorithm)
|
||||
var packed = new List<PackedItem>();
|
||||
var unpacked = new List<Item>();
|
||||
double edge = wallPad + wallThickness;
|
||||
|
||||
if (allItems.Count == 0)
|
||||
return (packed, minBox, unpacked);
|
||||
|
||||
double maxIW = Math.Max(EPS, maxBox.W - 2 * edge);
|
||||
double maxIH = Math.Max(EPS, maxBox.H - 2 * edge);
|
||||
double maxID = Math.Max(EPS, maxBox.D - 2 * edge);
|
||||
|
||||
// Manual priority — whatever order allItems is already in (e.g. table row
|
||||
// order in the UI), same as LayerPacker; no volume-based sorting.
|
||||
var items = allItems;
|
||||
|
||||
// Extreme points: candidate positions where the next item's corner may start.
|
||||
var points = new List<(double X, double Y, double Z)> { (edge, edge, edge) };
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
(double x, double y, double z, double w, double h, double d)? best = null;
|
||||
|
||||
// Prefer low, then near-origin points — keeps the pack compact/stable.
|
||||
foreach (var p in points.OrderBy(p => p.Y).ThenBy(p => p.X).ThenBy(p => p.Z))
|
||||
{
|
||||
foreach (var (iw, ih, id) in Orientations(item))
|
||||
{
|
||||
if (p.X + iw > edge + maxIW + EPS) continue;
|
||||
if (p.Y + ih > edge + maxIH + EPS) continue;
|
||||
if (p.Z + id > edge + maxID + EPS) continue;
|
||||
|
||||
if (Overlaps(packed, p.X, p.Y, p.Z, iw, ih, id, itemPad)) continue;
|
||||
|
||||
best = (p.X, p.Y, p.Z, iw, ih, id);
|
||||
break;
|
||||
}
|
||||
if (best != null) break;
|
||||
}
|
||||
|
||||
if (best == null)
|
||||
{
|
||||
unpacked.Add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
var (bx, by, bz, bw, bh, bd) = best.Value;
|
||||
packed.Add(new PackedItem(item, bx, by, bz, bw, bh, bd));
|
||||
|
||||
// New extreme points projected from the placed item's far corners.
|
||||
points.Add((bx + bw + itemPad, by, bz));
|
||||
points.Add((bx, by + bh + itemPad, bz));
|
||||
points.Add((bx, by, bz + bd + itemPad));
|
||||
}
|
||||
|
||||
if (packed.Count == 0)
|
||||
return (packed, minBox, unpacked);
|
||||
|
||||
var box = TightenBox(packed, edge, minBox, maxBox, wallThickness);
|
||||
return (packed, box, unpacked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two allowed orientations: original and rotated 90° around the vertical axis (W↔D).
|
||||
/// H is always preserved — items cannot be tilted sideways.
|
||||
/// </summary>
|
||||
private static IEnumerable<(double iW, double iH, double iD)> Orientations(Item proto)
|
||||
{
|
||||
yield return (proto.W, proto.H, proto.D);
|
||||
if (Math.Abs(proto.W - proto.D) > EPS)
|
||||
yield return (proto.D, proto.H, proto.W);
|
||||
}
|
||||
|
||||
private static bool Overlaps(
|
||||
List<PackedItem> packed, double x, double y, double z, double w, double h, double d,
|
||||
double itemPad)
|
||||
{
|
||||
foreach (var p in packed)
|
||||
{
|
||||
bool sepX = x + w + itemPad <= p.X + EPS || p.X + p.RW + itemPad <= x + EPS;
|
||||
bool sepY = y + h + itemPad <= p.Y + EPS || p.Y + p.RH + itemPad <= y + EPS;
|
||||
bool sepZ = z + d + itemPad <= p.Z + EPS || p.Z + p.RD + itemPad <= z + EPS;
|
||||
if (!(sepX || sepY || sepZ)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shrinks the box to just enclose all packed items plus edge on every side.
|
||||
/// </summary>
|
||||
private static Box TightenBox(
|
||||
List<PackedItem> packed, double edge,
|
||||
Box minBox, Box maxBox, double wallThickness)
|
||||
{
|
||||
double tw = Math.Clamp(Math.Ceiling(packed.Max(p => p.X + p.RW) + edge), minBox.W, maxBox.W);
|
||||
double th = Math.Clamp(Math.Ceiling(packed.Max(p => p.Y + p.RH) + edge), minBox.H, maxBox.H);
|
||||
double td = Math.Clamp(Math.Ceiling(packed.Max(p => p.Z + p.RD) + edge), minBox.D, maxBox.D);
|
||||
return new Box(tw, th, td, wallThickness);
|
||||
}
|
||||
// ... helper methods
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user