2016-05-19 17:55:46 -07:00
using MatterHackers.MatterControl ;
using NUnit.Framework ;
using System ;
using System.IO ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
using System.Reflection ;
using System.Text ;
using System.Globalization ;
using MatterHackers.MatterControl.SlicerConfiguration ;
namespace MatterControl.Tests.MatterControl
{
[TestFixture, Category("ConfigIni")]
public class ConfigIniTests
{
private static List < PrinterConfig > allPrinters ;
private static string matterControlDirectory = Path . GetFullPath ( Path . Combine ( ".." , ".." , ".." , ".." ) ) ;
private static string printerSettingsDirectory = Path . GetFullPath ( Path . Combine ( matterControlDirectory , "StaticData" , "PrinterSettings" ) ) ;
static ConfigIniTests ( )
{
allPrinters = ( from configIni in new DirectoryInfo ( printerSettingsDirectory ) . GetFiles ( "config.ini" , System . IO . SearchOption . AllDirectories )
2016-06-15 14:50:12 -07:00
let oemProfile = new OemProfile ( PrinterSettingsLayer . LoadFromIni ( configIni . FullName ) )
2016-05-19 17:55:46 -07:00
select new PrinterConfig
{
PrinterName = configIni . Directory . Name ,
Oem = configIni . Directory . Parent . Name ,
ConfigPath = configIni . FullName ,
2016-06-07 09:41:12 -07:00
ConfigIni = new LayerInfo ( )
{
2016-06-07 11:22:53 -07:00
RelativeFilePath = configIni . FullName . Substring ( printerSettingsDirectory . Length + 1 ) ,
// The config.ini layer cascade contains only itself
2016-06-15 14:50:12 -07:00
LayerCascade = new PrinterSettings ( oemProfile , new PrinterSettingsLayer ( ) ) ,
2016-06-07 09:41:12 -07:00
} ,
2016-06-07 11:22:53 -07:00
MatterialLayers = LoadLayers ( Path . Combine ( configIni . Directory . FullName , "material" ) , oemProfile ) ,
QualityLayers = LoadLayers ( Path . Combine ( configIni . Directory . FullName , "quality" ) , oemProfile )
2016-05-19 17:55:46 -07:00
} ) . ToList ( ) ;
}
2016-06-07 11:22:53 -07:00
private static List < LayerInfo > LoadLayers ( string layersDirectory , OemProfile oemProfile )
2016-06-07 09:41:12 -07:00
{
2016-06-07 11:22:53 -07:00
// The slice presets layer cascade contains the preset layer, with config.ini data as a parent
2016-06-07 09:41:12 -07:00
return Directory . Exists ( layersDirectory ) ?
Directory . GetFiles ( layersDirectory , "*.slice" ) . Select ( file = > new LayerInfo ( )
{
2016-06-07 11:22:53 -07:00
RelativeFilePath = file . Substring ( printerSettingsDirectory . Length + 1 ) ,
2016-06-15 14:50:12 -07:00
LayerCascade = new PrinterSettings ( new OemProfile ( PrinterSettingsLayer . LoadFromIni ( file ) ) , oemProfile . OemLayer )
2016-06-07 09:41:12 -07:00
} ) . ToList ( )
: new List < LayerInfo > ( ) ;
}
2016-05-19 17:55:46 -07:00
[Test]
public void CsvBedSizeExistsAndHasTwoValues ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 09:49:48 -07:00
// Bed size is not required in slice files
2016-06-07 11:22:53 -07:00
if ( settings . RelativeFilePath . IndexOf ( ".slice" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
2016-06-07 09:49:48 -07:00
{
return ;
}
2016-06-07 09:41:12 -07:00
2016-06-21 09:38:37 -07:00
string bedSize = settings . LayerCascade . GetValue ( SettingsKey . bed_size ) ;
2016-05-19 17:55:46 -07:00
// Must exist in all configs
2016-06-07 11:22:53 -07:00
Assert . IsNotNullOrEmpty ( bedSize , "[bed_size] must exist: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
string [ ] segments = bedSize . Trim ( ) . Split ( ',' ) ;
// Must be a CSV and have two values
2016-06-07 11:22:53 -07:00
Assert . AreEqual ( 2 , segments . Length , "[bed_size] should have two values separated by a comma: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
} ) ;
}
[Test]
public void CsvPrintCenterExistsAndHasTwoValues ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 09:49:48 -07:00
// Printer center is not required in slice files
2016-06-07 11:22:53 -07:00
if ( settings . RelativeFilePath . IndexOf ( ".slice" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
2016-06-07 09:49:48 -07:00
{
return ;
}
2016-06-21 09:38:37 -07:00
string printCenter = settings . LayerCascade . GetValue ( SettingsKey . print_center ) ;
2016-05-19 17:55:46 -07:00
// Must exist in all configs
2016-06-07 11:22:53 -07:00
Assert . IsNotNullOrEmpty ( printCenter , "[print_center] must exist: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
string [ ] segments = printCenter . Trim ( ) . Split ( ',' ) ;
// Must be a CSV and have only two values
2016-06-07 11:22:53 -07:00
Assert . AreEqual ( 2 , segments . Length , "[print_center] should have two values separated by a comma: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
} ) ;
}
[Test]
public void RetractLengthIsLessThanTwenty ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string retractLengthString = settings . LayerCascade . GetValue ( "retract_length" ) ;
2016-05-19 17:55:46 -07:00
if ( ! string . IsNullOrEmpty ( retractLengthString ) )
{
float retractLength ;
if ( ! float . TryParse ( retractLengthString , out retractLength ) )
{
2016-06-07 11:22:53 -07:00
Assert . Fail ( "Invalid [retract_length] value (float parse failed): " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
2016-06-07 11:22:53 -07:00
Assert . Less ( retractLength , 20 , "[retract_length]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void ExtruderCountIsGreaterThanZero ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string extruderCountString = settings . LayerCascade . GetValue ( "extruder_count" ) ;
2016-05-25 11:04:51 -07:00
if ( ! string . IsNullOrEmpty ( extruderCountString ) )
2016-05-19 17:55:46 -07:00
{
2016-05-25 11:04:51 -07:00
int extruderCount ;
if ( ! int . TryParse ( extruderCountString , out extruderCount ) )
{
2016-06-07 11:22:53 -07:00
Assert . Fail ( "Invalid [extruder_count] value (int parse failed): " + settings . RelativeFilePath ) ;
2016-05-25 11:04:51 -07:00
}
2016-05-19 17:55:46 -07:00
2016-05-25 11:04:51 -07:00
// Must be greater than zero
2016-06-07 11:22:53 -07:00
Assert . Greater ( extruderCount , 0 , "[extruder_count]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void MinFanSpeedOneHundredOrLess ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string fanSpeedString = settings . LayerCascade . GetValue ( "min_fan_speed" ) ;
2016-05-19 17:55:46 -07:00
if ( ! string . IsNullOrEmpty ( fanSpeedString ) )
{
// Must be valid int data
int minFanSpeed ;
if ( ! int . TryParse ( fanSpeedString , out minFanSpeed ) )
{
2016-06-07 11:22:53 -07:00
Assert . Fail ( "Invalid [min_fan_speed] value (int parse failed): " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
// Must be less than or equal to 100
2016-06-07 11:22:53 -07:00
Assert . LessOrEqual ( minFanSpeed , 100 , "[min_fan_speed]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void MaxFanSpeedOneHundredOrLess ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string fanSpeedString = settings . LayerCascade . GetValue ( "max_fan_speed" ) ;
2016-05-19 17:55:46 -07:00
if ( ! string . IsNullOrEmpty ( fanSpeedString ) )
{
// Must be valid int data
int maxFanSpeed ;
if ( ! int . TryParse ( fanSpeedString , out maxFanSpeed ) )
{
2016-06-07 11:22:53 -07:00
Assert . Fail ( "Invalid [max_fan_speed] value (int parse failed): " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
// Must be less than or equal to 100
2016-06-07 11:22:53 -07:00
Assert . LessOrEqual ( maxFanSpeed , 100 , "[max_fan_speed]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void NoCurlyBracketsInGcode ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
// TODO: Why aren't we testing all gcode sections?
string [ ] keysToTest = { "start_gcode" , "end_gcode" } ;
foreach ( string gcodeKey in keysToTest )
{
2016-06-07 11:22:53 -07:00
string gcode = settings . LayerCascade . GetValue ( gcodeKey ) ;
2016-05-19 17:55:46 -07:00
if ( gcode . Contains ( "{" ) | | gcode . Contains ( "}" ) )
{
2016-06-07 11:22:53 -07:00
Assert . Fail ( string . Format ( "[{0}] Curly brackets not allowed: {1}" , gcodeKey , settings . RelativeFilePath ) ) ;
2016-05-19 17:55:46 -07:00
}
}
} ) ;
}
2016-06-09 15:44:19 -07:00
[Test, Category("FixNeeded")]
2016-05-19 17:55:46 -07:00
public void BottomSolidLayersEqualsOneMM ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string bottomSolidLayers = settings . LayerCascade . GetValue ( "bottom_solid_layers" ) ;
2016-05-23 15:28:46 -07:00
if ( ! string . IsNullOrEmpty ( bottomSolidLayers ) )
2016-05-19 17:55:46 -07:00
{
2016-05-23 15:28:46 -07:00
if ( bottomSolidLayers ! = "1mm" )
{
printer . RuleViolated = true ;
return ;
}
2016-05-19 17:55:46 -07:00
2016-06-07 11:22:53 -07:00
Assert . AreEqual ( "1mm" , bottomSolidLayers , "[bottom_solid_layers] must be 1mm: " + settings . RelativeFilePath ) ;
2016-05-23 15:28:46 -07:00
}
2016-05-19 17:55:46 -07:00
} ) ;
}
[Test]
public void NoFirstLayerTempInStartGcode ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string startGcode = settings . LayerCascade . GetValue ( "start_gcode" ) ;
Assert . False ( startGcode . Contains ( "first_layer_temperature" ) , "[start_gcode] should not contain [first_layer_temperature]" + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
} ) ;
}
[Test]
public void NoFirstLayerBedTempInStartGcode ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string startGcode = settings . LayerCascade . GetValue ( "start_gcode" ) ;
Assert . False ( startGcode . Contains ( "first_layer_bed_temperature" ) , "[start_gcode] should not contain [first_layer_bed_temperature]" + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
} ) ;
}
2016-06-09 15:44:19 -07:00
[Test, Category("FixNeeded")]
2016-05-25 11:04:51 -07:00
public void FirstLayerHeightLessThanNozzleDiameterXExtrusionMultiplier ( )
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
if ( settings . LayerCascade . GetValue ( "output_only_first_layer" ) = = "1" )
2016-05-31 15:11:29 -07:00
{
return ;
}
2016-06-21 09:38:37 -07:00
float nozzleDiameter = float . Parse ( settings . LayerCascade . GetValue ( SettingsKey . nozzle_diameter ) ) ;
float layerHeight = float . Parse ( settings . LayerCascade . GetValue ( SettingsKey . layer_height ) ) ;
2016-05-19 17:55:46 -07:00
2016-05-25 11:04:51 -07:00
float firstLayerExtrusionWidth ;
2016-06-07 11:22:53 -07:00
string firstLayerExtrusionWidthString = settings . LayerCascade . GetValue ( "first_layer_extrusion_width" ) ;
2016-05-25 11:04:51 -07:00
if ( ! string . IsNullOrEmpty ( firstLayerExtrusionWidthString ) & & firstLayerExtrusionWidthString . Trim ( ) ! = "0" )
{
firstLayerExtrusionWidth = ValueOrPercentageOf ( firstLayerExtrusionWidthString , nozzleDiameter ) ;
}
else
{
firstLayerExtrusionWidth = nozzleDiameter ;
}
2016-06-21 09:38:37 -07:00
string firstLayerHeightString = settings . LayerCascade . GetValue ( SettingsKey . first_layer_height ) ;
2016-05-19 17:55:46 -07:00
if ( ! string . IsNullOrEmpty ( firstLayerHeightString ) )
{
float firstLayerHeight = ValueOrPercentageOf ( firstLayerHeightString , layerHeight ) ;
2016-05-25 11:04:51 -07:00
double minimumLayerHeight = firstLayerExtrusionWidth * 0.85 ;
2016-05-19 17:55:46 -07:00
// TODO: Remove once validated and resolved
2016-05-25 11:04:51 -07:00
if ( firstLayerHeight > = minimumLayerHeight )
2016-05-19 17:55:46 -07:00
{
printer . RuleViolated = true ;
return ;
}
2016-06-07 11:22:53 -07:00
Assert . Less ( firstLayerHeight , minimumLayerHeight , "[first_layer_height] must be less than [firstLayerExtrusionWidth]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
2016-05-25 11:04:51 -07:00
2016-05-19 17:55:46 -07:00
} ) ;
}
2016-06-09 15:44:19 -07:00
[Test, Category("FixNeeded")]
2016-05-23 14:30:27 -07:00
public void LayerHeightLessThanNozzleDiameter ( )
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
if ( settings . LayerCascade . GetValue ( "output_only_first_layer" ) = = "1" )
2016-05-31 15:18:16 -07:00
{
return ;
}
2016-06-21 09:38:37 -07:00
float nozzleDiameter = float . Parse ( settings . LayerCascade . GetValue ( SettingsKey . nozzle_diameter ) ) ;
float layerHeight = float . Parse ( settings . LayerCascade . GetValue ( SettingsKey . layer_height ) ) ;
2016-05-19 17:55:46 -07:00
2016-05-25 11:04:51 -07:00
double minimumLayerHeight = nozzleDiameter * 0.85 ;
2016-05-19 17:55:46 -07:00
// TODO: Remove once validated and resolved
2016-05-25 11:04:51 -07:00
if ( layerHeight > = minimumLayerHeight )
2016-05-19 17:55:46 -07:00
{
printer . RuleViolated = true ;
return ;
}
2016-06-07 11:22:53 -07:00
Assert . Less ( layerHeight , minimumLayerHeight , "[layer_height] must be less than [minimumLayerHeight]: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
} ) ;
}
[Test]
2016-05-25 11:04:51 -07:00
public void FirstLayerExtrusionWidthGreaterThanNozzleDiameterIfSet ( )
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-21 09:38:37 -07:00
float nozzleDiameter = float . Parse ( settings . LayerCascade . GetValue ( SettingsKey . nozzle_diameter ) ) ;
2016-05-19 17:55:46 -07:00
2016-06-07 11:22:53 -07:00
string firstLayerExtrusionWidthString = settings . LayerCascade . GetValue ( "first_layer_extrusion_width" ) ;
2016-05-19 17:55:46 -07:00
if ( ! string . IsNullOrEmpty ( firstLayerExtrusionWidthString ) )
{
float firstLayerExtrusionWidth = ValueOrPercentageOf ( firstLayerExtrusionWidthString , nozzleDiameter ) ;
2016-05-25 11:04:51 -07:00
if ( firstLayerExtrusionWidth = = 0 )
2016-05-19 17:55:46 -07:00
{
2016-05-25 11:04:51 -07:00
// Ignore zeros
2016-05-19 17:55:46 -07:00
return ;
}
2016-06-07 11:22:53 -07:00
Assert . GreaterOrEqual ( firstLayerExtrusionWidth , nozzleDiameter , "[first_layer_extrusion_width] must be nozzle diameter or greater: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void SupportMaterialAssignedToExtruderOne ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
string supportMaterialExtruder = settings . LayerCascade . GetValue ( "support_material_extruder" ) ;
2016-05-23 14:30:27 -07:00
if ( ! string . IsNullOrEmpty ( supportMaterialExtruder ) & & printer . Oem ! = "Esagono" )
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
Assert . AreEqual ( "1" , supportMaterialExtruder , "[support_material_extruder] must be assigned to extruder 1: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
[Test]
public void SupportInterfaceMaterialAssignedToExtruderOne ( )
{
2016-06-07 11:22:53 -07:00
ValidateOnAllPrinters ( ( printer , settings ) = >
2016-05-19 17:55:46 -07:00
{
2016-06-09 16:02:54 -07:00
// Make exception for extruder assignment on 3D Stuffmaker slice files
if ( printer . Oem = = "3D Stuffmaker" & & settings . RelativeFilePath . IndexOf ( ".slice" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
return ;
}
2016-06-07 11:22:53 -07:00
string supportMaterialInterfaceExtruder = settings . LayerCascade . GetValue ( "support_material_interface_extruder" ) ;
2016-05-23 14:30:27 -07:00
if ( ! string . IsNullOrEmpty ( supportMaterialInterfaceExtruder ) & & printer . Oem ! = "Esagono" )
2016-05-19 17:55:46 -07:00
{
2016-06-07 11:22:53 -07:00
Assert . AreEqual ( "1" , supportMaterialInterfaceExtruder , "[support_material_interface_extruder] must be assigned to extruder 1: " + settings . RelativeFilePath ) ;
2016-05-19 17:55:46 -07:00
}
} ) ;
}
private static float ValueOrPercentageOf ( string valueOrPercent , float baseValue )
{
if ( valueOrPercent . Contains ( "%" ) )
{
float percentage = float . Parse ( valueOrPercent . Replace ( "%" , "" ) ) / 100 ;
return baseValue * percentage ;
}
else
{
return float . Parse ( valueOrPercent ) ;
}
}
/// <summary>
/// Calls the given delegate for each known printer, passing in a PrinterConfig object that has
/// config.ini loaded into a SettingsLayer as well as state about the printer
/// </summary>
/// <param name="action">The action to invoke for each printer</param>
2016-06-07 09:41:12 -07:00
private void ValidateOnAllPrinters ( Action < PrinterConfig , LayerInfo > action )
2016-05-19 17:55:46 -07:00
{
var ruleViolations = new List < string > ( ) ;
foreach ( var printer in allPrinters )
{
printer . RuleViolated = false ;
2016-06-07 09:41:12 -07:00
action ( printer , printer . ConfigIni ) ;
2016-05-19 17:55:46 -07:00
if ( printer . RuleViolated )
{
2016-06-07 11:22:53 -07:00
ruleViolations . Add ( printer . ConfigIni . RelativeFilePath ) ;
2016-06-07 09:41:12 -07:00
}
foreach ( var layer in printer . MatterialLayers )
{
printer . RuleViolated = false ;
action ( printer , layer ) ;
if ( printer . RuleViolated )
{
2016-06-07 11:22:53 -07:00
ruleViolations . Add ( layer . RelativeFilePath ) ;
2016-06-07 09:41:12 -07:00
}
}
foreach ( var layer in printer . QualityLayers )
{
printer . RuleViolated = false ;
action ( printer , layer ) ;
if ( printer . RuleViolated )
{
2016-06-07 11:22:53 -07:00
ruleViolations . Add ( layer . RelativeFilePath ) ;
2016-06-07 09:41:12 -07:00
}
2016-05-19 17:55:46 -07:00
}
}
2016-05-23 14:30:27 -07:00
Assert . IsTrue (
ruleViolations . Count = = 0 , /* Use == instead of Assert.AreEqual to better convey failure details */
string . Format ( "One or more printers violate this rule: \r\n\r\n{0}\r\n" , string . Join ( "\r\n" , ruleViolations . ToArray ( ) ) ) ) ;
2016-05-19 17:55:46 -07:00
}
private class PrinterConfig
{
public string PrinterName { get ; set ; }
public string Oem { get ; set ; }
public string ConfigPath { get ; set ; }
2016-06-07 09:41:12 -07:00
public LayerInfo ConfigIni { get ; set ; }
2016-05-19 17:55:46 -07:00
// HACK: short term hack to support a general purpose test rollup function for cases where multiple config files
// violate a rule and in the short term we want to report and resolve the issues in batch rather than having a
// single test failure. Long term the single test failure better communicates the issue and assist with troubleshooting
// by using .AreEqual .LessOrEqual, etc. to communicate intent
public bool RuleViolated { get ; set ; } = false ;
2016-06-07 09:41:12 -07:00
public List < LayerInfo > MatterialLayers { get ; internal set ; }
public List < LayerInfo > QualityLayers { get ; internal set ; }
}
private class LayerInfo
{
2016-06-07 11:22:53 -07:00
public string RelativeFilePath { get ; set ; }
2016-06-15 14:50:12 -07:00
public PrinterSettings LayerCascade { get ; set ; }
2016-05-19 17:55:46 -07:00
}
}
}