Merge pull request #632 from aspadm/anet-n4-format

FileFormats: Add Anet N4 support
This commit is contained in:
Tiago Conceição
2022-12-31 19:29:45 +00:00
committed by GitHub
9 changed files with 670 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
# generated by PrusaSlicer 2.5.0+win64 on 2022-12-31 at 19:07:52 UTC
absolute_correction = 0
area_fill = 50
bed_custom_model =
bed_custom_texture =
bed_shape = 0x0,68.04x0,68.04x120.96,0x120.96
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 = 0
high_viscosity_tilt_time = 10
host_type = octoprint
inherits = Original Prusa SL1
max_exposure_time = 120
max_initial_exposure_time = 300
max_print_height = 135
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\nPRINTER_VENDOR_ANET\nPRINTER_MODEL_N4\nFILEFORMAT_N4\n\nSTART_CUSTOM_VALUES\nLiftHeight_6\nLiftSpeed_60\nEND_CUSTOM_VALUES
printer_settings_id =
printer_technology = SLA
printer_variant = default
printer_vendor =
printhost_apikey =
printhost_cafile =
relative_correction = 1,1
relative_correction_x = 1
relative_correction_y = 1
relative_correction_z = 1
slow_tilt_time = 8
thumbnails = 400x400,800x480
+1
View File
@@ -138,6 +138,7 @@ But also, I need victims for test subject. Proceed at your own risk!
- LGS120 (Longer Orange 120)
- LGS4K (Longer Orange 4K & mono)
- Flashforge SVGX
- Anet N4
- ZIP (Generic / Phrozen Zip)
- VDA.ZIP (Voxeldance Additive)
- VDT (Voxeldance Tango)
+1
View File
@@ -12,6 +12,7 @@
<file>gr1.bt</file>
<file>lgs.bt</file>
<file>mdlp.bt</file>
<file>n4.bt</file>
<file>osf.bt</file>
<file>osla.bt</file>
<file>photons.bt</file>
+57
View File
@@ -0,0 +1,57 @@
//------------------------------------------------
//--- 010 Editor v8.0.1 Binary Template
//
// File: Anet N4
// Authors: Tiago Conceição
//------------------------------------------------
BigEndian();
typedef struct() {
uint32 WhitePixelsCount <fgcolor=cBlack, bgcolor=cWhite>; // White pixels region (border including corner pixels)
uint32 XMin <fgcolor=cBlack, bgcolor=cWhite>; // Bounding rectangle X
uint32 YMin <fgcolor=cBlack, bgcolor=cWhite>; // Bounding rectangle Y
uint32 XMax <fgcolor=cBlack, bgcolor=cWhite>; // Bounding rectangle Right
uint32 YMax <fgcolor=cBlack, bgcolor=cWhite>; // Bounding rectangle Bottom
uint32 BitsCount <fgcolor=cBlack, bgcolor=cWhite>;
ubyte LayerRLE[(BitsCount + 7) >> 3] <fgcolor=cBlack, bgcolor=cRed>;
} layerData;
struct HEADER {
uint32 VersionLen <fgcolor=cBlack, bgcolor=cRed>; // 2
wchar_t Version[VersionLen/2] <fgcolor=cBlack, bgcolor=cRed>; // 3
uint32 NameLength <fgcolor=cBlack, bgcolor=cRed>;
wchar_t FileName[NameLength/2] <fgcolor=cBlack, bgcolor=cRed>; // File name without extension
uint32 DescriptionLength <fgcolor=cBlack, bgcolor=cRed>;
wchar_t Description[DescriptionLength/2] <fgcolor=cBlack, bgcolor=cRed>; // File name without extension
double XYPixelSize <fgcolor=cBlack, bgcolor=cRed>; // mm
double LayerHeight <fgcolor=cBlack, bgcolor=cRed>; // mm; from 0.03 to 0.08
uint32 BaseLayersCount <fgcolor=cBlack, bgcolor=cRed>; // Number of extent filled additional first layers; do not use!
uint32 FilledBaseLayersCount <fgcolor=cBlack, bgcolor=cRed>; // Number of fully filled first layers inside BaseLayersCount; do not use!
uint ExposureTime <fgcolor=cBlack, bgcolor=cRed>; // from 3 to 25
uint BottomExposureTime <fgcolor=cBlack, bgcolor=cRed>; // from 60 to 120
uint32 BottomLayerCount <fgcolor=cBlack, bgcolor=cRed>; // from 2 to 10
uint32 LiftSpeed <fgcolor=cBlack, bgcolor=cRed>; // mm/s, from 1 to 10
uint32 LiftHeight <fgcolor=cBlack, bgcolor=cRed>; // mm, from 3 to 10
uint32 PreviewResolutionX <fgcolor=cBlack, bgcolor=cRed>; // 260
uint32 PreviewResolutionY <fgcolor=cBlack, bgcolor=cRed>; // 140
uint32 PreviewSize <fgcolor=cBlack, bgcolor=cRed>; // 72866
ubyte PreviewContent[PreviewSize] <fgcolor=cBlack, bgcolor=0x00FF00>; // BMP image, BGR565
double VolumeMicroL <fgcolor=cBlack, bgcolor=cRed>; // µl
uint32 EncodedPrintTime <fgcolor=cBlack, bgcolor=cRed>; // s; for unknown reason always broken in original slicer
uint32 LayerCount <fgcolor=cBlack, bgcolor=cRed>;
} header;
struct LAYERS {
local int i;
for( i = 0; i < header.LayerCount; i++ ){
layerData lD();
}
} layers;
uint32 SupportsCount <fgcolor=cBlack, bgcolor=cYellow>; // Supports count, always 0
+548
View File
@@ -0,0 +1,548 @@
/*
* 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 BinarySerialization;
using Emgu.CV;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using UVtools.Core.Converters;
using UVtools.Core.Extensions;
using UVtools.Core.Layers;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats;
/// <summary>
/// This file format is based on B9Creator job file (.b9j) with defined version = 3
/// and added several new fields (as example, preview image).
/// Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount).
/// </summary>
public sealed class AnetN4File : FileFormat
{
#region Constants
public const ushort RESOLUTION_X = 1440;
public const ushort RESOLUTION_Y = 2560;
public const float DISPLAY_WIDTH = 68.04f;
public const float DISPLAY_HEIGHT = 120.96f;
public const float MACHINE_Z = 135f;
/// <summary>
/// Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating.
/// </summary>
private static readonly byte[] BmpHeader = { 66, 77, 162, 4, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 40, 0, 0, 0, 4, 1, 0, 0, 140, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 96, 4, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0 };
#endregion
#region Members
private uint _resolutionX = RESOLUTION_X;
private uint _resolutionY = RESOLUTION_Y;
private float _displayWidth = DISPLAY_WIDTH;
private float _displayHeight = DISPLAY_HEIGHT;
#endregion
#region Sub Classes
#region Header
public class Header
{
[FieldOrder(0)][FieldEndianness(Endianness.Big)] public uint VersionLength { get; set; } = 2;
[FieldOrder(1)][FieldEncoding("UTF-16BE")][FieldLength(nameof(VersionLength))] public string Version { get; set; } = "3";
[FieldOrder(2)][FieldEndianness(Endianness.Big)] public uint NameLength { get; set; } = (uint)About.SoftwareWithVersion.Length * 2;
[FieldOrder(3)][FieldEncoding("UTF-16BE")][FieldLength(nameof(NameLength))] public string Name { get; set; } = About.SoftwareWithVersion;
[FieldOrder(4)][FieldEndianness(Endianness.Big)] public uint DescriptionLength { get; set; } = (uint)About.SoftwareWithVersion.Length * 2;
[FieldOrder(5)][FieldEncoding("UTF-16BE")][FieldLength(nameof(DescriptionLength))] public string Description { get; set; } = About.SoftwareWithVersion;
[FieldOrder(6)][FieldEndianness(Endianness.Big)] public double XYPixelSize { get; set; } = 0.04725; // mm
[FieldOrder(7)][FieldEndianness(Endianness.Big)] public double LayerHeight { get; set; } // mm; from 0.03 to 0.08
[FieldOrder(8)][FieldEndianness(Endianness.Big)] public uint BaseLayersCount { get; set; } // Number of extent filled additional first layers; do not use!
[FieldOrder(9)][FieldEndianness(Endianness.Big)] public uint FilledBaseLayersCount { get; set; } // Number of fully filled first layers inside BaseLayersCount; do not use!
[FieldOrder(10)][FieldEndianness(Endianness.Big)] public uint ExposureTime { get; set; } = 6; // from 3 to 25
[FieldOrder(11)][FieldEndianness(Endianness.Big)] public uint BottomExposureTime { get; set; } = 90; // from 60 to 120
[FieldOrder(12)][FieldEndianness(Endianness.Big)] public uint BottomLayerCount { get; set; } = DefaultBottomLayerCount; // from 2 to 10
[FieldOrder(13)][FieldEndianness(Endianness.Big)] public uint LiftSpeed { get; set; } = (uint)Math.Ceiling(SpeedConverter.Convert(DefaultLiftSpeed, CoreSpeedUnit, SpeedUnit.MillimetersPerSecond)); // mm/s, from 1 to 10
[FieldOrder(14)][FieldEndianness(Endianness.Big)] public uint LiftHeight { get; set; } = (uint)DefaultLiftHeight; // mm, from 3 to 10
[FieldOrder(15)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionX { get; set; } = 260;
[FieldOrder(16)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionY { get; set; } = 140;
[FieldOrder(17)][FieldEndianness(Endianness.Big)] public uint PreviewSize { get; set; } = 72866;
[FieldOrder(18)][FieldEndianness(Endianness.Big)][FieldLength(nameof(PreviewSize))] public byte[] PreviewContent { get; set; } = Array.Empty<byte>(); // BMP image, BGR565
[FieldOrder(19)][FieldEndianness(Endianness.Big)] public double VolumeMicroL { get; set; } // µl
[FieldOrder(20)][FieldEndianness(Endianness.Big)] public uint EncodedPrintTime { get; set; } // s; for unknown reason always broken in original slicer
[FieldOrder(21)][FieldEndianness(Endianness.Big)] public uint LayersCount { get; set; } // Not include BaseLayers
public override string ToString()
{
return $"{nameof(VersionLength)}: {VersionLength}, {nameof(Version)}: {Version}, {nameof(NameLength)}: {NameLength}, {nameof(Name)}: {Name}, {nameof(DescriptionLength)}: {DescriptionLength}, {nameof(Description)}: {Description}, {nameof(XYPixelSize)}: {XYPixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(BaseLayersCount)}: {BaseLayersCount}, {nameof(FilledBaseLayersCount)}: {FilledBaseLayersCount}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(PreviewResolutionX)}: {PreviewResolutionX}, {nameof(PreviewResolutionY)}: {PreviewResolutionY}, {nameof(PreviewSize)}: {PreviewSize}, {nameof(PreviewContent)}: {PreviewContent}, {nameof(VolumeMicroL)}: {VolumeMicroL}, {nameof(EncodedPrintTime)}: {EncodedPrintTime}, {nameof(LayersCount)}: {LayersCount}";
}
}
#endregion
#region LayerDef
public class LayerDef
{
/// <summary>
/// White pixels region (border including corner pixels)
/// </summary>
[FieldOrder(0)][FieldEndianness(Endianness.Big)] public uint WhitePixelsCount { get; set; }
[FieldOrder(1)][FieldEndianness(Endianness.Big)] public int XMin { get; set; }
[FieldOrder(2)][FieldEndianness(Endianness.Big)] public int YMin { get; set; }
[FieldOrder(3)][FieldEndianness(Endianness.Big)] public int XMax { get; set; } = RESOLUTION_X - 1;
[FieldOrder(4)][FieldEndianness(Endianness.Big)] public int YMax { get; set; } = RESOLUTION_Y - 1;
[FieldOrder(5)][FieldEndianness(Endianness.Big)] public uint BitsCount { get; set; }
[Ignore]
public uint RleBytesCount
{
get => (BitsCount + 7) >> 3;
set => BitsCount = value << 3;
}
[Ignore] public byte[] EncodedRle { get; set; } = null!;
public LayerDef()
{
}
public void SetFrom(Layer layer)
{
WhitePixelsCount = layer.NonZeroPixelCount; // To be re-set latter while encoding
XMin = layer.BoundingRectangle.X;
YMin = layer.BoundingRectangle.Y;
XMax = layer.BoundingRectangle.Right;
YMax = layer.BoundingRectangle.Bottom;
}
public override string ToString()
{
return $"{nameof(WhitePixelsCount)}: {WhitePixelsCount}, {nameof(XMin)}: {XMin}, {nameof(YMin)}: {YMin}, {nameof(XMax)}: {XMax}, {nameof(YMax)}: {YMax}, {nameof(BitsCount)}: {BitsCount}, {nameof(RleBytesCount)}: {RleBytesCount}, {nameof(EncodedRle)}: {EncodedRle?.Length}";
}
public byte[] Encode(Mat mat)
{
uint ComputeRepeatsSize(uint repeats)
{
return (uint)Math.Ceiling(Math.Log2(repeats));
}
void SetBits(List<byte> data, uint pos, uint value, uint count = 1)
{
if (data.Count * 8 < pos + count)
{
data.AddRange(new byte[(pos + count + 7 - data.Count * 8) / 8]);
}
for (int off = (int)(pos + count - 1); off + 1 > pos; --off)
{
byte tmp = data[off / 8];
byte mask = (byte)(1 << (off % 8));
data[off / 8] = ((int)value & 1) == 1 ? (byte)(tmp | mask) : (byte)(tmp & ~mask);
value >>= 1;
}
}
if (mat.Width != RESOLUTION_X || mat.Height != RESOLUTION_Y)
{
throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {mat.Width}x{mat.Height}");
}
WhitePixelsCount = 0;
uint singleColorLength = 0;
uint compressedPos = 33;
var rawData = new List<byte>();
var spanMat = mat.GetDataByteSpan();
bool isWhitePrev = spanMat[0] > 127;
SetBits(rawData, 0, (uint)mat.Width, 16);
SetBits(rawData, 16, (uint)mat.Height, 16);
SetBits(rawData, 32, isWhitePrev ? 1u : 0u);
for (int i = 0; i < spanMat.Length; i++)
{
bool isWhiteCurrent = spanMat[i] > 127; // No AA
if (isWhiteCurrent)
{
WhitePixelsCount++;
}
if (isWhiteCurrent == isWhitePrev)
{
singleColorLength++;
}
if (isWhiteCurrent != isWhitePrev || i == spanMat.Length - 1)
{
isWhitePrev = isWhiteCurrent;
var repeatsSize = ComputeRepeatsSize(singleColorLength);
SetBits(rawData, compressedPos, repeatsSize, 5);
SetBits(rawData, compressedPos + 5, singleColorLength, repeatsSize + 1);
compressedPos += 6 + repeatsSize;
singleColorLength = 1;
}
}
EncodedRle = rawData.ToArray();
RleBytesCount = (uint)EncodedRle.Length;
BitsCount = compressedPos;
return EncodedRle;
}
public Mat Decode(bool consumeRle = true)
{
uint GetBits(uint pos, uint count = 1)
{
if (pos + count > BitsCount)
{
throw new IndexOutOfRangeException($"Trying to read {count} bits from pos {pos}, but total size is {BitsCount} bits.");
}
uint res = 0;
for (uint i = pos; i < pos + count; i++)
{
res <<= 1;
if ((EncodedRle[i >> 0x3] & (byte)(0x1u << (int)(i & 0x7u))) != 0)
{
res |= 1;
}
}
return res;
}
if ((BitsCount + 7) / 8 != EncodedRle.Length)
{
throw new IndexOutOfRangeException($"Incorrect RLE data size {EncodedRle.Length * 8}, except {BitsCount} bits.");
}
uint rleWidth = GetBits(0, 16);
uint rleHeight = GetBits(16, 16);
var mat = EmguExtensions.InitMat(new Size((int)rleWidth, (int)rleHeight));
var imageLength = mat.GetLength();
byte brightness = (byte)((GetBits(32) == 1) ? 0xff : 0x0);
int pixelPos = 0;
uint bitPos = 33;
while (pixelPos < imageLength)
{
uint keySize = GetBits(bitPos, 5);
uint stripSize = GetBits(bitPos + 5, keySize + 1);
bitPos += keySize + 6;
mat.FillSpan(ref pixelPos, (int)stripSize, brightness);
brightness = (byte)~brightness;
}
if (consumeRle) EncodedRle = null!;
return mat;
}
}
#endregion
#endregion
#region Properties
public Header HeaderSettings { get; private set; } = new();
public override FileFormatType FileType => FileFormatType.Binary;
public override FileExtension[] FileExtensions { get; } = {
new(typeof(AnetN4File), "n4", "Anet N4"),
};
public override SpeedUnit FormatSpeedUnit => SpeedUnit.MillimetersPerSecond;
public override byte AntiAliasing => 1; // Format does not support anti-aliasing
public override PrintParameterModifier[]? PrintParameterModifiers { get; } =
{
PrintParameterModifier.ExposureTime,
PrintParameterModifier.BottomExposureTime,
PrintParameterModifier.BottomLayerCount,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.LiftHeight,
};
public override Size[]? ThumbnailsOriginalSize { get; } = { new(260, 140) };
public override uint ResolutionX
{
get => _resolutionX;
set
{
if (!RaiseAndSetIfChanged(ref _resolutionX, value)) return;
if(value != RESOLUTION_X) throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {ResolutionX}x{ResolutionY}");
HeaderSettings.XYPixelSize = PixelSizeMax;
}
}
public override uint ResolutionY
{
get => _resolutionY;
set
{
if (!RaiseAndSetIfChanged(ref _resolutionY, value)) return;
if (value != RESOLUTION_Y) throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {ResolutionX}x{ResolutionY}");
HeaderSettings.XYPixelSize = PixelSizeMax;
}
}
public override float DisplayWidth
{
get => _displayWidth;
set
{
if (!RaiseAndSetIfChanged(ref _displayWidth, value)) return;
HeaderSettings.XYPixelSize = PixelSizeMax;
}
}
public override float DisplayHeight
{
get => _displayHeight;
set
{
if (!RaiseAndSetIfChanged(ref _displayHeight, value)) return;
HeaderSettings.XYPixelSize = PixelSizeMax;
}
}
public override FlipDirection DisplayMirror
{
get => FlipDirection.Horizontally;
set { }
}
public override float LayerHeight
{
get => Layer.RoundHeight((float)HeaderSettings.LayerHeight);
set
{
HeaderSettings.LayerHeight = Layer.RoundHeight((double)value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
get => base.LayerCount;
set => base.LayerCount = HeaderSettings.LayersCount = LayerCount;
}
public override ushort BottomLayerCount
{
get => (ushort)HeaderSettings.BottomLayerCount;
set => base.BottomLayerCount = (ushort)(HeaderSettings.BottomLayerCount = value);
}
public override float BottomExposureTime
{
get => HeaderSettings.BottomExposureTime;
set => base.BottomExposureTime = HeaderSettings.BottomExposureTime = (uint)Math.Round(value);
}
public override float ExposureTime
{
get => HeaderSettings.ExposureTime;
set => base.ExposureTime = HeaderSettings.ExposureTime = (uint)Math.Round(value);
}
public override float BottomLiftHeight => LiftHeight;
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
set => base.LiftHeight = HeaderSettings.LiftHeight = (uint)Math.Round(value);
}
public override float BottomLiftSpeed => LiftSpeed;
public override float LiftSpeed
{
get => SpeedConverter.Convert(HeaderSettings.LiftSpeed, SpeedUnit.MillimetersPerSecond, CoreSpeedUnit);
set
{
HeaderSettings.LiftSpeed = (uint)Math.Round(SpeedConverter.Convert(value, CoreSpeedUnit, SpeedUnit.MillimetersPerSecond));
base.LiftSpeed = SpeedConverter.Convert(HeaderSettings.LiftSpeed, SpeedUnit.MillimetersPerSecond, CoreSpeedUnit);
}
}
public override float BottomRetractSpeed => LiftSpeed;
public override float RetractSpeed => LiftSpeed;
public override float PrintTime
{
get => base.PrintTime;
set
{
base.PrintTime = value;
HeaderSettings.EncodedPrintTime = (uint)base.PrintTime;
}
}
public override float MaterialMilliliters
{
get => base.MaterialMilliliters;
set
{
base.MaterialMilliliters = value;
HeaderSettings.VolumeMicroL = Math.Round(base.MaterialMilliliters * 1000.0, 3);
}
}
public override string MachineName => "Anet N4";
public override object[] Configs => new object[] { HeaderSettings };
#endregion
#region Constructors
public AnetN4File()
{
MachineZ = MACHINE_Z;
}
#endregion
#region Methods
protected override void OnBeforeEncode(bool isPartialEncode)
{
HeaderSettings.Name = FilenameNoExt!;
HeaderSettings.Description = $"{About.SoftwareWithVersion} @ {DateTime.Now}";
}
protected override void EncodeInternally(OperationProgress progress)
{
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write);
var previewBuffer = new byte[72866]; // 72866
BmpHeader.CopyTo(previewBuffer, 0);
if (CreatedThumbnailsCount > 0)
{
EncodeImage(DATATYPE_BGR565, Thumbnails[0]!).CopyTo(previewBuffer, BmpHeader.Length);
}
HeaderSettings.PreviewContent = previewBuffer;
outputFile.WriteSerialize(HeaderSettings);
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
var layerData = new LayerDef[LayerCount];
foreach (var batch in BatchLayersIndexes())
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
var layer = this[layerIndex];
using var mat = layer.LayerMat;
layerData[layerIndex] = new LayerDef();
layerData[layerIndex].SetFrom(layer);
layerData[layerIndex].Encode(mat);
progress.LockAndIncrement();
});
foreach (var layerIndex in batch)
{
progress.ThrowIfCancellationRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
layerData[layerIndex].EncodedRle = null!; // Free
}
}
outputFile.WriteBytes(new byte[4]); // Supports count, always 0
Debug.WriteLine("Encode Results:");
Debug.WriteLine(HeaderSettings);
Debug.WriteLine("-End-");
}
protected override void DecodeInternally(OperationProgress progress)
{
using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read);
HeaderSettings = Helpers.Deserialize<Header>(inputFile);
if (HeaderSettings.Version is not "3")
{
throw new FileLoadException($"Not a valid N4 file: Version doesn't match, got {HeaderSettings.Version} instead of 3)", FileFullPath);
}
if (HeaderSettings.PreviewSize != 72866 || HeaderSettings.PreviewContent is null)
{
throw new FileLoadException($"Not a valid N4 file: incorrect preview format ({HeaderSettings.PreviewSize})", FileFullPath);
}
Thumbnails[0] = DecodeImage(DATATYPE_BGR565, HeaderSettings.PreviewContent[BmpHeader.Length..], ThumbnailsOriginalSize![0]);
Debug.WriteLine(HeaderSettings);
Init(HeaderSettings.LayersCount, DecodeType == FileDecodeType.Partial);
var layersDefinitions = new LayerDef[HeaderSettings.LayersCount];
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
foreach (var batch in BatchLayersIndexes())
{
foreach (var layerIndex in batch)
{
progress.ThrowIfCancellationRequested();
var layerDef = Helpers.Deserialize<LayerDef>(inputFile);
layersDefinitions[layerIndex] = layerDef;
if (DecodeType == FileDecodeType.Full)
{
layerDef.EncodedRle = inputFile.ReadBytes(layerDef.RleBytesCount);
}
else
{
inputFile.Seek(layerDef.RleBytesCount, SeekOrigin.Current);
}
Debug.Write($"LAYER {layerIndex} -> ");
Debug.WriteLine(layerDef);
}
if (DecodeType == FileDecodeType.Full)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
using var mat = layersDefinitions[layerIndex].Decode();
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
progress.LockAndIncrement();
});
}
}
RebuildLayersProperties();
}
protected override void PartialSaveInternally(OperationProgress progress)
{
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write);
outputFile.Seek(0, SeekOrigin.Begin);
outputFile.WriteSerialize(HeaderSettings);
}
#endregion
}
+1
View File
@@ -385,6 +385,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
new ZCodexFile(), // zcodex
new MDLPFile(), // MKS v1
new GR1File(), // GR1 Workshop
new AnetN4File(), // N4
new FlashForgeSVGXFile(), // SVGX
new OSLAFile(), // OSLA
new OSFFile(), // OSF
+2
View File
@@ -212,6 +212,8 @@ namespace UVtools.Core.Printer
new(PrinterBrand.Creality, "CT133 Pro", "CT133PRO", 3840, 2160, 293.76f, 165.24f, 300),
new(PrinterBrand.Creality, "CT-005 Pro", "CT-005", 3840, 2400, 192, 120, 250),*/
new(PrinterBrand.Anet, "Anet N4", "N4", 1440, 2560, 68.04f, 120.96f, 135f, FlipDirection.Horizontally),
new(PrinterBrand.AnyCubic, "AnyCubic Photon M3", "Photon M3", 4096, 2560, 163.84f, 102.40f, 180f, FlipDirection.Horizontally),
new(PrinterBrand.AnyCubic, "AnyCubic Photon M3 Max", "Photon M3 Max", 6480, 3600, 298.08f, 165.60f, 300f, FlipDirection.Horizontally),
new(PrinterBrand.AnyCubic, "AnyCubic Photon M3 Plus", "Photon M3 Plus", 5760, 3600, 198.15f, 123.84f, 245f, FlipDirection.Horizontally),
+1
View File
@@ -10,6 +10,7 @@ namespace UVtools.Core.Printer
public enum PrinterBrand : byte
{
Generic,
Anet,
AnyCubic,
Creality,
Elegoo,
+17
View File
@@ -1371,6 +1371,23 @@
<param name="mode"></param>
<returns>Created <see cref="T:System.IO.Compression.ZipArchiveEntry"/></returns>
</member>
<member name="T:UVtools.Core.FileFormats.AnetN4File">
<summary>
This file format is based on B9Creator job file (.b9j) with defined version = 3
and added several new fields (as example, preview image).
Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount).
</summary>
</member>
<member name="F:UVtools.Core.FileFormats.AnetN4File.BmpHeader">
<summary>
Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating.
</summary>
</member>
<member name="P:UVtools.Core.FileFormats.AnetN4File.LayerDef.WhitePixelsCount">
<summary>
White pixels region (border including corner pixels)
</summary>
</member>
<member name="P:UVtools.Core.FileFormats.ChituboxFile.Header.Magic">
<summary>
Gets a magic number identifying the file type.