diff --git a/MatterControlLib/ApplicationView/SceneOperations.cs b/MatterControlLib/ApplicationView/SceneOperations.cs index e97516c40..70717fb50 100644 --- a/MatterControlLib/ApplicationView/SceneOperations.cs +++ b/MatterControlLib/ApplicationView/SceneOperations.cs @@ -677,16 +677,11 @@ namespace MatterHackers.MatterControl return new SceneOperation("Align") { OperationType = typeof(IObject3D), - ResultType = typeof(AlignObject3D_2), + ResultType = typeof(AlignObject3D_3), TitleGetter = () => "Align".Localize(), Action = (sceneContext) => { - var scene = sceneContext.Scene; - var selectedItem = scene.SelectedItem; - var align = new AlignObject3D_2(); - align.AddSelectionAsChildren(scene, selectedItem); - align.Name = align.NameFromChildren(); - align.NameOverriden = false; + new AlignObject3D_3().WrapSelectedItemAndSelect(sceneContext.Scene); }, Icon = (theme) => StaticData.Instance.LoadIcon("align_left_dark.png", 16, 16).SetToColor(theme.TextColor).SetPreMultiply(), HelpTextGetter = () => "At least 2 parts must be selected".Localize().Stars(), diff --git a/MatterControlLib/DesignTools/Operations/AlignObject3D_3.cs b/MatterControlLib/DesignTools/Operations/AlignObject3D_3.cs new file mode 100644 index 000000000..fa2c00b95 --- /dev/null +++ b/MatterControlLib/DesignTools/Operations/AlignObject3D_3.cs @@ -0,0 +1,434 @@ +/* +Copyright (c) 2018, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.PartPreviewWindow.View3D; +using MatterHackers.VectorMath; +using Newtonsoft.Json; +using Aabb = MatterHackers.VectorMath.AxisAlignedBoundingBox; + +namespace MatterHackers.MatterControl.DesignTools.Operations +{ + public class AlignObject3D_3 : OperationSourceContainerObject3D, IPropertyGridModifier, IBuildsOnThread + { + private CancellationTokenSource cancellationToken; + + public AlignObject3D_3() + { + Name = "Align"; + } + + [ShowAsList] + [DisplayName("Anchor")] + public SelectedChildren SelectedChild { get; set; } = new SelectedChildren(); + + [SectionStart("X Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_left.png", "align_center_x.png", "align_right.png", "align_origin.png" }, InvertIcons = true)] + public Align XAlign { get; set; } = Align.None; + + public bool XOptions { get; set; } = false; + + [DisplayName("SubAlign")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_to_right.png", "align_to_center_x.png", "align_to_left.png", "align_origin.png" }, InvertIcons = true)] + public Align XSubAlign { get; set; } = Align.None; + + [DisplayName("Offset")] + [Slider(-20, 20, useSnappingGrid: true)] + public DoubleOrExpression XOffset { get; set; } = 0; + + [SectionStart("Y Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_bottom.png", "align_center_y.png", "align_top.png", "align_origin.png" }, InvertIcons = true)] + public Align YAlign { get; set; } = Align.None; + + public bool YOptions { get; set; } = false; + + [DisplayName("SubAlign")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_to_top.png", "align_to_center_y.png", "align_to_bottom.png", "align_origin.png" }, InvertIcons = true)] + public Align YSubAlign { get; set; } = Align.None; + + [DisplayName("Offset")] + [Slider(-20, 20, useSnappingGrid: true)] + public DoubleOrExpression YOffset { get; set; } = 0; + + [SectionStart("Z Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_bottom.png", "align_center_y.png", "align_top.png", "align_origin.png" }, InvertIcons = true)] + public Align ZAlign { get; set; } = Align.None; + + public bool ZOptions { get; set; } = false; + + [DisplayName("SubAlign")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_to_top.png", "align_to_center_y.png", "align_to_bottom.png", "align_origin.png" }, InvertIcons = true)] + public Align ZSubAlign { get; set; } = Align.None; + + [DisplayName("Offset")] + [Slider(-20, 20, useSnappingGrid: true)] + public DoubleOrExpression ZOffset { get; set; } = 0; + + public override bool CanApply => true; + + [JsonIgnore] + private Aabb AnchorBounds + { + get + { + var aabb = this.GetAxisAlignedBoundingBox(); + + if (SelectedChild.Count > 0) + { + if (Children.Where(c => SelectedChild.FirstOrDefault() == c.ID).FirstOrDefault() == null) + { + // none of our children have the selected id so clear the list + SelectedChild.Clear(); + } + } + + if (SelectedChild.Count == 0) + { + SelectedChild.Add(this.Children.FirstOrDefault().ID); + } + + var sourceChild = this.Children.Where(c => c.ID == SelectedChild.FirstOrDefault()).FirstOrDefault(); + + if (sourceChild != null) + { + aabb = sourceChild.GetAxisAlignedBoundingBox(); + } + + return aabb; + } + } + + [JsonIgnore] + private IObject3D SelectedObject + { + get + { + if (SelectedChild.Count > 0) + { + return this.Children.Where(c => c.ID == SelectedChild.FirstOrDefault()).FirstOrDefault(); + } + + return this.Children.FirstOrDefault(); + } + } + + public static Vector3 GetPositionToAlignTo(IObject3D objectToAlignTo, FaceAlign boundingFacesToAlignTo, Vector3 extraOffset) + { + var positionToAlignTo = default(Vector3); + if (IsSet(boundingFacesToAlignTo, FaceAlign.Left, FaceAlign.Right)) + { + positionToAlignTo.X = objectToAlignTo.GetAxisAlignedBoundingBox().MinXYZ.X; + } + + if (IsSet(boundingFacesToAlignTo, FaceAlign.Right, FaceAlign.Left)) + { + positionToAlignTo.X = objectToAlignTo.GetAxisAlignedBoundingBox().MaxXYZ.X; + } + + if (IsSet(boundingFacesToAlignTo, FaceAlign.Front, FaceAlign.Back)) + { + positionToAlignTo.Y = objectToAlignTo.GetAxisAlignedBoundingBox().MinXYZ.Y; + } + + if (IsSet(boundingFacesToAlignTo, FaceAlign.Back, FaceAlign.Front)) + { + positionToAlignTo.Y = objectToAlignTo.GetAxisAlignedBoundingBox().MaxXYZ.Y; + } + + if (IsSet(boundingFacesToAlignTo, FaceAlign.Bottom, FaceAlign.Top)) + { + positionToAlignTo.Z = objectToAlignTo.GetAxisAlignedBoundingBox().MinXYZ.Z; + } + + if (IsSet(boundingFacesToAlignTo, FaceAlign.Top, FaceAlign.Bottom)) + { + positionToAlignTo.Z = objectToAlignTo.GetAxisAlignedBoundingBox().MaxXYZ.Z; + } + + return positionToAlignTo + extraOffset; + } + + public override async void OnInvalidate(InvalidateArgs invalidateArgs) + { + if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Mesh)) + && invalidateArgs.Source != this + && !RebuildLocked) + { + await Rebuild(); + } + else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this)) + { + await Rebuild(); + } + else if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Name) + && !NameOverriden) + { + Name = NameFromChildren(); + NameOverriden = false; + base.OnInvalidate(invalidateArgs); + } + else if (SheetObject3D.NeedsRebuild(this, invalidateArgs)) + { + await Rebuild(); + } + else + { + // and also always pass back the actual type + base.OnInvalidate(invalidateArgs); + } + } + + public bool IsBuilding => this.cancellationToken != null; + + public void CancelBuild() + { + var threadSafe = this.cancellationToken; + if (threadSafe != null) + { + threadSafe.Cancel(); + } + } + + public override Task Rebuild() + { + this.DebugDepth("Rebuild"); + + return ApplicationController.Instance.Tasks.Execute( + "Combine".Localize(), + null, + (reporter, cancellationTokenSource) => + { + this.cancellationToken = cancellationTokenSource; + + using (RebuildLock()) + { + RemoveAllButSource(); + + // reset the position + Children.Modify(list => + { + foreach (var child in SourceContainer.Children) + { + list.Add(child.Clone()); + } + }); + + SourceContainer.Visible = false; + + this.Children.Modify(list => + { + if (list.Count == 0) + { + return; + } + + var anchorBounds = AnchorBounds; + var children = list.Where(c => c.GetType() != typeof(OperationSourceObject3D) + && c.ID != SelectedChild.FirstOrDefault()); + + Align MapSubAlign(Align align) + { + switch (align) + { + case Align.Min: + return Align.Max; + case Align.Max: + return Align.Min; + default: + return align; + } + } + + // align all the objects to the anchor + foreach (var child in children) + { + if (XAlign != Align.None) + { + AlignAxis(0, + (XOptions && XSubAlign != Align.None) ? MapSubAlign(XSubAlign) : XAlign, + GetSubAlignOffset(anchorBounds, 0, XAlign), + XOffset.Value(this), + child); + } + if (YAlign != Align.None) + { + AlignAxis(1, + (YOptions && YSubAlign != Align.None) ? MapSubAlign(YSubAlign) : YAlign, + GetSubAlignOffset(anchorBounds, 1, YAlign), + YOffset.Value(this), + child); + } + if (ZAlign != Align.None) + { + AlignAxis(2, + (ZOptions && ZSubAlign != Align.None) ? MapSubAlign(ZSubAlign) : ZAlign, + GetSubAlignOffset(anchorBounds, 2, ZAlign), + ZOffset.Value(this), + child); + } + } + }); + + if (!NameOverriden) + { + Name = NameFromChildren(); + NameOverriden = false; + } + + var removeItems = Children.Where(c => c.OutputType == PrintOutputTypes.Hole && c.Visible); + if (removeItems.Any()) + { + var keepItems = Children.Where(c => c.OutputType != PrintOutputTypes.Hole && c.Visible); + + // apply any holes before we return + var resultItems = SubtractObject3D_2.DoSubtract(this, + keepItems, + removeItems, + reporter, + cancellationToken.Token); + + RemoveAllButSource(); + + // add back in the results of the hole removal + Children.Modify(list => + { + foreach (var child in resultItems) + { + list.Add(child); + child.Visible = true; + } + }); + } + } + + this.cancellationToken = null; + UiThread.RunOnIdle(() => + { + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Matrix)); + }); + + return Task.CompletedTask; + }); + } + + public void UpdateControls(PublicPropertyChange change) + { + change.SetRowVisible(nameof(XSubAlign), () => XOptions); + change.SetRowVisible(nameof(XOffset), () => XOptions); + change.SetRowVisible(nameof(YSubAlign), () => YOptions); + change.SetRowVisible(nameof(YOffset), () => YOptions); + change.SetRowVisible(nameof(ZSubAlign), () => ZOptions); + change.SetRowVisible(nameof(ZOffset), () => ZOptions); + } + + private static bool IsSet(FaceAlign variableToCheck, FaceAlign faceToCheckFor, FaceAlign faceToAssertNot) + { + if ((variableToCheck & faceToCheckFor) != 0) + { + if ((variableToCheck & faceToAssertNot) != 0) + { + throw new Exception("You cannot have both " + faceToCheckFor.ToString() + " and " + faceToAssertNot.ToString() + " set when calling Align. The are mutually exclusive."); + } + + return true; + } + + return false; + } + + private void AlignAxis(int axis, Align align, double alignTo, double offset, IObject3D item) + { + var aabb = item.GetAxisAlignedBoundingBox(); + var translate = Vector3.Zero; + + switch (align) + { + case Align.Min: + translate[axis] = alignTo - aabb.MinXYZ[axis] + offset; + break; + + case Align.Center: + translate[axis] = alignTo - aabb.Center[axis] + offset; + break; + + case Align.Max: + translate[axis] = alignTo - aabb.MaxXYZ[axis] + offset; + break; + + case Align.Origin: + // find the origin in world space of the item + var itemOrigin = Vector3Ex.Transform(Vector3.Zero, item.WorldMatrix(this)); + translate[axis] = alignTo - itemOrigin[axis] + offset; + break; + } + + item.Translate(translate); + } + + private double GetSubAlignOffset(Aabb anchorBounds, int axis, Align alignTo) + { + switch (alignTo) + { + case Align.None: + return 0; + + case Align.Min: + return anchorBounds.MinXYZ[axis]; + + case Align.Center: + return anchorBounds.Center[axis]; + + case Align.Max: + return anchorBounds.MaxXYZ[axis]; + + case Align.Origin: + return Vector3Ex.Transform(Vector3.Zero, SelectedObject.WorldMatrix(this))[axis]; + + default: + throw new NotImplementedException(); + } + } + + public string NameFromChildren() + { + return CalculateName(this.Children, ", "); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/GroupObject3D.cs b/MatterControlLib/DesignTools/Operations/GroupObject3D.cs index cee1e16b1..1682aafc2 100644 --- a/MatterControlLib/DesignTools/Operations/GroupObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/GroupObject3D.cs @@ -55,6 +55,8 @@ namespace MatterHackers.MatterControl.DesignTools.Operations Name = "Group".Localize(); } + // We can't use Subtracts Apply as it will leave a group if there are multiple object results + // and we want to always leave the individual results after the ungroup. public override void Apply(UndoBuffer undoBuffer) { using (RebuildLock()) @@ -91,7 +93,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations { var selections = new SelectedChildren(); - foreach(var child in SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf().Children.Where(i => i.WorldOutputType(this) == PrintOutputTypes.Hole && !(i is OperationSourceObject3D) )) + foreach(var child in SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf().Children.Where(i => i.WorldOutputType(this) == PrintOutputTypes.Hole && !(i is OperationSourceObject3D) )) { selections.Add(child.ID); } diff --git a/MatterControlLib/DesignTools/PublicPropertyEditor.cs b/MatterControlLib/DesignTools/PublicPropertyEditor.cs index 1cfa1878c..5c4fd3e84 100644 --- a/MatterControlLib/DesignTools/PublicPropertyEditor.cs +++ b/MatterControlLib/DesignTools/PublicPropertyEditor.cs @@ -1281,7 +1281,7 @@ namespace MatterHackers.MatterControl.DesignTools Margin = new BorderDouble(0, 3, 0, 0), }; - var parentOfSubtractTargets = sourceContainer.SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = sourceContainer.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); var sourceChildren = parentOfSubtractTargets.Children.ToList(); diff --git a/MatterControlLib/PartPreviewWindow/ItemColorButton.cs b/MatterControlLib/PartPreviewWindow/ItemColorButton.cs index 760d89ca4..f71dea13c 100644 --- a/MatterControlLib/PartPreviewWindow/ItemColorButton.cs +++ b/MatterControlLib/PartPreviewWindow/ItemColorButton.cs @@ -101,7 +101,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } public static GuiWidget NewColorSelector(ThemeConfig theme, - Color selectedColor, + Color startingColor, ThemeConfig menuTheme, Action update, Action> getPickedColor, @@ -120,7 +120,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow var pickerContainer = content.AddChild(new GuiWidget(128 * DeviceScale, 128 * DeviceScale)); var picker = pickerContainer.AddChild(new RadialColorPicker() { - SelectedColor = selectedColor.WithAlpha(255), + SelectedColor = startingColor.WithAlpha(255), BackgroundColor = Color.Transparent, HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Stretch, @@ -147,7 +147,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // put in an html edit field htmlField.Initialize(0); - htmlField.SetValue(selectedColor.Html.Substring(1, 6), false); + htmlField.SetValue(startingColor.Html.Substring(1, 6), false); htmlField.ClearUndoHistory(); htmlField.ValueChanged += (s, e) => { @@ -162,7 +162,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { // we did not understand the color // set it back to selectedColor - htmlField.SetValue(selectedColor.Html.Substring(1, 6), false); + htmlField.SetValue(startingColor.Html.Substring(1, 6), false); } else // valid color set { @@ -221,7 +221,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow HAnchor = HAnchor.Fit | HAnchor.Left, VAnchor = VAnchor.Absolute, ToolTipText = "Set the rendering for the object to be transparent".Localize(), - Checked = selectedColor.Alpha0To255 < 255, + Checked = startingColor.Alpha0To255 < 255, }; rightContent.AddChild(transparent); diff --git a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs index 4e0c8ad44..a3d87b28d 100644 --- a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs +++ b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs @@ -288,8 +288,15 @@ namespace MatterHackers.MatterControl.PartPreviewWindow if (!(selectedItem.GetType().GetCustomAttributes(typeof(HideMeterialAndColor), true).FirstOrDefault() is HideMeterialAndColor)) { + var firstDetectedColor = selectedItem.VisibleMeshes()?.FirstOrDefault()?.WorldColor(); + var worldColor = Color.White; + if (firstDetectedColor != null) + { + worldColor = firstDetectedColor.Value; + } + // put in a color edit field - var colorField = new ColorField(theme, selectedItem.Color, GetNextSelectionColor, true); + var colorField = new ColorField(theme, worldColor, GetNextSelectionColor, true); colorField.Initialize(0); colorField.ValueChanged += (s, e) => { @@ -311,7 +318,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } var currentOutputType = selectedItem.WorldOutputType(); - if (currentOutputType != PrintOutputTypes.Solid || currentOutputType != PrintOutputTypes.Default) + if (currentOutputType != PrintOutputTypes.Solid && currentOutputType != PrintOutputTypes.Default) { undoBuffer.AddAndDo(new ChangeColor(selectedItem, colorField.Color)); } diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/CombineObject3D_2.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/CombineObject3D_2.cs index e10af18a0..0404ec87a 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Actions/CombineObject3D_2.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/CombineObject3D_2.cs @@ -79,7 +79,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D var rebuildLocks = this.RebuilLockAll(); - return ApplicationController.Instance.Tasks.Execute( + return ApplicationController.Instance.Tasks.Execute( "Combine".Localize(), null, (reporter, cancellationTokenSource) => @@ -105,10 +105,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D this.cancellationToken = null; UiThread.RunOnIdle(() => { - rebuildLocks.Dispose(); - this.CancelAllParentBuilding(); - Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children)); + rebuildLocks.Dispose(); + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children)); }); + return Task.CompletedTask; }); } diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractAndReplaceObject3D_2.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractAndReplaceObject3D_2.cs index 49451015e..d990d7508 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractAndReplaceObject3D_2.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractAndReplaceObject3D_2.cs @@ -87,7 +87,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D if (layer.Scene.SelectedItem != null && layer.Scene.SelectedItem == this) { - var parentOfSubtractTargets = this.SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); var removeObjects = parentOfSubtractTargets.Children .Where(i => SelectedChildren.Contains(i.ID)) @@ -195,7 +195,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D SourceContainer.Visible = true; RemoveAllButSource(); - var parentOfPaintTargets = SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfPaintTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); if (parentOfPaintTargets.Children.Count() < 2) { diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractObject3D_2.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractObject3D_2.cs index b55df7c58..344c72aa3 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractObject3D_2.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractObject3D_2.cs @@ -86,7 +86,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D if (layer.Scene.SelectedItem != null && layer.Scene.SelectedItem == this) { - var parentOfSubtractTargets = this.SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); var removeObjects = parentOfSubtractTargets.Children .Where(i => SelectedChildren.Contains(i.ID)) @@ -109,7 +109,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D if (SelectedChildren.Count == 0) { - SelectedChildren.Add(SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf().Children.Last().ID); + SelectedChildren.Add(SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf().Children.Last().ID); } } @@ -212,28 +212,29 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D Subtract(CancellationToken.None, null); } - private (IEnumerable, IEnumerable) GetSubtractItems() + private static (IEnumerable, IEnumerable) GetSubtractItems(IObject3D source, SelectedChildren selectedChildren) { - var parentOfSubtractTargets = SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); - - if (parentOfSubtractTargets.Children.Count() < 2) - { - if (parentOfSubtractTargets.Children.Count() == 1) - { - this.Children.Add(SourceContainer.Clone()); - SourceContainer.Visible = false; - } + var parentOfSubtractTargets = source.FirstWithMultipleChildrenDescendantsAndSelf(); + // if there are 0 results + if (parentOfSubtractTargets.Children.Count() == 0) + { return (null, null); + } + + // if there is only 1 result (regardless of it being a keep or remove) return it as a keep + if (parentOfSubtractTargets.Children.Count() == 1) + { + return (new IObject3D[] { source }, null); } var removeItems = parentOfSubtractTargets.Children - .Where((i) => SelectedChildren + .Where((i) => selectedChildren .Contains(i.ID)) .SelectMany(c => c.VisibleMeshes()); var keepItems = parentOfSubtractTargets.Children - .Where((i) => !SelectedChildren + .Where((i) => !selectedChildren .Contains(i.ID)) .SelectMany(c => c.VisibleMeshes()); @@ -247,98 +248,45 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D CleanUpSelectedChildrenIDs(this); - var (keepItems, removeItems) = GetSubtractItems(); - var removeItemsCount = removeItems == null ? 0 : removeItems.Count(); - var keepItemsCount = keepItems == null ? 0 : keepItems.Count(); + var (keepItems, removeItems) = GetSubtractItems(SourceContainer, SelectedChildren); - if (removeItems?.Any() == true - && keepItems?.Any() == true) - { - foreach (var keep in keepItems) + var resultItems = DoSubtract(SourceContainer, + keepItems, + removeItems, + reporter, + cancellationToken, + Processing, + InputResolution, + OutputResolution); + + foreach(var resultsItem in resultItems) + { + this.Children.Add(resultsItem); + + if (!RemoveSubtractObjects) { -#if false - var items = removeItems.Select(i => (i.Mesh, i.WorldMatrix(SourceContainer))).ToList(); - items.Insert(0, (keep.Mesh, keep.Matrix)); - var resultsMesh = BooleanProcessing.DoArray(items, - CsgModes.Subtract, - Processing, - InputResolution, - OutputResolution, - reporter, - cancellationToken); -#else - var totalOperations = removeItemsCount * keepItemsCount; - double amountPerOperation = 1.0 / totalOperations; - double ratioCompleted = 0; - - var progressStatus = new ProgressStatus + this.Children.Modify((list) => { - Status = "Do CSG" - }; - - var resultsMesh = keep.Mesh; - var keepWorldMatrix = keep.WorldMatrix(SourceContainer); - - foreach (var remove in removeItems) - { - resultsMesh = BooleanProcessing.Do(resultsMesh, - keepWorldMatrix, - // other mesh - remove.Mesh, - remove.WorldMatrix(SourceContainer), - // operation type - CsgModes.Subtract, - Processing, - InputResolution, - OutputResolution, - // reporting - reporter, - amountPerOperation, - ratioCompleted, - progressStatus, - cancellationToken); - - // after the first time we get a result the results mesh is in the right coordinate space - keepWorldMatrix = Matrix4X4.Identity; - - // report our progress - ratioCompleted += amountPerOperation; - progressStatus.Progress0To1 = ratioCompleted; - reporter?.Report(progressStatus); - } - -#endif - // store our results mesh - var resultsItem = new Object3D() - { - Mesh = resultsMesh, - Visible = false, - OwnerID = keep.ID - }; - - // copy all the properties but the matrix - resultsItem.CopyWorldProperties(keep, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible))); - // and add it to this - this.Children.Add(resultsItem); - - if (!RemoveSubtractObjects) - { - this.Children.Modify((list) => + foreach (var item in removeItems) { - foreach (var item in removeItems) + var newObject = new Object3D() { - var newObject = new Object3D() - { - Mesh = item.Mesh - }; + Mesh = item.Mesh + }; - newObject.CopyWorldProperties(item, SourceContainer, Object3DPropertyFlags.All & (~Object3DPropertyFlags.Visible)); - list.Add(newObject); - } - }); - } + newObject.CopyWorldProperties(item, SourceContainer, Object3DPropertyFlags.All & (~Object3DPropertyFlags.Visible)); + list.Add(newObject); + } + }); } + } + if (Children.Count == 1) + { + // we only have the source item, leave it visible + } + else // hide the source and show the children + { bool first = true; foreach (var child in Children) { @@ -356,11 +304,98 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D } } + public static IEnumerable DoSubtract(IObject3D sourceContainer, + IEnumerable keepItems, + IEnumerable removeItems, + IProgress reporter, + CancellationToken cancellationToken, + ProcessingModes processingMode = ProcessingModes.Polygons, + ProcessingResolution inputResolution = ProcessingResolution._64, + ProcessingResolution outputResolution = ProcessingResolution._64) + { + var results = new List(); + if (keepItems?.Any() == true) + { + if (removeItems?.Any() == true) + { + foreach (var keep in keepItems) + { +#if false + var items = removeItems.Select(i => (i.Mesh, i.WorldMatrix(sourceContainer))).ToList(); + items.Insert(0, (keep.Mesh, keep.Matrix)); + var resultsMesh = BooleanProcessing.DoArray(items, + CsgModes.Subtract, + processingMode, + inputResolution, + outputResolution, + reporter, + cancellationToken); +#else + var totalOperations = removeItems.Count() * keepItems.Count(); + double amountPerOperation = 1.0 / totalOperations; + double ratioCompleted = 0; + + var progressStatus = new ProgressStatus + { + Status = "Do CSG" + }; + + var resultsMesh = keep.Mesh; + var keepWorldMatrix = keep.WorldMatrix(sourceContainer); + + foreach (var remove in removeItems) + { + resultsMesh = BooleanProcessing.Do(resultsMesh, + keepWorldMatrix, + // other mesh + remove.Mesh, + remove.WorldMatrix(sourceContainer), + // operation type + CsgModes.Subtract, + processingMode, + inputResolution, + outputResolution, + // reporting + reporter, + amountPerOperation, + ratioCompleted, + progressStatus, + cancellationToken); + + // after the first time we get a result the results mesh is in the right coordinate space + keepWorldMatrix = Matrix4X4.Identity; + + // report our progress + ratioCompleted += amountPerOperation; + progressStatus.Progress0To1 = ratioCompleted; + reporter?.Report(progressStatus); + } + +#endif + // store our results mesh + var resultsItem = new Object3D() + { + Mesh = resultsMesh, + Visible = false, + OwnerID = keep.ID + }; + + // copy all the properties but the matrix + resultsItem.CopyWorldProperties(keep, sourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible))); + // and add it to this + results.Add(resultsItem); + } + } + } + + return results; + } + public static void CleanUpSelectedChildrenIDs(OperationSourceContainerObject3D item) { if (item is ISelectableChildContainer selectableChildContainer) { - var parentOfSubtractTargets = item.SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = item.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); var allVisibleIDs = parentOfSubtractTargets.Children.Select(i => i.ID); // remove any names from SelectedChildren that are not a child we can select @@ -384,7 +419,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D public override string NameFromChildren() { - var (keepItems, removeItems) = GetSubtractItems(); + var (keepItems, removeItems) = GetSubtractItems(SourceContainer, SelectedChildren); return CalculateName(keepItems, ", ", " - ", removeItems, ", "); } } diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractPathObject3D.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractPathObject3D.cs index 799180541..733d0ec3d 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractPathObject3D.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/SubtractPathObject3D.cs @@ -156,7 +156,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D SourceContainer.Visible = true; RemoveAllButSource(); - var parentOfSubtractTargets = SourceContainer.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf(); if (parentOfSubtractTargets.Children.Count() < 2) { @@ -243,7 +243,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D { if (item is ISelectableChildContainer selectableChildContainer) { - var parentOfSubtractTargets = item.DescendantsAndSelfMultipleChildrenFirstOrSelf(); + var parentOfSubtractTargets = item.FirstWithMultipleChildrenDescendantsAndSelf(); var allVisibleNames = parentOfSubtractTargets.Children.Select(i => i.ID); // remove any names from SelectedChildren that are not a child we can select diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 3d0bfff6b..8d5c8129f 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 3d0bfff6b8e76cab9e732fc2565863d7dd2ee5e5 +Subproject commit 8d5c8129f1ab281e613a7eef0f05a24074371d90