Move MatterControl source code into a subdirectory

This commit is contained in:
Nettika 2026-01-28 21:30:58 -08:00
parent 2c6e34243a
commit 70af2d9ae8
Signed by: nettika
SSH key fingerprint: SHA256:f+PJrfIq49zrQ6dQrHj18b+PJKmAldeAMiGdj8IzXCA
2007 changed files with 13 additions and 8 deletions

View file

@ -0,0 +1,249 @@
/*
Copyright (c) 2017, 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh;
using MatterHackers.PolygonMesh.Csg;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[ShowUpdateButton]
public class CombineObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier, IBuildsOnThread
{
private CancellationTokenSource cancellationToken;
public CombineObject3D_2()
{
Name = "Combine";
}
#if DEBUG
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public IplicitSurfaceMethod MeshAnalysis { get; set; }
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#else
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
private IplicitSurfaceMethod MeshAnalysis { get; set; }
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#endif
public bool IsBuilding => this.cancellationToken != null;
public static void CheckManifoldData(CombineObject3D_2 item, IObject3D result)
{
if (!result.Mesh.IsManifold())
{
// create a new combine of a and b and add it to the root
var combine = new CombineObject3D_2();
var participants = item.SourceContainer.VisibleMeshes().Where(m => m.WorldOutputType(item.SourceContainer) != PrintOutputTypes.Hole);
// all participants are manifold
foreach (var participant in participants)
{
combine.SourceContainer.Children.Add(new Object3D()
{
Mesh = participant.Mesh.Copy(new CancellationToken()),
Matrix = participant.Matrix
});
}
var scene = result.Parents().Last();
scene.Children.Add(combine);
}
}
public override Task Rebuild()
{
this.DebugDepth("Rebuild");
var rebuildLocks = this.RebuilLockAll();
return ApplicationController.Instance.Tasks.Execute(
"Combine".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
this.cancellationToken = cancellationTokenSource;
reporter?.Invoke(0, null);
try
{
Combine(cancellationTokenSource.Token, reporter);
if (cancellationToken.IsCancellationRequested)
{
// the combine was canceled set our children to the source object children
SourceContainer.Visible = true;
RemoveAllButSource();
Children.Modify((list) =>
{
foreach (var child in SourceContainer.Children)
{
list.Add(child);
}
});
SourceContainer.Visible = false;
}
}
catch
{
}
if (!NameOverriden)
{
Name = NameFromChildren();
NameOverriden = false;
}
this.cancellationToken = null;
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public void Combine()
{
Combine(CancellationToken.None, null);
}
public override string NameFromChildren()
{
return CalculateName(SourceContainer.Children, " + ");
}
private void Combine(CancellationToken cancellationToken, Action<double, string> reporter)
{
SourceContainer.Visible = true;
RemoveAllButSource();
var holes = SourceContainer.VisibleMeshes().Where(m => m.WorldOutputType(SourceContainer) == PrintOutputTypes.Hole);
Mesh resultsMesh = null;
var participants = SourceContainer.VisibleMeshes().Where(m => m.WorldOutputType(SourceContainer) != PrintOutputTypes.Hole);
if (participants.Count() == 0)
{
if (holes.Count() == 0)
{
return;
}
}
else
{
resultsMesh = Object3D.CombineParticipants(SourceContainer, participants, cancellationToken, reporter, Processing, InputResolution, OutputResolution);
}
var resultsItem = new Object3D()
{
Mesh = resultsMesh
};
if (holes != null)
{
var holesMesh = CombineParticipants(SourceContainer, holes, cancellationToken, null);
if (holesMesh != null)
{
var holesItem = new Object3D()
{
Mesh = holesMesh,
OutputType = PrintOutputTypes.Hole
};
if (resultsMesh != null)
{
var resultItems = SubtractObject3D_2.DoSubtract(this,
new List<IObject3D>() { resultsItem },
new List<IObject3D>() { holesItem },
null,
cancellationToken);
resultsItem.Mesh = resultItems.First().Mesh;
}
else
{
holesItem.CopyProperties(holes.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
this.Children.Add(holesItem);
SourceContainer.Visible = false;
return;
}
}
}
resultsItem.CopyProperties(participants.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
this.Children.Add(resultsItem);
#if DEBUG
//resultsItem.Mesh.MergeVertices(.01);
//resultsItem.Mesh.CleanAndMerge();
//CheckManifoldData(this, resultsItem);
#endif
SourceContainer.Visible = false;
}
public void UpdateControls(PublicPropertyChange change)
{
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
}
public void CancelBuild()
{
var threadSafe = this.cancellationToken;
if (threadSafe != null)
{
threadSafe.Cancel();
}
}
}
}

View file

@ -0,0 +1,142 @@
/*
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 MatterHackers.Agg;
using MatterHackers.Agg.UI;
using System;
using System.Linq;
namespace MatterHackers.MatterControl.DesignTools
{
public class GridWidget : FlowLayoutWidget
{
public int GridWidth => Children[0].Children.Count;
public int GridHeight => Children.Count;
public void SetColumnWidth(int index, double width)
{
for (int y = 0; y < GridHeight; y++)
{
Children[y].Children[index].Width = width * GuiWidget.DeviceScale;
}
}
public void SetRowHeight(int index, double height)
{
for (int x = 0; x < GridHeight; x++)
{
Children[index].Children[x].Height = height;
}
}
public double GetColumnWidth(int index)
{
return Children[0].Children[index].Width;
}
public double GetRowHeight(int index)
{
return Children[index].Children[0].Height;
}
public GridWidget(int gridWidth, int gridHeight, double columnWidth = 60, double rowHeight = 14, ThemeConfig theme = null)
: base(FlowDirection.TopToBottom)
{
for (int y = 0; y < gridHeight; y++)
{
var row = new FlowLayoutWidget();
this.AddChild(row);
for (int x = 0; x < gridWidth; x++)
{
row.AddChild(new GuiWidget()
{
Width = columnWidth * GuiWidget.DeviceScale,
Height = rowHeight * GuiWidget.DeviceScale,
Border = 1,
BorderColor = theme == null ? Color.LightGray : theme.BorderColor20,
});
}
}
}
public GuiWidget GetCell(int x, int y)
{
if (x < GridWidth && y < GridHeight)
{
return Children[y].Children[x];
}
return null;
}
public void ExpandRowToMaxHeight(int index)
{
var maxHeight = GetRowHeight(index);
for (int x = 0; x < GridWidth; x++)
{
var cell = GetCell(x, index).Children.FirstOrDefault();
if (cell != null)
{
maxHeight = Math.Max(maxHeight, cell.Height + cell.DeviceMarginAndBorder.Height);
}
}
SetRowHeight(index, maxHeight);
}
public void ExpandColumnToMaxWidth(int index)
{
var maxWidth = GetColumnWidth(index);
for (int y = 0; y < GridHeight; y++)
{
var cell = GetCell(index, y).Children.FirstOrDefault();
if (cell != null)
{
maxWidth = Math.Max(maxWidth, cell.Width + cell.DeviceMarginAndBorder.Width);
}
}
SetColumnWidth(index, maxWidth);
}
public void ExpandToFitContent()
{
for (int x = 0; x < GridWidth; x++)
{
ExpandColumnToMaxWidth(x);
}
for (int y = 0; y < GridHeight; y++)
{
ExpandRowToMaxHeight(y);
}
}
}
}

View file

@ -0,0 +1,223 @@
/*
Copyright (c) 2023, 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh.Csg;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[ShowUpdateButton]
public class IntersectionObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier
{
public IntersectionObject3D_2()
{
Name = "Intersection";
}
#if DEBUG
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public IplicitSurfaceMethod MeshAnalysis { get; set; }
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#else
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
private IplicitSurfaceMethod MeshAnalysis { get; set; }
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#endif
private CancellationTokenSource cancellationToken;
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");
var rebuildLocks = this.RebuilLockAll();
return ApplicationController.Instance.Tasks.Execute(
"Intersection".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
try
{
Intersect(cancellationTokenSource.Token, reporter);
}
catch
{
}
if (!NameOverriden)
{
Name = NameFromChildren();
NameOverriden = false;
}
this.cancellationToken = null;
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public override string NameFromChildren()
{
return CalculateName(SourceContainer.Children, " & ");
}
public void Intersect()
{
Intersect(CancellationToken.None, null);
}
private void Intersect(CancellationToken cancellationToken, Action<double, string> reporter)
{
SourceContainer.Visible = true;
RemoveAllButSource();
var participants = SourceContainer.VisibleMeshes();
if (participants.Count() < 2)
{
if (participants.Count() == 1)
{
var newMesh = new Object3D();
newMesh.CopyProperties(participants.First(), Object3DPropertyFlags.All);
newMesh.Mesh = participants.First().Mesh;
this.Children.Add(newMesh);
SourceContainer.Visible = false;
}
return;
}
var items = participants.Select(i => (i.Mesh, i.WorldMatrix(SourceContainer)));
#if false
var resultsMesh = BooleanProcessing.DoArray(items,
CsgModes.Intersect,
Processing,
InputResolution,
OutputResolution,
reporter,
cancellationToken);
#else
var totalOperations = items.Count() - 1;
double amountPerOperation = 1.0 / totalOperations;
double ratioCompleted = 0;
var resultsMesh = items.First().Item1;
var keepWorldMatrix = items.First().Item2;
bool first = true;
foreach (var next in items)
{
if (first)
{
first = false;
continue;
}
resultsMesh = BooleanProcessing.Do(resultsMesh,
keepWorldMatrix,
// other mesh
next.Item1,
next.Item2,
// operation type
CsgModes.Intersect,
Processing,
InputResolution,
OutputResolution,
// reporting
reporter,
amountPerOperation,
ratioCompleted,
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;
reporter?.Invoke(ratioCompleted, null);
}
#endif
if (resultsMesh != null)
{
var resultsItem = new Object3D()
{
Mesh = resultsMesh
};
resultsItem.CopyProperties(participants.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
this.Children.Add(resultsItem);
SourceContainer.Visible = false;
}
}
public void UpdateControls(PublicPropertyChange change)
{
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
}
}
}

View file

@ -0,0 +1,314 @@
/*
Copyright (c) 2017, 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 MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.DataConverters3D.UndoCommands;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh;
using MatterHackers.VectorMath;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
public abstract class MeshWrapperObject3D : Object3D
{
public MeshWrapperObject3D()
{
}
public override bool CanApply => true;
public override void Apply(UndoBuffer undoBuffer)
{
using (RebuildLock())
{
var thisCopy = this.Clone();
using (thisCopy.RebuilLockAll())
{
var ownedMeshWrappers = thisCopy.Descendants().Where(o => o.OwnerID == thisCopy.ID).ToList();
var newMeshObjects = new List<IObject3D>();
// remove all the meshWrappers (collapse the children)
foreach (var ownedMeshWrapper in ownedMeshWrappers)
{
var wrapperParent = ownedMeshWrapper.Parent;
if (ownedMeshWrapper.Visible)
{
var newMesh = new Object3D()
{
Mesh = ownedMeshWrapper.Mesh.Copy(CancellationToken.None)
};
newMesh.CopyProperties(ownedMeshWrapper, Object3DPropertyFlags.All);
// move the mesh to the actual new position
var matrix = ownedMeshWrapper.WorldMatrix(thisCopy);
newMesh.Mesh.Transform(matrix);
// then set the matrix to identity
newMesh.Matrix = Matrix4X4.Identity;
newMesh.Name = thisCopy.Name;
newMeshObjects.Add(newMesh);
}
// remove it
wrapperParent.Children.Remove(ownedMeshWrapper);
}
thisCopy.Matrix = Matrix4X4.Identity;
thisCopy.Children.Modify(children =>
{
children.Clear();
children.AddRange(newMeshObjects);
foreach (var child in children)
{
child.MakeNameNonColliding();
}
});
List<IObject3D> newChildren = new List<IObject3D>();
// push our matrix into a copy of our children
foreach (var child in thisCopy.Children)
{
var newChild = child.Clone();
newChildren.Add(newChild);
newChild.Matrix *= thisCopy.Matrix;
var flags = Object3DPropertyFlags.Visible;
if (thisCopy.Color.alpha != 0) flags |= Object3DPropertyFlags.Color;
if (thisCopy.OutputType != PrintOutputTypes.Default) flags |= Object3DPropertyFlags.OutputType;
if (thisCopy.MaterialIndex != -1) flags |= Object3DPropertyFlags.MaterialIndex;
newChild.CopyProperties(thisCopy, flags);
}
// and replace us with the children
var replaceCommand = new ReplaceCommand(new List<IObject3D> { this }, newChildren, false);
if (undoBuffer != null)
{
undoBuffer.AddAndDo(replaceCommand);
}
else
{
replaceCommand.Do();
}
}
}
Invalidate(InvalidateType.Children);
}
/// <summary>
/// MeshWrapperObject3D overrides GetAabb so that it can only check the geometry that it has created
/// </summary>
/// <param name="matrix"></param>
/// <returns></returns>
public override AxisAlignedBoundingBox GetAxisAlignedBoundingBox(Matrix4X4 matrix)
{
AxisAlignedBoundingBox totalBounds = AxisAlignedBoundingBox.Empty();
// This needs to be Descendants because we need to move past the first visible mesh to our owned objects
foreach (var child in this.Descendants().Where(i => i.OwnerID == this.ID && i.Visible))
{
var childMesh = child.Mesh;
if (childMesh != null)
{
// Add the bounds of each child object
var childBounds = childMesh.GetAxisAlignedBoundingBox(child.WorldMatrix(this) * matrix);
// Check if the child actually has any bounds
if (childBounds.XSize > 0)
{
totalBounds += childBounds;
}
}
}
return totalBounds;
}
public IEnumerable<(IObject3D original, IObject3D meshCopy)> WrappedObjects()
{
return this.Descendants()
.Where((obj) => obj.OwnerID == this.ID)
.Select((mw) => (mw.Children.First(), mw));
}
public override void Cancel(UndoBuffer undoBuffer)
{
using (RebuildLock())
{
var thisClone = this.Clone();
using (thisClone.RebuildLock())
{
// remove all the mesh wrappers that we own
var meshWrappers = thisClone.Descendants().Where(o => o.OwnerID == thisClone.ID).ToArray();
foreach (var meshWrapper in meshWrappers)
{
meshWrapper.Cancel(null);
}
foreach (var child in thisClone.Children)
{
child.OutputType = PrintOutputTypes.Default;
// push our matrix into a copy of our children (so they don't jump away)
using (child.RebuildLock())
{
child.Matrix *= thisClone.Matrix;
}
}
// collapse our children into our parent
// and replace us with the children
var replaceCommand = new ReplaceCommand(new[] { this }, thisClone.Children.ToList(), false);
if (undoBuffer != null)
{
undoBuffer.AddAndDo(replaceCommand);
}
else
{
replaceCommand.Do();
}
}
}
Invalidate(InvalidateType.Children);
}
public void ResetMeshWrapperMeshes(Object3DPropertyFlags flags, CancellationToken cancellationToken)
{
using (RebuildLock())
{
this.DebugDepth("Reset MWM");
// Remove everything above the objects that have the meshes we are wrapping that are mesh wrappers
var wrappers = this.Descendants().Where(o => o.OwnerID == this.ID).ToList();
foreach (var wrapper in wrappers)
{
using (wrapper.RebuildLock())
{
var remove = wrapper.Parent;
while (remove is ModifiedMeshObject3D)
{
var hold = remove;
remove.Cancel(null);
remove = hold.Parent;
}
}
}
// if there are not already, wrap all meshes with our id (some inner object may have changed it's meshes)
AddMeshWrapperToAllChildren();
this.Mesh = null;
var participants = this.Descendants().Where(o => o.OwnerID == this.ID).ToList();
foreach (var item in participants)
{
var firstChild = item.Children.First();
using (item.RebuildLock())
{
// set the mesh back to a copy of the child mesh
item.Mesh = firstChild.Mesh.Copy(cancellationToken);
// and reset the properties
item.CopyProperties(firstChild, flags & (~Object3DPropertyFlags.Matrix));
}
}
}
}
public void WrapSelectedItemAndSelect(InteractiveScene scene)
{
using (RebuildLock())
{
var selectedItems = scene.GetSelectedItems();
if (selectedItems.Count > 0)
{
// clear the selected item
scene.SelectedItem = null;
WrapItems(selectedItems);
scene.UndoBuffer.AddAndDo(
new ReplaceCommand(
new List<IObject3D>(selectedItems),
new List<IObject3D> { this }));
// and select this
scene.SelectedItem = this;
}
}
Invalidate(InvalidateType.Properties);
}
public void WrapItems(List<IObject3D> items)
{
using (RebuildLock())
{
var clonedItemsToAdd = new List<IObject3D>(items.Select((i) => i.Clone()));
Children.Modify((list) =>
{
list.Clear();
foreach (var child in clonedItemsToAdd)
{
list.Add(child);
}
});
AddMeshWrapperToAllChildren();
this.MakeNameNonColliding();
}
Invalidate(InvalidateType.Properties);
}
private void AddMeshWrapperToAllChildren()
{
// Wrap every first descendant that has a mesh
foreach (var child in this.VisibleMeshes().ToList())
{
// have to check that NO child of the visible mesh has us as the parent id
if (!child.DescendantsAndSelf().Where((c) => c.OwnerID == this.ID).Any())
{
// wrap the child
child.Parent.Children.Modify((System.Action<List<IObject3D>>)((List<IObject3D> list) =>
{
list.Remove(child);
list.Add((IObject3D)new ModifiedMeshObject3D(child, this.ID));
}));
}
}
}
}
}

View file

@ -0,0 +1,77 @@
/*
Copyright (c) 2017, 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 MatterHackers.DataConverters3D;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.VectorMath;
using System.Linq;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
/// <summary>
/// The goal of MeshWrapper is to provide a mutated version of a source item by some operation. To do so we wrap and clone all
/// properties of the source item and reset the source matrix to Identity, given that it now exists on the wrapping parent.
/// </summary>
public class ModifiedMeshObject3D : Object3D
{
public ModifiedMeshObject3D()
{
}
public override void OnInvalidate(InvalidateArgs invalidateType)
{
var firstChild = this.Children.FirstOrDefault();
if (firstChild != null)
{
if (invalidateType.InvalidateType.HasFlag(InvalidateType.Color))
{
this.Color = firstChild.Color;
}
if (invalidateType.InvalidateType.HasFlag(InvalidateType.Material))
{
this.MaterialIndex = firstChild.MaterialIndex;
}
}
base.OnInvalidate(invalidateType);
}
public ModifiedMeshObject3D(IObject3D child, string ownerId)
{
Children.Add(child);
this.CopyProperties(child, Object3DPropertyFlags.All);
this.OwnerID = ownerId;
this.Mesh = child.Mesh;
child.Matrix = Matrix4X4.Identity;
}
}
}

View file

@ -0,0 +1,260 @@
/*
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 MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.MatterControl.PartPreviewWindow;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using System.Linq;
namespace MatterHackers.MatterControl.DesignTools
{
public class SheetEditorWidget : FlowLayoutWidget
{
public SheetData SheetData { get; private set; }
Point2D selectedCell = new Point2D(-1, -1);
Dictionary<(int, int), GuiWidget> CellWidgetsByLocation = new Dictionary<(int, int), GuiWidget>();
public UndoBuffer UndoBuffer { get; }
private ThemeConfig theme;
private ThemedTextEditWidget editSelectedName;
public ThemedTextEditWidget EditSelectedExpression { get; private set; }
private GridWidget gridWidget;
public SheetEditorWidget(SheetData sheetData, SheetObject3D sheetObject, UndoBuffer undoBuffer, ThemeConfig theme)
: base(FlowDirection.TopToBottom)
{
this.UndoBuffer = undoBuffer;
this.theme = theme;
HAnchor = HAnchor.MaxFitOrStretch;
this.SheetData = sheetData;
var cellEditNameWidth = 80 * GuiWidget.DeviceScale;
// put in the edit row
var editSelectionGroup = this.AddChild(new FlowLayoutWidget()
{
HAnchor = HAnchor.Stretch,
VAnchor = VAnchor.Fit,
});
editSelectedName = new ThemedTextEditWidget("", theme, cellEditNameWidth, messageWhenEmptyAndNotSelected: "Name".Localize())
{
HAnchor = HAnchor.Absolute,
};
editSelectedName.ActualTextEditWidget.EditComplete += SelectedName_EditComplete;
editSelectionGroup.AddChild(editSelectedName);
EditSelectedExpression = new ThemedTextEditWidget("", theme, messageWhenEmptyAndNotSelected: "Select cell to edit".Localize())
{
HAnchor = HAnchor.Stretch,
};
editSelectionGroup.AddChild(EditSelectedExpression);
EditSelectedExpression.ActualTextEditWidget.EditComplete += ActualTextEditWidget_EditComplete1;
gridWidget = new GridWidget(sheetData.Width + 1, sheetData.Height + 1, theme: theme)
{
Margin = new BorderDouble(7, 0),
};
this.AddChild(gridWidget);
for (int x = 0; x < sheetData.Width; x++)
{
var letterCell = gridWidget.GetCell(x + 1, 0);
letterCell.AddChild(new TextWidget(((char)('A' + x)).ToString())
{
HAnchor = HAnchor.Center,
VAnchor = VAnchor.Center,
TextColor = theme.TextColor,
});
letterCell.BackgroundColor = theme.SlightShade;
}
gridWidget.SetColumnWidth(0, 20);
for (int y = 0; y < sheetData.Height; y++)
{
// add row count
var numCell = gridWidget.GetCell(0, y + 1);
numCell.AddChild(new TextWidget((y + 1).ToString())
{
TextColor = theme.TextColor,
HAnchor = HAnchor.Center,
VAnchor = VAnchor.Center,
});
numCell.BackgroundColor = theme.SlightShade;
for (int x = 0; x < sheetData.Width; x++)
{
var capturedX = x;
var capturedY = y;
var edit = new SheetFieldWidget(this, x, y, theme);
CellWidgetsByLocation.Add((capturedX, capturedY), edit);
edit.MouseUp += (s, e) => SelectCell(capturedX, capturedY);
gridWidget.GetCell(x + 1, y + 1).AddChild(edit);
}
gridWidget.ExpandToFitContent();
}
if (sheetObject != null)
{
PropertyEditor.AddWebPageLinkIfRequired(sheetObject, this, theme);
}
}
private void ActualTextEditWidget_EditComplete1(object sender, EventArgs e)
{
if (selectedCell.x == -1)
{
return;
}
SheetData[selectedCell.x, selectedCell.y].Expression = EditSelectedExpression.Text;
CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].Text = EditSelectedExpression.Text;
SheetData.Recalculate();
}
private void SelectedName_EditComplete(object sender, EventArgs e)
{
if (selectedCell.x == -1)
{
return;
}
var existingNames = new HashSet<string>();
for (int y = 0; y < SheetData.Height; y++)
{
for (int x = 0; x < SheetData.Width; x++)
{
if (x != selectedCell.x || y != selectedCell.y)
{
var currentName = SheetData[x, y].Name;
if (!string.IsNullOrEmpty(currentName))
{
existingNames.Add(currentName.ToLower());
}
}
}
}
var reservedWords = new string[] { "pi", "e", "true", "false", "round" };
// add all the reserved words to the existing names
foreach (var reservedWord in reservedWords)
{
existingNames.Add(reservedWord);
}
// first replace spaces with '_'
var name = editSelectedName.Text.Replace(' ', '_');
// next make sure we don't have the exact name already
name = Util.GetNonCollidingName(name, (name) =>
{
return !existingNames.Contains(name.ToLower());
}, false);
editSelectedName.Text = name;
SheetData[selectedCell.x, selectedCell.y].Name = name;
SheetData.Recalculate();
}
public override void OnDraw(Graphics2D graphics2D)
{
base.OnDraw(graphics2D);
// draw the selected widget
var x = selectedCell.x;
var y = selectedCell.y;
if (x < 0 || x >= SheetData.Width || y < 0 || y >= SheetData.Height)
{
// out of bounds
return;
}
var cell = gridWidget.GetCell(x + 1, y + 1);
var bounds = cell.TransformToParentSpace(this, cell.LocalBounds);
graphics2D.Rectangle(bounds, theme.PrimaryAccentColor);
}
public void SelectCell(int x, int y)
{
if (x < 0 || x >= SheetData.Width || y < 0 || y >= SheetData.Height)
{
// out of bounds
return;
}
if (selectedCell.x != -1)
{
CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].BorderColor = Color.Transparent;
}
selectedCell.x = x;
selectedCell.y = y;
CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].BorderColor = theme.PrimaryAccentColor;
EditSelectedExpression.Text = SheetData[x, y].Expression;
if (string.IsNullOrEmpty(SheetData[x, y].Name))
{
editSelectedName.Text = $"{(char)('A' + x)}{y + 1}";
}
else
{
editSelectedName.Text = SheetData[x, y].Name;
}
gridWidget.GetCell(x + 1, y + 1).Children.FirstOrDefault()?.Focus();
}
}
public class SheetEditor : IObjectEditor
{
string IObjectEditor.Name => "Sheet Editor";
IEnumerable<Type> IObjectEditor.SupportedTypes() => new[] { typeof(SheetObject3D) };
public GuiWidget Create(object item, UndoBuffer undoBuffer, ThemeConfig theme)
{
if (item is SheetObject3D sheetObject)
{
return new SheetEditorWidget(sheetObject.SheetData, sheetObject, undoBuffer, theme);
}
return null;
}
}
}

View file

@ -0,0 +1,295 @@
/*
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 MatterHackers.Agg.UI;
namespace MatterHackers.MatterControl.DesignTools
{
public class SheetFieldWidget : GuiWidget
{
private SheetEditorWidget sheetEditorWidget;
private int x;
private int y;
private TextWidget content;
private string undoContent;
private SheetData SheetData => sheetEditorWidget.SheetData;
private enum EditModes
{
Unknown,
QuickText,
FullEdit
}
private EditModes EditMode { get; set; }
public SheetFieldWidget(SheetEditorWidget sheetEditorWidget, int x, int y, ThemeConfig theme)
{
this.sheetEditorWidget = sheetEditorWidget;
this.x = x;
this.y = y;
this.Name = $"Cell {x},{y}";
HAnchor = HAnchor.Stretch;
VAnchor = VAnchor.Stretch;
Selectable = true;
content = new TextWidget("")
{
TextColor = theme.TextColor,
AutoExpandBoundsToText = true,
VAnchor = VAnchor.Bottom,
};
SheetData.Recalculated += (s, e) =>
{
UpdateContents();
};
UpdateContents();
this.AddChild(content);
}
private void UpdateContents()
{
content.Text = SheetData.GetCellValue(SheetData.CellId(x, y));
}
public override void OnKeyPress(KeyPressEventArgs keyPressEvent)
{
// this must be called first to ensure we get the correct Handled state
base.OnKeyPress(keyPressEvent);
if (!keyPressEvent.Handled)
{
if (keyPressEvent.KeyChar < 32
&& keyPressEvent.KeyChar != 13
&& keyPressEvent.KeyChar != 9)
{
return;
}
if (EditMode == EditModes.Unknown)
{
EditMode = EditModes.QuickText;
this.content.Text = "";
}
this.content.Text += keyPressEvent.KeyChar.ToString();
keyPressEvent.Handled = true;
UpdateSheetEditField();
}
}
private void UpdateSheetEditField()
{
this.sheetEditorWidget.EditSelectedExpression.Text = this.content.Text;
}
public override void OnKeyDown(KeyEventArgs keyEvent)
{
// handle the tab early so it does not get consumed with switching to the next widget
if (!keyEvent.Handled)
{
switch (keyEvent.KeyCode)
{
case Keys.Tab:
Navigate(1, 0);
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
}
}
// this must be called first to ensure we get the correct Handled state
base.OnKeyDown(keyEvent);
if (!keyEvent.Handled)
{
switch (keyEvent.KeyCode)
{
case Keys.Escape:
// reset the selction to what it was before we started editing
this.content.Text = undoContent;
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
// and go back to the unknown state
EditMode = EditModes.Unknown;
UpdateSheetEditField();
break;
case Keys.Left:
Navigate(-1, 0);
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
case Keys.Down:
Navigate(0, 1);
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
case Keys.Right:
Navigate(1, 0);
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
case Keys.Up:
Navigate(0, -1);
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
case Keys.Enter:
switch (EditMode)
{
// go into full edit
case EditModes.Unknown:
EditMode = EditModes.FullEdit;
break;
// finish edit and move down
case EditModes.QuickText:
case EditModes.FullEdit:
Navigate(0, 1);
// make sure we know we are edit complete
EditComplete();
break;
}
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
break;
case Keys.Delete:
switch (EditMode)
{
case EditModes.Unknown:
// delete content
this.content.Text = "";
break;
case EditModes.QuickText:
// do nothing
break;
case EditModes.FullEdit:
// delete from front
break;
}
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
UpdateSheetEditField();
break;
case Keys.Back:
switch (EditMode)
{
case EditModes.Unknown:
// delete text
this.content.Text = "";
break;
case EditModes.QuickText:
// delete from back
if (this.content.Text.Length > 0)
{
this.content.Text = this.content.Text.Substring(0, this.content.Text.Length - 1);
}
break;
case EditModes.FullEdit:
// delete from back
break;
}
keyEvent.SuppressKeyPress = true;
keyEvent.Handled = true;
UpdateSheetEditField();
break;
}
}
}
public override void OnFocusChanged(System.EventArgs e)
{
base.OnFocusChanged(e);
if (this.Focused)
{
undoContent = this.content.Text;
EditMode = EditModes.Unknown;
}
else
{
EditComplete();
}
}
private void EditComplete()
{
if (this.content.Text != undoContent)
{
var newValue = this.content.Text;
var oldValue = SheetData[x, y].Expression;
// this needs to support undo buffer
sheetEditorWidget.UndoBuffer.AddAndDo(new UndoRedoActions(() =>
{
this.content.Text = oldValue;
// make sure the is a sheet update
SheetData[x, y].Expression = this.content.Text;
SheetData.Recalculate();
undoContent = this.content.Text;
},
() =>
{
this.content.Text = newValue;
// make sure the is a sheet update
SheetData[x, y].Expression = this.content.Text;
SheetData.Recalculate();
undoContent = this.content.Text;
}));
}
EditMode = EditModes.Unknown;
}
private void Navigate(int xOffset, int yOffset)
{
sheetEditorWidget.SelectCell(x + xOffset, y + yOffset);
}
}
}

View file

@ -0,0 +1,222 @@
/*
Copyright (c) 2017, 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.
*/
/*********************************************************************/
/**************************** OBSOLETE! ******************************/
/************************ USE NEWER VERSION **************************/
/*********************************************************************/
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.PolygonMesh;
using MatterHackers.PolygonMesh.Csg;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[Obsolete("Use SubtractAndReplaceObject3D_2 instead", false)]
[ShowUpdateButton(SuppressPropertyChangeUpdates = true)]
public class SubtractAndReplaceObject3D : MeshWrapperObject3D, ISelectableChildContainer
{
public SubtractAndReplaceObject3D()
{
Name = "Subtract and Replace";
}
public SelectedChildren ItemsToSubtract { get; set; } = new SelectedChildren();
public SelectedChildren SelectedChildren => ItemsToSubtract;
public override async void OnInvalidate(InvalidateArgs invalidateType)
{
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
&& invalidateType.Source != this
&& !RebuildLocked)
{
await Rebuild();
}
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
&& invalidateType.Source == this)
{
await Rebuild();
}
else
{
base.OnInvalidate(invalidateType);
}
}
public void SubtractAndReplace()
{
SubtractAndReplace(CancellationToken.None, null);
}
public void SubtractAndReplace(CancellationToken cancellationToken, Action<double, string> reporter)
{
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
var paintObjects = this.Children
.Where((i) => ItemsToSubtract.Contains(i.ID))
.SelectMany((h) => h.DescendantsAndSelf())
.Where((c) => c.OwnerID == this.ID).ToList();
var keepObjects = this.Children
.Where((i) => !ItemsToSubtract.Contains(i.ID))
.SelectMany((h) => h.DescendantsAndSelf())
.Where((c) => c.OwnerID == this.ID).ToList();
if (paintObjects.Any()
&& keepObjects.Any())
{
var totalOperations = paintObjects.Count * keepObjects.Count;
double amountPerOperation = 1.0 / totalOperations;
double ratioCompleted = 0;
foreach (var paint in paintObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
{
var transformedPaint = paint.obj3D.Mesh.Copy(cancellationToken);
transformedPaint.Transform(paint.matrix);
var inverseRemove = paint.matrix.Inverted;
Mesh paintMesh = null;
foreach (var keep in keepObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
{
var transformedKeep = keep.obj3D.Mesh.Copy(cancellationToken);
transformedKeep.Transform(keep.matrix);
// remove the paint from the original
var subtract = BooleanProcessing.Do(keep.obj3D.Mesh,
keep.matrix,
paint.obj3D.Mesh,
paint.matrix,
CsgModes.Subtract,
ProcessingModes.Polygons,
ProcessingResolution._64,
ProcessingResolution._64,
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
var intersect = BooleanProcessing.Do(keep.obj3D.Mesh,
keep.matrix,
paint.obj3D.Mesh,
paint.matrix,
CsgModes.Intersect,
ProcessingModes.Polygons,
ProcessingResolution._64,
ProcessingResolution._64,
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
var inverseKeep = keep.matrix.Inverted;
subtract.Transform(inverseKeep);
using (keep.obj3D.RebuildLock())
{
keep.obj3D.Mesh = subtract;
}
// keep all the intersections together
if (paintMesh == null)
{
paintMesh = intersect;
}
else // union into the current paint
{
paintMesh = BooleanProcessing.Do(paintMesh,
Matrix4X4.Identity,
intersect,
Matrix4X4.Identity,
CsgModes.Subtract,
ProcessingModes.Polygons,
ProcessingResolution._64,
ProcessingResolution._64,
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
}
if (cancellationToken.IsCancellationRequested)
{
break;
}
}
// move the paint mesh back to its original coordinates
paintMesh.Transform(inverseRemove);
using (paint.obj3D.RebuildLock())
{
paint.obj3D.Mesh = paintMesh;
}
paint.obj3D.Color = paint.obj3D.WorldColor().WithContrast(keepObjects.First().WorldColor(), 2).ToColor();
}
}
}
public override Task Rebuild()
{
var rebuildLocks = this.RebuilLockAll();
// spin up a task to calculate the paint
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(), null, (reporter, cancellationTokenSource) =>
{
try
{
SubtractAndReplace(cancellationTokenSource.Token, reporter);
}
catch
{
}
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
}
}

View file

@ -0,0 +1,330 @@
/*
Copyright (c) 2023, 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;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh;
using MatterHackers.PolygonMesh.Csg;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[ShowUpdateButton]
public class SubtractAndReplaceObject3D_2 : OperationSourceContainerObject3D, ISelectableChildContainer, ICustomEditorDraw, IPropertyGridModifier
{
public SubtractAndReplaceObject3D_2()
{
Name = "Subtract and Replace";
}
public bool DoEditorDraw(bool isSelected)
{
return isSelected;
}
[HideFromEditor]
public SelectedChildren ComputedChildren { get; set; } = new SelectedChildren();
[DisplayName("Part(s) to Subtract and Replace")]
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
#if DEBUG
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public IplicitSurfaceMethod MeshAnalysis { get; set; }
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#else
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
private IplicitSurfaceMethod MeshAnalysis { get; set; }
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#endif
public void AddEditorTransparents(Object3DControlsLayer layer, List<Object3DView> transparentMeshes, DrawEventArgs e)
{
if (layer.Scene.SelectedItem != null
&& layer.Scene.SelectedItem == this)
{
var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
var removeObjects = parentOfSubtractTargets.Children
.Where(i => SelectedChildren.Contains(i.ID))
.SelectMany(c => c.VisibleMeshes())
.ToList();
foreach (var item in removeObjects)
{
var color = item.WorldColor(checkOutputType: true);
transparentMeshes.Add(new Object3DView(item, color.WithAlpha(color.Alpha0To1 * .2)));
}
}
}
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
{
return;
}
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
{
return AxisAlignedBoundingBox.Empty();
}
public override async void OnInvalidate(InvalidateArgs invalidateType)
{
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
&& invalidateType.Source != this
&& !RebuildLocked)
{
await Rebuild();
}
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
&& invalidateType.Source == this)
{
await Rebuild();
}
else
{
base.OnInvalidate(invalidateType);
}
}
private CancellationTokenSource cancellationToken;
public bool IsBuilding => this.cancellationToken != null;
public void CancelBuild()
{
var threadSafe = this.cancellationToken;
if (threadSafe != null)
{
threadSafe.Cancel();
}
}
public override Task Rebuild()
{
var rebuildLocks = this.RebuilLockAll();
// spin up a task to calculate the paint
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
try
{
SubtractAndReplace(cancellationTokenSource.Token, reporter);
var newComputedChildren = new SelectedChildren();
foreach (var id in SelectedChildren)
{
newComputedChildren.Add(id);
}
ComputedChildren = newComputedChildren;
}
catch
{
}
this.cancellationToken = null;
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public void SubtractAndReplace()
{
SubtractAndReplace(CancellationToken.None, null);
}
private void SubtractAndReplace(CancellationToken cancellationToken, Action<double, string> reporter)
{
SourceContainer.Visible = true;
RemoveAllButSource();
var parentOfPaintTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
if (parentOfPaintTargets.Children.Count() < 2)
{
if (parentOfPaintTargets.Children.Count() == 1)
{
this.Children.Add(SourceContainer.Clone());
SourceContainer.Visible = false;
}
return;
}
SubtractObject3D_2.CleanUpSelectedChildrenIDs(this);
var paintObjects = parentOfPaintTargets.Children
.Where((i) => SelectedChildren
.Contains(i.ID))
.SelectMany(c => c.VisibleMeshes())
.ToList();
var keepItems = parentOfPaintTargets.Children
.Where((i) => !SelectedChildren
.Contains(i.ID));
var keepVisibleItems = keepItems.SelectMany(c => c.VisibleMeshes()).ToList();
if (paintObjects.Any()
&& keepVisibleItems.Any())
{
var totalOperations = paintObjects.Count * keepVisibleItems.Count * 2;
double amountPerOperation = 1.0 / totalOperations;
double ratioCompleted = 0;
foreach (var keep in keepVisibleItems)
{
var keepResultsMesh = keep.Mesh;
var keepWorldMatrix = keep.WorldMatrix(SourceContainer);
foreach (var paint in paintObjects)
{
if (cancellationToken.IsCancellationRequested)
{
SourceContainer.Visible = true;
RemoveAllButSource();
return;
}
Mesh paintMesh = BooleanProcessing.Do(keepResultsMesh,
keepWorldMatrix,
// paint data
paint.Mesh,
paint.WorldMatrix(SourceContainer),
// operation type
CsgModes.Intersect,
Processing,
InputResolution,
OutputResolution,
// reporting data
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
ratioCompleted += amountPerOperation;
keepResultsMesh = BooleanProcessing.Do(keepResultsMesh,
keepWorldMatrix,
// point data
paint.Mesh,
paint.WorldMatrix(SourceContainer),
// operation type
CsgModes.Subtract,
Processing,
InputResolution,
OutputResolution,
// reporting data
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
// after the first time we get a result the results mesh is in the right coordinate space
keepWorldMatrix = Matrix4X4.Identity;
// store our intersection (paint) results mesh
var paintResultsItem = new Object3D()
{
Mesh = paintMesh,
Visible = false,
OwnerID = paint.ID
};
// copy all the properties but the matrix
paintResultsItem.CopyWorldProperties(paint, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
// and add it to this
this.Children.Add(paintResultsItem);
// report our progress
ratioCompleted += amountPerOperation;
reporter?.Invoke(ratioCompleted, "Do CSG".Localize());
}
// store our results mesh
var keepResultsItem = new Object3D()
{
Mesh = keepResultsMesh,
Visible = false,
OwnerID = keep.ID
};
// copy all the properties but the matrix
keepResultsItem.CopyWorldProperties(keep, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
// and add it to this
this.Children.Add(keepResultsItem);
}
foreach (var child in Children)
{
child.Visible = true;
}
SourceContainer.Visible = false;
}
}
public void UpdateControls(PublicPropertyChange change)
{
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
}
}
}

View file

@ -0,0 +1,184 @@
/*
Copyright (c) 2023, 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.
*/
/*********************************************************************/
/**************************** OBSOLETE! ******************************/
/************************ USE NEWER VERSION **************************/
/*********************************************************************/
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.PolygonMesh;
using MatterHackers.PolygonMesh.Csg;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[Obsolete("Use SubtractObject3D_2 instead", false)]
[ShowUpdateButton(SuppressPropertyChangeUpdates = true)]
public class SubtractObject3D : MeshWrapperObject3D, ISelectableChildContainer
{
public SubtractObject3D()
{
Name = "Subtract";
}
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
public override async void OnInvalidate(InvalidateArgs invalidateType)
{
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
&& invalidateType.Source != this
&& !RebuildLocked)
{
await Rebuild();
}
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
&& invalidateType.Source == this)
{
await Rebuild();
}
base.OnInvalidate(invalidateType);
}
public override Task Rebuild()
{
this.DebugDepth("Rebuild");
var rebuildLocks = this.RebuilLockAll();
// spin up a task to remove holes from the objects in the group
return ApplicationController.Instance.Tasks.Execute(
"Subtract".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
try
{
Subtract(cancellationTokenSource.Token, reporter);
}
catch
{
}
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public void Subtract()
{
Subtract(CancellationToken.None, null);
}
public void Subtract(CancellationToken cancellationToken, Action<double, string> reporter)
{
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
bool ItemInSubtractList(IObject3D item)
{
if (SelectedChildren.Contains(item.ID))
{
return true;
}
// check if the wrapped item is in the subtract list
if (item.Children.Count > 0 && SelectedChildren.Contains(item.Children.First().ID))
{
return true;
}
return false;
}
var removeObjects = this.Children
.Where(i => ItemInSubtractList(i))
.SelectMany(h => h.DescendantsAndSelf())
.Where(c => c.OwnerID == this.ID).ToList();
var keepObjects = this.Children
.Where(i => !ItemInSubtractList(i))
.SelectMany(h => h.DescendantsAndSelf())
.Where(c => c.OwnerID == this.ID).ToList();
if (removeObjects.Any()
&& keepObjects.Any())
{
var totalOperations = removeObjects.Count * keepObjects.Count;
double amountPerOperation = 1.0 / totalOperations;
double ratioCompleted = 0;
foreach (var remove in removeObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
{
foreach (var keep in keepObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
{
reporter?.Invoke(0, "Do CSG".Localize());
var result = BooleanProcessing.Do(keep.obj3D.Mesh,
keep.matrix,
remove.obj3D.Mesh,
remove.matrix,
CsgModes.Subtract,
ProcessingModes.Polygons,
ProcessingResolution._64,
ProcessingResolution._64,
reporter,
amountPerOperation,
ratioCompleted,
cancellationToken);
var inverse = keep.matrix.Inverted;
result.Transform(inverse);
using (keep.obj3D.RebuildLock())
{
keep.obj3D.Mesh = result;
}
ratioCompleted += amountPerOperation;
reporter?.Invoke(ratioCompleted, null);
}
remove.obj3D.Visible = false;
}
}
}
}
}

View file

@ -0,0 +1,337 @@
/*
Copyright (c) 2017, 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;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh.Csg;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
[ShowUpdateButton]
public class SubtractObject3D_2 : OperationSourceContainerObject3D, ISelectableChildContainer, ICustomEditorDraw, IPropertyGridModifier, IBuildsOnThread
{
public SubtractObject3D_2()
{
Name = "Subtract";
NameOverriden = false;
}
[DisplayName("Part(s) to Subtract")]
public virtual SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
#if DEBUG
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public IplicitSurfaceMethod MeshAnalysis { get; set; }
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#else
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
private IplicitSurfaceMethod MeshAnalysis { get; set; }
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
#endif
public bool RemoveSubtractObjects { get; set; } = true;
public bool DoEditorDraw(bool isSelected)
{
return isSelected;
}
public void AddEditorTransparents(Object3DControlsLayer layer, List<Object3DView> transparentMeshes, DrawEventArgs e)
{
if (layer.Scene.SelectedItem != null
&& layer.Scene.SelectedItem == this)
{
var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
var removeObjects = parentOfSubtractTargets.Children
.Where(i => SelectedChildren.Contains(i.ID))
.SelectMany(c => c.VisibleMeshes())
.ToList();
foreach (var item in removeObjects)
{
var color = item.WorldColor(checkOutputType: true);
transparentMeshes.Add(new Object3DView(item, color.WithAlpha(color.Alpha0To1 * .2)));
}
}
}
public override async void WrapSelectedItemAndSelect(InteractiveScene scene)
{
// this will ask the subtract to do a rebuild
base.WrapSelectedItemAndSelect(scene);
if (SelectedChildren.Count == 0)
{
SelectedChildren.Add(SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf().Children.Last().ID);
}
}
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
{
return;
}
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
{
return AxisAlignedBoundingBox.Empty();
}
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 (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}
else if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Name)
&& !NameOverriden)
{
Name = NameFromChildren();
NameOverriden = false;
}
else
{
base.OnInvalidate(invalidateArgs);
}
}
private CancellationTokenSource cancellationToken;
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");
var rebuildLocks = this.RebuilLockAll();
return ApplicationController.Instance.Tasks.Execute(
"Subtract".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
this.cancellationToken = cancellationTokenSource;
try
{
Subtract(cancellationTokenSource.Token, reporter);
}
catch
{
}
if (!NameOverriden)
{
Name = NameFromChildren();
NameOverriden = false;
}
this.cancellationToken = null;
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public void Subtract()
{
Subtract(CancellationToken.None, null);
}
private static (IEnumerable<IObject3D>, IEnumerable<IObject3D>) GetSubtractItems(IObject3D source, SelectedChildren selectedChildren)
{
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
.Contains(i.ID))
.SelectMany(c => c.VisibleMeshes());
var keepItems = parentOfSubtractTargets.Children
.Where((i) => !selectedChildren
.Contains(i.ID))
.SelectMany(c => c.VisibleMeshes());
return (keepItems, removeItems);
}
private void Subtract(CancellationToken cancellationToken, Action<double, string> reporter)
{
SourceContainer.Visible = true;
RemoveAllButSource();
CleanUpSelectedChildrenIDs(this);
var (keepItems, removeItems) = GetSubtractItems(SourceContainer, SelectedChildren);
var resultItems = DoSubtract(SourceContainer,
keepItems,
removeItems,
reporter,
cancellationToken,
Processing,
InputResolution,
OutputResolution);
foreach (var resultsItem in resultItems)
{
this.Children.Add(resultsItem);
if (!RemoveSubtractObjects)
{
this.Children.Modify((list) =>
{
foreach (var item in removeItems)
{
var newObject = new Object3D()
{
Mesh = item.Mesh
};
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)
{
if (first)
{
// hide the source item
child.Visible = false;
first = false;
}
else
{
child.Visible = true;
}
}
}
}
public static void CleanUpSelectedChildrenIDs(OperationSourceContainerObject3D item)
{
if (item is ISelectableChildContainer selectableChildContainer)
{
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
foreach (var id in selectableChildContainer.SelectedChildren.ToArray())
{
if (!allVisibleIDs.Contains(id))
{
selectableChildContainer.SelectedChildren.Remove(id);
}
}
}
}
public void UpdateControls(PublicPropertyChange change)
{
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
}
public override string NameFromChildren()
{
var (keepItems, removeItems) = GetSubtractItems(SourceContainer, SelectedChildren);
return CalculateName(keepItems, ", ", " - ", removeItems, ", ");
}
}
}

View file

@ -0,0 +1,268 @@
/*
Copyright (c) 2023, 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 MatterControlLib.DesignTools.Operations.Path;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.Agg.VertexSource;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh.Processors;
using MatterHackers.VectorMath;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
{
public class SubtractPathObject3D : OperationSourceContainerObject3D, IEditorDraw, IObject3DControlsProvider, IPrimaryOperationsSpecifier, IPathProvider
{
public SubtractPathObject3D()
{
Name = "Subtract";
}
[DisplayName("Part(s) to Subtract")]
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
{
this.DrawPath();
}
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
{
return this.GetWorldspaceAabbOfDrawPath();
}
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
{
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
}
public override async void OnInvalidate(InvalidateArgs invalidateType)
{
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
&& invalidateType.Source != this
&& !RebuildLocked)
{
await Rebuild();
}
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
&& invalidateType.Source == this)
{
await Rebuild();
}
else
{
base.OnInvalidate(invalidateType);
}
}
public override bool CanApply => true;
public bool MeshIsSolidObject => false;
public VertexStorage VertexStorage { get; set; }
public override void Apply(UndoBuffer undoBuffer)
{
this.FlattenToPathObject(undoBuffer);
}
public override Task Rebuild()
{
this.DebugDepth("Rebuild");
var rebuildLocks = this.RebuilLockAll();
return ApplicationController.Instance.Tasks.Execute(
"Subtract".Localize(),
null,
(reporter, cancellationTokenSource) =>
{
try
{
Subtract(cancellationTokenSource.Token, reporter);
}
catch
{
}
// set the mesh to show the path
if (this.GetRawPath() != null)
{
var extrudeMesh = this.GetRawPath().Extrude(Constants.PathPolygonsHeight);
if (extrudeMesh.Vertices.Count() > 5)
{
this.Mesh = extrudeMesh;
}
else
{
this.Mesh = null;
}
}
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
this.CancelAllParentBuilding();
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public void Subtract()
{
Subtract(CancellationToken.None, null);
}
private void Subtract(CancellationToken cancellationToken, Action<double, string> reporter)
{
SourceContainer.Visible = true;
RemoveAllButSource();
var parentOfSubtractTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
if (parentOfSubtractTargets.Children.Count() < 2)
{
if (parentOfSubtractTargets.Children.Count() == 1)
{
this.Children.Add(SourceContainer.Clone());
SourceContainer.Visible = false;
}
return;
}
CleanUpSelectedChildrenNames(this);
var removeVisibleItems = parentOfSubtractTargets.Children
.Where((i) => SelectedChildren
.Contains(i.ID))
.SelectMany(c => c.VisiblePathProviders())
.ToList();
var keepItems = parentOfSubtractTargets.Children
.Where((i) => !SelectedChildren
.Contains(i.ID));
var keepVisibleItems = keepItems.SelectMany(c => c.VisiblePathProviders()).ToList();
if (removeVisibleItems.Any()
&& keepVisibleItems.Any())
{
var totalOperations = removeVisibleItems.Count * keepVisibleItems.Count;
double amountPerOperation = 1.0 / totalOperations;
double ratioCompleted = 0;
bool first = true;
foreach (var keep in keepVisibleItems)
{
var keepObject3D = keep as Object3D;
var resultsVertexSource = keep.GetTransformedPath(this);
foreach (var remove in removeVisibleItems)
{
resultsVertexSource = resultsVertexSource.MergePaths(remove.GetTransformedPath(this), ClipperLib.ClipType.ctDifference);
// report our progress
ratioCompleted += amountPerOperation;
reporter?.Invoke(ratioCompleted, "Do Subtract".Localize());
}
if (first)
{
this.VertexStorage = new VertexStorage(resultsVertexSource);
this.CopyProperties(keepObject3D, Object3DPropertyFlags.Color);
first = false;
}
else
{
this.GetRawPath().MergePaths(resultsVertexSource, ClipperLib.ClipType.ctUnion);
}
}
// this.VertexSource = this.VertexSource.Transform(Matrix.Inverted);
first = true;
foreach (var child in Children)
{
if (first)
{
// hide the source item
child.Visible = false;
first = false;
}
else
{
child.Visible = true;
}
}
}
}
public static void CleanUpSelectedChildrenNames(OperationSourceContainerObject3D item)
{
if (item is ISelectableChildContainer selectableChildContainer)
{
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
foreach (var name in selectableChildContainer.SelectedChildren.ToArray())
{
if (!allVisibleNames.Contains(name))
{
selectableChildContainer.SelectedChildren.Remove(name);
}
}
}
}
public IEnumerable<SceneOperation> GetOperations()
{
return PathObject3DAbstract.GetOperations(this.GetType());
}
public IVertexSource GetRawPath()
{
return VertexStorage;
}
}
}