Files
OpenNest/OpenNest/Controls/CutOffHandler.cs
T
ajandClaude Opus 5.5 f88b805e68 fix(cut-off): select head-locked cut-offs by click and window
Clicking and window selection paired the cut-off program's codes as
rapid/cut at even positions. A head-locked far-edge segment carries lock
comments (and, cut from the far edge, a rapid return) between its cuts,
so no pair matched and the line could not be selected, though it drew.

CutOff.CutStrokes now walks the motions once for the renderer, click
hit test, window selection and the Sheet Cut-Off preview, so what is
selectable matches what is drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:36:58 -05:00

122 lines
4.0 KiB
C#

using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
using OpenNest.Undo;
namespace OpenNest.Controls
{
internal class CutOffHandler
{
private readonly PlateView view;
private Dictionary<Part, Geometry.Entity> dragPerimeterCache;
// Open from the press on a cut-off to the release: a drag undoes as one move.
private ChangeScope drag;
// The lines that meet the dragged cut-off and follow it.
private CutOffJoints joints;
private bool moved;
public CutOffHandler(PlateView view)
{
this.view = view;
}
public bool IsDragging { get; private set; }
/// <summary>Whether Sheet Cut-Off draws full-length lines instead of stopping at the first crossing.</summary>
public bool FullLengthNewLines { get; set; }
public CutOff TryStartDrag(Vector point, double tolerance)
{
var hitCutOff = GetCutOffAtPoint(point, tolerance);
if (hitCutOff == null)
return null;
IsDragging = true;
dragPerimeterCache = Plate.BuildPerimeterCache(view.Plate);
joints = CutOffTrim.Joints(view.Plate, hitCutOff, view.CutOffSettings);
moved = false;
drag = view.BeginChange("Move Cut-Off");
return hitCutOff;
}
public void UpdateDrag(Vector currentPoint, CutOff cutOff)
{
if (!IsDragging || cutOff == null)
return;
drag?.MarkChanged();
if (cutOff.Axis == CutOffAxis.Vertical)
cutOff.Position = new Vector(currentPoint.X, cutOff.Position.Y);
else
cutOff.Position = new Vector(cutOff.Position.X, currentPoint.Y);
cutOff.Regenerate(view.Plate, view.CutOffSettings, dragPerimeterCache);
moved = true;
joints?.MoveTo(cutOff.Axis == CutOffAxis.Vertical ? cutOff.Position.X : cutOff.Position.Y);
foreach (var line in joints?.Lines ?? new List<CutOff>())
line.Regenerate(view.Plate, view.CutOffSettings, dragPerimeterCache);
view.Invalidate();
}
public void EndDrag()
{
if (!IsDragging)
return;
IsDragging = false;
dragPerimeterCache = null;
view.Plate.RegenerateCutOffs(view.CutOffSettings);
if (moved && joints != null)
PlateSequencing.PlaceCutOffs(view.Plate, joints.Lines);
joints = null;
view.Invalidate();
var scope = drag;
drag = null;
scope?.Complete();
scope?.Dispose();
}
/// <summary>
/// Applies a trim, extend or swap as one undoable change: the new limits, regenerated
/// cut-offs, and the changed cut-offs' segments placed where the cut-off rules need them.
/// False, changing nothing, when the edit was refused.
/// </summary>
public bool Apply(CutOffEdit edit, string label)
{
if (edit == null || !edit.Succeeded)
return false;
using var change = view.BeginChange(label);
change.MarkChanged();
edit.Apply();
view.Plate.RegenerateCutOffs(view.CutOffSettings);
PlateSequencing.PlaceCutOffs(view.Plate, edit.Changes.Select(c => c.CutOff));
view.Invalidate();
change.Complete();
return true;
}
public CutOff GetCutOffAtPoint(Vector point, double tolerance)
{
if (view.Plate?.CutOffs == null)
return null;
foreach (var cutoff in view.Plate.CutOffs)
{
foreach (var line in CutOff.CutStrokes(cutoff.Drawing?.Program))
{
if (line.ClosestPointTo(point).DistanceTo(point) <= tolerance)
return cutoff;
}
}
return null;
}
}
}