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
66 changes: 66 additions & 0 deletions Helpers/Behaviors/ParentScrollBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Stack_Solver.Helpers.Behaviors;

/// <summary>
/// Attached behavior that forwards mouse wheel events to the nearest scrollable parent <see cref="ScrollViewer"/>.
/// Use this when nested controls (DataGrid, NumberBox, etc.) consume wheel events and prevent parent scrolling.
/// </summary>
public static class ParentScrollBehavior
{
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(ParentScrollBehavior),
new PropertyMetadata(false, OnEnabledChanged));

public static bool GetEnabled(DependencyObject obj) => (bool)obj.GetValue(EnabledProperty);
public static void SetEnabled(DependencyObject obj, bool value) => obj.SetValue(EnabledProperty, value);

private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not UIElement element)
return;

if ((bool)e.NewValue)
{
element.AddHandler(UIElement.PreviewMouseWheelEvent, new MouseWheelEventHandler(OnPreviewMouseWheel), handledEventsToo: true);
}
else
{
element.RemoveHandler(UIElement.PreviewMouseWheelEvent, new MouseWheelEventHandler(OnPreviewMouseWheel));
}
}

private static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (FindScrollableParent(sender as DependencyObject) is { } scrollViewer)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}

private static ScrollViewer? FindScrollableParent(DependencyObject? element)
{
var parent = element is null ? null : VisualTreeHelper.GetParent(element);

while (parent is not null)
{
if (parent is ScrollViewer sv && CanScroll(sv))
return sv;

parent = VisualTreeHelper.GetParent(parent);
}

return null;
}

private static bool CanScroll(ScrollViewer sv) =>
sv.ScrollableHeight > 0 ||
(sv.VerticalScrollBarVisibility != ScrollBarVisibility.Disabled &&
sv.ComputedVerticalScrollBarVisibility == Visibility.Visible);
}
59 changes: 59 additions & 0 deletions Helpers/Layering/LayerCandidateHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Stack_Solver.Models.Layering;

namespace Stack_Solver.Helpers.Layering
{
public static class LayerCandidateHelper
{
private const double UtilizationTolerance = 1e-6;

public static List<Layer> SelectBestBySkuCounts(IEnumerable<Layer> layers)
{
if (layers == null)
return [];

var bestByKey = new Dictionary<string, Layer>(StringComparer.Ordinal);

foreach (var layer in layers)
{
if (layer == null)
continue;

var key = BuildSkuCountKey(layer);
if (!bestByKey.TryGetValue(key, out var incumbent) || IsBetter(layer, incumbent))
bestByKey[key] = layer;
}

return [.. bestByKey.Values
.OrderByDescending(l => l.Metadata.Utilization)
.ThenByDescending(l => l.Items.Count)];
}

public static bool IsBetter(Layer candidate, Layer incumbent, double tolerance = UtilizationTolerance)
{
if (incumbent == null)
return true;
if (candidate == null)
return false;

double delta = candidate.Metadata.Utilization - incumbent.Metadata.Utilization;
if (delta > tolerance)
return true;

if (Math.Abs(delta) <= tolerance)
return candidate.Items.Count > incumbent.Items.Count;

return false;
}

public static string BuildSkuCountKey(Layer layer)
{
if (layer == null)
return string.Empty;

return string.Join(",", layer.Items
.GroupBy(i => i.SkuType.SkuId)
.OrderBy(g => g.Key, StringComparer.Ordinal)
.Select(g => $"{g.Key}:{g.Count()}"));
}
}
}
31 changes: 31 additions & 0 deletions Helpers/Layering/SkuVariantFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Stack_Solver.Models;

namespace Stack_Solver.Helpers.Layering
{
public readonly record struct SkuVariant(string VariantId, SKU Sku, int SpanX, int SpanY, bool Rotated);

public static class SkuVariantFactory
{
public static List<SkuVariant> CreateAllOrientations(IEnumerable<SKU>? skus)
{
var variants = new List<SkuVariant>();
if (skus == null)
return variants;

foreach (var sku in skus)
{
if (sku == null)
continue;

variants.Add(new SkuVariant($"{sku.SkuId}_n", sku, sku.Length, sku.Width, false));

if (sku.Rotatable && sku.Length != sku.Width)
{
variants.Add(new SkuVariant($"{sku.SkuId}_r", sku, sku.Width, sku.Length, true));
}
}

return variants;
}
}
}
11 changes: 7 additions & 4 deletions Models/GenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
public class GenerationOptions
{
public int MaxSolverTime { get; set; }
public int MaxCandidates { get; set; }
public int MaxCPSATCandidates { get; set; }

public int BLFAttempts { get; set; }

public GenerationOptions() { }

public GenerationOptions(int maxSolverTime, int maxCandidates)
public GenerationOptions(int maxSolverTime, int maxCandidates, int blfAttempts)
{
MaxSolverTime = maxSolverTime;
MaxCandidates = maxCandidates;
MaxCPSATCandidates = maxCandidates;
BLFAttempts = blfAttempts;
}

public static GenerationOptions From(GenerationOptions? source)
{
if (source == null) return new GenerationOptions();
return new GenerationOptions(source.MaxSolverTime, source.MaxCandidates);
return new GenerationOptions(source.MaxSolverTime, source.MaxCPSATCandidates, source.BLFAttempts);
}
}
}
Loading
Loading