From 03cc686292fb5260adbb93b1e3396992b794a5fb Mon Sep 17 00:00:00 2001 From: jlewin Date: Thu, 27 Jun 2019 18:54:01 -0700 Subject: [PATCH] Add gradientspace gsSlicer --- .gitmodules | 11 +- MatterHackers.gsSlicer/BasicSlicer.cs | 158 ++++++ .../MatterControl.gsSlicer.csproj | 29 ++ .../MatterHackersPrinter.cs | 129 +++++ MatterHackers.gsSlicer/Slicer/CoreTypes.cs | 70 +++ .../Slicer/GeometrySlicer.cs | 292 +++++++++++ .../Slicer/PrintMeshSettings.cs | 74 +++ .../Slicer/PrintSettings.cs | 462 ++++++++++++++++++ .../Slicer/SettingsSerializer.cs | 403 +++++++++++++++ .../Slicer/ToolpathGenerator.cs | 179 +++++++ MatterHackers.gsSlicer/gsSlicerPlugin.cs | 51 ++ Submodules/agg-sharp | 2 +- Submodules/geometry3Sharp | 1 + Submodules/gsGCode | 1 + Submodules/gsSlicer | 1 + 15 files changed, 1861 insertions(+), 2 deletions(-) create mode 100644 MatterHackers.gsSlicer/BasicSlicer.cs create mode 100644 MatterHackers.gsSlicer/MatterControl.gsSlicer.csproj create mode 100644 MatterHackers.gsSlicer/MatterHackersPrinter.cs create mode 100644 MatterHackers.gsSlicer/Slicer/CoreTypes.cs create mode 100644 MatterHackers.gsSlicer/Slicer/GeometrySlicer.cs create mode 100644 MatterHackers.gsSlicer/Slicer/PrintMeshSettings.cs create mode 100644 MatterHackers.gsSlicer/Slicer/PrintSettings.cs create mode 100644 MatterHackers.gsSlicer/Slicer/SettingsSerializer.cs create mode 100644 MatterHackers.gsSlicer/Slicer/ToolpathGenerator.cs create mode 100644 MatterHackers.gsSlicer/gsSlicerPlugin.cs create mode 160000 Submodules/geometry3Sharp create mode 160000 Submodules/gsGCode create mode 160000 Submodules/gsSlicer diff --git a/.gitmodules b/.gitmodules index 164898729..2a3a5610c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,13 @@ url = https://github.com/MatterHackers/MatterSlice.git [submodule "Submodules/agg-sharp"] path = Submodules/agg-sharp - url = https://github.com/MatterHackers/agg-sharp.git \ No newline at end of file + url = https://github.com/MatterHackers/agg-sharp.git +[submodule "Submodules/geometry3Sharp"] + path = Submodules/geometry3Sharp + url = https://github.com/MatterHackers/geometry3Sharp +[submodule "Submodules/gsGCode"] + path = Submodules/gsGCode + url = https://github.com/MatterHackers/gsGCode +[submodule "Submodules/gsSlicer"] + path = Submodules/gsSlicer + url = https://github.com/MatterHackers/gsSlicer diff --git a/MatterHackers.gsSlicer/BasicSlicer.cs b/MatterHackers.gsSlicer/BasicSlicer.cs new file mode 100644 index 000000000..89fbcb33b --- /dev/null +++ b/MatterHackers.gsSlicer/BasicSlicer.cs @@ -0,0 +1,158 @@ +/* +Copyright (c) 2019, 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.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using cotangent; +using g3; +using gs; +using MatterHackers.Agg; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.SlicerConfiguration; +using MatterHackers.VectorMath; + +namespace MatterHackers.gsBundle +{ + public class BasicSlicer : IObjectSlicer + { + public async Task Slice(IObject3D object3D, IEnumerable printableItems, PrinterSettings printerSettings, string filePath, IProgress progressReporter, CancellationToken cancellationToken) + { + + using (var outputStream = File.OpenWrite(filePath)) + { + var sourceMeshes = new List(); + + foreach (var item in object3D.VisibleMeshes().Where(d => d.MeshPath != null)) + { + string sourceFilePath = await item.ResolveFilePath(null, cancellationToken); + + // Load Mesh + if (File.Exists(sourceFilePath)) + { + var mesh = StandardMeshReader.ReadMesh(sourceFilePath); + if (mesh != null) + { + sourceMeshes.Add(mesh); + } + + var printCenter = printerSettings.GetValue(SettingsKey.print_center); + ApplyTransform(mesh, item.WorldMatrix(), printCenter); + } + } + + PrintSettings settings = LoadSettingsForPrinter(printerSettings); + + // Construct slicer + var slicer = new GeometrySlicer(); + slicer.SliceMeshes(sourceMeshes, settings); + + bool valid = slicer.ExtractResultsIfValid(out PrintMeshAssembly meshes, out PlanarSliceStack slices); + + // Construct GCode generator + var pathGenerator = new ToolpathGenerator(); + pathGenerator.CreateToolPaths(meshes, slices, settings); + + // Write GCode file + var gcodeWriter = new StandardGCodeWriter(); + + var streamWriter = new StreamWriter(outputStream); + + gcodeWriter.WriteFile(pathGenerator.CurrentGCode, streamWriter); + + return true; + } + } + + private static PrintSettings LoadSettingsForPrinter(PrinterSettings printerSettings) + { + var ss = new MatterHackersPrinter(printerSettings); + + var settings = new PrintSettings(); + settings.UpdateFromSettings(ss); + + return settings; + } + + // Modeled after FlipLeftRightCoordSystems and ConvertYUpToZUp examples + public static void ApplyTransform(IDeformableMesh mesh, Matrix4X4 matrix, Vector2 printCenter) + { + int NV = mesh.MaxVertexID; + for (int vid = 0; vid < NV; ++vid) + { + if (mesh.IsVertex(vid)) + { + Vector3d v = mesh.GetVertex(vid); + + // Transform point to MatterControl bed translation + var vec3 = new Vector3(v.x, v.y, v.z); + var transformed = vec3.Transform(matrix); + + // Update and reset + v.x = transformed.X - printCenter.X; + v.y = transformed.Y - printCenter.Y; + v.z = transformed.Z; + + mesh.SetVertex(vid, v); + } + } + } + + public Dictionary Exports { get; } = new Dictionary() + { + [SettingsKey.external_perimeter_speed] = new ExportField(""), + [SettingsKey.make] = new ExportField(""), + [SettingsKey.model] = new ExportField(""), + [SettingsKey.build_height] = new ExportField(""), + [SettingsKey.nozzle_diameter] = new ExportField(""), + [SettingsKey.filament_diameter] = new ExportField(""), + [SettingsKey.has_heated_bed] = new ExportField(""), + [SettingsKey.has_hardware_leveling] = new ExportField(""), + [SettingsKey.layer_height] = new ExportField(""), + [SettingsKey.temperature1] = new ExportField(""), + [SettingsKey.bed_temperature] = new ExportField(""), + [SettingsKey.retract_length] = new ExportField(""), + [SettingsKey.retract_speed] = new ExportField(""), + [SettingsKey.travel_speed] = new ExportField(""), + [SettingsKey.first_layer_speed] = new ExportField(""), + [SettingsKey.bottom_solid_layers] = new ExportField(""), + [SettingsKey.top_solid_layers] = new ExportField("") + }; + + public bool ValidateFile(string filePath) + { + // TODO: Implement solution + System.Diagnostics.Debugger.Break(); + return true; + } + } +} diff --git a/MatterHackers.gsSlicer/MatterControl.gsSlicer.csproj b/MatterHackers.gsSlicer/MatterControl.gsSlicer.csproj new file mode 100644 index 000000000..74c853676 --- /dev/null +++ b/MatterHackers.gsSlicer/MatterControl.gsSlicer.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + + + + F:\Sources\mccentral\MatterControl\bin\Debug + false + + + + + + + + + + + + + + + + + + + + diff --git a/MatterHackers.gsSlicer/MatterHackersPrinter.cs b/MatterHackers.gsSlicer/MatterHackersPrinter.cs new file mode 100644 index 000000000..3e9713e66 --- /dev/null +++ b/MatterHackers.gsSlicer/MatterHackersPrinter.cs @@ -0,0 +1,129 @@ +/* +Copyright (c) 2019, 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 gs; +using MatterHackers.Agg; +using MatterHackers.MatterControl.SlicerConfiguration; +using MatterHackers.VectorMath; + +namespace MatterHackers.gsBundle +{ + public class MatterHackersPrinter : GenericRepRapSettings + { + public MatterHackersPrinter(PrinterSettings settings) + { + string make = settings.GetValue(SettingsKey.make); + string model = settings.GetValue(SettingsKey.model); + + var printCenter = settings.GetValue(SettingsKey.print_center); + var bedSize = settings.GetValue(SettingsKey.bed_size); + + machineInfo = new FFFMachineInfo() + { + ManufacturerName = make, + ManufacturerUUID = agg_basics.GetLongHashCode(make).ToString(), + ModelIdentifier = model, + ModelUUID = agg_basics.GetLongHashCode(model).ToString(), + Class = MachineClass.PlasticFFFPrinter, + + BedSizeXMM = (int)settings.BedBounds.Width, + BedSizeYMM = (int)settings.BedBounds.Height, + // BedSizeZMM = settings.GetValue(SettingsKey.build_height), + MaxHeightMM = settings.GetValue(SettingsKey.build_height), + + NozzleDiamMM = settings.GetValue(SettingsKey.nozzle_diameter), + FilamentDiamMM = settings.GetValue(SettingsKey.filament_diameter), + + // Set bed origin factors based on printer center/bedsize + BedOriginFactorX = printCenter.X / bedSize.X, + BedOriginFactorY = printCenter.Y / bedSize.Y, + + HasHeatedBed = settings.GetValue(SettingsKey.has_heated_bed), + HasAutoBedLeveling = settings.GetValue(SettingsKey.has_hardware_leveling), + EnableAutoBedLeveling = false, + + // TODO: Consider how to adapt and/or update for MatterControl printers + MaxExtruderTempC = 250, + MaxBedTempC = 80, + + MaxExtrudeSpeedMMM = 80 * 60, + MaxTravelSpeedMMM = 120 * 60, + MaxZTravelSpeedMMM = 100 * 60, + MaxRetractSpeedMMM = 45 * 60, + MinLayerHeightMM = 0.05, + MaxLayerHeightMM = 0.3, + }; + + LayerHeightMM = settings.GetValue(SettingsKey.layer_height); + + ExtruderTempC = (int)settings.GetValue(SettingsKey.temperature1); + HeatedBedTempC = (int)settings.GetValue(SettingsKey.bed_temperature); + + RoofLayers = settings.GetValue(SettingsKey.top_solid_layers); + FloorLayers = settings.GetValue(SettingsKey.bottom_solid_layers); + + SolidFillNozzleDiamStepX = 1.0; + RetractDistanceMM = settings.GetValue(SettingsKey.retract_length); + + RetractSpeed = settings.GetValue(SettingsKey.retract_speed) * 60; + ZTravelSpeed = settings.Helpers.GetMovementSpeeds()["z"] * 60; + RapidTravelSpeed = settings.GetValue(SettingsKey.travel_speed) * 60; + CarefulExtrudeSpeed = settings.GetValue(SettingsKey.first_layer_speed) * 60; + RapidExtrudeSpeed = Machine.MaxExtrudeSpeedMMM; + + // Looks like fractional of perimeter speed + var outerSpeed = settings.GetValue(SettingsKey.external_perimeter_speed); + var innerSpeed = settings.GetValue(SettingsKey.external_perimeter_speed); + + OuterPerimeterSpeedX = outerSpeed / innerSpeed; + } + + public override AssemblerFactoryF AssemblerType() + { + return (GCodeBuilder builder, SingleMaterialFFFSettings settings) => + { + return new RepRapAssembler(builder, settings) + { + HeaderCustomizerF = HeaderCustomF + }; + }; + } + + protected void HeaderCustomF(RepRapAssembler.HeaderState state, GCodeBuilder Builder) + { + if (state == RepRapAssembler.HeaderState.BeforePrime) + { + if (Machine.HasAutoBedLeveling && Machine.EnableAutoBedLeveling) + { + Builder.BeginGLine(29, "auto-level bed"); + } + } + } + } +} diff --git a/MatterHackers.gsSlicer/Slicer/CoreTypes.cs b/MatterHackers.gsSlicer/Slicer/CoreTypes.cs new file mode 100644 index 000000000..57e91134b --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/CoreTypes.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace cotangent +{ + + + public enum LineWidthType + { + World, + Pixel + } + + + public enum FrameType + { + LocalFrame = 0, + WorldFrame = 1 + }; + + + public enum UpDirection + { + ZUp = 0, + YUp = 1 + } + + + public enum PivotLocation + { + Center = 0, + BaseCenter = 1 + } + + + + + + + // will/may be called per-frame to give a chance to do something with shortcut keys + // return true to indicate that key was handled, ie "capture" it + public interface IShortcutKeyHandler + { + bool HandleShortcuts(); + } + + + public interface ITextEntryTarget + { + bool ConsumeAllInput(); + bool OnBeginTextEntry(); + bool OnEndTextEntry(); + bool OnBackspace(); + bool OnDelete(); + bool OnReturn(); + bool OnEscape(); + bool OnLeftArrow(); + bool OnRightArrow(); + bool OnCharacters(string s); + } + + + public enum CameraInteractionState + { + BeginCameraAction, + EndCameraAction, + Ignore + } + +} diff --git a/MatterHackers.gsSlicer/Slicer/GeometrySlicer.cs b/MatterHackers.gsSlicer/Slicer/GeometrySlicer.cs new file mode 100644 index 000000000..e4a92b1e7 --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/GeometrySlicer.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using g3; +using gs; +using gs.info; + +namespace cotangent +{ + /// + /// Auto-compute slicing of current CC.PrintMeshes + /// + /// [TODO] + /// - be smarter about polylines? + /// - don't necessarily always need to discard SlicerMeshes, for example if only + /// slicing params changed, we can avoid copy (maybe irrelevant though) + /// + public class GeometrySlicer + { + PrintMeshAssembly SlicerMeshes; + PlanarSliceStack SliceSet; + bool SliceStackValid; + object data_lock = new object(); + + bool show_slice_polylines = false; + public bool ShowSlicePolylines + { + get { return show_slice_polylines; } + set { set_slice_polylines_visible(value); } + } + + public delegate void SlicingStateChangeHandler(); + public event SlicingStateChangeHandler SlicingInvalidatedEvent; + public event SlicingStateChangeHandler SlicingUpdatedEvent; + + // public event SlicingProgressHandler SlicingProgressEvent; + + public bool PauseSlicing = false; + + public GeometrySlicer() + { + InvalidateSlicing(); + } + + public bool IsResultValid() + { + bool valid = false; + lock (data_lock) + { + valid = SliceStackValid; + } + return valid; + } + + public bool ExtractResultsIfValid(out PrintMeshAssembly printMeshes, out PlanarSliceStack slices) + { + bool valid = false; + lock (data_lock) + { + printMeshes = SlicerMeshes; + slices = SliceSet; + valid = SliceStackValid; + } + return valid; + } + + public void InvalidateSlicing() + { + //cancel_active_compute(); + + lock (data_lock) + { + SlicerMeshes = null; + SliceSet = null; + SliceStackValid = false; + } + + // discard_slice_polylines(); + + } + + public void SliceMeshes(List sourceMeshes, PrintSettings printSettings) + { + SliceStackValid = false; + + try + { + var sliceTask = new SliceTask(); + sliceTask.meshCopies = sourceMeshes; + sliceTask.Compute(printSettings); + + SlicerMeshes = sliceTask.MeshAssembly; + SliceSet = sliceTask.SliceStack; + SliceStackValid = true; + } + catch (Exception ex) + { + } + } + + float last_spawn_time = 0; + + SliceTask active_compute; + object active_compute_lock = new object(); + + class SliceTask + { + // input data + public Frame3f[] meshToScene; + public Vector3f[] localScale; + public List meshCopies { get; set; } + public PrintMeshSettings[] meshSettings; + + // computed data + public bool Finished; + public bool Success; + public PrintMeshAssembly MeshAssembly; + public PlanarSliceStack SliceStack; + + // internal + MeshPlanarSlicerPro slicer; + + public SliceTask() + { + Finished = false; + Success = false; + } + + public void Compute(PrintSettings printSettings) + { + int N = meshCopies.Count(); + + slicer = new MeshPlanarSlicerPro() + { + LayerHeightMM = printSettings.LayerHeightMM, + // [RMS] 1.5 here is a hack. If we don't leave a bit of space then often the filament gets squeezed right at + // inside/outside transitions, which is bad. Need a better way to handle. + OpenPathDefaultWidthMM = printSettings.NozzleDiameterMM * 1.5, + SetMinZValue = 0, + SliceFactoryF = PlanarSlicePro.FactoryF + }; + + if (printSettings.OpenMode == PrintSettings.OpenMeshMode.Clipped) + slicer.DefaultOpenPathMode = PrintMeshOptions.OpenPathsModes.Clipped; + else if (printSettings.OpenMode == PrintSettings.OpenMeshMode.Embedded) + slicer.DefaultOpenPathMode = PrintMeshOptions.OpenPathsModes.Embedded; + else if (printSettings.OpenMode == PrintSettings.OpenMeshMode.Ignored) + slicer.DefaultOpenPathMode = PrintMeshOptions.OpenPathsModes.Ignored; + + if (printSettings.StartLayers > 0) + { + int start_layers = printSettings.StartLayers; + double std_layer_height = printSettings.LayerHeightMM; + double start_layer_height = printSettings.StartLayerHeightMM; + slicer.LayerHeightF = (layer_i) => + { + return (layer_i < start_layers) ? start_layer_height : std_layer_height; + }; + } + + try + { + MeshAssembly = new PrintMeshAssembly(); + + for (int k = 0; k < N; ++k) + { + DMesh3 mesh = meshCopies[k]; + //Frame3f mapF = meshToScene[k]; + PrintMeshSettings settings = new PrintMeshSettings(); + + var options = new PrintMeshOptions + { + IsSupport = (settings.ObjectType == PrintMeshSettings.ObjectTypes.Support), + IsCavity = (settings.ObjectType == PrintMeshSettings.ObjectTypes.Cavity), + IsCropRegion = (settings.ObjectType == PrintMeshSettings.ObjectTypes.CropRegion), + IsOpen = settings.OuterShellOnly, + OpenPathMode = PrintMeshSettings.Convert(settings.OpenMeshMode), + Extended = new ExtendedPrintMeshOptions() + { + ClearanceXY = settings.Clearance, + OffsetXY = settings.OffsetXY + } + }; + + //Vector3f scale = localScale[k]; + //MeshTransforms.Scale(mesh, scale.x, scale.y, scale.z); + //MeshTransforms.FromFrame(mesh, mapF); + //MeshTransforms.FlipLeftRightCoordSystems(mesh); + //MeshTransforms.ConvertYUpToZUp(mesh); + + var decomposer = new MeshAssembly(mesh) + { + HasNoVoids = settings.NoVoids + }; + decomposer.Decompose(); + + MeshAssembly.AddMeshes(decomposer.ClosedSolids, options); + + PrintMeshOptions openOptions = options.Clone(); + MeshAssembly.AddMeshes(decomposer.OpenMeshes, openOptions); + } + + if (slicer.Add(MeshAssembly) == false) + throw new Exception("error adding PrintMeshAssembly to Slicer!!"); + + // set clip box + Box2d clip_box = new Box2d( + Vector2d.Zero, + new Vector2d(printSettings.BedSizeXMM / 2, printSettings.BedSizeYMM / 2)); + + slicer.ValidRegions = new List() { + new GeneralPolygon2d(new Polygon2d(clip_box.ComputeVertices())) + }; + + SliceStack = slicer.Compute(); + + Success = true; + } + catch (Exception e) + { + //DebugUtil.Log("GeometrySlicer.Compute: exception: " + e.Message); + System.Diagnostics.Debugger.Break(); + Success = false; + } + + Finished = true; + } + } + + void set_slice_polylines_visible(bool bSet) + { + if (bSet == show_slice_polylines) + return; + + compute_slice_polylines(); + show_slice_polylines = true; + } + + void compute_slice_polylines() + { + // fMaterial mat1 = MaterialUtil.CreateFlatMaterialF(Colorf.Black); + // fMaterial mat2 = MaterialUtil.CreateFlatMaterialF(Colorf.BlueMetal); + + // [TODO] do we need to hold data_lock here? seems like no since main thread is blocked, + // then it would never be the case that we are setting SliceSet = null + + // create geometry + int slice_i = 0; + //SlicePolylines = new List(); + + foreach (PlanarSlice slice in SliceSet.Slices) + { + //DebugUtil.Log(2, "Slice has {0} solids", slice.Solids.Count); + Colorf slice_color = (slice_i % 2 == 0) ? Colorf.Black : Colorf.BlueMetal; + // fMaterial slice_mat = (slice_i % 2 == 0) ? mat1 : mat2; + slice_i++; + foreach (GeneralPolygon2d poly in slice.Solids) + { + List polyLine = new List(); + for (int pi = 0; pi <= poly.Outer.VertexCount; ++pi) + { + int i = pi % poly.Outer.VertexCount; + Vector2d v2 = poly.Outer[i]; + Vector2d n2 = poly.Outer.GetTangent(i).Perp; + + Vector3d v3 = new Vector3d(v2.x, v2.y, slice.Z); + v3 = MeshTransforms.ConvertZUpToYUp(v3); + v3 = MeshTransforms.FlipLeftRightCoordSystems(v3); + Vector3d n3 = MeshTransforms.ConvertZUpToYUp(new Vector3d(n2.x, n2.y, 0)); + n3 = MeshTransforms.FlipLeftRightCoordSystems(n3); + n3.Normalize(); + v3 += 0.1f * n3; + + polyLine.Add((Vector3f)v3); + } + + // Do something with polyline.... + Console.WriteLine(polyLine); + + ////DebugUtil.Log(2, "Polyline has {0} vertiecs", polyLine.Count); + //fPolylineGameObject go = GameObjectFactory.CreatePolylineGO( + // "slice_outer", polyLine, slice_color, 0.1f, LineWidthType.World); + //go.SetMaterial(slice_mat, true); + //CC.ActiveScene.RootGameObject.AddChild(go, false); + //SlicePolylines.Add(go); + } + } + } + } +} diff --git a/MatterHackers.gsSlicer/Slicer/PrintMeshSettings.cs b/MatterHackers.gsSlicer/Slicer/PrintMeshSettings.cs new file mode 100644 index 000000000..f41c2868d --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/PrintMeshSettings.cs @@ -0,0 +1,74 @@ +using System; + +namespace cotangent +{ + public static class DebugUtil + { + internal static void Log(string v) + { + Console.WriteLine(v); + //throw new NotImplementedException(); + } + + internal static void Log(int v1, string v2) + { + Console.WriteLine("{0} - {1}", v1, v2); + } + + internal static void Log(int v1, string v2, string msg, string trace) + { + Console.WriteLine("{0} - {1} {2}", v1, v2, msg); + } + } + + public class PrintMeshSettings + { + // WARNING: this class is serialized! Do not change enum constants! + public int Version = 1; + + public enum ObjectTypes + { + Solid = 0, Support = 1, Cavity = 2, CropRegion = 3, Ignored = 4 + } + + public enum OpenMeshModes + { + Default = 0, Clipped = 1, Embedded = 2, Ignored = 3, + } + + public ObjectTypes ObjectType = ObjectTypes.Solid; + public bool NoVoids = false; + public bool OuterShellOnly = false; + public OpenMeshModes OpenMeshMode = OpenMeshModes.Default; + + public double Clearance = 0; + public double OffsetXY = 0; + + + public PrintMeshSettings Clone() { + return new PrintMeshSettings() { + ObjectType = this.ObjectType, + NoVoids = this.NoVoids, + OuterShellOnly = this.OuterShellOnly, + OpenMeshMode = this.OpenMeshMode, + Clearance = this.Clearance, + OffsetXY = this.OffsetXY + }; + } + + + public static gs.PrintMeshOptions.OpenPathsModes Convert(OpenMeshModes mode) + { + switch (mode) { + case OpenMeshModes.Clipped: return gs.PrintMeshOptions.OpenPathsModes.Clipped; + case OpenMeshModes.Embedded: return gs.PrintMeshOptions.OpenPathsModes.Embedded; + case OpenMeshModes.Ignored: return gs.PrintMeshOptions.OpenPathsModes.Ignored; + default: + case OpenMeshModes.Default: return gs.PrintMeshOptions.OpenPathsModes.Default; + } + } + + + } + +} diff --git a/MatterHackers.gsSlicer/Slicer/PrintSettings.cs b/MatterHackers.gsSlicer/Slicer/PrintSettings.cs new file mode 100644 index 000000000..e184c3852 --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/PrintSettings.cs @@ -0,0 +1,462 @@ + + +using System; +using gs; + +namespace cotangent +{ + public class PrintSettings + { + protected SingleMaterialFFFSettings Active; + + protected bool bValueModified = false; + public bool HasModifiedValues + { + get { return bValueModified; } + } + + + protected double layer_height = 0.2; + public double LayerHeightMM + { + get { return layer_height; } + set { if (layer_height != value) { layer_height = value; setting_modified(true, true); } } + } + public bool LayerHeightMM_Modified { get { return layer_height != Active.LayerHeightMM; } } + + + public enum OpenMeshMode + { + Clipped = 0, Embedded = 1, Ignored = 2 + } + OpenMeshMode open_mode = OpenMeshMode.Clipped; + public OpenMeshMode OpenMode + { + get { return open_mode; } + } + public int OpenModeInt + { + get { return (int)open_mode; } + set + { + OpenMeshMode newmode = (OpenMeshMode)value; + if (open_mode != newmode) + { + open_mode = newmode; setting_modified(true, true); + } + } + } + + + protected int outer_shells = 2; + public int OuterShells + { + get { return outer_shells; } + set { if (outer_shells != value) { outer_shells = value; setting_modified(false, true); } } + } + public bool OuterShells_Modified { get { return outer_shells != Active.Shells; } } + + + protected int roof_layers = 2; + public int RoofLayers + { + get { return roof_layers; } + set { if (roof_layers != value) { roof_layers = value; setting_modified(false, true); } } + } + public bool RoofLayers_Modified { get { return roof_layers != Active.RoofLayers; } } + + + protected int floor_layers = 2; + public int FloorLayers + { + get { return floor_layers; } + set { if (floor_layers != value) { floor_layers = value; setting_modified(false, true); } } + } + public bool FloorLayers_Modified { get { return floor_layers != Active.FloorLayers; } } + + + protected double infill_step = 3.0; + public double InfillStepX + { + get { return infill_step; } + set { if (infill_step != value) { infill_step = value; setting_modified(false, true); } } + } + public bool InfillStepX_Modified { get { return infill_step != Active.SparseLinearInfillStepX; } } + + + protected int interior_solid_region_shells = 0; + public int InteriorSolidRegionShells + { + get { return interior_solid_region_shells; } + set { if (interior_solid_region_shells != value) { interior_solid_region_shells = value; setting_modified(false, true); } } + } + public bool InteriorSolidRegionShells_Modified { get { return interior_solid_region_shells != Active.InteriorSolidRegionShells; } } + + + protected bool clip_self_overlaps = false; + public bool ClipSelfOverlaps + { + get { return clip_self_overlaps; } + set { if (clip_self_overlaps != value) { clip_self_overlaps = value; setting_modified(false, true); } } + } + public bool ClipSelfOverlaps_Modified { get { return clip_self_overlaps != Active.ClipSelfOverlaps; } } + + + + + /* + * Start layer settings + */ + protected int start_layers = 0; + public int StartLayers + { + get { return start_layers; } + set { if (start_layers != value) { start_layers = value; setting_modified(true, true); } } + } + public bool StartLayers_Modified { get { return start_layers != Active.StartLayers; } } + + + protected double start_layer_height = 0.2; + public double StartLayerHeightMM + { + get { return start_layer_height; } + set { if (start_layer_height != value) { start_layer_height = value; setting_modified(true, true); } } + } + public bool StartLayerHeightMM_Modified { get { return start_layer_height != Active.StartLayerHeightMM; } } + + + + /* + * Support settings + */ + + protected bool generate_support = false; + public bool GenerateSupport + { + get { return generate_support; } + set { if (generate_support != value) { generate_support = value; setting_modified(true, true); } } + } + public bool GenerateSupport_Modified { get { return generate_support != Active.GenerateSupport; } } + + protected double overhang_angle = 35.0; + public double OverhangAngleDeg + { + get { return overhang_angle; } + set { if (overhang_angle != value) { overhang_angle = value; setting_modified(false, true); } } + } + public bool OverhangAngleDeg_Modified { get { return overhang_angle != Active.SupportOverhangAngleDeg; } } + + protected bool support_minz_tips = true; + public bool SupportMinZTips + { + get { return support_minz_tips; } + set { if (support_minz_tips != value) { support_minz_tips = value; setting_modified(false, true); } } + } + public bool SupportMinZTips_Modified { get { return support_minz_tips != Active.SupportMinZTips; } } + + protected double support_step = 3.0; + public double SupportStepX + { + get { return support_step; } + set { if (support_step != value) { support_step = value; setting_modified(false, true); } } + } + public bool SupportStepX_Modified { get { return support_step != Active.SupportSpacingStepX; } } + + + protected bool enable_support_shell = false; + public bool EnableSupportShell + { + get { return enable_support_shell; } + set { if (enable_support_shell != value) { enable_support_shell = value; setting_modified(false, true); } } + } + public bool EnableSupportShell_Modified { get { return enable_support_shell != Active.EnableSupportShell; } } + + + protected bool enable_support_release_opt = false; + public bool EnableSupportReleaseOpt + { + get { return enable_support_release_opt; } + set { if (enable_support_release_opt != value) { enable_support_release_opt = value; setting_modified(false, true); } } + } + public bool EnableSupportReleaseOpt_Modified { get { return enable_support_release_opt != Active.EnableSupportReleaseOpt; } } + + + protected double support_solid_space = 0.3; + public double SupportSolidSpace + { + get { return support_solid_space; } + set { if (support_solid_space != value) { support_solid_space = value; setting_modified(false, true); } } + } + public bool SupportSolidSpace_Modified { get { return support_solid_space != Active.SupportSolidSpace; } } + + + /* + * Bridging settings + */ + + protected bool enable_bridging = false; + public bool EnableBridging + { + get { return enable_bridging; } + set { if (enable_bridging != value) { enable_bridging = value; setting_modified(false, true); } } + } + public bool EnableBridging_Modified { get { return enable_bridging != Active.EnableBridging; } } + + protected double max_bridge_dist = 10.0; + public double MaxBridgeDistanceMM + { + get { return max_bridge_dist; } + set { if (max_bridge_dist != value) { max_bridge_dist = value; setting_modified(false, true); } } + } + public bool MaxBridgeDistanceMM_Modified { get { return max_bridge_dist != Active.MaxBridgeWidthMM; } } + + + + /* + * Extra settings + */ + + protected int layer_range_min = 1; + public int LayerRangeMin + { + get { return layer_range_min; } + set { if (layer_range_min != value) { layer_range_min = value; setting_modified(false, true); } } + } + public bool LayerRangeMin_Modified { get { return layer_range_min != (Active.LayerRangeFilter.a + 1); } } + + + protected int layer_range_max = 999999999; + public int LayerRangeMax + { + get { return layer_range_max; } + set { if (layer_range_max != value) { layer_range_max = value; setting_modified(false, true); } } + } + public bool LayerRangeMax_Modified { get { return layer_range_max != (Active.LayerRangeFilter.b + 1); } } + + + + /* + * Machine settings + */ + + + protected double nozzle_diam = 0.4; + public double NozzleDiameterMM + { + get { return nozzle_diam; } + set { if (nozzle_diam != value) { nozzle_diam = value; setting_modified(true, true); } } + } + public bool NozzleDiameterMM_Modified { get { return nozzle_diam != Active.Machine.NozzleDiamMM; } } + + protected double filament_diam = 0.4; + public double FilamentDiameterMM + { + get { return filament_diam; } + set { if (filament_diam != value) { filament_diam = value; setting_modified(true, true); } } + } + public bool FilamentDiameterMM_Modified { get { return filament_diam != Active.Machine.FilamentDiamMM; } } + + + protected int extruder_temp = 230; + public int ExtruderTempC + { + get { return extruder_temp; } + set { if (extruder_temp != value) { extruder_temp = value; setting_modified(false, true); } } + } + public bool ExtruderTempC_Modified { get { return extruder_temp != Active.ExtruderTempC; } } + + + public bool HasHeatedBed + { + get { return Active.Machine.HasHeatedBed; } + } + + protected int bed_temp = 0; + public int BedTempC + { + get { return bed_temp; } + set { if (bed_temp != value) { bed_temp = value; setting_modified(false, true); } } + } + public bool BedTempC_Modified { get { return bed_temp != Active.HeatedBedTempC; } } + + + protected int print_speed = 90; + public int PrintSpeedMMS + { + get { return print_speed; } + set { if (print_speed != value) { print_speed = value; setting_modified(false, true); } } + } + public bool PrintSpeedMMS_Modified { get { return print_speed != (int)Math.Round(Active.RapidExtrudeSpeed / 60, 0); } } + + + protected int travel_speed = 150; + public int TravelSpeedMMS + { + get { return travel_speed; } + set { if (travel_speed != value) { travel_speed = value; setting_modified(false, true); } } + } + public bool TravelSpeedMMS_Modified { get { return travel_speed != (int)Math.Round(Active.RapidTravelSpeed / 60, 0); } } + + + protected int fan_speed_x = 100; + public int FanSpeedX + { + get { return fan_speed_x; } + set { if (fan_speed_x != value) { fan_speed_x = value; setting_modified(false, true); } } + } + public bool FanSpeedX_Modified { get { return fan_speed_x != (int)Math.Round(Active.FanSpeedX * 100, 0); } } + + + protected int bed_size_x = 100; + public int BedSizeXMM + { + get { return bed_size_x; } + set { if (bed_size_x != value) { bed_size_x = value; setting_modified(false, true); } } + } + public bool BedSizeXMM_Modified { get { return bed_size_x != (int)Active.Machine.BedSizeXMM; } } + + protected int bed_size_y = 100; + public int BedSizeYMM + { + get { return bed_size_y; } + set { if (bed_size_y != value) { bed_size_y = value; setting_modified(false, true); } } + } + public bool BedSizeYMM_Modified { get { return bed_size_y != (int)Active.Machine.BedSizeYMM; } } + + protected int bed_size_z = 100; + public int BedSizeZMM + { + get { return bed_size_z; } + set { if (bed_size_z != value) { bed_size_z = value; setting_modified(false, true); } } + } + public bool BedSizeZMM_Modified { get { return bed_size_z != (int)Active.Machine.MaxHeightMM; } } + + + public delegate void SettingsModifiedEvent(PrintSettings settings); + public event SettingsModifiedEvent OnNewSettings; + public event SettingsModifiedEvent OnSettingModified; + + + void setting_modified(bool invalidate_slicing, bool invalidate_paths) + { + //if (invalidate_slicing) + // CC.InvalidateSlicing(); + //else if (invalidate_paths) + // CC.InvalidateToolPaths(); + bValueModified = true; + OnSettingModified?.Invoke(this); + } + + + + public void UpdateFromSettings(PlanarAdditiveSettings settings) + { + if (settings == null) + return; + if (settings is SingleMaterialFFFSettings == false) + throw new Exception("PrintSettings.UpdateFromSettings: invalid settings type!"); + + SingleMaterialFFFSettings ss = settings as SingleMaterialFFFSettings; + Active = ss; + + LayerHeightMM = ss.LayerHeightMM; + OuterShells = ss.Shells; + RoofLayers = ss.RoofLayers; + FloorLayers = ss.FloorLayers; + InfillStepX = ss.SparseLinearInfillStepX; + ClipSelfOverlaps = ss.ClipSelfOverlaps; + InteriorSolidRegionShells = ss.InteriorSolidRegionShells; + StartLayers = ss.StartLayers; + StartLayerHeightMM = ss.StartLayerHeightMM; + + GenerateSupport = ss.GenerateSupport; + OverhangAngleDeg = ss.SupportOverhangAngleDeg; + SupportMinZTips = ss.SupportMinZTips; + EnableSupportShell = ss.EnableSupportShell; + EnableSupportReleaseOpt = ss.EnableSupportReleaseOpt; + SupportStepX = ss.SupportSpacingStepX; + SupportSolidSpace = ss.SupportSolidSpace; + + EnableBridging = ss.EnableBridging; + MaxBridgeDistanceMM = ss.MaxBridgeWidthMM; + + LayerRangeMin = ss.LayerRangeFilter.a + 1; + LayerRangeMax = ss.LayerRangeFilter.b + 1; + + NozzleDiameterMM = ss.Machine.NozzleDiamMM; + FilamentDiameterMM = ss.Machine.FilamentDiamMM; + ExtruderTempC = ss.ExtruderTempC; + BedTempC = ss.HeatedBedTempC; + PrintSpeedMMS = (int)Math.Round(ss.RapidExtrudeSpeed / 60, 0); + TravelSpeedMMS = (int)Math.Round(ss.RapidTravelSpeed / 60, 0); + FanSpeedX = (int)Math.Round(ss.FanSpeedX * 100, 0); + BedSizeXMM = (int)ss.Machine.BedSizeXMM; + BedSizeYMM = (int)ss.Machine.BedSizeYMM; + BedSizeZMM = (int)ss.Machine.MaxHeightMM; + + bValueModified = false; + OnNewSettings?.Invoke(this); + } + + public void WriteToSettings(PlanarAdditiveSettings settings) + { + if (settings is SingleMaterialFFFSettings == false) + throw new Exception("PrintSettings.UpdateToSettings: invalid settings type!"); + WriteToSettings(settings as SingleMaterialFFFSettings); + } + public void WriteToSettings(SingleMaterialFFFSettings settings) + { + settings.LayerHeightMM = LayerHeightMM; + settings.Shells = OuterShells; + settings.RoofLayers = RoofLayers; + settings.FloorLayers = FloorLayers; + settings.SparseLinearInfillStepX = InfillStepX; + settings.ClipSelfOverlaps = ClipSelfOverlaps; + settings.InteriorSolidRegionShells = InteriorSolidRegionShells; + settings.StartLayers = StartLayers; + settings.StartLayerHeightMM = StartLayerHeightMM; + + settings.GenerateSupport = GenerateSupport; + settings.SupportOverhangAngleDeg = OverhangAngleDeg; + settings.SupportMinZTips = SupportMinZTips; + settings.EnableSupportShell = EnableSupportShell; + settings.EnableSupportReleaseOpt = EnableSupportReleaseOpt; + settings.SupportSpacingStepX = SupportStepX; + settings.SupportSolidSpace = SupportSolidSpace; + + settings.EnableBridging = EnableBridging; + settings.MaxBridgeWidthMM = MaxBridgeDistanceMM; + + int use_min = Math.Max(0, LayerRangeMin - 1); + int use_max = (LayerRangeMax == 0) ? 999999999 : LayerRangeMax - 1; + if (use_max < use_min) + use_max = use_min; + settings.LayerRangeFilter = new g3.Interval1i(use_min, use_max); + + settings.FanSpeedX = (double)FanSpeedX / 100.0; + + settings.Machine.NozzleDiamMM = NozzleDiameterMM; + settings.Machine.FilamentDiamMM = FilamentDiameterMM; + settings.ExtruderTempC = ExtruderTempC; + settings.HeatedBedTempC = BedTempC; + settings.RapidExtrudeSpeed = PrintSpeedMMS * 60; + settings.RapidTravelSpeed = TravelSpeedMMS * 60; + settings.Machine.BedSizeXMM = BedSizeXMM; + settings.Machine.BedSizeYMM = BedSizeYMM; + settings.Machine.MaxHeightMM = BedSizeZMM; + } + + + public SingleMaterialFFFSettings CloneCurrentSettings() + { + SingleMaterialFFFSettings clone = Active.CloneAs(); + WriteToSettings(clone); + return clone; + } + + + + + } + +} diff --git a/MatterHackers.gsSlicer/Slicer/SettingsSerializer.cs b/MatterHackers.gsSlicer/Slicer/SettingsSerializer.cs new file mode 100644 index 000000000..986788e5a --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/SettingsSerializer.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Globalization; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Linq; + + +namespace gs +{ + /// + /// Store/restore additive settings data structures using JSON serialization + /// + public class SettingsSerializer + { + + public SettingsSerializer() + { + + } + + /// + /// Read all .txt files in Path and try to parse each one as a Settings object + /// + public bool RestoreFromFolder(string sPath, out List SettingsList, out List SourceFilePaths) + { + SettingsList = new List(); + SourceFilePaths = new List(); + + string[] files = Directory.GetFiles(sPath); + foreach (string filePath in files) + { + if (!filePath.EndsWith(".txt")) + continue; + + var save_culture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + string data = File.ReadAllText(filePath); + Thread.CurrentThread.CurrentCulture = save_culture; + + SingleMaterialFFFSettings settings = JsonToSettings(data); + + if (settings == null) + { + //DebugUtil.Log("SettingsSerializer.RestoreFromFolder: error reading " + filePath); + } + else + { + SettingsList.Add(settings); + SourceFilePaths.Add(filePath); + } + } + + return SettingsList.Count > 0; + } + + + static Assembly GSGCODE_ASM = null; + + + /// + /// Parse the json in data string into the right type of Settings object + /// + public SingleMaterialFFFSettings JsonToSettings(string data) + { + if (!is_settings_json(data)) + return null; + + // [RMS] because we split gsGCode into a separate dll, Type.GetType() will not find it. + // We have to use a handle to the relevant assembly. + if (GSGCODE_ASM == null) + GSGCODE_ASM = Assembly.Load("gsGCode"); + + int typeIdx = data.IndexOf("\"ClassTypeName\""); + SingleMaterialFFFSettings settings = null; + if (typeIdx > 0) + { + try + { + int commaIdx = typeIdx + 1; + while (data[commaIdx] != ',') + commaIdx++; + int startIdx = typeIdx + 18; + string className = data.Substring(startIdx, commaIdx - startIdx - 1); + //var type = Type.GetType(className); + var type = GSGCODE_ASM.GetType(className); + object o = JsonConvert.DeserializeObject(data, type); + settings = o as SingleMaterialFFFSettings; + } + catch + { + // ignore disasters + } + } + + // either no typename, or we failed to construct it + if (settings == null) + { + try + { + settings = JsonConvert.DeserializeObject(data); + } + catch + { + } + } + + return settings; + } + + + + bool is_settings_json(string s) + { + return s.Contains("ManufacturerUUID") && s.Contains("ModelUUID"); + } + + + + + //public void RefreshSettingsFromDisk(MachineDatabase db, MachinePreset preset) + //{ + // if (File.Exists(preset.SourcePath) == false) + // throw new Exception("SettingsSerializer.RefreshSettingsFromDisk: path " + preset.SourcePath + " does not exist!"); + + // var save_culture = Thread.CurrentThread.CurrentCulture; + // Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + // string data = File.ReadAllText(preset.SourcePath); + // Thread.CurrentThread.CurrentCulture = save_culture; + + // SingleMaterialFFFSettings settings = JsonToSettings(data); + // if ( settings == null ) + // throw new Exception("SettingsSerializer.RefreshSettingsFromDisk: json data could not be parsed!"); + + // preset.Settings = settings; + //} + + + + //public void StoreSettings(MachineDatabase db, MachinePreset preset, bool bCreate = false) + //{ + // JsonSerializerSettings jsonSettings = makeWriteSerializer(); + // if (bCreate == false && File.Exists(preset.SourcePath) == false) + // throw new Exception("SettingsSerializer.StoreSettings: path " + preset.SourcePath + " does not exist!"); + + // var save_culture = Thread.CurrentThread.CurrentCulture; + // Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + + // string json = JsonConvert.SerializeObject(preset.Settings, jsonSettings); + // System.IO.File.WriteAllText(preset.SourcePath, json); + + // Thread.CurrentThread.CurrentCulture = save_culture; + //} + + + + //public void UpdateSettingsFromJson(MachineDatabase db, MachinePreset preset, string newJson, bool bCreate) + //{ + // JsonSerializerSettings jsonSettings = makeWriteSerializer(); + // if (bCreate == false && File.Exists(preset.SourcePath) == false) + // throw new Exception("SettingsSerializer.StoreSettings: path " + preset.SourcePath + " does not exist!"); + + // var save_culture = Thread.CurrentThread.CurrentCulture; + // Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + // System.IO.File.WriteAllText(preset.SourcePath, newJson); + // Thread.CurrentThread.CurrentCulture = save_culture; + + // RefreshSettingsFromDisk(db, preset); + //} + + + + + public bool ValidateSettingsJson(string json) + { + // [RMS] this just checks if string is valid json... + if (!is_valid_json(json)) + return false; + + var settings = JsonToSettings(json); + if (settings == null) + return false; + + return true; + } + + + + /// + /// check if input string is valid json + /// details: https://stackoverflow.com/questions/14977848/how-to-make-sure-that-string-is-valid-json-using-json-net + /// + protected bool is_valid_json(string json) + { + string strInput = json.Trim(); + if ((strInput.StartsWith("{") && strInput.EndsWith("}")) || //For object + (strInput.StartsWith("[") && strInput.EndsWith("]"))) //For array + { + try + { + var obj = JToken.Parse(strInput); + return true; + } + catch (JsonReaderException jex) + { + //Exception in parsing json + Console.WriteLine(jex.Message); + return false; + } + catch (Exception ex) //some other exception + { + Console.WriteLine(ex.ToString()); + return false; + } + } + else + { + return false; + } + } + + + + + + //public void GenerateSettingsFolder(MachineDatabase db, string rootPath) + //{ + // JsonSerializerSettings jsonSettings = makeWriteSerializer(); + + // foreach ( Manufacturer mfg in db.Manufacturers ) { + // string mfgPath = Path.Combine(rootPath, mfg.Name); + // if (!Directory.Exists(mfgPath)) { + // Directory.CreateDirectory(mfgPath); + // if (!Directory.Exists(mfgPath)) + // throw new Exception("SettingsSerializer: cannot create directory for manufacturer " + mfg.Name); + // } + + // IReadOnlyList machines = db.ModelsForManufacturer(mfg); + // foreach ( MachineModel machine in machines) { + // string machinePath = Path.Combine(mfgPath, machine.Name); + // if (! Directory.Exists(machinePath) ) { + // Directory.CreateDirectory(machinePath); + // if (!Directory.Exists(machinePath)) + // throw new Exception("SettingsSerializer: cannot create directory for machine " + mfg.Name + "::" + machine.Name); + // } + + + // PlanarAdditiveSettings settings = machine.DefaultPreset.Settings; + // string settingsPath = Path.Combine(machinePath, settings.Identifier + ".txt"); + + // var save_culture = Thread.CurrentThread.CurrentCulture; + // Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + // string json = JsonConvert.SerializeObject(settings, jsonSettings); + // System.IO.File.WriteAllText(settingsPath, json); + // Thread.CurrentThread.CurrentCulture = save_culture; + // } + // } + //} + + + + //public string CreateNewSettingsFolder(MachineDatabase db, Manufacturer mfg, MachineModel machine, string rootPath) + //{ + // string mfgPath = Path.Combine(rootPath, mfg.Name); + // if (!Directory.Exists(mfgPath)) { + // Directory.CreateDirectory(mfgPath); + // if (!Directory.Exists(mfgPath)) + // throw new Exception("SettingsSerializer: cannot create directory for manufacturer " + mfg.Name); + // } + + // string machinePath = Path.Combine(mfgPath, machine.Name); + // if (!Directory.Exists(machinePath)) { + // Directory.CreateDirectory(machinePath); + // if (!Directory.Exists(machinePath)) + // throw new Exception("SettingsSerializer: cannot create directory for machine " + mfg.Name + "::" + machine.Name); + // } + + // return machinePath; + //} + + + + JsonSerializerSettings makeWriteSerializer() + { + JsonSerializerSettings jsonSettings = new JsonSerializerSettings(); + jsonSettings.Converters.Add( + new Newtonsoft.Json.Converters.StringEnumConverter()); + jsonSettings.Formatting = Formatting.Indented; + jsonSettings.NullValueHandling = NullValueHandling.Ignore; + jsonSettings.ContractResolver = SettingsContractResolver.MyInstance; + return jsonSettings; + } + + + JsonSerializerSettings makeReadSerializer() + { + JsonSerializerSettings jsonSettings = new JsonSerializerSettings(); + jsonSettings.Converters.Add( + new Newtonsoft.Json.Converters.StringEnumConverter()); + jsonSettings.Formatting = Formatting.Indented; + jsonSettings.NullValueHandling = NullValueHandling.Ignore; + return jsonSettings; + } + + + } + + + + + + + /// + /// JSON.Net settings contract - this lets you sort and filter the fields that get serialized + /// + public class SettingsContractResolver : DefaultContractResolver + { + public static readonly SettingsContractResolver MyInstance = new SettingsContractResolver(); + + public HashSet IgnoreFields = new HashSet(); + + public SettingsContractResolver() + { + IgnoreFields.Add("LayerRangeFilter"); + IgnoreFields.Add("BaseMachine"); + } + + // use this to filter out fields + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + JsonProperty property = base.CreateProperty(member, memberSerialization); + if (property.DeclaringType == typeof(SingleMaterialFFFSettings) && IgnoreFields.Contains(property.PropertyName)) + { + property.Ignored = true; + } + return property; + } + + // sort property names alphabetically + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) + { + List sorted = + base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); + + if (type == typeof(FFFMachineInfo)) + sorted = SortMachine(sorted); + + // [TODO] construct an ordering here? + + return sorted; + } + + + static readonly string[] machine_order = new string[] { + "ManufacturerName", + "ManufacturerUUID", + "ModelIdentifier", + "ModelUUID", + "Class", + "BedSizeXMM", + "BedSizeYMM", + "MaxHeightMM", + "FilamentDiamMM", + "NozzleDiamMM", + "MinExtruderTempC", + "MaxExtruderTempC", + "HasHeatedBed", + "MinBedTempC", + "MaxBedTempC", + "MinLayerHeightMM", + "MaxLayerHeightMM" + }; + + List SortMachine(List input) + { + List sorted = new List(input.Count); + bool[] used = new bool[input.Count]; + for (int i = 0; i < machine_order.Length; ++i) + { + int idx = input.FindIndex((v) => { return v.PropertyName.Equals(machine_order[i]); }); + if (idx >= 0) + { + sorted.Add(input[idx]); + } + used[idx] = true; + } + for (int i = 0; i < input.Count; ++i) + { + if (used[i] == false) + sorted.Add(input[i]); + } + return sorted; + } + + } + +} diff --git a/MatterHackers.gsSlicer/Slicer/ToolpathGenerator.cs b/MatterHackers.gsSlicer/Slicer/ToolpathGenerator.cs new file mode 100644 index 000000000..bac5e284c --- /dev/null +++ b/MatterHackers.gsSlicer/Slicer/ToolpathGenerator.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using g3; +using gs; + +namespace cotangent +{ + public class ToolpathGenerator + { + private object active_compute_lock = new object(); + + public GCodeFile CurrentGCode; + public ToolpathSet Toolpaths; + public LayersDetector LayerInfo; + public SingleMaterialFFFSettings Settings; + + public PlanarSliceStack Slices; + + public bool ToolpathsValid; + public bool ToolpathsFailed; + + public bool ShowActualGCodePaths = false; + + public bool PauseToolpathing = false; + + public ToolpathGenerator() + { + } + + public void CreateToolPaths(PrintMeshAssembly meshes, PlanarSliceStack slices, PrintSettings settings) + { + // have to wait for valid slice stack + if (slices == null) + { + return; + } + + //mark_spawn_time(); + this.BuildToolPaths(new GenerationTask + { + PrintSettings = settings.CloneCurrentSettings(), + Meshes = meshes, + SliceSet = slices, + InterpretGCodePaths = ShowActualGCodePaths + }, settings); + } + + private void BuildToolPaths(GenerationTask generationTask, PrintSettings settings) + { + lock (active_compute_lock) + { + DebugUtil.Log("[ToolpathGenerator] Spawning Compute!!"); + generationTask.Compute(settings); + + if (generationTask.Success) + { + CurrentGCode = generationTask.gcode; + Toolpaths = generationTask.paths; + LayerInfo = generationTask.layerInfo; + Settings = generationTask.PrintSettings; + Slices = generationTask.SliceSet; + ToolpathsValid = true; + ToolpathsFailed = false; + + //CC.Objects.SetToolpaths(this); + } + else + { + CurrentGCode = null; + Toolpaths = null; + LayerInfo = null; + Settings = null; + Slices = null; + ToolpathsValid = false; + ToolpathsFailed = true; + } + } + } + + class GenerationTask + { + // input data + public SingleMaterialFFFSettings PrintSettings; + public PrintMeshAssembly Meshes; + public PlanarSliceStack SliceSet; + public bool InterpretGCodePaths; + + // computed data + public bool Finished; + public bool Success; + public bool RequestCancel; + public GCodeFile gcode; + public ToolpathSet paths; + public LayersDetector layerInfo; + + // internal + SingleMaterialFFFPrintGenerator printer; + + public GenerationTask() + { + Finished = false; + Success = false; + RequestCancel = false; + } + + public void Compute(PrintSettings settings) + { + RequestCancel = false; + + printer = + new SingleMaterialFFFPrintGenerator(Meshes, SliceSet, PrintSettings); + + if (PrintSettings.EnableSupportReleaseOpt) + { + printer.LayerPostProcessor = new SupportConnectionPostProcessor() + { + ZOffsetMM = PrintSettings.SupportReleaseGap + }; + } + + // if we aren't interpreting GCode, we want generator to return its path set + printer.AccumulatePathSet = (InterpretGCodePaths == false); + + // set clip region + Box2d clip_box = new Box2d(Vector2d.Zero, + new Vector2d(settings.BedSizeXMM / 2, settings.BedSizeYMM / 2)); + printer.PathClipRegions = new List() { + new GeneralPolygon2d(new Polygon2d(clip_box.ComputeVertices())) + }; + + printer.ErrorF = (msg, trace) => + { + if (RequestCancel == false) + DebugUtil.Log(2, "Slicer Error! msg: {0} stack {1}", msg, trace); + }; + + DebugUtil.Log(2, "Generating gcode..."); + + try + { + if (printer.Generate() == false) + throw new Exception("generate failed"); // this will be caught below + + gcode = printer.Result; + + //DebugUtil.Log(2, "Interpreting gcode..."); + + if (InterpretGCodePaths) + { + GCodeToToolpaths converter = new GCodeToToolpaths(); + MakerbotInterpreter interpreter = new MakerbotInterpreter(); + interpreter.AddListener(converter); + InterpretArgs interpArgs = new InterpretArgs(); + interpreter.Interpret(gcode, interpArgs); + paths = converter.PathSet; + } + else + paths = printer.AccumulatedPaths; + + //DebugUtil.Log(2, "Detecting layers..."); + layerInfo = new LayersDetector(paths); + + Success = true; + + } + catch (Exception e) + { + DebugUtil.Log("ToolpathGenerator.Compute: exception: " + e.Message); + Success = false; + } + + Finished = true; + } + } + } +} diff --git a/MatterHackers.gsSlicer/gsSlicerPlugin.cs b/MatterHackers.gsSlicer/gsSlicerPlugin.cs new file mode 100644 index 000000000..6bd56fb35 --- /dev/null +++ b/MatterHackers.gsSlicer/gsSlicerPlugin.cs @@ -0,0 +1,51 @@ +/* +Copyright (c) 2019, 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.MatterControl.Extensibility; +using MatterHackers.MatterControl.SlicerConfiguration; + +namespace MatterHackers.gsBundle +{ + public class gsSlicerPlugin : IApplicationPlugin + { + public void Initialize() + { + PrinterSettings.Slicer = new BasicSlicer(); + } + + public PluginInfo MetaData => new PluginInfo() + { + About = "gsSlicer for MatterControl", + Developer = "MatterHackers Inc.", + Name = "MH-gsSlicer", + Url = "https://www.matterhackers.com/MatterControl", + UUID = "0384EBD9-072F-4295-AE48-270FC256B48B" + }; + } +} diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 29ad0540f..98150c5a7 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 29ad0540f5742e1a196174cdaf430410921b2cfc +Subproject commit 98150c5a700d51594df7de70e576a21a12912a94 diff --git a/Submodules/geometry3Sharp b/Submodules/geometry3Sharp new file mode 160000 index 000000000..595abad18 --- /dev/null +++ b/Submodules/geometry3Sharp @@ -0,0 +1 @@ +Subproject commit 595abad18cb7818eea14bad060b3e958118af44e diff --git a/Submodules/gsGCode b/Submodules/gsGCode new file mode 160000 index 000000000..0d9c4761e --- /dev/null +++ b/Submodules/gsGCode @@ -0,0 +1 @@ +Subproject commit 0d9c4761e7e0ad35c7feb5094635c3b81aedff82 diff --git a/Submodules/gsSlicer b/Submodules/gsSlicer new file mode 160000 index 000000000..4ca5200c3 --- /dev/null +++ b/Submodules/gsSlicer @@ -0,0 +1 @@ +Subproject commit 4ca5200c3346a3df572deb2b189f22060a8348f6