Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copilot Instructions

## Project Guidelines
- User prefers clean, minimalist code changes and asks to only implement exactly what was requested without extra additions.
26 changes: 23 additions & 3 deletions App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Serilog;
using Stack_Solver.Data;
using Stack_Solver.Data.Repositories;
using Stack_Solver.Infrastructure;
using Stack_Solver.Models;
using Stack_Solver.Models.Inputs;
using Stack_Solver.Services;
using Stack_Solver.Validation;
using Stack_Solver.ViewModels.Pages;
using Stack_Solver.ViewModels.Windows;
using Stack_Solver.Views.Pages;
Expand All @@ -31,6 +35,10 @@ public partial class App
// https://docs.microsoft.com/dotnet/core/extensions/logging
private static readonly IHost _host = Host
.CreateDefaultBuilder()
.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(context.Configuration);
})
.ConfigureAppConfiguration(c =>
{
c.SetBasePath(Path.GetDirectoryName(AppContext.BaseDirectory)!);
Expand Down Expand Up @@ -67,7 +75,9 @@ public partial class App
sp.GetRequiredService<IEventAggregator>(),
sp.GetRequiredService<ILayerVisualizationService>(),
sp.GetRequiredService<IOptions<GenerationOptions>>(),
sp.GetRequiredService<IOptions<PalletDefaultsOptions>>()));
sp.GetRequiredService<IOptions<PalletDefaultsOptions>>(),
sp.GetRequiredService<IValidator<PalletSettingsDto>>(),
sp.GetRequiredService<IValidator<SkuQuantityDto>>()));
services.AddSingleton<TruckLoadingPage>();
services.AddSingleton<TruckLoadingViewModel>();
services.AddSingleton<JobManagerPage>();
Expand All @@ -76,6 +86,8 @@ public partial class App
services.AddSingleton<IEventAggregator, EventAggregator>();
services.AddSingleton<ILayerVisualizationService, LayerVisualizationService>();

services.AddValidatorsFromAssemblyContaining<SkuInputDtoValidator>();

services.AddDbContextFactory<ApplicationDbContext>(options =>
{
options.UseSqlite($"Data Source={AppPaths.DatabaseFile}");
Expand All @@ -102,29 +114,37 @@ public static IServiceProvider Services
/// </summary>
private async void OnStartup(object sender, StartupEventArgs e)
{
Log.Information("Application starting");

await _host.StartAsync();
Log.Information("Host started");

// Initialize database
var dbInit = Services.GetRequiredService<DatabaseInitializer>();
await dbInit.InitializeAsync();
Log.Information("Database initialized");
}

/// <summary>
/// Occurs when the application is closing.
/// </summary>
private async void OnExit(object sender, ExitEventArgs e)
{
Log.Information("Application shutting down");
await _host.StopAsync();
Log.Information("Host stopped");

_host.Dispose();
Log.CloseAndFlush();
}


/// <summary>
/// Occurs when an exception is thrown by an application but not handled.
/// </summary>
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
// For more info see https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception?view=windowsdesktop-6.0
Log.Error(e.Exception, "Unhandled dispatcher exception");
}
}
}
Binary file removed Box.ico
Binary file not shown.
24 changes: 22 additions & 2 deletions Data/Repositories/SkuRepository.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Stack_Solver.Models;

namespace Stack_Solver.Data.Repositories
{
public class SkuRepository(IDbContextFactory<ApplicationDbContext> factory) : ISkuRepository
public class SkuRepository(IDbContextFactory<ApplicationDbContext> factory, ILogger<SkuRepository> logger) : ISkuRepository
{
public event EventHandler<SKU>? SkuAdded;
public event EventHandler<SKU>? SkuUpdated;
Expand All @@ -26,6 +27,10 @@ public async Task AddAsync(SKU sku, CancellationToken ct = default)
using var db = await factory.CreateDbContextAsync(ct);
db.Skus.Add(sku);
await db.SaveChangesAsync(ct);
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("SKU added: {SkuId}", sku.SkuId);
}
SkuAdded?.Invoke(this, sku);
}

Expand All @@ -34,19 +39,34 @@ public async Task UpdateAsync(SKU sku, CancellationToken ct = default)
using var db = await factory.CreateDbContextAsync(ct);
db.Skus.Update(sku);
await db.SaveChangesAsync(ct);
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("SKU updated: {SkuId}", sku.SkuId);
}
SkuUpdated?.Invoke(this, sku);
}

