496 lines
22 KiB
C#
496 lines
22 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Linq;
|
||
using System.Windows.Forms;
|
||
|
||
namespace BoxPacker3D
|
||
{
|
||
public class Viewer3D
|
||
{
|
||
private Panel panel;
|
||
|
||
public Viewer3D(Panel panel)
|
||
{
|
||
this.panel = panel;
|
||
panel.Paint += Panel_Paint;
|
||
_packedItems = new List<PackedItem>();
|
||
}
|
||
|
||
private List<PackedItem> _packedItems;
|
||
private Box _box;
|
||
private double _rotX = 30, _rotY = 30, _zoom = 1.0;
|
||
private double _wallPad = 0, _itemPad = 0;
|
||
|
||
public void Clear()
|
||
{
|
||
_packedItems = new List<PackedItem>();
|
||
_box = null;
|
||
panel.Invalidate();
|
||
}
|
||
|
||
public void Render(List<PackedItem> packedItems, Box box,
|
||
double rotX, double rotY, double zoom = 1.0,
|
||
double wallPad = 0, double itemPad = 0)
|
||
{
|
||
_packedItems = packedItems;
|
||
_box = box;
|
||
_rotX = rotX; _rotY = rotY;
|
||
_zoom = Math.Max(0.1, zoom);
|
||
_wallPad = wallPad; _itemPad = itemPad;
|
||
panel.Invalidate();
|
||
}
|
||
|
||
private void Panel_Paint(object sender, PaintEventArgs e)
|
||
{
|
||
var g = e.Graphics;
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
g.Clear(Color.FromArgb(18, 18, 28));
|
||
if (_box == null) { DrawWelcome(g); return; }
|
||
DrawScene(g, panel.Width, panel.Height);
|
||
}
|
||
|
||
private void DrawWelcome(Graphics g)
|
||
{
|
||
int w = panel.Width, h = panel.Height;
|
||
using (var brush = new SolidBrush(Color.FromArgb(40, 40, 60)))
|
||
g.FillRectangle(brush, 0, 0, w, h);
|
||
using (var pen = new Pen(Color.FromArgb(35, 35, 55), 1))
|
||
{
|
||
for (int x = 0; x < w; x += 40) g.DrawLine(pen, x, 0, x, h);
|
||
for (int y = 0; y < h; y += 40) g.DrawLine(pen, 0, y, w, y);
|
||
}
|
||
using (var font = new Font("Segoe UI", 14f))
|
||
using (var brush = new SolidBrush(Color.FromArgb(80, 100, 160)))
|
||
{
|
||
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
|
||
g.DrawString("Zadajte rozmery škatule a kusy,\npotom kliknite 🚀 SPUSTIŤ BALENIE",
|
||
font, brush, new RectangleF(0, 0, w, h), sf);
|
||
}
|
||
}
|
||
|
||
public Bitmap RenderToBitmap(List<PackedItem> packedItems, Box box,
|
||
double rotX, double rotY, double zoom, int width, int height,
|
||
double wallPad = 0, double itemPad = 0, bool whiteBg = false)
|
||
{
|
||
var bmp = new Bitmap(width, height);
|
||
using (var g = Graphics.FromImage(bmp))
|
||
{
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
g.Clear(whiteBg ? Color.White : Color.FromArgb(18, 18, 28));
|
||
DrawSceneEx(g, width, height, packedItems, box, rotX, rotY, zoom, wallPad, itemPad, null, whiteBg);
|
||
}
|
||
return bmp;
|
||
}
|
||
|
||
public Bitmap RenderLayerToBitmap(List<PackedItem> packedItems, Box box,
|
||
double rotX, double rotY, double zoom,
|
||
double layerYMin, double layerYMax,
|
||
int layerIndex, int totalLayers,
|
||
int width, int height,
|
||
double wallPad = 0, double itemPad = 0, bool whiteBg = false)
|
||
{
|
||
var bmp = new Bitmap(width, height);
|
||
using (var g = Graphics.FromImage(bmp))
|
||
{
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
g.Clear(whiteBg ? Color.White : Color.FromArgb(18, 18, 28));
|
||
|
||
var layerItems = packedItems
|
||
.Where(p => p.Y < layerYMax - 0.001 && p.Y + p.RH > layerYMin + 0.001)
|
||
.ToList();
|
||
|
||
DrawSceneEx(g, width, height, layerItems, box, rotX, rotY, zoom, wallPad, itemPad,
|
||
$"Vrstva {layerIndex + 1} / {totalLayers} (Y: {layerYMin:F1} – {layerYMax:F1} mm)", whiteBg);
|
||
|
||
// Draw layer boundary indicators (subtle horizontal planes at YMin and YMax)
|
||
DrawLayerBoundaries(g, width, height, box, layerYMin, layerYMax, rotX, rotY, zoom, whiteBg);
|
||
}
|
||
return bmp;
|
||
}
|
||
|
||
private void DrawLayerBoundaries(Graphics g, int w, int h, Box box,
|
||
double yMin, double yMax, double rotX, double rotY, double zoom, bool whiteBg)
|
||
{
|
||
double radX = rotX * Math.PI / 180.0;
|
||
double radY = rotY * Math.PI / 180.0;
|
||
double maxDim = Math.Max(Math.Max(box.W, box.H), box.D);
|
||
double scale = Math.Min(w, h) * 0.38 / maxDim * zoom;
|
||
double cx = w / 2.0, cy = h / 2.0;
|
||
|
||
// Draw two horizontal planes (YMin and YMax) as subtle dotted lines
|
||
var planeColor = whiteBg ? Color.FromArgb(80, 255, 100, 100) : Color.FromArgb(60, 255, 180, 100);
|
||
using (var pen = new Pen(planeColor, 1f) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot })
|
||
{
|
||
// YMin plane
|
||
DrawHorizontalPlane(g, pen, box, yMin, scale, cx, cy, radX, radY);
|
||
// YMax plane
|
||
DrawHorizontalPlane(g, pen, box, yMax, scale, cx, cy, radX, radY);
|
||
}
|
||
}
|
||
|
||
private void DrawHorizontalPlane(Graphics g, Pen pen, Box box, double y,
|
||
double scale, double cx, double cy, double radX, double radY)
|
||
{
|
||
double yRel = y - box.H / 2; // relative to box center
|
||
double hw = box.W / 2, hd = box.D / 2;
|
||
|
||
// Four corners of the plane
|
||
var p1 = Project(-hw, yRel, -hd, scale, cx, cy, radX, radY);
|
||
var p2 = Project( hw, yRel, -hd, scale, cx, cy, radX, radY);
|
||
var p3 = Project( hw, yRel, hd, scale, cx, cy, radX, radY);
|
||
var p4 = Project(-hw, yRel, hd, scale, cx, cy, radX, radY);
|
||
|
||
// Draw rectangle
|
||
g.DrawLine(pen, p1, p2);
|
||
g.DrawLine(pen, p2, p3);
|
||
g.DrawLine(pen, p3, p4);
|
||
g.DrawLine(pen, p4, p1);
|
||
}
|
||
|
||
private void DrawScene(Graphics g, int w, int h)
|
||
=> DrawSceneEx(g, w, h, _packedItems, _box, _rotX, _rotY, _zoom, _wallPad, _itemPad, null, false);
|
||
|
||
private void DrawSceneEx(Graphics g, int w, int h,
|
||
List<PackedItem> items, Box box,
|
||
double rotX, double rotY, double zoom,
|
||
double wallPad, double itemPad,
|
||
string layerLabel, bool whiteBg)
|
||
{
|
||
double radX = rotX * Math.PI / 180.0;
|
||
double radY = rotY * Math.PI / 180.0;
|
||
|
||
double maxDim = Math.Max(Math.Max(box.W, box.H), box.D);
|
||
double scale = Math.Min(w, h) * 0.38 / maxDim * zoom;
|
||
double cx = w / 2.0, cy = h / 2.0;
|
||
|
||
var sorted = items
|
||
.Select(pi => (pi, depth: Transform(
|
||
pi.X + pi.RW / 2 - box.W / 2,
|
||
pi.Y + pi.RH / 2 - box.H / 2,
|
||
pi.Z + pi.RD / 2 - box.D / 2, radX, radY).Z))
|
||
.OrderBy(t => t.depth)
|
||
.ToList();
|
||
|
||
DrawBoxOutline(g, scale, cx, cy, box, radX, radY, alpha: whiteBg ? 100 : 40, wallPad: wallPad, whiteBg: whiteBg);
|
||
|
||
if (box.WallThickness > 0.001)
|
||
DrawWallThicknessZone(g, scale, cx, cy, box, radX, radY, whiteBg);
|
||
|
||
if (wallPad > 0.001)
|
||
DrawPaddingZone(g, scale, cx, cy, box, radX, radY, wallPad, whiteBg);
|
||
|
||
foreach (var (pi, _) in sorted)
|
||
DrawCuboid(g, pi, scale, cx, cy, box, radX, radY);
|
||
|
||
DrawBoxOutline(g, scale, cx, cy, box, radX, radY, alpha: whiteBg ? 220 : 180, wallPad: wallPad, whiteBg: whiteBg);
|
||
|
||
DrawLegend(g, w, h, items, whiteBg);
|
||
DrawInfo(g, w, h, box, items.Count, whiteBg);
|
||
|
||
if (layerLabel != null)
|
||
{
|
||
using (var font = new Font("Segoe UI", 11f, FontStyle.Bold))
|
||
using (var brush = new SolidBrush(whiteBg ? Color.FromArgb(40, 80, 160) : Color.FromArgb(220, 200, 100, 30)))
|
||
{
|
||
var sf = new StringFormat { Alignment = StringAlignment.Center };
|
||
g.DrawString(layerLabel, font, brush, new RectangleF(0, 8, w, 28), sf);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static (double X, double Y, double Z) Transform(double x, double y, double z,
|
||
double radX, double radY)
|
||
{
|
||
double x1 = x * Math.Cos(radY) + z * Math.Sin(radY);
|
||
double y1 = y;
|
||
double z1 = -x * Math.Sin(radY) + z * Math.Cos(radY);
|
||
double x2 = x1;
|
||
double y2 = y1 * Math.Cos(radX) - z1 * Math.Sin(radX);
|
||
double z2 = y1 * Math.Sin(radX) + z1 * Math.Cos(radX);
|
||
return (x2, y2, z2);
|
||
}
|
||
|
||
private PointF Project(double x, double y, double z,
|
||
double scale, double cx, double cy, double radX, double radY)
|
||
{
|
||
var (tx, ty, _) = Transform(x, y, z, radX, radY);
|
||
return new PointF((float)(cx + tx * scale), (float)(cy - ty * scale));
|
||
}
|
||
|
||
private void DrawCuboid(Graphics g, PackedItem pi,
|
||
double scale, double cx, double cy,
|
||
Box box, double radX, double radY)
|
||
{
|
||
double x0 = pi.X - box.W / 2, y0 = pi.Y - box.H / 2, z0 = pi.Z - box.D / 2;
|
||
double x1 = x0 + pi.RW, y1 = y0 + pi.RH, z1 = z0 + pi.RD;
|
||
|
||
var pts = Corners8(x0, y0, z0, x1, y1, z1, scale, cx, cy, radX, radY);
|
||
var baseColor = pi.Item.Color;
|
||
|
||
DrawFace(g, new[] { pts[4], pts[5], pts[6], pts[7] }, Shade(baseColor, 1.00f));
|
||
DrawFace(g, new[] { pts[5], pts[1], pts[2], pts[6] }, Shade(baseColor, 0.75f));
|
||
DrawFace(g, new[] { pts[3], pts[2], pts[6], pts[7] }, Shade(baseColor, 0.55f));
|
||
|
||
using (var pen = new Pen(Color.FromArgb(200, Color.Black), 0.8f))
|
||
{
|
||
g.DrawPolygon(pen, new[] { pts[4], pts[5], pts[6], pts[7] });
|
||
g.DrawPolygon(pen, new[] { pts[5], pts[1], pts[2], pts[6] });
|
||
g.DrawPolygon(pen, new[] { pts[3], pts[2], pts[6], pts[7] });
|
||
}
|
||
}
|
||
|
||
private void DrawPaddingZone(Graphics g, double scale, double cx, double cy,
|
||
Box box, double radX, double radY, double pad, bool whiteBg)
|
||
{
|
||
double edge = pad + box.WallThickness;
|
||
double hw = box.W / 2 - edge, hh = box.H / 2 - edge, hd = box.D / 2 - edge;
|
||
if (hw <= 0 || hh <= 0 || hd <= 0) return;
|
||
var pts = new PointF[8];
|
||
pts[0] = Project(-hw, -hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[1] = Project(hw, -hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[2] = Project(hw, hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[3] = Project(-hw, hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[4] = Project(-hw, -hh, hd, scale, cx, cy, radX, radY);
|
||
pts[5] = Project(hw, -hh, hd, scale, cx, cy, radX, radY);
|
||
pts[6] = Project(hw, hh, hd, scale, cx, cy, radX, radY);
|
||
pts[7] = Project(-hw, hh, hd, scale, cx, cy, radX, radY);
|
||
|
||
var col = whiteBg ? Color.FromArgb(150, 200, 150, 50) : Color.FromArgb(60, 255, 220, 80);
|
||
using (var pen = new Pen(col, 1f))
|
||
{
|
||
pen.DashStyle = DashStyle.Dot;
|
||
g.DrawPolygon(pen, new[] { pts[0], pts[1], pts[2], pts[3] });
|
||
g.DrawPolygon(pen, new[] { pts[4], pts[5], pts[6], pts[7] });
|
||
g.DrawLine(pen, pts[0], pts[4]); g.DrawLine(pen, pts[1], pts[5]);
|
||
g.DrawLine(pen, pts[2], pts[6]); g.DrawLine(pen, pts[3], pts[7]);
|
||
}
|
||
}
|
||
|
||
/// Draws inner wire cube + corner connectors to visualise the thickness of the box walls.
|
||
private void DrawWallThicknessZone(Graphics g, double scale, double cx, double cy,
|
||
Box box, double radX, double radY, bool whiteBg)
|
||
{
|
||
double t = box.WallThickness;
|
||
double hwO = box.W / 2, hhO = box.H / 2, hdO = box.D / 2;
|
||
double hwI = hwO - t, hhI = hhO - t, hdI = hdO - t;
|
||
if (hwI <= 0 || hhI <= 0 || hdI <= 0) return;
|
||
|
||
// Outer 8 corners (used for connector lines)
|
||
var outer = new PointF[8];
|
||
outer[0] = Project(-hwO, -hhO, -hdO, scale, cx, cy, radX, radY);
|
||
outer[1] = Project( hwO, -hhO, -hdO, scale, cx, cy, radX, radY);
|
||
outer[2] = Project( hwO, hhO, -hdO, scale, cx, cy, radX, radY);
|
||
outer[3] = Project(-hwO, hhO, -hdO, scale, cx, cy, radX, radY);
|
||
outer[4] = Project(-hwO, -hhO, hdO, scale, cx, cy, radX, radY);
|
||
outer[5] = Project( hwO, -hhO, hdO, scale, cx, cy, radX, radY);
|
||
outer[6] = Project( hwO, hhO, hdO, scale, cx, cy, radX, radY);
|
||
outer[7] = Project(-hwO, hhO, hdO, scale, cx, cy, radX, radY);
|
||
|
||
// Inner 8 corners
|
||
var inner = new PointF[8];
|
||
inner[0] = Project(-hwI, -hhI, -hdI, scale, cx, cy, radX, radY);
|
||
inner[1] = Project( hwI, -hhI, -hdI, scale, cx, cy, radX, radY);
|
||
inner[2] = Project( hwI, hhI, -hdI, scale, cx, cy, radX, radY);
|
||
inner[3] = Project(-hwI, hhI, -hdI, scale, cx, cy, radX, radY);
|
||
inner[4] = Project(-hwI, -hhI, hdI, scale, cx, cy, radX, radY);
|
||
inner[5] = Project( hwI, -hhI, hdI, scale, cx, cy, radX, radY);
|
||
inner[6] = Project( hwI, hhI, hdI, scale, cx, cy, radX, radY);
|
||
inner[7] = Project(-hwI, hhI, hdI, scale, cx, cy, radX, radY);
|
||
|
||
// Cardboard-brown tones (slightly darker on white backgrounds for print visibility)
|
||
var innerCol = whiteBg ? Color.FromArgb(220, 120, 80, 40)
|
||
: Color.FromArgb(200, 180, 140, 90);
|
||
var connCol = whiteBg ? Color.FromArgb(160, 140, 95, 55)
|
||
: Color.FromArgb(140, 180, 140, 90);
|
||
|
||
// Inner wire cube (solid line)
|
||
using (var pen = new Pen(innerCol, 1.3f))
|
||
{
|
||
g.DrawPolygon(pen, new[] { inner[0], inner[1], inner[2], inner[3] });
|
||
g.DrawPolygon(pen, new[] { inner[4], inner[5], inner[6], inner[7] });
|
||
g.DrawLine(pen, inner[0], inner[4]); g.DrawLine(pen, inner[1], inner[5]);
|
||
g.DrawLine(pen, inner[2], inner[6]); g.DrawLine(pen, inner[3], inner[7]);
|
||
}
|
||
|
||
// 8 corner connectors (outer corner → inner corner)
|
||
using (var pen = new Pen(connCol, 0.9f))
|
||
{
|
||
for (int i = 0; i < 8; i++)
|
||
g.DrawLine(pen, outer[i], inner[i]);
|
||
}
|
||
}
|
||
|
||
private void DrawBoxOutline(Graphics g, double scale, double cx, double cy,
|
||
Box box, double radX, double radY, int alpha, double wallPad, bool whiteBg)
|
||
{
|
||
double hw = box.W / 2, hh = box.H / 2, hd = box.D / 2;
|
||
var pts = new PointF[8];
|
||
pts[0] = Project(-hw, -hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[1] = Project(hw, -hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[2] = Project(hw, hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[3] = Project(-hw, hh, -hd, scale, cx, cy, radX, radY);
|
||
pts[4] = Project(-hw, -hh, hd, scale, cx, cy, radX, radY);
|
||
pts[5] = Project(hw, -hh, hd, scale, cx, cy, radX, radY);
|
||
pts[6] = Project(hw, hh, hd, scale, cx, cy, radX, radY);
|
||
pts[7] = Project(-hw, hh, hd, scale, cx, cy, radX, radY);
|
||
|
||
var col = whiteBg ? Color.FromArgb(alpha, 40, 80, 180) : Color.FromArgb(alpha, 100, 200, 255);
|
||
using (var pen = new Pen(col, 1.5f))
|
||
{
|
||
pen.DashStyle = DashStyle.Dash;
|
||
g.DrawPolygon(pen, new[] { pts[0], pts[1], pts[2], pts[3] });
|
||
g.DrawPolygon(pen, new[] { pts[4], pts[5], pts[6], pts[7] });
|
||
g.DrawLine(pen, pts[0], pts[4]); g.DrawLine(pen, pts[1], pts[5]);
|
||
g.DrawLine(pen, pts[2], pts[6]); g.DrawLine(pen, pts[3], pts[7]);
|
||
}
|
||
|
||
if (alpha >= 100)
|
||
{
|
||
using var dimFont = new Font("Segoe UI", 11f, FontStyle.Bold);
|
||
var colW = whiteBg ? Color.FromArgb(200, 190, 50, 50) : Color.FromArgb(220, 255, 110, 110);
|
||
var colH = whiteBg ? Color.FromArgb(200, 50, 150, 50) : Color.FromArgb(220, 110, 230, 110);
|
||
var colD = whiteBg ? Color.FromArgb(200, 50, 50, 190) : Color.FromArgb(220, 110, 130, 255);
|
||
|
||
// midpoint helpers
|
||
PointF Mid(PointF a, PointF b) => new PointF((a.X + b.X) / 2, (a.Y + b.Y) / 2);
|
||
|
||
void DrawDimLabel(string text, PointF pos, Color dimCol)
|
||
{
|
||
var sz = g.MeasureString(text, dimFont);
|
||
float tx = pos.X - sz.Width / 2;
|
||
float ty = pos.Y - sz.Height / 2;
|
||
// dark background rect for readability
|
||
var bgCol = whiteBg ? Color.FromArgb(180, 255, 255, 255) : Color.FromArgb(180, 20, 20, 30);
|
||
using var bgBr = new SolidBrush(bgCol);
|
||
g.FillRectangle(bgBr, tx - 2, ty - 1, sz.Width + 4, sz.Height + 2);
|
||
using var br = new SolidBrush(dimCol);
|
||
g.DrawString(text, dimFont, br, tx, ty);
|
||
}
|
||
|
||
// šírka: midpoint of bottom-front edge, offset down
|
||
var midW = Mid(pts[0], pts[1]);
|
||
DrawDimLabel($"šírka {box.W:F0}", new PointF(midW.X, midW.Y + 24), colW);
|
||
|
||
// dĺžka: midpoint of bottom-right edge, offset right+down
|
||
var midD = Mid(pts[1], pts[5]);
|
||
DrawDimLabel($"dĺžka {box.D:F0}", new PointF(midD.X + 24, midD.Y + 10), colD);
|
||
|
||
// výška: midpoint of right-front edge, offset right
|
||
var midH = Mid(pts[1], pts[2]);
|
||
DrawDimLabel($"výška {box.H:F0}", new PointF(midH.X + 30, midH.Y), colH);
|
||
}
|
||
}
|
||
|
||
private PointF[] Corners8(double x0, double y0, double z0,
|
||
double x1, double y1, double z1,
|
||
double scale, double cx, double cy, double radX, double radY)
|
||
{
|
||
return new[]
|
||
{
|
||
Project(x0, y0, z0, scale, cx, cy, radX, radY),
|
||
Project(x1, y0, z0, scale, cx, cy, radX, radY),
|
||
Project(x1, y1, z0, scale, cx, cy, radX, radY),
|
||
Project(x0, y1, z0, scale, cx, cy, radX, radY),
|
||
Project(x0, y0, z1, scale, cx, cy, radX, radY),
|
||
Project(x1, y0, z1, scale, cx, cy, radX, radY),
|
||
Project(x1, y1, z1, scale, cx, cy, radX, radY),
|
||
Project(x0, y1, z1, scale, cx, cy, radX, radY),
|
||
};
|
||
}
|
||
|
||
private static void DrawFace(Graphics g, PointF[] corners, Color color)
|
||
{
|
||
using (var brush = new SolidBrush(Color.FromArgb(210, color)))
|
||
g.FillPolygon(brush, corners);
|
||
}
|
||
|
||
private static Color Shade(Color c, float f) => Color.FromArgb(
|
||
Math.Min(255, (int)(c.R * f)),
|
||
Math.Min(255, (int)(c.G * f)),
|
||
Math.Min(255, (int)(c.B * f)));
|
||
|
||
private void DrawLegend(Graphics g, int w, int h, List<PackedItem> items, bool whiteBg)
|
||
{
|
||
if (items == null || items.Count == 0) return;
|
||
var unique = items
|
||
.GroupBy(p => p.Item.Name)
|
||
.Select(grp => (grp.Key, grp.First().Item.Color, grp.Count()))
|
||
.ToList();
|
||
|
||
int lx = 10, ly = 10;
|
||
var txtCol = whiteBg ? Color.Black : Color.White;
|
||
using (var titleFont = new Font("Segoe UI", 8.5f, FontStyle.Bold))
|
||
using (var itemFont = new Font("Segoe UI", 8f))
|
||
{
|
||
using (var b = new SolidBrush(txtCol))
|
||
g.DrawString("Legenda:", titleFont, b, lx, ly);
|
||
ly += 18;
|
||
foreach (var (name, color, cnt) in unique)
|
||
{
|
||
using (var b = new SolidBrush(color))
|
||
g.FillRectangle(b, lx, ly, 12, 12);
|
||
using (var p = new Pen(Color.Black, 0.5f))
|
||
g.DrawRectangle(p, lx, ly, 12, 12);
|
||
using (var b = new SolidBrush(txtCol))
|
||
g.DrawString($"{name} (×{cnt})", itemFont, b, lx + 16, ly);
|
||
ly += 16;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void DrawInfo(Graphics g, int w, int h, Box box, int count, bool whiteBg)
|
||
{
|
||
string info = $"Škatuľa: {box.W} × {box.D} × {box.H} mm | Kusov: {count}";
|
||
var txtCol = whiteBg ? Color.FromArgb(60, 60, 60) : Color.FromArgb(120, 120, 140);
|
||
using (var font = new Font("Segoe UI", 8.5f))
|
||
using (var brush = new SolidBrush(txtCol))
|
||
{
|
||
var sf = new StringFormat { Alignment = StringAlignment.Center };
|
||
g.DrawString(info, font, brush, new RectangleF(0, h - 22, w, 20), sf);
|
||
}
|
||
}
|
||
|
||
public static List<(double YMin, double YMax)> GetLayers(List<PackedItem> packed)
|
||
{
|
||
if (packed == null || packed.Count == 0) return new List<(double, double)>();
|
||
|
||
var yVals = new SortedSet<double>();
|
||
yVals.Add(0);
|
||
foreach (var p in packed)
|
||
{
|
||
yVals.Add(Math.Round(p.Y, 3));
|
||
yVals.Add(Math.Round(p.Y + p.RH, 3));
|
||
}
|
||
|
||
var sorted = yVals.ToList();
|
||
var layers = new List<(double, double)>();
|
||
for (int i = 0; i < sorted.Count - 1; i++)
|
||
{
|
||
double yMin = sorted[i], yMax = sorted[i + 1];
|
||
bool hasItem = packed.Any(p =>
|
||
p.Y < yMax - 0.001 && p.Y + p.RH > yMin + 0.001);
|
||
if (hasItem)
|
||
layers.Add((yMin, yMax));
|
||
}
|
||
|
||
var merged = new List<(double, double)>();
|
||
if (layers.Count == 0) return merged;
|
||
double curMin = layers[0].Item1, curMax = layers[0].Item2;
|
||
for (int i = 1; i < layers.Count; i++)
|
||
{
|
||
bool newItemStartsHere = packed.Any(p => Math.Abs(p.Y - layers[i].Item1) < 0.001);
|
||
if (newItemStartsHere)
|
||
{
|
||
merged.Add((curMin, curMax));
|
||
curMin = layers[i].Item1;
|
||
}
|
||
curMax = layers[i].Item2;
|
||
}
|
||
merged.Add((curMin, curMax));
|
||
return merged;
|
||
}
|
||
}
|
||
}
|