diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index b9669acb2..dfdb33dd4 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -789,6 +789,58 @@ namespace MatterHackers.MatterControl this.Graph = new GraphConfig(); this.Library.ContentProviders.Add(new[] { "stl", "obj", "amf", "mcx" }, new MeshContentProvider()); this.Library.ContentProviders.Add("gcode", new GCodeContentProvider()); + this.Library.ContentProviders.Add(new[] { "png", "gif", "jpg", "jpeg" }, new ImageContentProvider()); + + this.Graph.RegisterOperation( + typeof(ImageObject3D), + "Image to Path".Localize(), + (sceneItem, scene) => + { + if (sceneItem is IObject3D imageObject) + { + var path = new ImageToPath(); + sceneItem.WrapWith(path, scene); + path.Rebuild(null); + } + + return Task.CompletedTask; + }, + (sceneItem) => true, + (sceneItem) => true); + + this.Graph.RegisterOperation( + typeof(IPathObject), + "Linear Extrude".Localize(), + (sceneItem, scene) => + { + if (sceneItem is IPathObject imageObject) + { + var extrued = new LinearExtrude(); + sceneItem.WrapWith(extrued, scene); + extrued.Rebuild(null); + } + + return Task.CompletedTask; + }, + (sceneItem) => true, + (sceneItem) => true); + + this.Graph.RegisterOperation( + typeof(IPathObject), + "Smooth Path".Localize(), + (sceneItem, scene) => + { + if (sceneItem is IPathObject imageObject) + { + var smoothPath = new SmoothPath(); + sceneItem.WrapWith(smoothPath, scene); + smoothPath.Rebuild(null); + } + + return Task.CompletedTask; + }, + (sceneItem) => true, + (sceneItem) => true); ActiveSliceSettings.SettingChanged.RegisterEvent((s, e) => { diff --git a/DesignTools/LithophaneObject3D.cs b/DesignTools/LithophaneObject3D.cs index c6ff0f962..a34100b33 100644 --- a/DesignTools/LithophaneObject3D.cs +++ b/DesignTools/LithophaneObject3D.cs @@ -59,7 +59,7 @@ namespace MatterHackers.MatterControl.Plugins.Lithophane public Vector3 ImageOffset { get; private set; } = Vector3.Zero; - public void Rebuild(UndoBuffer undoBuffer) + public override void Rebuild(UndoBuffer undoBuffer) { this.DebugDepth("Rebuild"); var activeImage = AggContext.ImageIO.LoadImage(this.Image.AssetPath); diff --git a/DesignTools/Operations/Image/AlphaThresholdFunction.cs b/DesignTools/Operations/Image/AlphaThresholdFunction.cs new file mode 100644 index 000000000..24ffca984 --- /dev/null +++ b/DesignTools/Operations/Image/AlphaThresholdFunction.cs @@ -0,0 +1,79 @@ +/* +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 MatterHackers.Agg; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class AlphaThresholdFunction : IThresholdFunction + { + protected int start0To255 = 120; + protected int end0To255 = 255; + + public AlphaThresholdFunction() + { + } + + public AlphaThresholdFunction(int start, int end) + { + this.start0To255 = start; + this.end0To255 = end; + } + + public double Threshold0To1(Color color) + { + return GetThresholded0To1(color.Alpha0To255); + } + + protected double GetThresholded0To1(int inValue0To255) + { + double outValue = 0; + if (inValue0To255 < start0To255) + { + outValue = 0; + } + else if (inValue0To255 > end0To255) + { + outValue = 1; + } + else + { + outValue = (double)(inValue0To255 - start0To255) / (double)(end0To255 - start0To255); + + if (outValue < 0 || outValue > 255) + { + throw new ArgumentOutOfRangeException(); + } + } + + return outValue; + } + } +} \ No newline at end of file diff --git a/DesignTools/Operations/Image/IThresholdFunction.cs b/DesignTools/Operations/Image/IThresholdFunction.cs new file mode 100644 index 000000000..c465aaec8 --- /dev/null +++ b/DesignTools/Operations/Image/IThresholdFunction.cs @@ -0,0 +1,38 @@ +/* +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; + +namespace MatterHackers.MatterControl.DesignTools +{ + public interface IThresholdFunction + { + double Threshold0To1(Color color); + } +} \ No newline at end of file diff --git a/DesignTools/Operations/Image/ImageToPath.cs b/DesignTools/Operations/Image/ImageToPath.cs new file mode 100644 index 000000000..42dc805ac --- /dev/null +++ b/DesignTools/Operations/Image/ImageToPath.cs @@ -0,0 +1,256 @@ +/* +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 ClipperLib; +using MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.Transform; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MarchingSquares; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MatterHackers.MatterControl.DesignTools +{ + using MatterHackers.Agg.UI; + using MatterHackers.RenderOpenGl.OpenGl; + using MatterHackers.VectorMath; + using Polygon = List; + using Polygons = List>; + + public class ImageToPath : Object3D, IPublicPropertyObject, IPathObject, IEditorDraw + { + private static ImageBuffer generatingThumbnailIcon = AggContext.StaticData.LoadIcon(Path.Combine("building_thumbnail_40x40.png")); + + public ImageToPath() + { + Name = "Path".Localize(); + } + + public enum ThresholdFunctions { Intensity, Alpha, Hue } + + public override bool CanRemove => true; + + public ThresholdFunctions FeatureDetector { get; set; } = ThresholdFunctions.Intensity; + public int EndThreshold { get; internal set; } = 255; + public int StartThreshold { get; internal set; } = 120; + + [JsonIgnore] + public IVertexSource VertexSource { get; set; } = new VertexStorage(); + + [JsonIgnore] + private ImageBuffer Image + { + get + { + var item = this.Descendants().Where((d) => d is ImageObject3D).FirstOrDefault(); + if (item is ImageObject3D imageItem) + { + return imageItem.Image; + } + + return null; + } + } + + private IThresholdFunction ThresholdFunction + { + get + { + switch (FeatureDetector) + { + case ThresholdFunctions.Intensity: + return new MapOnMaxIntensity(StartThreshold, EndThreshold); + + case ThresholdFunctions.Alpha: + return new AlphaThresholdFunction(StartThreshold, EndThreshold); + + case ThresholdFunctions.Hue: + break; + } + + return new MapOnMaxIntensity(StartThreshold, EndThreshold); + } + } + + public void DrawEditor(object sender, DrawEventArgs e) + { + bool first = true; + Vector2 lastPosition = Vector2.Zero; + var aabb = this.VisibleMeshes().FirstOrDefault().GetAxisAlignedBoundingBox(); + foreach (var vertex in VertexSource.Vertices()) + { + var position = vertex.position; + if (first) + { + first = false; + GL.PushMatrix(); + GL.PushAttrib(AttribMask.EnableBit); + GL.MultMatrix(this.WorldMatrix().GetAsFloatArray()); + + GL.Disable(EnableCap.Texture2D); + GL.Disable(EnableCap.Blend); + + GL.Begin(BeginMode.Lines); + GL.Color4(255, 0, 0, 255); + } + + if (vertex.IsLineTo) + { + GL.Vertex3(lastPosition.X, lastPosition.Y, aabb.maxXYZ.Z + 0.002); + GL.Vertex3(position.X, position.Y, aabb.maxXYZ.Z + 0.002); + } + + lastPosition = position; + } + + // if we drew anything + if (!first) + { + GL.End(); + GL.PopAttrib(); + GL.PopMatrix(); + } + } + + public void GenerateMarchingSquaresAndLines(Action progressReporter, ImageBuffer image, IThresholdFunction thresholdFunction) + { + if (image != null) + { + // Regenerate outline + var marchingSquaresData = new MarchingSquaresByte( + image, + thresholdFunction.Threshold0To1, + 0); + + progressReporter?.Invoke(0, "Creating Outline"); + + marchingSquaresData.CreateLineSegments(); + progressReporter?.Invoke(.1, null); + + int pixelsToIntPointsScale = 1000; + var lineLoops = marchingSquaresData.CreateLineLoops(pixelsToIntPointsScale); + + progressReporter?.Invoke(.15, null); + + var min = new IntPoint(-10, -10); + var max = new IntPoint(10 + image.Width * pixelsToIntPointsScale, 10 + image.Height * pixelsToIntPointsScale); + + var boundingPoly = new Polygon(); + boundingPoly.Add(min); + boundingPoly.Add(new IntPoint(min.X, max.Y)); + boundingPoly.Add(max); + boundingPoly.Add(new IntPoint(max.X, min.Y)); + + // now clip the polygons to get the inside and outside polys + var clipper = new Clipper(); + clipper.AddPaths(lineLoops, PolyType.ptSubject, true); + clipper.AddPath(boundingPoly, PolyType.ptClip, true); + + var polygonShape = new Polygons(); + progressReporter?.Invoke(.3, null); + + clipper.Execute(ClipType.ctIntersection, polygonShape); + + progressReporter?.Invoke(.55, null); + + polygonShape = Clipper.CleanPolygons(polygonShape, 100); + + progressReporter?.Invoke(.75, null); + + VertexStorage rawVectorShape = PlatingHelper.PolygonToPathStorage(polygonShape); + + var aabb = this.VisibleMeshes().FirstOrDefault().GetAxisAlignedBoundingBox(); + var xScale = aabb.XSize / image.Width; + + var affine = Affine.NewScaling(1.0 / pixelsToIntPointsScale * xScale); + affine *= Affine.NewTranslation(-aabb.XSize / 2, -aabb.YSize / 2); + + this.VertexSource = new VertexSourceApplyTransform(rawVectorShape, affine); + + progressReporter?.Invoke(1, null); + + Invalidate(new InvalidateArgs(this, InvalidateType.Path)); + } + } + + public override void OnInvalidate(InvalidateArgs invalidateType) + { + if (invalidateType.InvalidateType.HasFlag(InvalidateType.Image) + && invalidateType.Source != this + && !RebuildSuspended) + { + Rebuild(null); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + public override void Rebuild(UndoBuffer undoBuffer) + { + SuspendRebuild(); + + // Make a fast simple path + this.GenerateMarchingSquaresAndLines(null, generatingThumbnailIcon, new MapOnMaxIntensity()); + + // now create a long running task to process the image + ApplicationController.Instance.Tasks.Execute( + "Extrude Image".Localize(), + (reporter, cancellationToken) => + { + var progressStatus = new ProgressStatus(); + this.GenerateMarchingSquaresAndLines( + (progress0to1, status) => + { + progressStatus.Progress0To1 = progress0to1; + progressStatus.Status = status; + reporter.Report(progressStatus); + }, + Image, + ThresholdFunction); + + return Task.CompletedTask; + }); + + ResumeRebuild(); + + Invalidate(new InvalidateArgs(this, InvalidateType.Path)); + base.Rebuild(undoBuffer); + } + } +} \ No newline at end of file diff --git a/DesignTools/Operations/Image/LinearExtrude.cs b/DesignTools/Operations/Image/LinearExtrude.cs new file mode 100644 index 000000000..da90a6910 --- /dev/null +++ b/DesignTools/Operations/Image/LinearExtrude.cs @@ -0,0 +1,90 @@ +/* +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.VertexSource; +using MatterHackers.DataConverters3D; +using Newtonsoft.Json; +using System.Linq; + +namespace MatterHackers.MatterControl.DesignTools +{ + using MatterHackers.Agg.UI; + using MatterHackers.Localizations; + + public class LinearExtrude : Object3D, IPublicPropertyObject + { + public double Height { get; set; } = 5; + + [JsonIgnore] + private IVertexSource VertexSource + { + get + { + var item = this.Descendants().Where((d) => d is IPathObject).FirstOrDefault(); + if (item is IPathObject pathItem) + { + return pathItem.VertexSource; + } + + return null; + } + } + + public LinearExtrude() + { + Name = "Linear Extrude".Localize(); + } + + public override void OnInvalidate(InvalidateArgs invalidateType) + { + if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Content) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Path)) + && invalidateType.Source != this + && !RebuildSuspended) + { + Rebuild(null); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + public override void Rebuild(UndoBuffer undoBuffer) + { + SuspendRebuild(); + Mesh = VertexSourceToMesh.Extrude(this.VertexSource, Height); + ResumeRebuild(); + + Invalidate(new InvalidateArgs(this, InvalidateType.Mesh)); + base.Rebuild(undoBuffer); + } + } +} \ No newline at end of file diff --git a/DesignTools/Operations/Image/MapOnMaxIntensity.cs b/DesignTools/Operations/Image/MapOnMaxIntensity.cs new file mode 100644 index 000000000..1b6cc28a2 --- /dev/null +++ b/DesignTools/Operations/Image/MapOnMaxIntensity.cs @@ -0,0 +1,83 @@ +/* +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 MatterHackers.Agg; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class MapOnMaxIntensity : IThresholdFunction + { + protected int start0To255 = 120; + protected int end0To255 = 255; + + public MapOnMaxIntensity() + { + } + + public MapOnMaxIntensity(int start, int end) + { + this.start0To255 = start; + this.end0To255 = end; + } + + public double Threshold0To1(Color color) + { + // this is on I from HSI + return GetThresholded0To1((color.red + color.blue + color.green) / 3); + + // this is on L from HSL + //return GetThresholded0To1(Math.Max(color.red, Math.Max(color.blue, color.green))); + } + + protected double GetThresholded0To1(int inValue0To255) + { + double outValue = 0; + if (inValue0To255 < start0To255) + { + outValue = 0; + } + else if (inValue0To255 > end0To255) + { + outValue = 1; + } + else + { + outValue = (double)(inValue0To255 - start0To255) / (double)(end0To255 - start0To255); + + if (outValue < 0 || outValue > 255) + { + throw new ArgumentOutOfRangeException(); + } + } + + return outValue; + } + } +} \ No newline at end of file diff --git a/DesignTools/Operations/Object3DExtensions.cs b/DesignTools/Operations/Object3DExtensions.cs index 47dcb0c5e..583ddbc72 100644 --- a/DesignTools/Operations/Object3DExtensions.cs +++ b/DesignTools/Operations/Object3DExtensions.cs @@ -109,8 +109,8 @@ namespace MatterHackers.MatterControl.DesignTools.Operations private static VertexStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType) { - List> aPolys = VertexSourceToClipperPolygons.CreatePolygons(a); - List> bPolys = VertexSourceToClipperPolygons.CreatePolygons(b); + List> aPolys = a.CreatePolygons(); + List> bPolys = b.CreatePolygons(); Clipper clipper = new Clipper(); @@ -122,7 +122,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations Clipper.CleanPolygons(intersectedPolys); - VertexStorage output = VertexSourceToClipperPolygons.CreateVertexStorage(intersectedPolys); + VertexStorage output = intersectedPolys.CreateVertexStorage(); output.Add(0, 0, ShapePath.FlagsAndCommand.Stop); @@ -131,7 +131,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations public static VertexStorage Offset(this IVertexSource a, double distance) { - List> aPolys = VertexSourceToClipperPolygons.CreatePolygons(a); + List> aPolys = a.CreatePolygons(); ClipperOffset offseter = new ClipperOffset(); offseter.AddPaths(aPolys, JoinType.jtMiter, EndType.etClosedPolygon); @@ -140,7 +140,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations Clipper.CleanPolygons(solution); - VertexStorage output = VertexSourceToClipperPolygons.CreateVertexStorage(solution); + VertexStorage output = solution.CreateVertexStorage(); output.Add(0, 0, ShapePath.FlagsAndCommand.Stop); @@ -217,6 +217,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations public static void WrapWith(this IObject3D originalItem, IObject3D wrapper, InteractiveScene scene) { + originalItem.SuspendRebuild(); originalItem.Parent.Children.Modify(list => { list.Remove(originalItem); @@ -230,6 +231,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations }); scene.SelectedItem = wrapper; + originalItem.ResumeRebuild(); } public static Matrix4X4 ApplyAtBoundsCenter(this IObject3D objectWithBounds, Matrix4X4 transformToApply) diff --git a/DesignTools/Operations/SmoothPath.cs b/DesignTools/Operations/Path/SmoothPath.cs similarity index 69% rename from DesignTools/Operations/SmoothPath.cs rename to DesignTools/Operations/Path/SmoothPath.cs index b05ca1d13..a18371650 100644 --- a/DesignTools/Operations/SmoothPath.cs +++ b/DesignTools/Operations/Path/SmoothPath.cs @@ -31,28 +31,75 @@ using System.Collections.Generic; using ClipperLib; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools.Operations { + using MatterHackers.Agg.VertexSource; + using MatterHackers.DataConverters2D; + using Newtonsoft.Json; + using System.Linq; using Polygon = List; using Polygons = List>; - public class SmoothPath : Object3D, IPublicPropertyObject + public class SmoothPath : Object3D, IPublicPropertyObject, IPathObject { public Polygons PathData; + [JsonIgnore] + public IVertexSource VertexSource + { + get + { + var vertexSourc = this.Children.OfType().FirstOrDefault(); + return vertexSourc?.VertexSource; + } + set + { + var vertexSourc = this.Children.OfType().FirstOrDefault(); + if(vertexSourc != null) + { + vertexSourc.VertexSource = value; + } + } + } + public SmoothPath() { + Name = "Smooth Path".Localize(); } - public void Rebuild(UndoBuffer undoBuffer) + public override void OnInvalidate(InvalidateArgs invalidateType) + { + if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Content) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Path)) + && invalidateType.Source != this + && !RebuildSuspended) + { + Rebuild(null); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + public override void Rebuild(UndoBuffer undoBuffer) { this.DebugDepth("Rebuild"); + SuspendRebuild(); + DoSmoothing(); + ResumeRebuild(); + + Invalidate(new InvalidateArgs(this, InvalidateType.Path)); } - public static Polygons DoSmoothing(Polygons inputPolygons, long maxDist = 300, int interations = 3, bool closedPath = true) + public void DoSmoothing(long maxDist = 300, int interations = 3, bool closedPath = true) { + var inputPolygons = VertexSource.CreatePolygons(); + Polygons outputPolygons = new Polygons(); foreach (Polygon inputPolygon in inputPolygons) { @@ -97,7 +144,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations outputPolygons.Add(smoothedPositions); } - return outputPolygons; + VertexSource = outputPolygons.CreateVertexStorage(); } } diff --git a/DesignTools/Primitives/BaseObject3D.cs b/DesignTools/Primitives/BaseObject3D.cs index 0dadc441a..e464e0e97 100644 --- a/DesignTools/Primitives/BaseObject3D.cs +++ b/DesignTools/Primitives/BaseObject3D.cs @@ -74,7 +74,7 @@ namespace MatterHackers.MatterControl.DesignTools var vertexSource = this.VertexSource ?? this.Parent.Children.OfType().FirstOrDefault().VertexSource; // Convert VertexSource into expected Polygons - Polygons polygonShape = (vertexSource == null) ? null : VertexSourceToClipperPolygons.CreatePolygons(vertexSource); + Polygons polygonShape = (vertexSource == null) ? null : vertexSource.CreatePolygons(); GenerateBase(polygonShape); diff --git a/DesignTools/Primitives/ImageObject3D.cs b/DesignTools/Primitives/ImageObject3D.cs index b626b0b5e..219b73980 100644 --- a/DesignTools/Primitives/ImageObject3D.cs +++ b/DesignTools/Primitives/ImageObject3D.cs @@ -83,8 +83,6 @@ namespace MatterHackers.MatterControl.DesignTools { _image = InvertLightness.DoInvertLightness(_image); } - - base.Mesh = this.InitMesh() ?? PlatonicSolids.CreateCube(100, 100, 0.2); } else // bad load { @@ -94,6 +92,14 @@ namespace MatterHackers.MatterControl.DesignTools graphics2D.DrawString("Bad Load", 100, 100); } + + // we don't want to invalidate on the mesh chage + SuspendRebuild(); + base.Mesh = this.InitMesh() ?? PlatonicSolids.CreateCube(100, 100, 0.2); + ResumeRebuild(); + + // send the invalidate on image change + this.OnInvalidate(new InvalidateArgs(this, InvalidateType.Image)); } return _image; @@ -110,7 +116,7 @@ namespace MatterHackers.MatterControl.DesignTools _invert = value; _image = null; - this.OnInvalidate(new InvalidateArgs(this, InvalidateType.Content)); + this.OnInvalidate(new InvalidateArgs(this, InvalidateType.Image)); } } } diff --git a/Library/ContentProviders/ImageContentProvider.cs b/Library/ContentProviders/ImageContentProvider.cs new file mode 100644 index 000000000..43eef0459 --- /dev/null +++ b/Library/ContentProviders/ImageContentProvider.cs @@ -0,0 +1,107 @@ +/* +Copyright (c) 2017, 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.IO; +using System.Threading; +using System.Threading.Tasks; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Platform; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.Library; + +namespace MatterHackers.MatterControl.DesignTools +{ + /// + /// Loads IObject3D and thumbnails for ImageBuffer based ILibraryItem objects + /// + public class ImageContentProvider : ISceneContentProvider + { + public async Task CreateItem(ILibraryItem item, Action reporter) + { + return await Task.Run(async () => + { + var imageBuffer = await this.LoadImage(item); + if (imageBuffer != null) + { + string assetPath = ""; + + if (item is FileSystemFileItem fileItem) + { + assetPath = fileItem.Path; + } + else if (item is ILibraryAssetStream streamInterface) + { + using (var streamAndLength = await streamInterface.GetStream(null)) + { + assetPath = AssetObject3D.AssetManager.StoreStream(streamAndLength.Stream, Path.GetExtension(streamInterface.FileName)); + } + } + + return new ImageObject3D() + { + AssetPath = assetPath, + Name = "Image" + }; + } + else + { + return null; + } + }); + } + + private async Task LoadImage(ILibraryItem item) + { + // Load the image at its native size, let the caller scale or resize + if (item is ILibraryAssetStream streamInterface) + { + using (var streamAndLength = await streamInterface.GetStream(null)) + { + var imageBuffer = new ImageBuffer(); + if (AggContext.ImageIO.LoadImageData(streamAndLength.Stream, imageBuffer)) + { + imageBuffer.SetRecieveBlender(new BlenderPreMultBGRA()); + return imageBuffer; + } + } + } + + return null; + } + + public async Task GetThumbnail(ILibraryItem item, int width, int height, ThumbnailSetter imageCallback) + { + var imageBuffer = await LoadImage(item); + imageCallback(imageBuffer, raytracedImage: true); + } + + public ImageBuffer DefaultImage => AggContext.StaticData.LoadIcon("140.png"); + } +} \ No newline at end of file diff --git a/Library/Providers/MatterControl/PrintQueueContainer.cs b/Library/Providers/MatterControl/PrintQueueContainer.cs index 46e2214a6..f87dac0d2 100644 --- a/Library/Providers/MatterControl/PrintQueueContainer.cs +++ b/Library/Providers/MatterControl/PrintQueueContainer.cs @@ -43,6 +43,7 @@ namespace MatterHackers.MatterControl.Library { public PrintQueueContainer() { + this.IsProtected = false; this.ChildContainers = new List(); this.Items = new List(); this.Name = "Print Queue".Localize(); diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs index 30a8b90bd..b503255a5 100644 --- a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs @@ -68,7 +68,7 @@ namespace MatterHackers.GCodeVisualizer if (moveLength > .1) // we get truncation errors from the slice engine when the length is very small, so don't do them { double area = extrusionVolumeMm3 / moveLength; - width = area / layerHeight; + width = area / layerHeight * 2; } } else diff --git a/MatterControl.csproj b/MatterControl.csproj index fa1155616..158e2f8f8 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -92,11 +92,17 @@ + + + + + + @@ -149,7 +155,7 @@ - + diff --git a/PartPreviewWindow/View3D/Actions/ImageEditor.cs b/PartPreviewWindow/View3D/Actions/ImageEditor.cs index 748b16753..d29f9f5d9 100644 --- a/PartPreviewWindow/View3D/Actions/ImageEditor.cs +++ b/PartPreviewWindow/View3D/Actions/ImageEditor.cs @@ -106,11 +106,11 @@ namespace MatterHackers.MatterControl.DesignTools } // add in the invert checkbox and change image button - var addButton = new TextButton("Change".Localize(), theme) + var changeButton = new TextButton("Change".Localize(), theme) { BackgroundColor = theme.MinimalShade }; - addButton.Click += (sender, e) => + changeButton.Click += (sender, e) => { UiThread.RunOnIdle(() => { @@ -159,7 +159,7 @@ namespace MatterHackers.MatterControl.DesignTools row.AddChild(new HorizontalSpacer()); - row.AddChild(addButton); + row.AddChild(changeButton); imageObject.Invalidated += (s, e) => { diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index e7a8e3284..e75912fda 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit e7a8e3284d916ed35fd804ef96d0e3eeaa0a0455 +Subproject commit e75912fda2c41da2a18d9c4e43a5879c580b2695