Files
UVtools/UVtools.Core/FileFormats/VDAFile.cs
T
Tiago Conceição a648610d59 v2.15.0
- **File formats:**
   - (Add) Wait time before cure: The time to rest/wait in seconds before cure a new layer
   - (Add) Wait time before after: The time to rest/wait in seconds after cure a new layer
   - (Add) Wait time after lift: The time to rest/wait in seconds after a lift/peel move
   - (Change) All gcode file formats dropped light-off delay field in favor of new 'Wait time before cure' field, setting light-off delay still valid but it redirects to the new field
   - (Change) Reorder 'Light-off delay' before 'Exposure time'
   - (Improvement) Recalculate the print time when a related property changes
   - (Fix) Generic time estimation calculation was ignoring exposure times
   - (Fix) Unable to load files with uppercase extensions
   - (Fix) ZIP: Use G1 at end of gcode instead of G0
   - (Fix) CWS: Use G1 for movements
   - **(Fix) CXDLP:** (#240)
      - Layer area calculation
      - Validation checksum
   - **(Fix) ZCODE:**
      - Use G1 at end of gcode instead of G0 to prevent crash to top bug on firmware
      - Put back the M18 motors off at end of gcode
- **GCode Builder/Parser:**
   - (Add) Allow to choose between G0 and G1 for layer movements and end gcode
   - (Fix) Safe guard: If the total print height is larger than set machine Z, do not raise/lower print on completeness
   - (Fix) Light-off delay is the real delay time and not the calculated movement of the lifts plus the extra time
   - (Improvement) Parse gcode line by line instead searching on a group of layers to allow a better control and identification
- **Tools:**
   - **Change print parameters:**
      - (Add) Tooltips to labels
      - (Add) Sun UTF-8 to the Light PWM value unit to describe intensity
   - (Improvement) Dynamic lifts: Round lift height and speed to 1 decimal
   - (Fix) Exposure time finder: Time estimation when using 'Use different settings for layers with same Z positioning'
- **Prusa Slicer:**
  - (Add) Note keyword: BottomWaitBeforeCure_xxx
  - (Add) Note keyword: WaitBeforeCure_xxx
  - (Add) Note keyword: BottomWaitAfterCure_xxx
  - (Add) Note keyword: WaitAfterCure_xxx
  - (Add) Note keyword: BottomWaitAfterLift_xxx
  - (Add) Note keyword: WaitAfterLift_xxx
  - (Change) Uniz IBEE: Remove light-off delay and implement wait time keywords
- **macOS:** (#236)
  - (Remove) osx legacy packages from build and downloads
  - (Fix) macOS: Simplify the libcvextern.dylib and remove libtesseract dependency
  - (Fix) macOS: Include libusb-1.0.0.dylib
  - Note: `brew install libusb` still required
- **UI:**
  - (Fix) Refresh gcode does not update text on UI for ZIP, CWS, ZCODEX files
  - (Fix) Operations: Import a .uvtop file by drag and drop into the UI would not load the layer range
  - (Change) When convert a file, the result dialog will have Yes, No and Cancel actions, where No will open the converted file on current window, while Cancel will not perform any action (The old No behaviour)
2021-07-16 22:40:11 +01:00

426 lines
14 KiB
C#

/*
* 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.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Serialization;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats
{
#region Sub Classes
[Serializable]
[XmlRoot(ElementName = "root")]
public class VDARoot
{
[Serializable]
public class VDAFileInfo
{
[Serializable]
public class VDAVersion
{
public ushort Major { get; set; } = 1;
public ushort Minor { get; set; } = 2;
}
[Serializable]
public class VDAWritten
{
[Serializable]
[XmlRoot(ElementName = "By")]
public class VDABy
{
[XmlAttribute]
public string ApplicationName { get; set; } = About.Software;
[XmlAttribute]
public string ApplicationVersion { get; set; } = About.VersionStr;
public override string ToString()
{
return $"{ApplicationName} v{ApplicationVersion}";
}
public void Reset()
{
ApplicationName = About.Software;
ApplicationVersion = About.VersionStr;
}
}
public VDABy By { get; set; } = new();
public string When { get; set; } = DateTime.Now.ToString("u");
public void Reset()
{
When = DateTime.Now.ToString("u");
By.Reset();
}
}
public VDAVersion Version { get; set; } = new();
public VDAWritten Written { get; set; } = new();
}
[Serializable]
public class VDASlices
{
public ushort Count { get; set; } = 1;
[XmlElement("thickness")]
public float LayerHeight { get; set; }
[XmlElement("startHeight")]
public float StartHeight { get; set; }
[XmlElement("endHeight")]
public float EndHeight { get; set; }
[XmlElement("layersCount")]
public uint LayerCount { get; set; }
}
[Serializable]
public class VDAMachines
{
public string FileType { get; set; } = "ZIP File";
public string Resolution { get; set; } = "1920*1080P";
public string PixelXSize { get; set; } = "50um";
public string PixelYSize { get; set; } = "50um";
[XmlElement("Anti-Aliasing")]
public byte AntiAliasing { get; set; } = 1;
public float XLength { get; set; }
public float YWidth { get; set; }
public float ZHeight { get; set; }
}
public class VDALayer
{
[XmlElement("Index")]
public uint Index { get; set; }
[XmlElement("zvalue")]
public float ZPosition { get; set; }
[XmlElement("filename")]
public string Filename { get; set; }
public VDALayer()
{
}
public VDALayer(uint index, float zPosition, string filename)
{
Index = index;
ZPosition = zPosition;
Filename = filename;
}
}
public VDAFileInfo FileInfo { get; set; } = new();
public VDASlices Slices { get; set; } = new();
public VDAMachines Machines { get; set; } = new();
public List<VDALayer> Layers { get; set; } = new();
}
#endregion
public class VDAFile : FileFormat
{
#region Constants
#endregion
#region Properties
public VDARoot ManifestFile { get; set; } = new ();
public override FileFormatType FileType => FileFormatType.Archive;
public override FileExtension[] FileExtensions { get; } = {
new("vda.zip", "Voxeldance Additive Zip")
};
public override uint ResolutionX
{
get
{
var resolution = ManifestFile.Machines.Resolution.Split('*', StringSplitOptions.TrimEntries);
if (resolution.Length < 2) return 0;
uint.TryParse(resolution[0], out var xRes);
return xRes;
}
set
{
ManifestFile.Machines.Resolution = $"{value}*{ResolutionY}P";
RaisePropertyChanged();
}
}
public override uint ResolutionY
{
get
{
var resolution = ManifestFile.Machines.Resolution.Split('*', StringSplitOptions.TrimEntries);
if (resolution.Length < 2) return 0;
resolution[1] = resolution[1].TrimEnd('P');
uint.TryParse(resolution[1], out var yRes);
return yRes;
}
set
{
ManifestFile.Machines.Resolution = $"{ResolutionX}*{value}P";
RaisePropertyChanged();
}
}
public override float DisplayWidth
{
get
{
if (ManifestFile.Machines.XLength > 0) return ManifestFile.Machines.XLength;
var umStr= ManifestFile.Machines.PixelXSize.Replace("um", string.Empty, StringComparison.OrdinalIgnoreCase);
if (ushort.TryParse(umStr, out var um) && um > 0)
{
return (float) Math.Round(ResolutionX * um / 1000f, 2);
}
return ManifestFile.Machines.XLength;
}
set
{
ManifestFile.Machines.XLength = (float) Math.Round(value, 2);
ManifestFile.Machines.PixelXSize = $"{Math.Round(value / ResolutionX * 1000, 2)}um";
RaisePropertyChanged();
}
}
public override float DisplayHeight
{
get
{
if (ManifestFile.Machines.YWidth > 0) return ManifestFile.Machines.YWidth;
var umStr = ManifestFile.Machines.PixelYSize.Replace("um", string.Empty, StringComparison.OrdinalIgnoreCase);
if (ushort.TryParse(umStr, out var um) && um > 0)
{
return (float)Math.Round(ResolutionY * um / 1000f, 2);
}
return ManifestFile.Machines.YWidth;
}
set
{
ManifestFile.Machines.YWidth = (float)Math.Round(value, 2);
ManifestFile.Machines.PixelYSize = $"{Math.Round(value / ResolutionY * 1000, 2)}um";
RaisePropertyChanged();
}
}
public override float MachineZ
{
get => ManifestFile.Machines.ZHeight > 0 ? ManifestFile.Machines.ZHeight : base.MachineZ;
set
{
ManifestFile.Machines.ZHeight = value;
RaisePropertyChanged();
}
}
public override byte AntiAliasing
{
get => ManifestFile.Machines.AntiAliasing;
set
{
ManifestFile.Machines.AntiAliasing = value.Clamp(1, 16);
RaisePropertyChanged();
}
}
public override float LayerHeight
{
get => ManifestFile.Slices.LayerHeight;
set
{
ManifestFile.Slices.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
get => base.LayerCount;
set => base.LayerCount = ManifestFile.Slices.LayerCount = base.LayerCount;
}
public override object[] Configs => new object[] {
ManifestFile.FileInfo.Version,
ManifestFile.FileInfo.Written,
ManifestFile.Machines,
ManifestFile.Slices };
#endregion
#region Constructor
public VDAFile()
{ }
#endregion
#region Methods
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
{
using var outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create);
var manifestFilename = Path.GetFileName(fileFullPath).
Replace($".{FileExtensions[0].Extension}{TemporaryFileAppend}", ".xml").
Replace($".{FileExtensions[0].Extension}", ".xml");
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
progress.Token.ThrowIfCancellationRequested();
var layer = this[layerIndex];
var filename = $"{layerIndex + 1}".PadLeft(4, '0') + ".png";
outputFile.PutFileContent(filename, layer.CompressedBytes, ZipArchiveMode.Create);
progress++;
}
UpdateManifest();
XmlSerializer serializer = new(ManifestFile.GetType());
XmlSerializerNamespaces ns = new();
ns.Add("", "");
var entry = outputFile.CreateEntry(manifestFilename);
using var stream = entry.Open();
serializer.Serialize(stream, ManifestFile, ns);
}
protected override void DecodeInternally(string fileFullPath, OperationProgress progress)
{
using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read))
{
var entry = inputFile.Entries.FirstOrDefault(zipEntry => zipEntry.Name.EndsWith(".xml"));
if (entry is null)
{
Clear();
throw new FileLoadException($".xml manifest not found", fileFullPath);
}
try
{
var serializer = new XmlSerializer(ManifestFile.GetType());
using var stream = entry.Open();
ManifestFile = (VDARoot)serializer.Deserialize(stream);
}
catch (Exception e)
{
Clear();
throw new FileLoadException($"Unable to deserialize '{entry.Name}'\n{e}", fileFullPath);
}
LayerManager.Init(ManifestFile.Slices.LayerCount);
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
if (progress.Token.IsCancellationRequested) break;
var filename = $"{layerIndex + 1}".PadLeft(4, '0')+".png";
entry = inputFile.GetEntry(filename);
if (entry is null)
{
Clear();
throw new FileLoadException($"Layer {filename} not found", fileFullPath);
}
using var stream = entry.Open();
this[layerIndex] = new Layer(layerIndex, stream, LayerManager);
progress++;
}
}
LayerManager.GetBoundingRectangle(progress);
}
public override void SaveAs(string filePath = null, OperationProgress progress = null)
{
if (RequireFullEncode)
{
if (!string.IsNullOrEmpty(filePath))
{
FileFullPath = filePath;
}
Encode(FileFullPath, progress);
return;
}
if (!string.IsNullOrEmpty(filePath))
{
File.Copy(FileFullPath, filePath, true);
FileFullPath = filePath;
}
using var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update);
bool deleted;
do
{
deleted = false;
foreach (var zipEntry in outputFile.Entries)
{
if (!zipEntry.Name.EndsWith(".xml")) continue;
zipEntry.Delete();
deleted = true;
break;
}
} while (deleted);
var manifestFilename = Path.GetFileName(FileFullPath).
Replace($".{FileExtensions[0].Extension}{TemporaryFileAppend}", ".xml").
Replace($".{FileExtensions[0].Extension}", ".xml");
UpdateManifest();
XmlSerializer serializer = new(ManifestFile.GetType());
XmlSerializerNamespaces ns = new();
ns.Add("", "");
var entry = outputFile.CreateEntry(manifestFilename);
using var stream = entry.Open();
serializer.Serialize(stream, ManifestFile, ns);
}
public void UpdateManifest()
{
ManifestFile.FileInfo.Written.Reset();
ManifestFile.Slices.StartHeight = FirstLayer.PositionZ;
ManifestFile.Slices.EndHeight = LastLayer.PositionZ;
ManifestFile.Layers.Clear();
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
var layer = this[layerIndex];
ManifestFile.Layers.Add(new VDARoot.VDALayer(layerIndex, layer.PositionZ, layer.FormatFileName(4, false)));
}
}
#endregion
}
}