2017-09-17 12:01:18 -07:00
/ *
Copyright ( c ) 2017 , Lars Brubaker , John Lewin
All rights reserved .
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
1. Redistributions of source code must retain the above copyright notice , this
list of conditions and the following disclaimer .
2. Redistributions in binary form must reproduce the above copyright notice ,
this list of conditions and the following disclaimer in the documentation
and / or other materials provided with the distribution .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies ,
either expressed or implied , of the FreeBSD Project .
* /
using System ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using MatterHackers.Agg.UI ;
using MatterHackers.MatterControl.DataStorage ;
using MatterHackers.MatterControl.PrintQueue ;
using MatterHackers.MatterControl.SlicerConfiguration ;
using Newtonsoft.Json ;
namespace MatterHackers.MatterControl
{
using System.Threading ;
using MatterHackers.Agg ;
using MatterHackers.DataConverters3D ;
using MatterHackers.GCodeVisualizer ;
using MatterHackers.MatterControl.PrinterCommunication ;
using MatterHackers.MeshVisualizer ;
using MatterHackers.PolygonMesh ;
using MatterHackers.VectorMath ;
public class BedConfig
{
public event EventHandler ActiveLayerChanged ;
public event EventHandler LoadedGCodeChanged ;
public View3DConfig RendererOptions { get ; } = new View3DConfig ( ) ;
public PrintItemWrapper printItem = null ;
public PrinterConfig Printer { get ; set ; }
public Mesh PrinterShape { get ; private set ; }
public BedConfig ( PrinterConfig printer = null , bool loadLastBedplate = false )
{
this . Printer = printer ;
if ( loadLastBedplate )
{
// Find the last used bed plate mcx
var directoryInfo = new DirectoryInfo ( ApplicationDataStorage . Instance . PlatingDirectory ) ;
var firstFile = directoryInfo . GetFileSystemInfos ( "*.mcx" ) . OrderByDescending ( fl = > fl . CreationTime ) . FirstOrDefault ( ) ;
// Set as the current item - should be restored as the Active scene in the MeshViewer
if ( firstFile ! = null )
{
try
{
var loadedItem = new PrintItemWrapper ( new PrintItem ( firstFile . Name , firstFile . FullName ) ) ;
if ( loadedItem ! = null )
{
this . printItem = loadedItem ;
}
this . Scene . Load ( firstFile . FullName ) ;
}
catch { }
}
}
// Clear if not assigned above
if ( this . printItem = = null )
{
this . ClearPlate ( ) ;
}
}
internal void ClearPlate ( )
{
string now = DateTime . Now . ToString ( "yyyyMMdd-HHmmss" ) ;
string mcxPath = Path . Combine ( ApplicationDataStorage . Instance . PlatingDirectory , now + ".mcx" ) ;
2017-10-17 12:55:58 -07:00
// Clear existing
this . LoadedGCode = null ;
this . GCodeRenderer = null ;
2017-09-17 12:01:18 -07:00
this . printItem = new PrintItemWrapper ( new PrintItem ( now , mcxPath ) ) ;
File . WriteAllText ( mcxPath , new Object3D ( ) . ToJson ( ) ) ;
this . Scene . Load ( mcxPath ) ;
// TODO: Define and fire event and eliminate ActiveView3DWidget - model objects need to be dependency free. For the time being prevent application spin up in ClearPlate due to the call below - if MC isn't loaded, don't notify
if ( ! MatterControlApplication . IsLoading )
{
2017-10-25 17:04:55 -07:00
ApplicationController . Instance . ActiveView3DWidget ? . Invalidate ( ) ;
2017-09-17 12:01:18 -07:00
}
}
private GCodeFile loadedGCode ;
public GCodeFile LoadedGCode
{
get = > loadedGCode ;
set
{
if ( loadedGCode ! = value )
{
loadedGCode = value ;
LoadedGCodeChanged ? . Invoke ( null , null ) ;
}
}
}
public WorldView World { get ; } = new WorldView ( 0 , 0 ) ;
2017-09-20 15:29:30 -07:00
public double BuildHeight { get ; internal set ; }
2017-09-17 12:01:18 -07:00
public Vector3 ViewerVolume { get ; internal set ; }
public Vector2 BedCenter { get ; internal set ; }
public BedShape BedShape { get ; internal set ; }
// TODO: Make assignment private, wire up post slicing initialization here
public GCodeRenderer GCodeRenderer { get ; set ; }
public int ActiveLayerIndex
{
get
{
return activeLayerIndex ;
}
set
{
if ( activeLayerIndex ! = value )
{
activeLayerIndex = value ;
// Clamp activeLayerIndex to valid range
if ( this . GCodeRenderer = = null | | activeLayerIndex < 0 )
{
activeLayerIndex = 0 ;
}
else if ( activeLayerIndex > = this . LoadedGCode . LayerCount )
{
activeLayerIndex = this . LoadedGCode . LayerCount - 1 ;
}
// When the active layer changes we update the selected range accordingly - constrain to applicable values
2017-09-24 18:50:51 -07:00
if ( this . RenderInfo ! = null )
{
2017-10-21 20:22:14 -07:00
// TODO: Unexpected that rendering layer 2 requires that we set the range to 0-3. Seems like model should be updated to allow 0-2 to mean render up to layer 2
this . RenderInfo . EndLayerIndex = Math . Min ( this . LoadedGCode = = null ? 0 : this . LoadedGCode . LayerCount , Math . Max ( activeLayerIndex + 1 , 1 ) ) ;
2017-09-24 18:50:51 -07:00
}
2017-09-17 12:01:18 -07:00
ActiveLayerChanged ? . Invoke ( this , null ) ;
}
}
}
private int activeLayerIndex ;
public InteractiveScene Scene { get ; } = new InteractiveScene ( ) ;
public GCodeRenderInfo RenderInfo { get ; set ; }
public string GCodePath
{
get
{
bool isGCode = Path . GetExtension ( printItem . FileLocation ) . ToUpper ( ) = = ".GCODE" ;
return isGCode ? printItem . FileLocation : printItem . GetGCodePathAndFileName ( ) ;
}
}
BedMeshGenerator bedGenerator ;
private Mesh _bedMesh ;
public Mesh Mesh
{
get
{
if ( _bedMesh = = null )
{
bedGenerator = new BedMeshGenerator ( ) ;
//Construct the thing
_bedMesh = bedGenerator . CreatePrintBed ( Printer ) ;
Task . Run ( ( ) = >
{
try
{
string url = Printer . Settings . GetValue ( "PrinterShapeUrl" ) ;
string extension = Printer . Settings . GetValue ( "PrinterShapeExtension" ) ;
if ( string . IsNullOrEmpty ( url ) | | string . IsNullOrEmpty ( extension ) )
{
return ;
}
using ( var stream = ApplicationController . Instance . LoadHttpAsset ( url ) )
{
var mesh = MeshFileIo . Load ( stream , extension , CancellationToken . None ) . Mesh ;
BspNode bspTree = null ;
// if there is a chached bsp tree load it
var meshHashCode = mesh . GetLongHashCode ( ) ;
string cachePath = ApplicationController . CacheablePath ( "MeshBspData" , $"{meshHashCode}.bsp" ) ;
if ( File . Exists ( cachePath ) )
{
JsonConvert . DeserializeObject < BspNode > ( File . ReadAllText ( cachePath ) ) ;
}
else
{
// else calculate it
bspTree = FaceBspTree . Create ( mesh , 20 , true ) ;
// and save it
File . WriteAllText ( cachePath , JsonConvert . SerializeObject ( bspTree ) ) ;
}
// set the mesh to use the new tree
UiThread . RunOnIdle ( ( ) = >
{
mesh . FaceBspTree = bspTree ;
this . PrinterShape = mesh ;
// TODO: Need to send a notification that the mesh changed so the UI can pickup and render
} ) ;
}
}
catch { }
} ) ;
}
return _bedMesh ;
}
}
private Mesh _buildVolumeMesh ;
public Mesh BuildVolumeMesh
{
get
{
if ( _buildVolumeMesh = = null )
{
//Construct the thing
//_buildVolumeMesh = CreatePrintBed(printer);
}
return _buildVolumeMesh ;
}
}
internal void Render3DLayerFeatures ( DrawEventArgs e )
{
if ( this . RenderInfo ! = null )
{
// If needed, update the RenderType flags to match to current user selection
if ( RendererOptions . IsDirty )
{
this . RenderInfo . RefreshRenderType ( ) ;
RendererOptions . IsDirty = false ;
}
this . GCodeRenderer . Render3D ( this . RenderInfo , e ) ;
}
}
public void LoadGCode ( string filePath , CancellationToken cancellationToken , Action < double , string > progressReporter )
{
this . LoadedGCode = GCodeMemoryFile . Load ( filePath , cancellationToken , progressReporter ) ;
this . GCodeRenderer = new GCodeRenderer ( loadedGCode ) ;
if ( ActiveSliceSettings . Instance . PrinterSelected )
{
GCodeRenderer . ExtruderWidth = ActiveSliceSettings . Instance . GetValue < double > ( SettingsKey . nozzle_diameter ) ;
}
else
{
GCodeRenderer . ExtruderWidth = . 4 ;
}
try
{
// TODO: After loading we reprocess the entire document just to compute filament used. If it's a feature we need, seems like it should just be normal step during load and result stored in a property
GCodeRenderer . GCodeFileToDraw ? . GetFilamentUsedMm ( ActiveSliceSettings . Instance . GetValue < double > ( SettingsKey . filament_diameter ) ) ;
}
catch ( Exception ex )
{
Debug . Print ( ex . Message ) ;
}
}
2017-09-20 15:32:22 -07:00
public void InvalidateBedMesh ( )
2017-09-17 12:01:18 -07:00
{
2017-09-20 15:32:22 -07:00
// Invalidate bed mesh cache
2017-09-20 15:29:30 -07:00
_bedMesh = null ;
2017-09-17 12:01:18 -07:00
}
}
public class PrinterViewState
{
public bool SliceSettingsTabPinned
{
get = > UserSettings . Instance . get ( UserSettingsKey . SliceSettingsTabPinned ) = = "true" ;
set
{
UserSettings . Instance . set ( UserSettingsKey . SliceSettingsTabPinned , value ? "true" : "false" ) ;
}
}
public int SliceSettingsTabIndex
{
get
{
int . TryParse ( UserSettings . Instance . get ( UserSettingsKey . SliceSettingsTabIndex ) , out int tabIndex ) ;
return tabIndex ;
}
set
{
UserSettings . Instance . set ( UserSettingsKey . SliceSettingsTabIndex , value . ToString ( ) ) ;
}
}
public double SliceSettingsWidth
{
get
{
double . TryParse ( UserSettings . Instance . get ( UserSettingsKey . SliceSettingsWidth ) , out double controlWidth ) ;
return controlWidth ;
}
set
{
UserSettings . Instance . set ( UserSettingsKey . SliceSettingsWidth , value . ToString ( ) ) ;
}
}
}
public class PrinterConfig
{
public BedConfig Bed { get ; }
public PrinterViewState ViewState { get ; } = new PrinterViewState ( ) ;
2017-09-20 15:29:30 -07:00
private PrinterSettings _settings ;
public PrinterSettings Settings
{
get = > _settings ;
private set
{
if ( _settings ! = value )
{
_settings = value ;
this . ReloadSettings ( ) ;
2017-09-20 15:32:22 -07:00
this . Bed . InvalidateBedMesh ( ) ;
2017-09-20 15:29:30 -07:00
}
}
}
2017-09-17 13:30:05 -07:00
public PrinterConnection Connection { get ; private set ; }
2017-09-17 12:01:18 -07:00
private EventHandler unregisterEvents ;
2017-09-20 15:29:30 -07:00
public PrinterConfig ( bool loadLastBedplate , PrinterSettings settings )
2017-09-17 12:01:18 -07:00
{
this . Bed = new BedConfig ( this , loadLastBedplate ) ;
2017-09-20 15:29:30 -07:00
this . Connection = new PrinterConnection ( printer : this ) ;
2017-09-17 13:30:05 -07:00
2017-09-23 14:44:43 -07:00
this . Settings = settings ;
this . Settings . printer = this ;
2017-09-20 15:29:30 -07:00
ActiveSliceSettings . SettingChanged . RegisterEvent ( Printer_SettingChanged , ref unregisterEvents ) ;
2017-09-17 12:01:18 -07:00
}
2017-09-23 14:44:43 -07:00
internal void SwapToSettings ( PrinterSettings printerSettings )
{
_settings = printerSettings ;
ApplicationController . Instance . ReloadAll ( ) ;
}
2017-09-17 12:01:18 -07:00
private void ReloadSettings ( )
{
this . Bed . BuildHeight = this . Settings . GetValue < double > ( SettingsKey . build_height ) ;
this . Bed . ViewerVolume = new Vector3 ( this . Settings . GetValue < Vector2 > ( SettingsKey . bed_size ) , this . Bed . BuildHeight ) ;
this . Bed . BedCenter = this . Settings . GetValue < Vector2 > ( SettingsKey . print_center ) ;
this . Bed . BedShape = this . Settings . GetValue < BedShape > ( SettingsKey . bed_shape ) ;
}
private void Printer_SettingChanged ( object sender , EventArgs e )
{
if ( e is StringEventArgs stringEvent )
{
if ( stringEvent . Data = = SettingsKey . bed_size
| | stringEvent . Data = = SettingsKey . print_center
| | stringEvent . Data = = SettingsKey . build_height
| | stringEvent . Data = = SettingsKey . bed_shape )
{
this . ReloadSettings ( ) ;
2017-09-20 15:32:22 -07:00
this . Bed . InvalidateBedMesh ( ) ;
2017-09-17 12:01:18 -07:00
}
}
}
}
public class View3DConfig
{
public bool IsDirty { get ; internal set ; }
public bool RenderGrid
{
get
{
string value = UserSettings . Instance . get ( "GcodeViewerRenderGrid" ) ;
if ( value = = null )
{
RenderGrid = true ;
return true ;
}
return ( value = = "True" ) ;
}
set
{
UserSettings . Instance . set ( "GcodeViewerRenderGrid" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool RenderMoves
{
get { return ( UserSettings . Instance . get ( "GcodeViewerRenderMoves" ) = = "True" ) ; }
set
{
UserSettings . Instance . set ( "GcodeViewerRenderMoves" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool RenderRetractions
{
get { return ( UserSettings . Instance . get ( "GcodeViewerRenderRetractions" ) = = "True" ) ; }
set
{
UserSettings . Instance . set ( "GcodeViewerRenderRetractions" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool RenderSpeeds
{
get { return ( UserSettings . Instance . get ( "GcodeViewerRenderSpeeds" ) = = "True" ) ; }
set
{
UserSettings . Instance . set ( "GcodeViewerRenderSpeeds" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool SimulateExtrusion
{
get { return ( UserSettings . Instance . get ( "GcodeViewerSimulateExtrusion" ) = = "True" ) ; }
set
{
UserSettings . Instance . set ( "GcodeViewerSimulateExtrusion" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool TransparentExtrusion
{
get { return ( UserSettings . Instance . get ( "GcodeViewerTransparentExtrusion" ) = = "True" ) ; }
set
{
UserSettings . Instance . set ( "GcodeViewerTransparentExtrusion" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool HideExtruderOffsets
{
get
{
string value = UserSettings . Instance . get ( "GcodeViewerHideExtruderOffsets" ) ;
if ( value = = null )
{
return true ;
}
return ( value = = "True" ) ;
}
set
{
UserSettings . Instance . set ( "GcodeViewerHideExtruderOffsets" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
public bool SyncToPrint
{
get = > UserSettings . Instance . get ( "LayerViewSyncToPrint" ) = = "True" ;
set
{
UserSettings . Instance . set ( "LayerViewSyncToPrint" , value . ToString ( ) ) ;
this . IsDirty = true ;
}
}
}
}