ListBox is a native Win32 control so ControlStyles.OptimizedDoubleBuffer
had no effect. The erase-then-redraw cycle on each Invalidate() caused
visible flashing. Suppressing WM_ERASEBKGND is safe because OnDrawItem
already fills the complete item bounds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UpdateRemovePlateButton() was only called from PlateListChanged,
not CurrentPlateChanged, so the button stayed disabled after switching
away from the sentinel plate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes to TryUpgradeOrNewPlate and a new post-pass:
1. Change ShouldUpgrade from < to <= so upgrade wins when costs are
tied (e.g., all zero) — previously 0 < 0 was always false
2. Guard against "upgrades" that shrink a dimension — when options are
sorted by cost and costs are equal, the next option may have a
smaller length despite higher width (e.g., 72x96 after 60x144)
3. Revert plate size when upgrade fill fails — the plate was being
resized before confirming parts fit, leaving it at the wrong size
4. Add TryConsolidateTailPlates post-pass: after all nesting, find the
lowest-utilization new plate and try to absorb its parts into
another plate via upgrade. Eliminates wasteful tail plates (e.g.,
a 48x96 plate at 21% util for 2 parts that fit in upgraded space).
Real nest file: 6 plates → 5 plates, all 43 parts placed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert static class to instance with private constructor; shared
parameters (template, plateOptions, salvageRate, minRemnantSize,
progress, token) become fields, eliminating parameter threading
across all private methods (10→3 params on Nest entry point stays
unchanged; private methods drop from 7-9 params to 1-2)
- Extract FillAndPlace helper consolidating the repeated
clone→fill→add-to-plate→deduct-quantity pattern (was duplicated
in 4 call sites)
- Merge FindScrapZones/FindViableRemnants (98% duplicate) into single
FindRemnants(plate, minRemnantSize, scrapOnly) method
- Extract ScoreZone helper and collapse duplicate normal/rotated
orientation checks into single conditional
- Extract CreateNewPlateResult helper for repeated PlateResult
construction + PlateOption lookup pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The consolidation pass was iterating stale remnant lists after placing
parts, causing overlapping placements. Now recalculates remnants from
the plate after each fill operation. Also added plate options to the
real nest file integration test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small parts no longer create their own plates during the main pass.
Instead they're deferred to the consolidation pass which fills them
into remaining space on existing plates, packing multiple drawing
types together. Drops from 9 plates to 4 on the test nest file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After the main single-pass placement, leftover items are now packed
together using the engine's multi-item Nest()/PackArea() methods
instead of creating one plate per drawing. First tries packing into
remaining space on existing plates, then creates shared plates for
anything still remaining.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, parts classified as "Large" skipped all existing plates
and always created new ones. This caused one-unique-part-per-plate
behavior since most parts exceed half the plate dimension. Now large
parts search viable remnants on existing plates before creating new
ones, matching the intended part-first behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires part-first mode from AutoNestForm into RunAutoNestAsync: reads
PartFirstMode, SortOrder, MinRemnantSize, and AllowPlateCreation from
the form, passes them through to a new part-first branch that delegates
to MultiPlateNester.Nest instead of the plate-first loop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Small parts must only go into scrap zones (both dims < minRemnantSize)
to preserve viable remnants. The implementer had inverted this, giving
small parts access to all remnants. Also fixed the test to verify
remnant preservation behavior and removed unused FindAllRemnants helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the main Nest() method that ties together sorting,
classification, and placement across multiple plates. The method
processes items largest-first, placing medium/small parts into
remnant zones on existing plates before creating new ones. Includes
private helpers: TryPlaceOnExistingPlates, PlaceOnNewPlates,
TryUpgradeOrNewPlate, FindAllRemnants, and CloneItem.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds IsScrapRemnant(), FindScrapZones(), and FindViableRemnants() to
MultiPlateNester. A remnant is scrap only when both dimensions fall
below the minimum remnant size threshold (AND logic, not OR).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces PartClass enum and Classify() static method that categorizes
parts as Large (exceeds half work area in either dimension), Medium
(area > 1/9 work area), or Small.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements static MultiPlateNester.SortItems with BoundingBoxArea and Size sort orders, covered by two passing xUnit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BOM import was skipping BendDetectorRegistry.AutoDetect and
Bend.UpdateEtchEntities, so parts imported via BOM had no etch
or bend lines. Now matches the CadConverterForm import behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an item was too large for every plate option, its dimensions dominated
the global min-dimension filter, causing all candidate plates to be rejected.
This made auto-nesting exit immediately with no results even when the other
items could fit. Oversized items are now excluded from the filter so the
remaining items nest normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Try all valid best fit pairs instead of only the first when qty=2,
picking the best via IsBetterFill comparer (fixes suboptimal plate
selection during auto-nesting)
- Pre-compute best fits across all plate sizes once via
BestFitCache.ComputeForSizes instead of per-size GPU evaluation
- Early exit plate optimizer when all items fit (salvage < 100%)
- Trim slide offset sweep range to 50% overlap to reduce candidates
- Use actual geometry (ray-arc/ray-circle intersection) instead of
tessellated polygons for slide distance computation — eliminates
the massive line count from circle/arc tessellation
- Add RayArcDistance and RayCircleDistance to SpatialQuery
- Add PartGeometry.GetOffsetPerimeterEntities for non-tessellated
perimeter extraction
- Disable GPU slide computer (slower than CPU currently)
- Remove dead SelectBestFitPair virtual method and overrides
Reduces best fit computation from 7+ minutes to ~4 seconds for a
73x25" part with 30+ holes on a 48x96 plate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When plate costs are equal (e.g. all zero), the optimizer now picks the
plate size with the tightest density instead of the smallest plate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BOM import now loads the nest template to populate plate size, part
spacing, edge spacing, and quadrant instead of hard-coding defaults.
Spacing columns are shown per material+thickness group on the Groups
tab so each combo can be adjusted independently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The timer-based list update only removed depleted drawings but never
added them back when they became un-depleted (e.g., after deleting a
part from the plate).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Circle.ToPoints() always generated CCW points regardless of the Rotation
property, so reversing a circle contour in the CAD converter had no effect.
Now negates the step angle when Rotation is CW, matching Arc.ToPoints behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidated two stateless classes into one unified API: Dxf.Import(),
Dxf.GetGeometry(), Dxf.ExportPlate(), Dxf.ExportProgram(). Export
state moved into a private ExportContext. Removed bool+out pattern
from GetGeometry in favor of returning empty list on failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spline import now uses SplineConverter (arc-based) so the configurable
precision parameter is obsolete. Removed the setting from the options
dialog, DxfImporter property, Settings files, and all callsites.
Hardcoded 200 as the sampling density for the intermediate point
evaluation that feeds into SplineConverter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PlateListChanged handler was setting tabControl1.SelectedIndex = 0,
which forced the UI to the plates tab whenever a sentinel plate was
auto-created during part placement, disrupting the workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add LightGray separator lines between items to visually distinguish
adjacent quantity bars. Preserve scroll position and selection when
updating the drawing list by saving/restoring TopIndex and SelectedItem.
Use incremental item removal instead of full list rebuild when hiding
depleted drawings. Wrap list modifications in BeginUpdate/EndUpdate to
reduce flicker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed add and remove plate buttons from the plate header panel.
The plate tab toolbar now has add/remove buttons with the remove
button state driven by PlateManager.CanRemoveCurrent. MainForm's
Plate > Remove menu item also syncs on plate change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this, RemoveEmptyPlates would destroy the sentinel with no
recovery, and tail-plate subscriptions would go stale after plate list
mutations. Added tests for both scenarios.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EditNestForm now holds a Document instead of a bare Nest field,
eliminating duplicated LastSavePath, LastSaveDate, and SaveAs logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all backward-compat wrapper calls on EditNestForm (LoadFirstPlate,
LoadLastPlate, LoadNextPlate, LoadPreviousPlate, IsFirstPlate, IsLastPlate,
EnsureSentinelPlate, CurrentPlateIndex, PlateCount) with direct access to
activeForm.PlateManager. Remove the now-unused wrapper methods and properties
from EditNestForm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace direct plate collection event handlers, navigation methods, and
sentinel logic in EditNestForm with PlateManager delegation. Navigation
buttons, list selection, export, and plate removal now route through
PlateManager. Backward-compatible delegating wrappers kept for MainForm
until Task 7.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnPlateAdded now navigates to the new plate when suppressNavigation is
false, matching the original EditNestForm behavior. Fixed CanRemoveCurrent
test to account for this auto-navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- EnsureSentinel() maintains exactly one trailing empty plate, suppressing navigation events during mutation
- Reactive tail subscriptions (PartAdded/PartRemoved on last two plates) call EnsureSentinel automatically; re-subscribed after each plate list change
- BeginBatch()/EndBatch() defers sentinel enforcement during bulk operations
- GetOrCreateEmpty() returns or creates an empty plate; RemoveCurrent() removes the current plate with index clamping; CanRemoveCurrent guards deletion
- 13 new tests (30 total PlateManager tests), all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drawings created by BomImportForm and MCP InputTools were missing color
assignments, causing them to render with default empty color instead of
the standard part color palette. Moved PartColors and GetNextColor() to
Drawing in Core so all consumers share one definition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces PlateChangedEventArgs and PlateManager in OpenNest.Core to centralize plate navigation logic (CurrentIndex, LoadFirst/Last/Next/Previous/At, IsFirst/IsLast). Includes full xUnit test coverage (17 tests) verifying navigation, event firing, and disposal unsubscription.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FileListControl loses focus when interacting with other controls on the
form, making arrow key navigation stop working. Intercept Up/Down at
the form level via ProcessCmdKey and forward to the file list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Strategies now promote results to IsOverallBest when they beat the
pipeline best, so the UI updates immediately on improvement rather
than waiting for each phase to complete
- PlateView only updates the main view on overall-best results, fixing
intermediate angle-sweep layouts leaking to the plate display
- Skip Row/Column strategies for rectangle parts (redundant with Linear)
- Intercept Escape key at MainForm level via ProcessCmdKey so it always
reaches the active PlateView regardless of focus state
- Restore keyboard focus to PlateView after fill progress form closes
- Remnant engines use SelectBestFitPair for orientation-aware pair
selection; DefaultNestEngine tries both landscape and portrait pairs
- RemnantFiller preserves more parts during topmost-part removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Always keep a trailing empty plate so users can immediately place parts
without manually adding a plate. Auto-appends a new sentinel when parts
land on the last plate; trims excess trailing empties on removal.
Plate list now shows Parts count and Utilization % columns. Empty plates
are filtered from save and export. Sentinel updates are deferred via
BeginInvoke to avoid collection-modified exceptions and debounced to
prevent per-part overhead on bulk operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a right-click "Delete" option on the drawings tab that removes the
selected drawing and all its placed parts from every plate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebuild the dialog from a flat layout into grouped sections: engine
selector at top, Parts group with rotation columns and summary label,
Options group, collapsible Plate Optimizer with single-field size
parsing, and a clean button bar. Adds engine sync between dialog and
toolbar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add LoadPlateOptions() method to AutoNestForm that restores saved plate
options and salvage rate from the Nest. Call this method in
RunAutoNest_Click when opening the dialog if saved options exist, and save
settings back to Nest after dialog completion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PlateOptions and SalvageRate properties to the Nest class and
round-trip them through NestWriter/NestReader via a new PlateOptionDto.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When "Optimize plate size" is enabled in AutoNestForm, NestSinglePlateAsync
calls PlateOptimizer.Optimize instead of engine.Nest, trying multiple plate
sizes and resizing the plate to the winning option.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tries each candidate plate size via the nesting engine, compares results
by part count then net cost (accounting for salvage credit on remnant
material), and returns the best option.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add colored left-edge bars (green=met, orange=short) to indicate nesting
quantity status. Replace blue selection highlight with a border outline.
Add toolbar toggle to hide fully nested drawings, auto-updating as parts
are placed or removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>