From bb8d8b38596d43d00f38dee58f8ec40310c1830b Mon Sep 17 00:00:00 2001 From: MatterHackers Date: Sun, 5 Nov 2023 17:26:43 -0800 Subject: [PATCH] Improving path editor --- .../Operations/PathEditorFactory.cs | 9 +- .../Operations/PathEditorWidget.cs | 284 +++++++++++++++--- .../Operations/RadialPinchObject3D.cs | 25 +- .../UIFields/Vector2Field.cs | 14 +- .../UIFields/Vector3Field.cs | 9 +- Submodules/agg-sharp | 2 +- 6 files changed, 268 insertions(+), 75 deletions(-) diff --git a/MatterControlLib/DesignTools/Operations/PathEditorFactory.cs b/MatterControlLib/DesignTools/Operations/PathEditorFactory.cs index 6247fa67d..81de53dce 100644 --- a/MatterControlLib/DesignTools/Operations/PathEditorFactory.cs +++ b/MatterControlLib/DesignTools/Operations/PathEditorFactory.cs @@ -75,16 +75,11 @@ namespace MatterHackers.MatterControl.DesignTools private void VertexBufferChanged() { - object3D.Invalidate(InvalidateType.Path); + object3D.Invalidate(new InvalidateArgs(null, InvalidateType.Path)); } [AttributeUsage(AttributeTargets.Property)] - public class TopAndBottomMoveXOnlyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class XMustBeGreaterThan0Attribute : Attribute + public class ShowAxisAttribute : Attribute { } } diff --git a/MatterControlLib/DesignTools/Operations/PathEditorWidget.cs b/MatterControlLib/DesignTools/Operations/PathEditorWidget.cs index 15fcaeb07..c58241a75 100644 --- a/MatterControlLib/DesignTools/Operations/PathEditorWidget.cs +++ b/MatterControlLib/DesignTools/Operations/PathEditorWidget.cs @@ -34,15 +34,23 @@ using MatterHackers.Agg.UI; using MatterHackers.Agg.VertexSource; using MatterHackers.ImageProcessing; using MatterHackers.Localizations; +using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.VectorMath; using System; -using static MatterHackers.MatterControl.PartPreviewWindow.GCode2DWidget; namespace MatterHackers.MatterControl.DesignTools { public class PathEditorWidget : GuiWidget { + public enum ETransformState + { + Edit, + Move, + Scale + }; + private Vector2 lastMousePosition = new Vector2(0, 0); + private ETransformState mouseDownTransformOverride; private Vector2 mouseDownPosition = new Vector2(0, 0); private Action scaleChanged; private ThemeConfig theme; @@ -56,7 +64,7 @@ namespace MatterHackers.MatterControl.DesignTools UndoBuffer undoBuffer, ThemeConfig theme, Action vertexChanged, - Vector2 unscaledRenderOffset = default(Vector2), + Vector2 unscaledRenderOffset = default, double layerScale = 1, Action scaleChanged = null) { @@ -85,13 +93,76 @@ namespace MatterHackers.MatterControl.DesignTools var toolBar = new FlowLayoutWidget() { HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Bottom + Margin = 5, + BackgroundColor= theme.TextColor.WithAlpha(20), }; + toolBar.VAnchor |= VAnchor.Bottom; + this.AddChild(toolBar); - var menuTheme = ApplicationController.Instance.MenuTheme; - var homeButton = new ThemedTextIconButton("Home".Localize(), StaticData.Instance.LoadIcon("fa-home_16.png", 16, 16).GrayToColor(menuTheme.TextColor), theme) + AddHomeButton(theme, toolBar); + + toolBar.AddChild(new HorizontalSpacer()); + + AddPositionControls(theme, toolBar); + } + + public static readonly int VectorXYEditWidth = (int)(60 * GuiWidget.DeviceScale + .5); + + private void AddPositionControls(ThemeConfig theme, FlowLayoutWidget toolBar) + { + var tabIndex = 0; + var xEditWidget = new ThemedNumberEdit(0, theme, singleCharLabel: 'X', allowNegatives: true, allowDecimals: true, pixelWidth: VectorXYEditWidth, tabIndex: tabIndex) + { + TabIndex = tabIndex++, + SelectAllOnFocus = true, + Margin = theme.ButtonSpacing, + VAnchor = VAnchor.Center, + }; + xEditWidget.ActuallNumberEdit.EditComplete += (sender, e) => + { + if (controlPointBeingDragged > -1) + { + var vertexData = vertexStorage[controlPointBeingDragged]; + if (vertexData.Hint == CommandHint.C4Point) + { + // the prev point + if (controlPointBeingDragged > 0) + { + controlPointBeingDragged--; + vertexData = new VertexData(vertexData.Command, new Vector2(vertexData.Position.X, xEditWidget.ActuallNumberEdit.Value), vertexData.Hint); + controlPointBeingDragged++; + } + } + else + { + + } + } + }; + + xEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; + toolBar.AddChild(xEditWidget); + + var yEditWidget = new ThemedNumberEdit(0, theme, 'Y', allowNegatives: true, allowDecimals: true, pixelWidth: VectorXYEditWidth, tabIndex: tabIndex) + { + TabIndex = tabIndex++, + SelectAllOnFocus = true, + VAnchor = VAnchor.Center, + Margin = theme.ButtonSpacing, + }; + yEditWidget.ActuallNumberEdit.EditComplete += (sender, e) => + { + }; + yEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; + + toolBar.AddChild(yEditWidget); + } + + private void AddHomeButton(ThemeConfig theme, FlowLayoutWidget toolBar) + { + var homeButton = new ThemedIconButton(StaticData.Instance.LoadIcon("fa-home_16.png", 16, 16).GrayToColor(theme.TextColor), theme) { BackgroundColor = theme.SlightShade, HoverColor = theme.SlightShade.WithAlpha(75), @@ -102,10 +173,7 @@ namespace MatterHackers.MatterControl.DesignTools homeButton.Click += (s, e) => { - UiThread.RunOnIdle(() => - { - ApplicationController.LaunchBrowser("https://www.matterhackers.com/store/c/3d-printer-filament"); - }); + CenterPartInView(); }; } @@ -115,6 +183,23 @@ namespace MatterHackers.MatterControl.DesignTools private Affine ScalingTransform => Affine.NewScaling(layerScale, layerScale); private Affine TotalTransform => Affine.NewTranslation(unscaledRenderOffset) * ScalingTransform * Affine.NewTranslation(Width / 2, Height / 2); + private int controlPointBeingDragged = -1; + private int controlPointBeingHovered = -1; + + public void CenterPartInView() + { + if (vertexStorage != null) + { + var partBounds = vertexStorage.GetBounds(); + var weightedCenter = partBounds.Center; + + unscaledRenderOffset = -weightedCenter; + layerScale = Math.Min((Height - 30) / partBounds.Height, (Width - 30) / partBounds.Width); + + Invalidate(); + } + } + public override void OnDraw(Graphics2D graphics2D) { new VertexSourceApplyTransform(vertexStorage, TotalTransform).RenderCurve(graphics2D, theme.TextColor, 2, true, theme.PrimaryAccentColor.Blend(theme.TextColor, .5), theme.PrimaryAccentColor); @@ -127,59 +212,170 @@ namespace MatterHackers.MatterControl.DesignTools base.OnMouseDown(mouseEvent); if (MouseCaptured) { + controlPointBeingDragged = -1; + mouseDownPosition.X = mouseEvent.X; mouseDownPosition.Y = mouseEvent.Y; lastMousePosition = mouseDownPosition; + + mouseDownTransformOverride = TransformState; + + // check if not left button + switch (mouseEvent.Button) + { + case MouseButtons.Left: + if (Keyboard.IsKeyDown(Keys.ControlKey)) + { + if (Keyboard.IsKeyDown(Keys.Alt)) + { + mouseDownTransformOverride = ETransformState.Scale; + } + else + { + mouseDownTransformOverride = ETransformState.Move; + } + } + else + { + // we are in edit mode, check if we are over any control points + controlPointBeingDragged = GetControlPointIndex(mouseEvent.Position); + } + break; + + case MouseButtons.Middle: + mouseDownTransformOverride = ETransformState.Move; + break; + + case MouseButtons.Right: + mouseDownTransformOverride = ETransformState.Scale; + break; + } } } + private int GetControlPointIndex(Vector2 mousePosition) + { + double hitThreshold = 10; // Threshold for considering a hit, in screen pixels + + for (int i = 0; i < vertexStorage.Count; i++) + { + Vector2 controlPoint = vertexStorage[i].Position + unscaledRenderOffset; + ScalingTransform.transform(ref controlPoint); + // we center on the scren so we have to add that in after scaling + controlPoint += new Vector2(Width / 2, Height / 2); + + if ((controlPoint - mousePosition).Length <= hitThreshold) // Check if the mouse position is within the threshold + { + return i; // Control point index + } + } + + return -1; // No control point found at this position + } + public override void OnMouseMove(MouseEventArgs mouseEvent) { base.OnMouseMove(mouseEvent); - var mousePos = new Vector2(mouseEvent.X, mouseEvent.Y); if (MouseCaptured) { - var mouseDelta = mousePos - lastMousePosition; - switch (TransformState) + DoTranslateAndZoom(mouseEvent); + + if (controlPointBeingDragged > -1) { - case ETransformState.Scale: - double zoomDelta = 1; - if (mouseDelta.Y < 0) - { - zoomDelta = 1 - (-1 * mouseDelta.Y / 100); - } - else if (mouseDelta.Y > 0) - { - zoomDelta = 1 + (1 * mouseDelta.Y / 100); - } - - var mousePreScale = mouseDownPosition; - TotalTransform.inverse_transform(ref mousePreScale); - - layerScale *= zoomDelta; - - var mousePostScale = mouseDownPosition; - TotalTransform.inverse_transform(ref mousePostScale); - - unscaledRenderOffset += (mousePostScale - mousePreScale); - scaleChanged?.Invoke(unscaledRenderOffset, layerScale); - break; - - case ETransformState.Move: - default: // also treat everything else like a move + // we are dragging a control point + var mouseDelta = mouseEvent.Position - lastMousePosition; + if (mouseDelta.LengthSquared > 0) + { ScalingTransform.inverse_transform(ref mouseDelta); - - unscaledRenderOffset += mouseDelta; - scaleChanged?.Invoke(unscaledRenderOffset, layerScale); - break; + OffsetSelectedPoint(mouseDelta); + vertexChanged?.Invoke(); + } } - - Invalidate(); + } + else + { + // highlight any contorl points we are over } - lastMousePosition = mousePos; + lastMousePosition = mouseEvent.Position; + } + + private void OffsetSelectedPoint(Vector2 delta) + { + if (controlPointBeingDragged < 0 + || controlPointBeingDragged >= vertexStorage.Count) + { + return; + } + + var vertexData = vertexStorage[controlPointBeingDragged]; + + if (vertexData.Hint == CommandHint.C4Point) + { + for (int i = -1; i < 2; i++) + { + var pointIndex = controlPointBeingDragged + i; + // the prev point + if (pointIndex > 0 + && pointIndex < vertexStorage.Count) + { + var vertexData2 = vertexStorage[pointIndex]; + vertexStorage[pointIndex] = new VertexData(vertexData2.Command, vertexData2.Position + delta, vertexData2.Hint); + } + } + } + else + { + // only drag the point + vertexStorage[controlPointBeingDragged] = new VertexData(vertexData.Command, vertexData.Position + delta, vertexData.Hint); + } + } + + private void DoTranslateAndZoom(MouseEventArgs mouseEvent) + { + var mousePos = new Vector2(mouseEvent.X, mouseEvent.Y); + var mouseDelta = mousePos - lastMousePosition; + + switch (mouseDownTransformOverride) + { + case ETransformState.Scale: + double zoomDelta = 1; + if (mouseDelta.Y < 0) + { + zoomDelta = 1 - (-1 * mouseDelta.Y / 100); + } + else if (mouseDelta.Y > 0) + { + zoomDelta = 1 + (1 * mouseDelta.Y / 100); + } + + var mousePreScale = mouseDownPosition; + TotalTransform.inverse_transform(ref mousePreScale); + + layerScale *= zoomDelta; + + var mousePostScale = mouseDownPosition; + TotalTransform.inverse_transform(ref mousePostScale); + + unscaledRenderOffset += (mousePostScale - mousePreScale); + scaleChanged?.Invoke(unscaledRenderOffset, layerScale); + break; + + case ETransformState.Move: + ScalingTransform.inverse_transform(ref mouseDelta); + + unscaledRenderOffset += mouseDelta; + scaleChanged?.Invoke(unscaledRenderOffset, layerScale); + break; + + case ETransformState.Edit: + default: // also treat everything else like an edit + break; + } + + Invalidate(); } public override void OnMouseWheel(MouseEventArgs mouseEvent) diff --git a/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs b/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs index 86756e09b..b95733441 100644 --- a/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs @@ -31,7 +31,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection.Metadata.Ecma335; using System.Threading; using System.Threading.Tasks; using MatterHackers.Agg; @@ -59,8 +58,7 @@ namespace MatterHackers.MatterControl.DesignTools Name = "Radial Pinch".Localize(); } - [PathEditorFactory.TopAndBottomMoveXOnly] - [PathEditorFactory.XMustBeGreaterThan0] + [PathEditorFactory.ShowAxis] public PathEditorFactory.EditableVertexStorage PathForHorizontalOffsets { get; set; } = new PathEditorFactory.EditableVertexStorage(); [Description("Specifies the number of vertical cuts required to ensure the part can be pinched well.")] @@ -117,14 +115,6 @@ namespace MatterHackers.MatterControl.DesignTools return AxisAlignedBoundingBox.CenteredBox(new Vector3(1, 1, sourceAabb.ZSize), center).NewTransformed(this.WorldMatrix()); } - public Vector2 Point1 { get; set; } = new Vector2(0, 0); - public Vector2 Point2 {get; set;} = new Vector2(1, 4); - public Vector2 Point3 { get; set;} = new Vector2(2, 8); - public Vector2 Point4 { get; set;} = new Vector2(3, 12); - public Vector2 Point5 { get; set;} = new Vector2(4, 14); - public Vector2 Point6 { get; set;} = new Vector2(5, 16); - public Vector2 Point7 { get; set;} = new Vector2(6, 18); - public override Task Rebuild() { this.DebugDepth("Rebuild"); @@ -174,13 +164,22 @@ namespace MatterHackers.MatterControl.DesignTools var maxRadius = enclosingCircle.Radius + RotationOffset.Length; - //if (PathForHorizontalOffsets.Count == 0) + // if there is no path make a bad one + if (PathForHorizontalOffsets.Count == 0) { var bottomPoint = new Vector2(maxRadius, bottom * 10); var topPoint = new Vector2(maxRadius, top * 10); var middlePoint = (bottomPoint + topPoint) / 2; middlePoint.X *= 2; - + + var Point1 = new Vector2(0, 0); + var Point2 = new Vector2(1, 4); + var Point3 = new Vector2(2, 8); + var Point4 = new Vector2(3, 12); + var Point5 = new Vector2(4, 14); + var Point6 = new Vector2(5, 16); + var Point7 = new Vector2(6, 18); + PathForHorizontalOffsets.Clear(); PathForHorizontalOffsets.MoveTo(Point1); PathForHorizontalOffsets.Curve4(Point2, Point3, Point4); diff --git a/MatterControlLib/SlicerConfiguration/UIFields/Vector2Field.cs b/MatterControlLib/SlicerConfiguration/UIFields/Vector2Field.cs index 9c348f491..16cf92a2f 100644 --- a/MatterControlLib/SlicerConfiguration/UIFields/Vector2Field.cs +++ b/MatterControlLib/SlicerConfiguration/UIFields/Vector2Field.cs @@ -28,9 +28,7 @@ either expressed or implied, of the FreeBSD Project. */ using System.Linq; -using MatterHackers.Agg; using MatterHackers.Agg.UI; -using MatterHackers.MatterControl.DesignTools; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.SlicerConfiguration @@ -82,10 +80,11 @@ namespace MatterHackers.MatterControl.SlicerConfiguration xEditWidget.ActuallNumberEdit.EditComplete += (sender, e) => { this.SetValue( - string.Format("{0},{1}", xEditWidget.ActuallNumberEdit.Value.ToString("0.###"), yEditWidget.ActuallNumberEdit.Value.ToString("0.###")), - userInitiated: true); + string.Format("{0},{1}", + xEditWidget.ActuallNumberEdit.Value.ToString("0.###"), + yEditWidget.ActuallNumberEdit.Value.ToString("0.###")), + userInitiated: true); }; - xEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; container.AddChild(xEditWidget); @@ -101,10 +100,11 @@ namespace MatterHackers.MatterControl.SlicerConfiguration yEditWidget.ActuallNumberEdit.EditComplete += (sender, e) => { this.SetValue( - string.Format("{0},{1}", xEditWidget.ActuallNumberEdit.Value.ToString("0.###"), yEditWidget.ActuallNumberEdit.Value.ToString("0.###")), + string.Format("{0},{1}", + xEditWidget.ActuallNumberEdit.Value.ToString("0.###"), + yEditWidget.ActuallNumberEdit.Value.ToString("0.###")), userInitiated: true); }; - yEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; container.AddChild(yEditWidget); diff --git a/MatterControlLib/SlicerConfiguration/UIFields/Vector3Field.cs b/MatterControlLib/SlicerConfiguration/UIFields/Vector3Field.cs index 1eabbfb0e..abaf27245 100644 --- a/MatterControlLib/SlicerConfiguration/UIFields/Vector3Field.cs +++ b/MatterControlLib/SlicerConfiguration/UIFields/Vector3Field.cs @@ -88,8 +88,9 @@ namespace MatterHackers.MatterControl.SlicerConfiguration zEditWidget.ActuallNumberEdit.Value.ToString("0.###")), userInitiated: true); }; + xEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; - container.AddChild(xEditWidget); + container.AddChild(xEditWidget); double.TryParse(xyzStrings[1], out double currentYValue); @@ -110,8 +111,9 @@ namespace MatterHackers.MatterControl.SlicerConfiguration zEditWidget.ActuallNumberEdit.Value.ToString("0.###")), userInitiated: true); }; + yEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; - container.AddChild(yEditWidget); + container.AddChild(yEditWidget); double.TryParse(xyzStrings[2], out double currentZValue); @@ -132,8 +134,9 @@ namespace MatterHackers.MatterControl.SlicerConfiguration zEditWidget.ActuallNumberEdit.Value.ToString("0.###")), userInitiated: true); }; + zEditWidget.ActuallNumberEdit.KeyDown += NumberField.InternalTextEditWidget_KeyDown; - container.AddChild(zEditWidget); + container.AddChild(zEditWidget); this.Content = container; } diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 4329e51c1..3c76fcb18 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 4329e51c1d2a762a42176e1a1aac9bac43f02da6 +Subproject commit 3c76fcb1852d66078ab527e5871dd8c62865680b