diff options
22 files changed, 1045 insertions, 124 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b2650dc..c84aa05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 19/01/2021 - v2.3.1 + +* (Add) Calibrator - Stress Tower: Generates a stress tower to test the printer capabilities +* (Add) PrusaSlicer printer: UVtools Prusa SL1, for SL1 owners must use this profile to be UVtools compatible when using advanced tools +* (Fix) Tool - Calculator - Optimal model tilt: Layer height wasn't get pulled from loaded file and fixed to 0.05mm +* **(Fix) FileFormats:** + * When change a global print paramenter, it will only set this same parameter on every layer, keeping other parameters intact, it was reseting every parameter per layer to globals + * SL1: Verify if files are malformed and if there's missing configuration files (#126) + * CTBv3: Set EncryptionMode to 0x2000000F, this allow the use of per layer settings + ## 13/01/2021 - v2.3.0 * **PrusaSlicer:** diff --git a/ImportPrusaSlicerData.bat b/ImportPrusaSlicerData.bat index 1efb9a9..e81d434 100644 --- a/ImportPrusaSlicerData.bat +++ b/ImportPrusaSlicerData.bat @@ -6,62 +6,63 @@ SET OUTPUT_DIR=%~dp0PrusaSlicer SET PRINT_DIR=sla_print SET PRINTER_DIR=printer -SET files[0]=EPAX E6 Mono.ini -SET files[1]=EPAX E10 Mono.ini -SET files[2]=EPAX X1.ini -SET files[3]=EPAX X10.ini -SET files[4]=EPAX X10 4K Mono.ini -SET files[5]=EPAX X133 4K Mono.ini -SET files[6]=EPAX X156 4K Color.ini -SET files[7]=EPAX X1K 2K Mono.ini -SET files[8]=Zortrax Inkspire.ini -SET files[9]=Nova3D Elfin.ini -SET files[10]=Nova3D Bene4 Mono.ini -SET files[11]=AnyCubic Photon.ini -SET files[12]=AnyCubic Photon S.ini -SET files[13]=AnyCubic Photon Zero.ini -SET files[14]=AnyCubic Photon X.ini -SET files[15]=AnyCubic Photon Mono.ini -SET files[16]=AnyCubic Photon Mono SE.ini -SET files[17]=AnyCubic Photon Mono X.ini -SET files[18]=Elegoo Mars.ini -SET files[19]=Elegoo Mars 2 Pro.ini -SET files[20]=Elegoo Mars C.ini -SET files[21]=Elegoo Saturn.ini -SET files[22]=Peopoly Phenom.ini -SET files[23]=Peopoly Phenom L.ini -SET files[24]=Peopoly Phenom Noir.ini -SET files[25]=Peopoly Phenom XXL.ini -SET files[26]=QIDI Shadow5.5.ini -SET files[27]=QIDI Shadow6.0 Pro.ini -SET files[28]=QIDI S-Box.ini -SET files[29]=QIDI I-Box Mono.ini -SET files[30]=Phrozen Shuffle.ini -SET files[31]=Phrozen Shuffle Lite.ini -SET files[32]=Phrozen Shuffle XL.ini -SET files[33]=Phrozen Shuffle XL Lite.ini -SET files[34]=Phrozen Shuffle 16.ini -SET files[35]=Phrozen Shuffle 4K.ini -SET files[36]=Phrozen Sonic.ini -SET files[37]=Phrozen Sonic 4K.ini -SET files[38]=Phrozen Sonic Mighty 4K.ini -SET files[39]=Phrozen Sonic Mini.ini -SET files[40]=Phrozen Sonic Mini 4K.ini -SET files[41]=Phrozen Transform.ini -SET files[42]=Kelant S400.ini -SET files[43]=Wanhao D7.ini -SET files[44]=Wanhao D8.ini -SET files[45]=Wanhao CGR Mini Mono.ini -SET files[46]=Wanhao CGR Mono.ini -SET files[47]=Creality LD-002R.ini -SET files[48]=Creality LD-002H.ini -SET files[49]=Creality LD-006.ini -SET files[50]=Voxelab Polaris 5.5.ini -SET files[51]=Voxelab Proxima 6.ini -SET files[52]=Voxelab Ceres 8.9.ini -SET files[53]=Longer Orange 10.ini -SET files[54]=Longer Orange 30.ini -SET files[55]=Longer Orange4K.ini +SET files[0]=UVtools Prusa SL1.ini +SET files[1]=EPAX E6 Mono.ini +SET files[2]=EPAX E10 Mono.ini +SET files[3]=EPAX X1.ini +SET files[4]=EPAX X10.ini +SET files[5]=EPAX X10 4K Mono.ini +SET files[6]=EPAX X133 4K Mono.ini +SET files[7]=EPAX X156 4K Color.ini +SET files[8]=EPAX X1K 2K Mono.ini +SET files[9]=Zortrax Inkspire.ini +SET files[10]=Nova3D Elfin.ini +SET files[11]=Nova3D Bene4 Mono.ini +SET files[12]=AnyCubic Photon.ini +SET files[13]=AnyCubic Photon S.ini +SET files[14]=AnyCubic Photon Zero.ini +SET files[15]=AnyCubic Photon X.ini +SET files[16]=AnyCubic Photon Mono.ini +SET files[17]=AnyCubic Photon Mono SE.ini +SET files[18]=AnyCubic Photon Mono X.ini +SET files[19]=Elegoo Mars.ini +SET files[20]=Elegoo Mars 2 Pro.ini +SET files[21]=Elegoo Mars C.ini +SET files[22]=Elegoo Saturn.ini +SET files[23]=Peopoly Phenom.ini +SET files[24]=Peopoly Phenom L.ini +SET files[25]=Peopoly Phenom Noir.ini +SET files[26]=Peopoly Phenom XXL.ini +SET files[27]=QIDI Shadow5.5.ini +SET files[28]=QIDI Shadow6.0 Pro.ini +SET files[29]=QIDI S-Box.ini +SET files[30]=QIDI I-Box Mono.ini +SET files[31]=Phrozen Shuffle.ini +SET files[32]=Phrozen Shuffle Lite.ini +SET files[33]=Phrozen Shuffle XL.ini +SET files[34]=Phrozen Shuffle XL Lite.ini +SET files[35]=Phrozen Shuffle 16.ini +SET files[36]=Phrozen Shuffle 4K.ini +SET files[37]=Phrozen Sonic.ini +SET files[38]=Phrozen Sonic 4K.ini +SET files[39]=Phrozen Sonic Mighty 4K.ini +SET files[40]=Phrozen Sonic Mini.ini +SET files[41]=Phrozen Sonic Mini 4K.ini +SET files[42]=Phrozen Transform.ini +SET files[43]=Kelant S400.ini +SET files[44]=Wanhao D7.ini +SET files[45]=Wanhao D8.ini +SET files[46]=Wanhao CGR Mini Mono.ini +SET files[47]=Wanhao CGR Mono.ini +SET files[48]=Creality LD-002R.ini +SET files[49]=Creality LD-002H.ini +SET files[50]=Creality LD-006.ini +SET files[51]=Voxelab Polaris 5.5.ini +SET files[52]=Voxelab Proxima 6.ini +SET files[53]=Voxelab Ceres 8.9.ini +SET files[54]=Longer Orange 10.ini +SET files[55]=Longer Orange 30.ini +SET files[56]=Longer Orange4K.ini echo PrusaSlicer Printers Instalation echo This will replace printers, all changes will be discarded diff --git a/PrusaSlicer/printer/UVtools Prusa SL1.ini b/PrusaSlicer/printer/UVtools Prusa SL1.ini new file mode 100644 index 0000000..cd9ca65 --- /dev/null +++ b/PrusaSlicer/printer/UVtools Prusa SL1.ini @@ -0,0 +1,38 @@ +# generated by PrusaSlicer 2.3.0+win64 on 2021-01-16 at 22:46:54 UTC +absolute_correction = 0 +area_fill = 50 +bed_custom_model = +bed_custom_texture = +bed_shape = 1.48x1.02,67.48x1.02,67.48x119.02,1.48x119.02 +default_sla_material_profile = Prusa Orange Tough @0.05 +default_sla_print_profile = 0.05 Normal +display_height = 120.96 +display_mirror_x = 1 +display_mirror_y = 0 +display_orientation = landscape +display_pixels_x = 1440 +display_pixels_y = 2560 +display_width = 68.04 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +fast_tilt_time = 5 +gamma_correction = 1 +host_type = octoprint +inherits = Original Prusa SL1 +max_exposure_time = 120 +max_initial_exposure_time = 300 +max_print_height = 150 +min_exposure_time = 1 +min_initial_exposure_time = 1 +print_host = +printer_model = SL1 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\n +printer_settings_id = +printer_technology = SLA +printer_variant = default +printer_vendor = +printhost_apikey = +printhost_cafile = +relative_correction = 1,1 +slow_tilt_time = 8 +thumbnails = 400x400,800x480 @@ -206,8 +206,8 @@ git clone https://github.com/emgucv/emgucv emgucv cd emgucv git submodule update --init --recursive cd platforms/ubuntu/20.04 -./apt_install_dependency -./cmake_configure +./apt_install_dependency.sh +./cmake_configure.sh cmake build cd build; make; cd .. ``` diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs index 077b869..9a0ee94 100644 --- a/UVtools.Core/Extensions/PointExtensions.cs +++ b/UVtools.Core/Extensions/PointExtensions.cs @@ -9,13 +9,14 @@ namespace UVtools.Core.Extensions public static Point Rotate(this Point point, double angleDegree, Point pivot = default) { + if (angleDegree == 0 || angleDegree == 360) return point; double angle = angleDegree * Math.PI / 180; double cos = Math.Cos(angle); double sin = Math.Sin(angle); int dx = point.X - pivot.X; int dy = point.Y - pivot.Y; double x = cos * dx - sin * dy + pivot.X; - double y = sin * dx + cos * dy + pivot.X; + double y = sin * dx + cos * dy + pivot.Y; Point rotated = new Point((int)Math.Round(x), (int)Math.Round(y)); return rotated; diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index a9f6faa..242bc3d 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -33,6 +33,10 @@ namespace UVtools.Core.FileFormats private const byte RLE8EncodingLimit = 0x7d; // 125; private const ushort RLE16EncodingLimit = 0xFFF; + + private const uint ENCRYPTYION_MODE_CBDDLP = 0x8; // 0 or 8 + private const uint ENCRYPTYION_MODE_CTBv2 = 0xF; // 15 for ctb v2 files + private const uint ENCRYPTYION_MODE_CTBv3 = 0x2000000F; // 536870927 for ctb v3 files (This allow per layer settings, while 0xF don't) #endregion #region Sub Classes @@ -286,9 +290,9 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets the parameter used to control encryption. - /// Not totally understood. 0/8 for cbddlp files, 0xF (15) for ctb files. + /// Not totally understood. 0/8 for cbddlp files, 0xF (15) for ctb files, 0x2000000F (536870927) for v3 ctb files allow per layer parameters /// </summary> - [FieldOrder(9)] public uint EncryptionMode { get; set; } = 8; + [FieldOrder(9)] public uint EncryptionMode { get; set; } = ENCRYPTYION_MODE_CTBv3; /// <summary> /// Gets a number that increments with time or number of models sliced, or both. Zeroing it in output seems to have no effect. Possibly a user tracking bug. @@ -1352,16 +1356,22 @@ namespace UVtools.Core.FileFormats HeaderSettings.AntiAliasLevel = 1; - PrintParametersSettings.Padding4 = 0x1234; - - SlicerInfoSettings.EncryptionMode = 15; + if (HeaderSettings.Version <= 2) + { + if (SlicerInfoSettings.Unknown1 == 0) + SlicerInfoSettings.Unknown1 = 0x200; // 512 for v2 | 0 for v3 + SlicerInfoSettings.EncryptionMode = ENCRYPTYION_MODE_CTBv2; + PrintParametersSettings.Padding4 = 0x1234; // 4660 + } + else + { + SlicerInfoSettings.EncryptionMode = ENCRYPTYION_MODE_CTBv3; + } + if(SlicerInfoSettings.MysteriousId == 0) SlicerInfoSettings.MysteriousId = 0x12345678; - if(SlicerInfoSettings.Unknown1 == 0) - SlicerInfoSettings.Unknown1 = HeaderSettings.Version == 3 ? 0u : 0x200; - if (HeaderSettings.EncryptionKey == 0) { Random rnd = new Random(); @@ -1372,6 +1382,7 @@ namespace UVtools.Core.FileFormats { HeaderSettings.Version = 2; HeaderSettings.EncryptionKey = 0; + SlicerInfoSettings.EncryptionMode = ENCRYPTYION_MODE_CBDDLP; } uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 7fd6cff..462ffd1 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -623,7 +623,7 @@ namespace UVtools.Core.FileFormats e.PropertyName == nameof(LightPWM) ) { - LayerManager.RebuildLayersProperties(false); + LayerManager.RebuildLayersProperties(false, e.PropertyName); RebuildGCode(); if(e.PropertyName != nameof(BottomLightPWM) && e.PropertyName != nameof(LightPWM)) PrintTime = PrintTimeComputed; diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index 2555786..4f9a6a3 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -254,7 +254,7 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets the parameter used to control encryption. - /// Not totally understood. 0 for cbddlp files, 0xF for ctb files, 0x1c for phz + /// Not totally understood. 0 for cbddlp files, 0xF for ctb files, 0x1c (28) for phz /// </summary> [FieldOrder(45)] public uint EncryptionMode { get; set; } = 28; diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs index cb7997c..005b708 100644 --- a/UVtools.Core/FileFormats/SL1File.cs +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -25,6 +25,9 @@ namespace UVtools.Core.FileFormats { #region Constants + public const string IniConfig = "config.ini"; + public const string IniPrusaslicer = "prusaslicer.ini"; + public const string Keyword_FileFormat = "FILEFORMAT"; public const string Keyword_BottomLightOffDelay = "BottomLightOffDelay"; @@ -537,7 +540,7 @@ namespace UVtools.Core.FileFormats tw.Close(); } - entry = outputFile.CreateEntry("prusaslicer.ini"); + entry = outputFile.CreateEntry(IniPrusaslicer); using (TextWriter tw = new StreamWriter(entry.Open())) { foreach (var config in Configs) @@ -587,8 +590,8 @@ namespace UVtools.Core.FileFormats { base.Decode(fileFullPath, progress); - if (progress is null) progress = new OperationProgress(); - progress.ItemName = OperationProgress.StatusGatherLayers; + progress ??= new OperationProgress(); + progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); FileFullPath = fileFullPath; @@ -601,45 +604,51 @@ namespace UVtools.Core.FileFormats using (var inputFile = ZipFile.OpenRead(FileFullPath)) { - + List<string> iniFiles = new(); foreach (ZipArchiveEntry entity in inputFile.Entries) { if (!entity.Name.EndsWith(".ini")) continue; - using (StreamReader streamReader = new StreamReader(entity.Open())) + iniFiles.Add(entity.Name); + using StreamReader streamReader = new StreamReader(entity.Open()); + string line; + while ((line = streamReader.ReadLine()) != null) { - string line = null; - while ((line = streamReader.ReadLine()) != null) - { - string[] keyValue = line.Split(new[] {'='}, 2); - if (keyValue.Length < 2) continue; - keyValue[0] = keyValue[0].Trim(); - keyValue[1] = keyValue[1].Trim(); + string[] keyValue = line.Split(new[] {'='}, 2); + if (keyValue.Length < 2) continue; + keyValue[0] = keyValue[0].Trim(); + keyValue[1] = keyValue[1].Trim(); - var fieldName = IniKeyToMemberName(keyValue[0]); - bool foundMember = false; + var fieldName = IniKeyToMemberName(keyValue[0]); + bool foundMember = false; - foreach (var obj in Configs) - { - var attribute = obj.GetType().GetProperty(fieldName); - if (ReferenceEquals(attribute, null)) continue; - //Debug.WriteLine(attribute.Name); - Helpers.SetPropertyValue(attribute, obj, keyValue[1]); - - Statistics.ImplementedKeys.Add(keyValue[0]); - foundMember = true; - } - - + foreach (var obj in Configs) + { + var attribute = obj.GetType().GetProperty(fieldName); + if (ReferenceEquals(attribute, null)) continue; + //Debug.WriteLine(attribute.Name); + Helpers.SetPropertyValue(attribute, obj, keyValue[1]); - if (!foundMember) - { - Statistics.MissingKeys.Add(keyValue[0]); - } + Statistics.ImplementedKeys.Add(keyValue[0]); + foundMember = true; + } + + if (!foundMember) + { + Statistics.MissingKeys.Add(keyValue[0]); } } } + if (!iniFiles.Contains(IniConfig)) + { + throw new FileLoadException($"Malformed file: {IniConfig} is missing."); + } + if (!iniFiles.Contains(IniPrusaslicer)) + { + throw new FileLoadException($"Malformed file: {IniPrusaslicer} is missing."); + } + BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, DefaultBottomLiftHeight); BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, DefaultBottomLiftSpeed); LiftHeight = LookupCustomValue(Keyword_LiftHeight, DefaultLiftHeight); @@ -659,18 +668,16 @@ namespace UVtools.Core.FileFormats if (!entity.Name.EndsWith(".png")) continue; if (entity.Name.StartsWith("thumbnail")) { - using (Stream stream = entity.Open()) - { - Mat image = new Mat(); - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); - byte thumbnailIndex = - (byte) (image.Width == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Width && - image.Height == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Height - ? FileThumbnailSize.Small - : FileThumbnailSize.Large); - Thumbnails[thumbnailIndex] = image; - stream.Close(); - } + using Stream stream = entity.Open(); + Mat image = new Mat(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + byte thumbnailIndex = + (byte) (image.Width == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Width && + image.Height == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Height + ? FileThumbnailSize.Small + : FileThumbnailSize.Large); + Thumbnails[thumbnailIndex] = image; + stream.Close(); //thumbnailIndex++; diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 8095b3f..a9841ce 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -180,19 +180,26 @@ namespace UVtools.Core /// <summary> /// Rebuild layer properties based on slice settings /// </summary> - public void RebuildLayersProperties(bool recalculateZPos = true) + public void RebuildLayersProperties(bool recalculateZPos = true, string property = null) { //var layerHeight = SlicerFile.LayerHeight; for (uint layerIndex = 0; layerIndex < Count; layerIndex++) { var layer = this[layerIndex]; layer.Index = layerIndex; - layer.ExposureTime = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomExposureTime, SlicerFile.ExposureTime); - layer.LiftHeight = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLiftHeight, SlicerFile.LiftHeight); - layer.LiftSpeed = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLiftSpeed, SlicerFile.LiftSpeed); - layer.RetractSpeed = SlicerFile.RetractSpeed; - layer.LightPWM = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightPWM, SlicerFile.LightPWM); - layer.LightOffDelay = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightOffDelay, SlicerFile.LightOffDelay); + + if(property is null || property == nameof(SlicerFile.BottomExposureTime) || property == nameof(SlicerFile.ExposureTime)) + layer.ExposureTime = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomExposureTime, SlicerFile.ExposureTime); + if (property is null || property == nameof(SlicerFile.BottomLiftHeight) || property == nameof(SlicerFile.LiftHeight)) + layer.LiftHeight = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLiftHeight, SlicerFile.LiftHeight); + if (property is null || property == nameof(SlicerFile.BottomLiftSpeed) || property == nameof(SlicerFile.LiftSpeed)) + layer.LiftSpeed = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLiftSpeed, SlicerFile.LiftSpeed); + if (property is null || property == nameof(SlicerFile.RetractSpeed)) + layer.RetractSpeed = SlicerFile.RetractSpeed; + if (property is null || property == nameof(SlicerFile.BottomLightPWM) || property == nameof(SlicerFile.LightPWM)) + layer.LightPWM = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightPWM, SlicerFile.LightPWM); + if (property is null || property == nameof(SlicerFile.BottomLightOffDelay) || property == nameof(SlicerFile.LightOffDelay)) + layer.LightOffDelay = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightOffDelay, SlicerFile.LightOffDelay); if (recalculateZPos) { diff --git a/UVtools.Core/Operations/OperationCalculator.cs b/UVtools.Core/Operations/OperationCalculator.cs index ec9b90e..0070d17 100644 --- a/UVtools.Core/Operations/OperationCalculator.cs +++ b/UVtools.Core/Operations/OperationCalculator.cs @@ -391,9 +391,6 @@ namespace UVtools.Core.Operations public decimal TiltAngleDegrees => XYResolution > 0 ? (decimal) Math.Round(Math.Tanh((double) (_layerHeight / XYResolution)) * (180 / Math.PI), 3) : 0; - - - } } } diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs new file mode 100644 index 0000000..4d95608 --- /dev/null +++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs @@ -0,0 +1,437 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Drawing; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Objects; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationCalibrateStressTower : Operation + { + #region Members + private decimal _displayWidth; + private decimal _displayHeight; + private decimal _layerHeight = 0.05M; + private ushort _bottomLayers = 3; + private decimal _bottomExposure = 60; + private decimal _normalExposure = 12; + private decimal _baseDiameter = 30; + private decimal _baseHeight = 3; + private decimal _bodyHeight = 50; + private decimal _ceilHeight = 3; + private byte _chamferLayers = 6; + private bool _enableAntiAliasing = true; + private bool _mirrorOutput; + private byte _spirals = 2; + private decimal _spiralDiameter = 2; + private SpiralDirections _spiralDirection = SpiralDirections.Both; + private decimal _spiralAngleStepPerLayer = 1; + + #endregion + + #region Overrides + + public override bool CanROI => false; + + public override bool CanCancel => false; + + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + + public override string Title => "Stress tower"; + public override string Description => + "Generates a stress tower to test the printer capabilities.\n" + + "Note: The current opened file will be overwritten with this test, use a dummy or a not needed file."; + + public override string ConfirmationText => + $"generate the stress tower?"; + + public override string ProgressTitle => + $"Generating the stress tower"; + + public override string ProgressAction => "Generated"; + + public override StringTag Validate(params object[] parameters) + { + var sb = new StringBuilder(); + + if (_displayWidth <= 0) + { + sb.AppendLine("Display width must be a positive value."); + } + + if (_displayHeight <= 0) + { + sb.AppendLine("Display height must be a positive value."); + } + + return new StringTag(sb.ToString()); + } + + public override string ToString() + { + var result = $"[Layer Height: {_layerHeight}] " + + $"[Bottom layers: {_bottomLayers}] " + + $"[Exposure: {_bottomExposure}/{_normalExposure}] " + + $"[Base: H:{_baseHeight} D:{_baseDiameter}] " + + $"[Ceil: {_ceilHeight}] [Body: {_bodyHeight}] " + + $"[Chamfer: {_chamferLayers}] " + + $"[Spirals: {_spirals} Dir: {_spiralDirection} D:{_spiralDiameter} Angle: {_spiralAngleStepPerLayer}º]" + + $"[AA: {_enableAntiAliasing}] [Mirror: {_mirrorOutput}]"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Properties + + [XmlIgnore] + public Size Resolution { get; set; } = Size.Empty; + + public decimal DisplayWidth + { + get => _displayWidth; + set + { + if(!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return; + } + } + + public decimal DisplayHeight + { + get => _displayHeight; + set + { + if(!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return; + } + } + + public decimal LayerHeight + { + get => _layerHeight; + set + { + if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + RaisePropertyChanged(nameof(LayerCount)); + } + } + + public ushort Microns => (ushort)(LayerHeight * 1000); + + public ushort BottomLayers + { + get => _bottomLayers; + set + { + if(!RaiseAndSetIfChanged(ref _bottomLayers, value)) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + } + } + + public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2); + + public decimal BottomExposure + { + get => _bottomExposure; + set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2)); + } + + public decimal NormalExposure + { + get => _normalExposure; + set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2)); + } + + public uint LayerCount => (uint) Math.Floor((_baseHeight + _bodyHeight + _ceilHeight) / LayerHeight); + + public decimal TotalHeight => _baseHeight + _bodyHeight + _ceilHeight; + + public decimal BaseDiameter + { + get => _baseDiameter; + set => RaiseAndSetIfChanged(ref _baseDiameter, value); + } + + public decimal BaseHeight + { + get => _baseHeight; + set + { + if(!RaiseAndSetIfChanged(ref _baseHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public decimal BodyHeight + { + get => _bodyHeight; + set + { + if (!RaiseAndSetIfChanged(ref _bodyHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public decimal CeilHeight + { + get => _ceilHeight; + set + { + if(!RaiseAndSetIfChanged(ref _ceilHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public byte ChamferLayers + { + get => _chamferLayers; + set => RaiseAndSetIfChanged(ref _chamferLayers, value); + } + + public bool EnableAntiAliasing + { + get => _enableAntiAliasing; + set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value); + } + + public bool MirrorOutput + { + get => _mirrorOutput; + set => RaiseAndSetIfChanged(ref _mirrorOutput, value); + } + + public byte Spirals + { + get => _spirals; + set => RaiseAndSetIfChanged(ref _spirals, value); + } + + public decimal SpiralDiameter + { + get => _spiralDiameter; + set => RaiseAndSetIfChanged(ref _spiralDiameter, value); + } + + public SpiralDirections SpiralDirection + { + get => _spiralDirection; + set => RaiseAndSetIfChanged(ref _spiralDirection, value); + } + + public decimal SpiralAngleStepPerLayer + { + get => _spiralAngleStepPerLayer; + set => RaiseAndSetIfChanged(ref _spiralAngleStepPerLayer, value); + } + + #endregion + + #region Enums + + public enum SpiralDirections : byte + { + Clockwise, + Alternate, + Both + } + + public static Array SpiralDirectionsItems => Enum.GetValues(typeof(SpiralDirections)); + #endregion + + #region Equality + + private bool Equals(OperationCalibrateStressTower other) + { + return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _baseDiameter == other._baseDiameter && _baseHeight == other._baseHeight && _bodyHeight == other._bodyHeight && _ceilHeight == other._ceilHeight && _chamferLayers == other._chamferLayers && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _spirals == other._spirals && _spiralDiameter == other._spiralDiameter && _spiralDirection == other._spiralDirection && _spiralAngleStepPerLayer == other._spiralAngleStepPerLayer; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationCalibrateStressTower other && Equals(other); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(_layerHeight); + hashCode.Add(_bottomLayers); + hashCode.Add(_bottomExposure); + hashCode.Add(_normalExposure); + hashCode.Add(_baseDiameter); + hashCode.Add(_baseHeight); + hashCode.Add(_bodyHeight); + hashCode.Add(_ceilHeight); + hashCode.Add(_chamferLayers); + hashCode.Add(_enableAntiAliasing); + hashCode.Add(_mirrorOutput); + hashCode.Add(_spirals); + hashCode.Add(_spiralDiameter); + hashCode.Add((int) _spiralDirection); + hashCode.Add(_spiralAngleStepPerLayer); + return hashCode.ToHashCode(); + } + + #endregion + + #region Methods + public Mat[] GetLayers() + { + var layers = new Mat[LayerCount]; + + Slicer slicer = new(Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); + Point center = new Point(Resolution.Width / 2, Resolution.Height / 2); + uint baseRadius = slicer.PixelsFromMillimeters(_baseDiameter) / 2; + uint baseLayers = (ushort) Math.Floor(_baseHeight / _layerHeight); + uint bodyLayers = (ushort) Math.Floor(_bodyHeight / _layerHeight); + uint spiralLayers = (uint) Math.Floor(_spiralDiameter / _layerHeight); + uint ceilLayers = (ushort) Math.Floor(_ceilHeight / _layerHeight); + uint currrentlayer = baseLayers; + + decimal spiralOffsetAngle = 360m / _spirals; + uint spiralRadius = slicer.PixelsFromMillimeters(_spiralDiameter) / 2; + + /*const FontFace fontFace = FontFace.HersheyDuplex; + const double fontScale = 1; + const byte fontThickness = 2; + LineType lineType = _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected; + + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);*/ + Parallel.For(0, LayerCount, layerIndex => + { + layers[layerIndex] = EmguExtensions.InitMat(Resolution); + }); + + Parallel.For(0, baseLayers, layerIndex => + { + int chamferOffset = (int) Math.Max(0, _chamferLayers - layerIndex); + CvInvoke.Circle(layers[layerIndex], center, (int) baseRadius - chamferOffset, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + }); + + + Parallel.For(0, baseLayers+bodyLayers, layerIndex => + { + decimal angle = (layerIndex * _spiralAngleStepPerLayer) % 360m; + for (byte spiral = 0; spiral < _spirals; spiral++) + { + decimal spiralAngle = (spiralOffsetAngle * spiral + angle) % 360; + if (_spiralDirection == SpiralDirections.Alternate && spiral % 2 == 0) + { + spiralAngle = -spiralAngle; + } + Point location = new Point((int) (center.X - baseRadius + spiralRadius), center.Y); + var locationCW = location.Rotate((double) spiralAngle, center); + var locationCCW = location.Rotate((double) -spiralAngle, center); + + uint maxLayer = (uint) Math.Min(layerIndex + spiralLayers, baseLayers + bodyLayers); + + for (uint spiralLayerIndex = (uint) layerIndex; spiralLayerIndex < maxLayer; spiralLayerIndex++) + { + CvInvoke.Circle(layers[spiralLayerIndex], locationCW, (int)spiralRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + if (_spiralDirection == SpiralDirections.Both) + { + spiralAngle = -spiralAngle; + CvInvoke.Circle(layers[spiralLayerIndex], locationCCW, (int)spiralRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + } + } + } + }); + + currrentlayer += bodyLayers; + + Parallel.For(0, ceilLayers, i => + { + uint layerIndex = (uint)(currrentlayer + i); + CvInvoke.Circle(layers[layerIndex], center, (int)baseRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + }); + + + + if (_mirrorOutput) + { + Parallel.ForEach(layers, mat => CvInvoke.Flip(mat, mat, FlipType.Horizontal)); + } + + return layers; + } + + public Mat GetThumbnail() + { + Mat thumbnail = EmguExtensions.InitMat(new Size(400, 200), 3); + var fontFace = FontFace.HersheyDuplex; + var fontScale = 1; + var fontThickness = 2; + const byte xSpacing = 45; + const byte ySpacing = 45; + CvInvoke.PutText(thumbnail, "UVtools", new Point(140, 35), fontFace, fontScale, new MCvScalar(255, 27, 245), fontThickness + 1); + CvInvoke.Line(thumbnail, new Point(xSpacing, 0), new Point(xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(xSpacing, ySpacing + 5), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(thumbnail.Width - xSpacing, 0), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.PutText(thumbnail, "Stress Tower", new Point(xSpacing, ySpacing * 2), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness); + CvInvoke.PutText(thumbnail, $"{Microns}um @ {BottomExposure}s/{NormalExposure}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + CvInvoke.PutText(thumbnail, $"{_spirals} Spirals @ {_spiralAngleStepPerLayer}deg", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + return thumbnail; + } + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerCount); + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + + Parallel.For(0, LayerCount, layerIndex => + { + newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], slicerFile.LayerManager); + layers[layerIndex].Dispose(); + lock (progress) + { + progress++; + } + }); + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.LayerManager.RebuildLayersProperties(); + + /*var moveOp = new OperationMove(slicerFile.LayerManager.BoundingRectangle, slicerFile.Resolution) + { + IsCutMove = true, + LayerIndexEnd = slicerFile.LayerCount - 1 + }; + moveOp.Execute(slicerFile, progress);*/ + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + slicerFile.SuppressRebuildProperties = false; + + return true; + } + + #endregion + } +} diff --git a/UVtools.Core/Slicer.cs b/UVtools.Core/Slicer.cs new file mode 100644 index 0000000..11529d7 --- /dev/null +++ b/UVtools.Core/Slicer.cs @@ -0,0 +1,59 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ +using System; +using System.Drawing; + +namespace UVtools.Core +{ + public class Slicer + { + /// <summary> + /// Gets the size of resolution + /// </summary> + public Size Resolution { get; private set; } + + /// <summary> + /// Gets the size of display + /// </summary> + public SizeF Display { get; private set; } + + /// <summary> + /// Gets the pixels per millimeters + /// </summary> + public SizeF Ppmm { get; private set; } + + public Slicer(Size resolution, SizeF display) + { + Init(resolution, display); + } + + public void Init(Size resolution, SizeF display) + { + Resolution = resolution; + Display = display; + + Ppmm = new SizeF(resolution.Width / display.Width, resolution.Height / display.Height); + } + + public decimal MillimetersFromPixelsX(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Width, 2); + public decimal MillimetersFromPixelsY(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Height, 2); + public decimal MillimetersFromPixels (uint pixels) => (decimal) Math.Round(pixels / Math.Max(Ppmm.Width, Ppmm.Height), 2); + + public static decimal MillimetersFromPixelsX(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Width / display.Width), 2); + public static decimal MillimetersFromPixelsY(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Height / display.Height), 2); + + + + public uint PixelsFromMillimetersX(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Width); + public uint PixelsFromMillimetersY(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Height); + public uint PixelsFromMillimeters (decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Math.Max(Ppmm.Width, Ppmm.Height)); + + public static uint PixelsFromMillimetersX(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Width / display.Width * (double) millimeters); + public static uint PixelsFromMillimetersY(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Height / display.Height * (double) millimeters); + } +} diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 9fd54de..2131a03 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.3.0</Version> + <Version>2.3.1</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs index 4bf4191..f7bceba 100644 --- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs +++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs @@ -975,6 +975,9 @@ <Component Id="owc696F0AF8084A6AA7624CA0947E245A47" Guid="6aa7d092-d68b-a44d-f2c7-0a590404b19f"> <File Id="owf696F0AF8084A6AA7624CA0947E245A47" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\QIDI Shadow6.0 Pro.ini" KeyPath="yes" /> </Component> + <Component Id="owcD0FD9259BE924EB58AE9F7A85AE0A9E8" Guid="360b089c-8765-92db-0c8f-367d80202d8f"> + <File Id="owfD0FD9259BE924EB58AE9F7A85AE0A9E8" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\UVtools Prusa SL1.ini" KeyPath="yes" /> + </Component> <Component Id="owc2D767EDC65A9490BE5587F5807220F67" Guid="1b34ea21-cadd-c887-3f4e-8655903ef11a"> <File Id="owf2D767EDC65A9490BE5587F5807220F67" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\Voxelab Ceres 8.9.ini" KeyPath="yes" /> </Component> diff --git a/UVtools.WPF/Assets/Icons/chess-rook-16x16.png b/UVtools.WPF/Assets/Icons/chess-rook-16x16.png Binary files differnew file mode 100644 index 0000000..b75fa17 --- /dev/null +++ b/UVtools.WPF/Assets/Icons/chess-rook-16x16.png diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml new file mode 100644 index 0000000..d15b483 --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml @@ -0,0 +1,286 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" + x:Class="UVtools.WPF.Controls.Calibrators.CalibrateStressTowerControl"> + + <StackPanel Spacing="10"> + + <Border BorderBrush="Black" BorderThickness="1" Padding="5"> + <Expander IsExpanded="True"> + <Expander.Header> + <TextBlock Text="Step 1 - Commun properties" + FontWeight="Bold" + Cursor="Hand"/> + </Expander.Header> + + <Grid + RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,100,5,Auto,20,Auto,10,100,5,Auto"> + + <TextBlock + Grid.Row="0" Grid.Column="0" + VerticalAlignment="Center" + ToolTip.Tip="The printer display width. Required to calculate the pixels per mm." + Text="Display width:"/> + <NumericUpDown Grid.Row="0" Grid.Column="2" + ClipValueToMinMax="True" + Increment="0.1" + Minimum="0" + Maximum="10000" + FormatString="\{0:0.00\}" + Value="{Binding Operation.DisplayWidth}"/> + <TextBlock Grid.Row="0" Grid.Column="4" + VerticalAlignment="Center" + Text="mm"/> + + <TextBlock Grid.Row="0" Grid.Column="6" + VerticalAlignment="Center" + ToolTip.Tip="The printer display height. Required to calculate the pixels per mm." + Text="Display height:"/> + <NumericUpDown Grid.Row="0" Grid.Column="8" + ClipValueToMinMax="True" + Increment="0.1" + Minimum="0" + Maximum="10000" + FormatString="\{0:0.00\}" + Value="{Binding Operation.DisplayHeight}"/> + <TextBlock Grid.Row="0" Grid.Column="10" + VerticalAlignment="Center" + Text="mm"/> + + <TextBlock Grid.Row="2" Grid.Column="0" + VerticalAlignment="Center" + Text="Layer height:"/> + <NumericUpDown Grid.Row="2" Grid.Column="2" + ClipValueToMinMax="True" + Increment="0.01" + Minimum="0.01" + Maximum="0.30" + FormatString="\{0:0.00\}" + Value="{Binding Operation.LayerHeight}" + /> + <TextBlock Grid.Row="2" Grid.Column="4" + VerticalAlignment="Center" + Text="mm"/> + + <TextBlock Grid.Row="2" Grid.Column="6" + VerticalAlignment="Center" + Text="Bottom layers:"/> + <NumericUpDown Grid.Row="2" Grid.Column="8" + ClipValueToMinMax="True" + Increment="1" + Minimum="1" + Maximum="1000" + Value="{Binding Operation.BottomLayers}"/> + <TextBlock Grid.Row="2" Grid.Column="8" + VerticalAlignment="Center" + Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/> + + <TextBlock Grid.Row="4" Grid.Column="0" + VerticalAlignment="Center" + Text="Bottom exposure:"/> + <NumericUpDown Grid.Row="4" Grid.Column="2" + ClipValueToMinMax="True" + Increment="0.5" + Minimum="0.1" + Maximum="200" + FormatString="\{0:0.00\}" + Value="{Binding Operation.BottomExposure}"/> + <TextBlock Grid.Row="4" Grid.Column="4" + VerticalAlignment="Center" + Text="s"/> + + <TextBlock Grid.Row="4" Grid.Column="6" + VerticalAlignment="Center" + Text="Normal exposure:"/> + <NumericUpDown Grid.Row="4" Grid.Column="8" + ClipValueToMinMax="True" + Increment="0.5" + Minimum="0.1" + Maximum="200" + FormatString="\{0:0.00\}" + Value="{Binding Operation.NormalExposure}"/> + <TextBlock Grid.Row="4" Grid.Column="10" + VerticalAlignment="Center" + Text="s"/> + + <TextBlock Grid.Row="6" Grid.Column="0" + VerticalAlignment="Center" + Text="Base height:"/> + <NumericUpDown Grid.Row="6" Grid.Column="2" + ClipValueToMinMax="True" + Increment="1" + Minimum="0" + Maximum="100" + FormatString="\{0:0.00\}" + Value="{Binding Operation.BaseHeight}"/> + <TextBlock Grid.Row="6" Grid.Column="4" + VerticalAlignment="Center" + Text="mm"/> + + <TextBlock Grid.Row="6" Grid.Column="6" + VerticalAlignment="Center" + Text="Base diameter:"/> + <NumericUpDown Grid.Row="6" Grid.Column="8" + ClipValueToMinMax="True" + Increment="1" + Minimum="1" + Maximum="10000" + FormatString="\{0:0.00\}" + Value="{Binding Operation.BaseDiameter}"/> + <TextBlock Grid.Row="6" Grid.Column="10" + VerticalAlignment="Center" + Text="mm"/> + + <TextBlock Grid.Row="8" Grid.Column="0" + VerticalAlignment="Center" + Text="Ceil height:"/> + <NumericUpDown Grid.Row="8" Grid.Column="2" + ClipValueToMinMax="True" + Increment="1" + Minimum="0" + Maximum="100" + FormatString="\{0:0.00\}" + Value="{Binding Operation.CeilHeight}"/> + <TextBlock Grid.Row="8" Grid.Column="4" + VerticalAlignment="Center" + Text="mm"/> + + + <TextBlock Grid.Row="8" Grid.Column="6" + VerticalAlignment="Center" + Text="Body height:"/> + <NumericUpDown Grid.Row="8" Grid.Column="8" + ClipValueToMinMax="True" + Increment="1" + Minimum="0" + Maximum="10000" + FormatString="\{0:0.00\}" + Value="{Binding Operation.BodyHeight}"/> + <TextBlock Grid.Row="8" Grid.Column="10" + VerticalAlignment="Center" + Text="mm"/> + + <StackPanel Grid.Row="10" Grid.Column="6" + VerticalAlignment="Center" + Spacing="0"> + <TextBlock + FontWeight="Bold" + Text="Total layers:"/> + <TextBlock + FontWeight="Bold" + Text="Total height:"/> + </StackPanel> + + <StackPanel Grid.Row="10" Grid.Column="8" + VerticalAlignment="Center" + Spacing="0"> + <TextBlock + FontWeight="Bold" + Text="{Binding Operation.LayerCount}"/> + + <TextBlock + FontWeight="Bold" + Text="{Binding Operation.TotalHeight, StringFormat=\{0:0.00\}mm}"/> + + + </StackPanel> + + + + <TextBlock Grid.Row="10" Grid.Column="0" + VerticalAlignment="Center" + ToolTip.Tip="Chamfer the bottom layers" + Text="Chamfer layers:"/> + <NumericUpDown Grid.Row="10" Grid.Column="2" + ClipValueToMinMax="True" + Increment="1" + Minimum="0" + Maximum="255" + IsEnabled="{Binding Operation.ChamferModel}" + Value="{Binding Operation.ChamferLayers}"/> + + <CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" + VerticalAlignment="Center" + IsChecked="{Binding Operation.EnableAntiAliasing}" + Content="Enable Anti-Aliasing"/> + + <CheckBox Grid.Row="12" Grid.Column="8" + Grid.ColumnSpan="3" + ToolTip.Tip="Most of the printers requires a mirror output to print with the correct orientation" + IsChecked="{Binding Operation.MirrorOutput}" + Content="Mirror output" /> + + </Grid> + </Expander> + </Border> + + <Border BorderBrush="Black" BorderThickness="1" Padding="5"> + <Expander IsExpanded="True"> + <Expander.Header> + <TextBlock Text="Outer Spirals" + FontWeight="Bold" + Cursor="Hand"/> + + </Expander.Header> + + <Grid RowDefinitions="Auto,5,Auto" + ColumnDefinitions="Auto,10,100,5,Auto,20,Auto,10,100,5,Auto"> + + + <TextBlock Grid.Row="0" Grid.Column="0" + Text="Number of spirals:" + VerticalAlignment="Center"/> + <NumericUpDown Grid.Row="0" Grid.Column="2" + ClipValueToMinMax="True" + Increment="1" + Minimum="1" + Maximum="10" + Value="{Binding Operation.Spirals}"/> + + <TextBlock Grid.Row="0" Grid.Column="6" + VerticalAlignment="Center" + ToolTip.Tip="Clockwise: All spirals turn clockwise. +
Alternate: Each spiral turn into opposite direction. +
Both: Each spiral will turn in both directions, clockwise and counter-clockwise." + Text="Spiral direction:"/> + <ComboBox Grid.Row="0" Grid.Column="8" + Items="{Binding Operation.SpiralDirectionsItems}" + SelectedItem="{Binding Operation.SpiralDirection}"/> + + <TextBlock Grid.Row="2" Grid.Column="0" + Text="Spiral diameter:" + VerticalAlignment="Center"/> + <NumericUpDown Grid.Row="2" Grid.Column="2" + ClipValueToMinMax="True" + Increment="1" + Minimum="0.1" + Maximum="10000" + FormatString="\{0:0.00\}" + Value="{Binding Operation.SpiralDiameter}"/> + <TextBlock Grid.Row="2" Grid.Column="4" + Text="mm" + VerticalAlignment="Center"/> + + <TextBlock Grid.Row="2" Grid.Column="6" + Text="Step angle:" + ToolTip.Tip="Spirals will turn this angle per layer." + VerticalAlignment="Center"/> + <NumericUpDown Grid.Row="2" Grid.Column="8" + ClipValueToMinMax="True" + Increment="1" + Minimum="0.01" + Maximum="359.99" + FormatString="\{0:0.00\}" + Value="{Binding Operation.SpiralAngleStepPerLayer}"/> + <TextBlock Grid.Row="2" Grid.Column="10" + Text="º/layer" + VerticalAlignment="Center"/> + + </Grid> + </Expander> + </Border> + </StackPanel> +</UserControl> diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml.cs new file mode 100644 index 0000000..d06a3b0 --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml.cs @@ -0,0 +1,52 @@ +using System.Diagnostics; +using System.Timers; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using UVtools.Core.Operations; +using UVtools.WPF.Controls.Tools; +using UVtools.WPF.Extensions; +using UVtools.WPF.Windows; + +namespace UVtools.WPF.Controls.Calibrators +{ + public class CalibrateStressTowerControl : ToolControl + { + public OperationCalibrateStressTower Operation => BaseOperation as OperationCalibrateStressTower; + + public CalibrateStressTowerControl() + { + InitializeComponent(); + BaseOperation = new OperationCalibrateStressTower(); + + if (App.SlicerFile is not null) + { + Operation.LayerHeight = (decimal)App.SlicerFile.LayerHeight; + Operation.BottomLayers = App.SlicerFile.BottomLayerCount; + Operation.BottomExposure = (decimal)App.SlicerFile.BottomExposureTime; + Operation.NormalExposure = (decimal)App.SlicerFile.ExposureTime; + } + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public override void Callback(ToolWindow.Callbacks callback) + { + if (App.SlicerFile is null) return; + switch (callback) + { + case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.ProfileLoaded: + Operation.Resolution = App.SlicerFile.Resolution; + if (App.SlicerFile.DisplayWidth > 0) + Operation.DisplayWidth = (decimal)App.SlicerFile.DisplayWidth; + if (App.SlicerFile.DisplayHeight > 0) + Operation.DisplayHeight = (decimal)App.SlicerFile.DisplayHeight; + break; + } + } + } +} diff --git a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs index f91d037..893dcd5 100644 --- a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs @@ -28,7 +28,7 @@ namespace UVtools.WPF.Controls.Tools (decimal)SlicerFile.LiftHeight, (decimal)SlicerFile.BottomLiftHeight, (decimal)SlicerFile.LiftSpeed, (decimal)SlicerFile.BottomLiftSpeed, (decimal)SlicerFile.RetractSpeed, (decimal)SlicerFile.RetractSpeed), - CalcOptimalModelTilt = new OperationCalculator.OptimalModelTilt(SlicerFile.Resolution, SlicerFile.Display) + CalcOptimalModelTilt = new OperationCalculator.OptimalModelTilt(SlicerFile.Resolution, SlicerFile.Display, (decimal) SlicerFile.LayerHeight) }; Operation.CalcLightOffDelay.PropertyChanged += (sender, e) => diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 76ed47c..1118a95 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -253,6 +253,14 @@ namespace UVtools.WPF }, new() { + Tag = new OperationCalibrateStressTower(), + Icon = new Avalonia.Controls.Image + { + Source = new Bitmap(App.GetAsset("/Assets/Icons/chess-rook-16x16.png")) + } + }, + new() + { Tag = new OperationCalibrateExternalTests(), Icon = new Avalonia.Controls.Image { diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs index 257b213..1afadd9 100644 --- a/UVtools.WPF/Structures/OperationProfiles.cs +++ b/UVtools.WPF/Structures/OperationProfiles.cs @@ -45,6 +45,7 @@ namespace UVtools.WPF.Structures [XmlElement(typeof(OperationCalibrateXYZAccuracy))] [XmlElement(typeof(OperationCalibrateTolerance))] [XmlElement(typeof(OperationCalibrateGrayscale))] + [XmlElement(typeof(OperationCalibrateStressTower))] public List<Operation> Operations { get; internal set; } = new List<Operation>(); [XmlIgnore] diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index e2630d6..4c56924 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.3.0</Version> + <Version>2.3.1</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -85,6 +85,9 @@ <Compile Update="Controls\Calibrators\CalibrateExternalTestsControl.axaml.cs"> <DependentUpon>CalibrateExternalTestsControl.axaml</DependentUpon> </Compile> + <Compile Update="Controls\Calibrators\CalibrateStressTowerControl.axaml.cs"> + <DependentUpon>CalibrateStressTowerControl.axaml</DependentUpon> + </Compile> <Compile Update="Controls\Tools\ToolLayerRemoveControl.axaml.cs"> <DependentUpon>ToolLayerRemoveControl.axaml</DependentUpon> </Compile> |