public async Task DeleteAsync(string skuId, CancellationToken ct = default)
{
using var db = await factory.CreateDbContextAsync(ct);
var entity = await db.Skus.FindAsync(new object?[] { skuId }, ct);
var entity = await db.Skus.FindAsync([skuId], ct);
if (entity != null)
{
db.Skus.Remove(entity);
await db.SaveChangesAsync(ct);
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("SKU deleted: {SkuId}", skuId);
}
SkuDeleted?.Invoke(this, skuId);
}
else
{
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug("Delete skipped for missing SKU: {SkuId}", skuId);
}
}
}

public async Task<int> SaveChangesAsync(CancellationToken ct = default)
Expand Down
9 changes: 7 additions & 2 deletions Models/AppConfig.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
namespace Stack_Solver.Models
{
/// <summary>
/// Represents the application configuration settings.
/// </summary>
/// <remarks>This class requires the specification of both the configurations folder and the application
/// properties file name to function correctly.</remarks>
public class AppConfig
{
public string ConfigurationsFolder { get; set; }
public required string ConfigurationsFolder { get; set; }

public string AppPropertiesFileName { get; set; }
public required string AppPropertiesFileName { get; set; }
}
}
4 changes: 4 additions & 0 deletions Models/GenerationOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace Stack_Solver.Models
{
/// <summary>
/// Provides configuration options for the generation process.
/// </summary>
/// <remarks>This class stores the generation parameters for easier accessibility.</remarks>
public class GenerationOptions
{
public int MaxSolverTime { get; set; }
Expand Down
16 changes: 16 additions & 0 deletions Models/Inputs/PalletSettingsDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Stack_Solver.Models.Inputs
{
public class PalletSettingsDto
{
public int PalletLength { get; set; }
public int PalletWidth { get; set; }
public double PalletHeight { get; set; }
public bool UseCpsat { get; set; }
public int MaxCpsatCandidates { get; set; }
public int BlfAttempts { get; set; }
public int SolverTimeLimit { get; set; }
public int MaxStackHeight { get; set; }
public int MaxStackWeight { get; set; }
public double MaxSkuOverhang { get; set; }
}
}
13 changes: 13 additions & 0 deletions Models/Inputs/SkuInputDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Stack_Solver.Models.Inputs
{
public class SkuInputDto
{
public required string Name { get; set; }
public int Length { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public double Weight { get; set; }
public bool Rotatable { get; set; }
public string? Notes { get; set; }
}
}
8 changes: 8 additions & 0 deletions Models/Inputs/SkuQuantityDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Stack_Solver.Models.Inputs
{
public class SkuQuantityDto
{
public required string SkuId { get; set; }
public int Quantity { get; set; }
}
}
3 changes: 3 additions & 0 deletions Models/Layering/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Stack_Solver.Models.Layering
{
/// <summary>
/// A single layer in a stacking solution, containing positioned items, metadata and geometry information.
/// </summary>
public class Layer(string name, List<PositionedItem> items, LayerMetadata metadata)
{
public string Id { get; set; } = Guid.NewGuid().ToString();
Expand Down
29 changes: 25 additions & 4 deletions Models/Layering/LayerGeometry.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
namespace Stack_Solver.Models.Layering
{
/// <summary>
/// Represents the geometric configuration of a layer, including its dimensions, occupancy grid, and item placement
/// information.
/// </summary>
/// <param name="width">The number of columns in the layer's grid. Must be a positive integer.</param>
/// <param name="length">The number of rows in the layer's grid. Must be a positive integer.</param>
public class LayerGeometry(int width, int length)
{
public int Width { get; set; } = width;
public int Length { get; set; } = length;
public int Width { get; } = width;
public int Length { get; } = length;
public int GridStep { get; set; } = 1;

public bool[,] OccupancyGrid { get; set; } = new bool[width, length];
public bool[,] OccupancyGrid { get; } = new bool[width, length];
public int[,] ItemIndexGrid { get; } = CreateItemIndexGrid(width, length);

public List<Rect> ItemRectangles { get; set; } = [];
public List<Rect> ItemRectangles { get; } = [];

private static int[,] CreateItemIndexGrid(int width, int length)
{
var grid = new int[width, length];
for (int y = 0; y < width; y++)
{
for (int x = 0; x < length; x++)
{
grid[y, x] = -1;
}
}
return grid;
}
}
}
7 changes: 7 additions & 0 deletions Models/Layering/LayerSupportMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Stack_Solver.Models.Layering
{
/// <summary>
/// Summarizes how much of an upper layer lacks direct support from the layer beneath it.
/// </summary>
public readonly record struct LayerSupportMetrics(double TotalUnsupportedArea, double MaximumSkuOverhangArea);
}
9 changes: 9 additions & 0 deletions Models/Layering/PositionedItem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
namespace Stack_Solver.Models.Layering
{
/// <summary>
/// Represents an item placed at a specific position in a 2D layer, defined by its SKU type,
/// coordinates, and whether it's rotated or not.
/// </summary>
/// <param name="skuType">The SKU type.</param>
/// <param name="x">The x-coordinate of the item's position in 2D.</param>
/// <param name="y">The y-coordinate of the item's position in 2D.</param>
/// <param name="rotated">A value indicating whether the item is rotated. If <see langword="true"/>, the item's length and width are
/// swapped when determining its span.</param>
public class PositionedItem(SKU skuType, int x, int y, bool rotated)
{
public SKU SkuType { get; set; } = skuType;
Expand Down
3 changes: 3 additions & 0 deletions Models/Metadata/LayerMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace Stack_Solver.Models.Metadata
{
/// <summary>
/// Metadata about a specific layer in a stacking solution.
/// </summary>
public class LayerMetadata(double utilization, int height, string description)
{
public double Utilization { get; set; } = utilization;
Expand Down
3 changes: 3 additions & 0 deletions Models/Metadata/PalletMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace Stack_Solver.Models.Metadata
{
/// <summary>
/// Represents metadata for a pallet.
/// </summary>
public class PalletMetadata
{
public double TotalWeight { get; set; }
Expand Down
4 changes: 4 additions & 0 deletions Models/PalletDefaultsOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace Stack_Solver.Models
{
/// <summary>
/// Represents the default configuration options for pallet properties, including dimensions and weight limits.
/// </summary>
public class PalletDefaultsOptions
{
public string? DefaultCatalog { get; set; }
Expand All @@ -9,5 +12,6 @@ public class PalletDefaultsOptions
public double PalletHeight { get; set; } = 14.4;
public int MaxStackHeight { get; set; } = 180;
public int MaxStackWeight { get; set; } = 950;
public double MaxSkuOverhang { get; set; } = 0;
}
}
6 changes: 5 additions & 1 deletion Models/SKU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

namespace Stack_Solver.Models
{
/// <summary>
/// Represents a generic stock keeping unit (SKU) type with properties for identification, physical dimensions, weight,
/// rotatability, etc.
/// </summary>
public class SKU
{
[Key]
public string SkuId { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; }
public required string Name { get; set; }
public int Length { get; set; }
public int Width { get; set; }
public int Height { get; set; }
Expand Down
7 changes: 7 additions & 0 deletions Models/Supports/Pallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

namespace Stack_Solver.Models.Supports
{
/// <summary>
/// Represents a pallet that provides a surface for stacking multiple layers of items.
/// </summary>
/// <param name="name">The unique identifier for the pallet, used to distinguish it from other pallets.</param>
/// <param name="length">The length of the pallet.</param>
/// <param name="width">The width of the pallet.</param>
/// <param name="height">The height of the pallet.</param>
public class Pallet(string name, int length, int width, int height) : SupportSurface(name, length, width, height)
{
List<Layer> Layers { get; } = [];
Expand Down
7 changes: 7 additions & 0 deletions Models/Supports/SupportSurface.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
namespace Stack_Solver.Models.Supports
{
/// <summary>
/// Represents a generic support surface used for stacking cargo.
/// </summary>
/// <param name="name">The name of the support surface.</param>
/// <param name="length">The length of the support surface.</param>
/// <param name="width">The width of the support surface.</param>
/// <param name="height">The height of the support surface (without the cargo).</param>
public abstract class SupportSurface(string name, int length, int width, int height)
{
public string Id { get; set; } = Guid.NewGuid().ToString();
Expand Down
Binary file added Resources/Fonts/Unbounded-Black.ttf
Binary file not shown.
Binary file added Resources/Fonts/Unbounded-Bold.ttf
Binary file not shown.
Binary file added Resources/Fonts/Unbounded-ExtraLight.ttf
Binary file not shown.
Binary file added Resources/Fonts/Unbounded-Light.ttf
Binary file not shown.
Binary file added Resources/Fonts/Unbounded-Medium.ttf
Binary file not shown.
Binary file added Resources/Fonts/Unbounded-Regular.ttf
Binary file not shown.
Loading
Loading