Welcome! Log In Create A New Profile

Advanced

Solved : Layer Number and Time Remaining on Graphical LCD

Posted by DaveOB 
Solved : Layer Number and Time Remaining on Graphical LCD
July 15, 2015 04:59PM
Please Note : there has been an update and addition to the code in this post. Please see post #3 below for the most recent improvements. All code in this post is also in post #3.

OK. So I'm new to the 3D printers, and assembled a Prusa i3 kit just 2 weeks ago.

After a few test prints and getting comfortable, I moved it in to a wooden cube frame with plastic covering, and shipped it to out patio outside under cover.
Printing is all done with the gcode files on an SD card. I am extremely happy with the results, and really glad I bought from a reputable supplier with great backup and advice ( diyelectronics.co.za )

First thing I noticed compared to when I had it connected to the PC and running from Repetier, was I don't see the layer number or the time remaining on the LCD.

So 3 days of fiddling and I have a working solution.

But before I say how, please take extreme note and understand that I am not a qualified coder, and I am most likely unable to assist anyone that tries this with much troubleshooting if it goes wrong. So if you don't have a confirmed copy and backup of your Marlin code, please do not try this.

Here's the 'how' :

It may not be the right way, it may not be the best way, but it is my way. I know this 'time remaining' has been discussed so many times, and the fact is that there is no right and perfect way to do it. Print Lines remaining and Filament used vs Total needed are both subject to the same problems : some layers have larger perimeter lines which are printed slower and therefor affect print time, etc.

I just want a general idea of how long the job has to still run. Is it 4 hours or 4 minutes ?

My tests and attempts to parse the SD file worked OK. I was able to count the layers in the file, as well as the Print Lines in the file.

Using this info, I could show the layer number ( eg L001/076 ) and the estimated time remaining.

These are shown on the graphical LCD in the reverse panel line, where the X,Y & Z data is shown - which I decided was meaningless for me at the moment.



Problem with this approach was 2 things :

1. the parsing of a smallish gcode file, for a Marvin model with around 75 layers, and 27,000 print lines, was taking about 1 minute. This is a limitation of the speed that a file can be read from the SD card, and therefore would increase relative to the size of the print job.

2. I was almost OK with this time delay, except that I could not get the LCD to display a 'busy reading SD file' message and refresh.



So I changed the approach like this :

I am using Repetier with Slicer to slice the job.

I then get a script to run ( automatically ) and collect all the data that I want for the LCD display and calculations, and then rewrite the gcode file so the data I need is at the top.
This way, when I parse the SD file, I only need to read the first 6 or 7 lines, then I can close the file. This reading takes less than 1 second, and I instantly have all the data I need.


step 1.
As I am running Windows 7, I needed to install Perl ( so that the automatic post-processing script could be run by slicer ).
I used [strawberryperl.com]


step 2.
When Slicer is actioned on the model from within Repetier, it slices the file, and creates a temporary gcode file ( always with the same name ). This is the file that you see in Repetier > Preview > Gcode Editor after slicing the model, and before saving it to your PC.

Located the path where Slicer creates this temp file. On my PC it is C:\Users\User\AppData\Local\RepetierHost\composition.gcode ( the file name is always composition.gcode )

In the same location, create a file with a text editor, called AddLayerAndLines.pl

Contents of the file AddLayerAndLines.pl are :
#!/usr/bin/perl -i

use strict;
use warnings;
use utf8; 

#variables for layer count
my $layerCount = 0;
my $linesCount = 0;
my $file_name = "";
my $filamentmm = 0;
my $G1zCount = 0;
my $SkipLayers = 0;
my $TrackType = 0;
my @all_nums;

$file_name = $ARGV[0];

print "\n";
print "File Name ".$file_name."\n";
print "Reading file loop - gathering data\n";

$^I = '.bak';
while ( <> )  {

	if (index($_, ';newlayerhere') == 0) { # find the lines that contain the New Layer flag comment
		$layerCount++;
    }

	if (index($_, 'G1 Z') == 0) { # looks for the ;SLC flag comment and resets the line counter to 0 ( SLC used to count only print lines )
		$G1zCount++;
	}

	if (index($_, ';SLC') == 0) { # looks for the ;SLC flag comment and resets the line counter to 0 ( SLC used to count only print lines )
		$linesCount=0;
		$SkipLayers=$G1zCount;
	}

	if (index($_, 'G1 X') == 0) { # count the print lines that contain 'G1 X'
		$linesCount++;
	}

	if (index($_, ';TrackType=Filament') == 0) {
		$TrackType=1;
	}
	if (index($_, ';TrackType=PrintLines') == 0) {
		$TrackType=2;
	}
	
    if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) {
		# get the filament length listed in the gCode file (mm) -- # print $_; contains the line read from the file
		@all_nums = $_ =~ /(\d+)/g;  # (123, 456, 789) so creates an array of the numbers in the line
		print;
    } else {
        print;
    }
}

print "Layer Count = ".$layerCount."\n";
print "Lines Count = ".$linesCount."\n";
print "Skip pre-Layers = ".$SkipLayers."\n";
print "filamentmm = ".$all_nums[0]."\n";


use File::Basename;
use File::Copy;

print "Copying gCode to temp file\n";
my($file, $dir, $ext) = fileparse($file_name); # extract the dir from the original file name and use for same location of temp file
my $tempGfile = $dir . "TempScriptFile.gcode";
copy($file_name, $tempGfile);

#list the file names being used
print "Temp. File = ".$tempGfile."\n";
print "Orig. File = ".$file_name."\n";

#open the target file
open(my $fhS, '>', $file_name); 

if (open(my $fh, '<:encoding(UTF-8)', $tempGfile)) {
	printf $fhS "; layer count = %d\n", $layerCount;
	printf $fhS "; lines count = %d\n", $linesCount;
	printf $fhS "; PreLayers = %d\n", $SkipLayers;
	printf $fhS "; filament = %d\n", $all_nums[0];
	printf $fhS "; tracktype = %d\n", $TrackType;

	while (my $row = <$fh> ) {
		chomp $row;
		if (index($row, ';newlayerhere') == -1) {
			print $fhS "$row\n";
		}
	}
}

print "Operation completed\n";



Step 3.
Next, is to tell Slicer to automatically run this script after every time it slices a model.

Very easy to do.

In Repetier > Slicer > Configuration ( opens the slicer config window ) go to 'Print Settings' > 'Output Options'.

In the post-processing scripts, add the path and filename ( in my case : C:\Users\User\AppData\Local\RepetierHost\AddLayerAndLines.pl )


Step 4.

Next, we need a few modifications so that the gCode automatically contains the 'flags' that we need to work in the script.

In the same Slicer config window as above, go to 'Printer Settings' tab.

In the 'Start G-code box, add this to the end of what you may have in there :
;TrackType=Filament ; set =Filament or =PrintLines
;SLC

The TrackType can be set to either do the calculations of the estimated time remaining using the Filament, or the number of Print Lines.

The ';SLC' comment will cause the layer count to be reset to zero. So if you have executed any Z-axis moves before the job actually starts print, they will be ignored for the layer count. When your printer starts the print, it will show as 'layer 1'. Add the ;SLC line, as it will work only if it needs to be used.

In the 'Before Layer Change G-code box, add :
;newlayerhere

This is a very simple method. When slicer first creates the gCode file, it will add this comment at the point where a new layer is to start. We are using this 'flag' in the post-processing script, to count the number of layers in the print job. The post-processing script will then delete the comments from the gcode file, so the file size is not affected at all.

Save the changes.


Step 5.

Test the above.

In Repetier, select a model to print, make sure you have the correct 'Print Settings' and 'Printer Settings' selected on the Slicer tab, and also turn on the log screen ( there should be a 'Toggle Log' button on the toolbar ).

After Slicing the model, the log should look something like this :

22:09:59.590 : Slic3r command:C:\Program Files\Repetier-Host\Slic3r\slic3r.exe --load "C:\Users\User\AppData\Local\RepetierHost\slic3r.ini" --print-center 125,125 -o "C:\Users\User\AppData\Local\RepetierHost\composition.gcode" "C:\Users\User\AppData\Local\RepetierHost\composition.amf"
22:10:01.074 :  => Processing triangulated mesh
22:10:01.204 :  => Generating perimeters
22:10:01.536 :  => Preparing infill
22:10:01.986 :  => Infilling layers
22:10:02.191 :  => Generating skirt
22:10:02.199 :  => Exporting G-code to C:\Users\User\AppData\Local\RepetierHost\composition.gcode
22:10:02.978 :  => Running post-processing scripts
22:10:03.320 :  File Name C:\Users\User\AppData\Local\RepetierHost\composition.gcode
22:10:03.320 :  Reading file loop - gathering data
22:10:03.320 :  Layer Count = 75
22:10:03.320 :  Lines Count = 19306
22:10:03.320 :  Skip pre-Layers = 1
22:10:03.320 :  filamentmm = 671
22:10:03.320 :  Copying gCode to temp file
22:10:03.320 :  Temp. File = C:\Users\User\AppData\Local\RepetierHost\TempScriptFile.gcode
22:10:03.320 :  Orig. File = C:\Users\User\AppData\Local\RepetierHost\composition.gcode
22:10:03.320 :  Operation completed
22:10:03.330 :  Done. Process took 0 minutes and 2.256 seconds
22:10:03.330 :  Filament required: 671.5mm (1.6cm3)

and the top of the gcode file something like this :

; layer count = 75
; lines count = 19306
; PreLayers = 1
; filament = 671
; tracktype = 1
; generated by Slic3r 1.2.9 on 2015-07-15 at 22:10:02

; external perimeters extrusion width = 0.40mm
; perimeters extrusion width = 0.48mm
; infill extrusion width = 0.48mm
; solid infill extrusion width = 0.48mm
; top infill extrusion width = 0.48mm

M107
M190 S15 ; set bed temperature
M104 S15 ; set temperature
G28 ; home all axes
G1 Z6 F5000 ; lift nozzle
M92 E515
G1 Y20 F5000
;TrackType=Filament ; set =Filament or =PrintLines
;SLC
M109 S15 ; wait for temperature to be reached
G21 ; set units to millimeters
G90 ; use absolute coordinates
M82 ; use absolute distances for extrusion
G92 E0
G1 E-1.00000 F1800.00000
G92 E0
G1 Z0.300 F10200.000
G1 X114.868 Y116.678 F10200.000
G1 E1.00000 F1800.00000
G1 X115.412 Y116.197 E1.04850 F1080.000
G1 X115.932 Y115.813 E1.09171


Step 6.

Once you have this working, the next step is to modify the Marlin code so that it can read the additional 'parameters' that we added to the top of the gcode file, and display them on the LCD.

In the file :
Configuration.h

After the line:
#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER

I added :
// ==> my note : uncomment this line to make the graphical LCD show the Layer Count and Estimated Time Remaining instead of the XYZ positions
#define ShowLayerCountAndEstTime


in file : marlin.h

after:
extern float add_homing[3];

I added :
// my note : added global variables for tracking Layers and Print Lines
extern int LayerCountTotal;
extern int LayerCountCurrent;
extern float FilamentCurrent;
extern float PrintLinesTotal;
extern float PrintLinesCurrent;
extern float FilamentTotal;
extern int TrackType;


In file : Marlin_main.cpp

about line 245 after :
float zprobe_zoffset;

I added :
// my note : added variables for tracking Layers and Print Lines
int LayerCountTotal;
int LayerCountCurrent;
float FilamentCurrent;
float PrintLinesTotal;
float PrintLinesCurrent;
float FilamentTotal;
int LayersToIgnore;
bool CountLinesAndLayers = false;
int TrackType;


also in same file :

in void process_commands
changed :
    switch((int)code_value())
    {
    case 0: // G0 -> G1
    case 1: // G1
    if(Stopped == false) {

to this :
    switch((int)code_value())
    {
    case 0: // G0 -> G1
    case 1: // G1

		// my note : increase the PrintLines count by 1 to track current Printline Count and calculate the Estimated Time Remaining
		PrintLinesCurrent++;

		if(code_seen('E')){
			get_coordinates();
            float echange=destination[E_AXIS]-current_position[E_AXIS];
			FilamentCurrent = FilamentCurrent + echange;
		}

		// // my note : add code to increase the LCD layer count by 1 if the gCode contains a new Z axis instruction
		if(code_seen('Z')) LayerCountCurrent++;

		if(LayersToIgnore > 0){  // ignore the layer count before the ;SLC was detected ( if it exists in the gcode file )
			LayersToIgnore--;
			LayerCountCurrent = 0;
			PrintLinesCurrent = 0;
			FilamentCurrent = 0;
		}
      if(Stopped == false) {

and changed :
    case 23:
		starpos = (strchr(strchr_pointer + 4,'*'));
		if(starpos!=NULL)
			*(starpos)='\0';
		card.openFile(strchr_pointer + 4,true);
    break;

    case 24:
		card.startFileprint();
		starttime=millis();
		break;

to this :
    case 23: //M23 - Select file
		starpos = (strchr(strchr_pointer + 4,'*'));
		if(starpos!=NULL)
			*(starpos)='\0';

		// my note : add code to count the Z axis commands ( layers ) in the file and the G1 print lines in the file
		#ifdef SDSUPPORT
			#ifdef ShowLayerCountAndEstTime
				CountLinesAndLayers = true;
			#endif //layer counter
		#endif //SDSUPPORT

		if(CountLinesAndLayers != true) card.openFile(strchr_pointer + 4,true);  //this is in M23 - Select file  // name, read, replace
    break;

    case 24: //M24 - Start SD print
		// my note : SD card print starts here
		LayerCountCurrent = 0;
		card.startFileprint();
		starttime=millis();
		break;

and changed :
  else
  {
    SERIAL_ECHO_START;
    SERIAL_ECHOPGM(MSG_UNKNOWN_COMMAND);
    SERIAL_ECHO(cmdbuffer[bufindr]);
    SERIAL_ECHOLNPGM("\"");
  }

    ClearToSend();
		
}

void FlushSerialRequestResend()

to this :
  else
  {
    SERIAL_ECHO_START;
    SERIAL_ECHOPGM(MSG_UNKNOWN_COMMAND);
    SERIAL_ECHO(cmdbuffer[bufindr]);
    SERIAL_ECHOLNPGM("\"");
  }

    ClearToSend();
 

	if(CountLinesAndLayers == true){ // read the SD file and count the lines and layers
		CountLinesAndLayers = false;
		PrintLinesTotal = 0;
		LayerCountTotal = 0;
		FilamentTotal = 0;
		TrackType = 0;
		card.openFile(strchr_pointer + 4,true);
		chPos = 0;  // pointer for position of next char in buffer array
		int ReadingLineNumber = 1;
		while(!card.eof() && ReadingLineNumber < 6){
			int16_t n=card.get();
			serial_char = (char)n;
			if(chPos < 59) SDbuffer[chPos] = serial_char;
			chPos++;
			if(serial_char == '\n'){ // if the character read from the SD card is a line ending
				/*
				first 5 lines of the gCode file
				; layer count = 75
				; lines count = 19306
				; PreLayers = 1
				; filament = 671
				; tracktype = 1  // 1 = Filament, 2 = PrintLines
				*/
				if(ReadingLineNumber == 1){ // read the layer count
					if ( strstr(SDbuffer, "; layer count =") ) LayerCountTotal = atoi(&SDbuffer[15]);
				}
				if(ReadingLineNumber == 2){ // read the lines count
					if ( strstr(SDbuffer, "; lines count =") ) PrintLinesTotal = atof(&SDbuffer[15]);
				}
				if(ReadingLineNumber == 3){ // read the layers to skip in setup before print starts
					if ( strstr(SDbuffer, "; PreLayers =") ) LayersToIgnore = atoi(&SDbuffer[13]);
				}
				if(ReadingLineNumber == 4){ // read the filament length
					if ( strstr(SDbuffer, "; filament =") ) FilamentTotal = atof(&SDbuffer[12]);
				}
				if(ReadingLineNumber == 5){ // read the tracking type
					if ( strstr(SDbuffer, "; tracktype =") ) TrackType = atoi(&SDbuffer[13]);
				}
				chPos = 0; // reset the position for the next character in the buffer. Set to 0 = empty the buffer.
				ReadingLineNumber++;
			}
		}
		card.closefile();
		delay(200);
		card.openFile(strchr_pointer + 4,true);  //this is in M23 - Select file
	}  // end of : if(CountLinesAndLayers = true)
  

		
}  // end of : if(code_seen('M'))

void FlushSerialRequestResend()


next, in the file :
dogm_lcd_implementation.h

change :
// Fan
 u8g.setFont(FONT_STATUSMENU);
 u8g.setPrintPos(104,27);
 #if defined(FAN_PIN) && FAN_PIN > -1
 u8g.print(itostr3(int((fanSpeed*100)/256 + 1)));
 u8g.print("%");
 #else
 u8g.print("---");
 #endif
 
	// X, Y, Z-Coordinates - make white on black panel
	u8g.setFont(FONT_STATUSMENU);
	u8g.drawBox(0,29,128,10);
	u8g.setColorIndex(0);	// white on black

		u8g.setPrintPos(2,37);                           // move to position to print the 'X'
		u8g.print("X");                                  // print the 'X' character
		u8g.drawPixel(8,33);                             // top dot of the colon
		u8g.drawPixel(8,35);                             // bottom dot of the colon
		u8g.setPrintPos(10,37);                          // move to position to print the value
		u8g.print(ftostr31ns(current_position[X_AXIS])); // print the X axis value

		u8g.setPrintPos(43,37);                           // move to position to print the 'Y'
		lcd_printPGM(PSTR("Y"));
		u8g.drawPixel(49,33);
		u8g.drawPixel(49,35);
		u8g.setPrintPos(51,37);
		u8g.print(ftostr31ns(current_position[Y_AXIS]));

		u8g.setPrintPos(83,37);                           // move to position to print the 'Z'
		u8g.print("Z");
		u8g.drawPixel(89,33);
		u8g.drawPixel(89,35);
		u8g.setPrintPos(91,37);
		u8g.print(ftostr31(current_position[Z_AXIS]));

	u8g.setColorIndex(1);	// reset the display colors back to black on white

to this :
// Fan
 u8g.setFont(FONT_STATUSMENU);
 u8g.setPrintPos(104,27);
 #if defined(FAN_PIN) && FAN_PIN > -1
 u8g.print(itostr3(int((fanSpeed*100)/256 + 1)));
 u8g.print("%");
 #else
 u8g.print("---");
 #endif

	// X, Y, Z-Coordinates - make white on black panel
	u8g.setFont(FONT_STATUSMENU);
	u8g.drawBox(0,29,128,10);
	u8g.setColorIndex(0);	// white on black

	#ifdef ShowLayerCountAndEstTime	// Check if the config option to show the layer count ( instead of the XYZ positions ) is enabled

		char LCDline1[20];  // create the LCD line array with the data to be displayed
		LCDline1[0] = '\0'; // clear the line to be displayed

		if(PrintLinesTotal > 0 && LayerCountTotal > 0){

			int DHRP = 0; // integer for estimated hours remaining
			int DMRP = 0; // integer for estimated minutes remaining
			if(PrintLinesCurrent >= 10){ // start calcs after the first 10 lines have been printed

				long CurrentPrintTime=0;
				CurrentPrintTime = millis() - starttime;             // calc the milliseconds that the job has been running so far

				float EMR = 0;
				int SRP = 0;
				int MRP = 0;

				if(TrackType == 2){ // track the time remaining based on the PrintLines			
					// calc Est. Time Remaining using number of print lines done vs remaining
					EMR = (CurrentPrintTime / PrintLinesCurrent) * (PrintLinesTotal - PrintLinesCurrent); // calc the estimated millis remaining
					SRP = trunc(EMR / 1000);                         // convert remaining millis to seconds remaining for print
					MRP = trunc(SRP / 60);                           // convert seconds to minutes remaining for print
					if(MRP < 1) MRP = 1;                                 // keep the value at 1 minute for the last few layers when the actual value < 1 minute
					if(PrintLinesTotal == PrintLinesCurrent) MRP = 0;    // switch to zero when all lines printed
				}

				if(TrackType == 1){ // track the time remaining based on the Filament			
					// calc Est. Time Remaining using filament required vs filament used
					EMR = (CurrentPrintTime / FilamentCurrent) * (FilamentTotal - FilamentCurrent); // calc the estimated millis remaining
					SRP = trunc(EMR / 1000);                         // convert remaining millis to seconds remaining for print
					MRP = trunc(SRP / 60);                           // convert seconds to minutes remaining for print
					if(MRP < 1) MRP = 1;                                 // keep the value at 1 minute for the last few layers when the actual value < 1 minute
					if(FilamentTotal == FilamentCurrent) MRP = 0;    // switch to zero when all lines printed
				}

				DHRP = trunc(MRP / 60);                              // how many hours in MRP remaining for print
				DMRP = MRP - (DHRP * 60);                            // calc remaining minutes after subtracting DHRP hours
			}
			sprintf (LCDline1, "L%03d/%03d e%02d:%02d", LayerCountCurrent, LayerCountTotal, DHRP, DMRP);
			u8g.setPrintPos(2,37);
			u8g.print(LCDline1);
		}
	#endif

	if(PrintLinesTotal == 0 || LayerCountTotal == 0){   // otherwise display the standard XYZ positions

		u8g.setPrintPos(2,37);                           // move to position to print the 'X'
		u8g.print("X");                                  // print the 'X' character
		u8g.drawPixel(8,33);                             // top dot of the colon
		u8g.drawPixel(8,35);                             // bottom dot of the colon
		u8g.setPrintPos(10,37);                          // move to position to print the value
		u8g.print(ftostr31ns(current_position[X_AXIS])); // print the X axis value

		u8g.setPrintPos(43,37);                           // move to position to print the 'Y'
		lcd_printPGM(PSTR("Y"));
		u8g.drawPixel(49,33);
		u8g.drawPixel(49,35);
		u8g.setPrintPos(51,37);
		u8g.print(ftostr31ns(current_position[Y_AXIS]));

		u8g.setPrintPos(83,37);                           // move to position to print the 'Z'
		u8g.print("Z");
		u8g.drawPixel(89,33);
		u8g.drawPixel(89,35);
		u8g.setPrintPos(91,37);
		u8g.print(ftostr31(current_position[Z_AXIS]));
	}
	u8g.setColorIndex(1);	// reset the display colors back to black on white


That's it.

Compile the Marlin code and upload to your Arduino.

Next time you run the gCode file ( with the automatically modified header data ) it will start printing and show you the layer number ( and total layer count ) and the estimated time remaining ( hours and minutes ).

Again : WARNING : PLEASE DO NOT TRY THIS IF YOU DO NOT HAVE A BACKUP AND / OR YOU DO NOT KNOW HOW TO REVERSE IT. I will not be responsible for any actions.

Last note : I know that some of this code could be optimized, but as I said before : I am not a professional coder, so I made it work specifically for what I need with MY printer - I can not gaurantee it will work for everyone.

If anyone does try this and has any additions, corrections, or comments, please feel free to add them to this post for everyones' benefit.

Regards
Dave

edited to change some 'long' to 'float' to handle larger numbers of TotalPrintLines.

Edited 3 time(s). Last edit at 07/17/2015 04:43PM by DaveOB.
Re: Solved : Layer Number and Time Remaining on Graphical LCD
July 16, 2015 02:42AM
I think it is also worth mentioning here that the Estimated Time Remaining ( ETR ) is exactly that - Estimated.

If you use the Print Lines Completed method, or the Filament Used method, both methods rely on using the current printed value, compared to the total overall expected value.

Therefore, the more data you have printed, the more accurate your ETR will be.

Here's an example :

I am printing a small snowflake vase.
233 layers, 152208 print lines, 6981mmm filament, and slicer says expected time is 2h05m.

So I start printing. But consider that the first layer is printed at 30% speed, the first 3 layers have solid infill, and the rest of the print is 3 perimeter lines only on each layer.

So by the time the printer has done the first 3 layers, it has used a lot more filament than the rest of the model would use over 3 layers.

So the ETR is as follows :

Layer 1 = 1:41
Layer 5 = 1:25
Layer 10 = 1:50
Layer 23 = 2:39

so as you can see, the first few layers have a adverse effect on the calculation for the ETR.

My experience has shown that from around layer 15 ( on this 233 layer model ) the times start to level out to a reasonable value.


Update :

Here's some data from the vase print just completed :

Slicer estimated time was : 2:05

ETR = the calculated estimated time remaining, using the Filament method
LCDT = time elapsed on the LCD at the bottom, above the progress bar
Total = ETR + LCDT

Layer   ETR     LCDT   Total

  1     1:41     :01       1:42
  5     1:25     :04       1:29
 10     1:50     :06       1:56
 23     2:27     :12       2:39
 45     2:39     :22       3:01
 55     2:33     :28       3:01
 65     2:25     :33       2:58
 83     2:08     :42       2:50
 90     2:01     :46       2:47
106     1:45     :55       2:45
117     1:35    1:00       2:35
126     1:27    1:05       2:32
148     1:08    1:17       2:25
162      :56    1:25       2:21
213      :17    1:53       2:10



Update 2 :

I have just completed a print of the same vase using the Printer Lines method for tracking progress and estimating time remaining.

Slicer estimated time was : 2:05

ETR = the calculated estimated time remaining, using the Printer Lines method
LCDT = time elapsed on the LCD at the bottom, above the progress bar
Total = ETR + LCDT

Layer   ETR     LCDT   Total

  5     4:29     :04       4:33
 10     3:01     :06       3:07
 15     2:36     :07       2:43
 20     2:23     :09       2:32
 23     2:18     :11       2:29
 37     2:04     :18       2:22
 48     1:56     :24       2:20
 55     1:52     :27       2:19
 66     1:46     :33       2:19
109     1:22     :55       2:17
132     1:08    1:08       2:16
151      :56    1:18       2:14
179      :37    1:33       2:10
204      :20    1:47       2:07
226      :04    2:00       2:04
233      :00    2:05       2:05

The above number are showing that ( based on the specific model I was printing ) the ETR using the Filament method was starting to get reasonably close to the correct time at about the 50% mark, but the ETR using the Printer Lines method was a lot closer to the right numbers after only 10% of the print was completed.

Since Slic3r is quite accurate with predicting the Estimatred Print Time, I wonder if there is a way to get Slic3r to automatically add the time it has calculated in to the gCode file, as a comment, at regular intervals ( say, before each layer ).

Edited 2 time(s). Last edit at 07/16/2015 02:59PM by DaveOB.
Re: Solved : Layer Number and Time Remaining on Graphical LCD
July 17, 2015 03:53PM
Improvement with PC calculated Estimated Print Time on Windows PC.

Sorry if this is starting to look like a personal blog where I'm talking to myself, but I think if it took me so long to find all this, it must be able to help someone else down the line.

So following up on my earlier idea of somehow using the Estimated Printing Time that is displayed in Repetier ( after the slicing is done ) as a starting 'Time Remaining' value, I found that the time is not calculated and passed back from Slic3r to Repetier, but is apparently calculated by Repetier itself.

I was unable to find a way to read or retrieve that value.

However, in my searching, I did find something that is just as good :

in this forum thread :
[github.com]

search for the post / reply :
davr commented on Sep 29, 2013
Ok, I now have a small program that will produce time estimates that should match 100% the actual print time

I found reference to :
[github.com]

Downloaded the file from GitHub and extracted only the calc.exe file.

Calc.exe is an app that takes the gcode file, and reasonably accurately, calculates the print time. I also read that if uses some default parameters, and that you can change these by adding them to the top of the gCode file - I will experiment with that a little later.

Using calc.exe, I was able to get the print time, and added that to the top of my gcode file - remember from before, we add the data we want to the top of the gcode file, so we don't have to use the slow sd card file read on the Arduino to read the whole long gcode file - we just need to read the first few lines.

Using this new method ( full code mods listed below ), I tested a Marvin model print.


NOTE : the file calc.exe needs to be in the same path as the file : AddLayerAndLines.pl ( see below for instructions for that ).

Tested the new method ( calc.exe ) with a Marvin figure.

Repetier says 75 layers, 20077 lines, 672 mm filament, and 23:02 print time.

calc exe estimated print time at 00:21:32 ( h:m:s )

Actual print times and progress :

Layer   ETR remaining (m)    time elapsed (m)

 1      21                   0
 5      18                   3
17      15                   6
56       3                  18
62       1                  20
66       0                  21
75       0                  22

What is good to note here, is that right from layer 1, we have a reasonably accurate time remaining.

Please note that this is being done on a Windows OS, so I can not say how this would / could work on other operating systems - I just don't have the experience for that.


This is an updated list of changes that include the 'ETP' method ( Estimated Time for Printing )


step 1.
As I am running Windows 7, I needed to install Perl ( so that the automatic post-processing script could be run by slicer ).
I used [strawberryperl.com]


step 2.
When Slicer is actioned on the model from within Repetier, it slices the file, and creates a temporary gcode file ( always with the same name ). This is the file that you see in Repetier > Preview > Gcode Editor after slicing the model, and before saving it to your PC.

Located the path where Slicer creates this temp file. On my PC it is C:\Users\User\AppData\Local\RepetierHost\composition.gcode ( the file name is always composition.gcode )

In the same location, create a file with a text editor, called AddLayerAndLines.pl

Contents of the file AddLayerAndLines.pl are :
#!/usr/bin/perl -i

use strict;
use warnings;
use utf8; 
use File::Basename;

#variables for layer count
my $layerCount = 0;
my $linesCount = 0;
my $file_name = "";
my $filamentmm = 0;
my $G1zCount = 0;
my $SkipLayers = 0;
my $TrackType = 0;
my @all_nums;
my @all_numsT;

$file_name = $ARGV[0];
my($file, $dir, $ext) = fileparse($file_name); # extract the dir from the original file name and use for same location of temp file

#create the batch file
open(my $bf1, '>', $dir."calc1.bat"); 
	printf $bf1 "del ".$dir."calc1.txt\n";
	printf $bf1 $dir."calc.exe ".$dir."composition.gcode > ".$dir."calc1.txt\n";
	printf $bf1 "del ".$dir."calcTemp.txt\n";
	printf $bf1 "exit\n";
close $bf1;

#create a temp flag file that the batch file will delete after it has completed
open($bf1, '>', $dir."calcTemp.txt"); 
	printf $bf1 "This is a Flag File for deleting by the bat file when it is completed\n";
close $bf1;

#run the batch file
system("start ".$dir."calc1.bat");   # run the batch file

#wait for the batch file to complete. Completes when it deletes the flag file.
sleep 1 while -e $dir."calcTemp.txt";

#read the time from the calc1.txt output file

#calc1.txt contents example :
#Processed 19697 Gcodes and 216 Mcodes. 19562 blocks
#Total time: 00:21:32 s
if (open(my $fh, '<:encoding(UTF-8)', $dir."calc1.txt")) {
	while (my $row = <$fh> ) {
		chomp $row;
		if (index($row, 'Total time:') == 0) { # looks for the Total Time line in the calc1.txt file
		@all_numsT = $row =~ /(\d+)/g;  # (123, 456, 789) so creates an array of the numbers in the line
		}
	}
}

print "Located Total Time H : ".$all_numsT[0]."\n";
print "Located Total Time M : ".$all_numsT[1]."\n";
print "Located Total Time S : ".$all_numsT[2]."\n";

my $TotalETP = 0;

$TotalETP = ($all_numsT[0] * 60 * 60) + ($all_numsT[1] * 60) + ($all_numsT[2]);
print "ETP Seconds = ".$TotalETP."\n";


print "\n";
print "File Name ".$file_name."\n";
print "Reading file loop - gathering data\n";

$^I = '.bak';
while (<> ) {

	if (index($_, ';newlayerhere') == 0) { # find the lines that contain the New Layer flag comment
		$layerCount++;
    }

	if (index($_, 'G1 Z') == 0) { # looks for the ;SLC flag comment and resets the line counter to 0 ( SLC used to count only print lines )
		$G1zCount++;
	}

	if (index($_, ';SLC') == 0) { # looks for the ;SLC flag comment and resets the line counter to 0 ( SLC used to count only print lines )
		$linesCount=0;
		$SkipLayers=$G1zCount;
	}

	if (index($_, 'G1 X') == 0) { # count the print lines that contain 'G1 X'
		$linesCount++;
	}

	if (index($_, ';TrackType=Filament') == 0) {
		$TrackType=1;
	}
	if (index($_, ';TrackType=PrintLines') == 0) {
		$TrackType=2;
	}
	if (index($_, ';TrackType=ETP') == 0) {
		$TrackType=3;
	}
	
    if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) {
		# get the filament length listed in the gCode file (mm) -- # print $_; contains the line read from the file
		@all_nums = $_ =~ /(\d+)/g;  # (123, 456, 789) so creates an array of the numbers in the line
		print;
    } else {
        print;
    }
}

print "Layer Count = ".$layerCount."\n";
print "Lines Count = ".$linesCount."\n";
print "Skip pre-Layers = ".$SkipLayers."\n";
print "filamentmm = ".$all_nums[0]."\n";


use File::Basename;
use File::Copy;

print "Copying gCode to temp file\n";
#my($file, $dir, $ext) = fileparse($file_name); # extract the dir from the original file name and use for same location of temp file
my $tempGfile = $dir . "TempScriptFile.gcode";
copy($file_name, $tempGfile);

#list the file names being used
print "Temp. File = ".$tempGfile."\n";
print "Orig. File = ".$file_name."\n";

#open the target file
open(my $fhS, '>', $file_name); 

if (open(my $fh, '<:encoding(UTF-8)', $tempGfile)) {
	printf $fhS "; layer count = %d\n", $layerCount;
	printf $fhS "; lines count = %d\n", $linesCount;
	printf $fhS "; PreLayers = %d\n", $SkipLayers;
	printf $fhS "; filament = %d\n", $all_nums[0];
	printf $fhS "; ETP = %d\n", $TotalETP;
	printf $fhS "; tracktype = %d\n", $TrackType;

	while (my $row = <$fh> ) {
		chomp $row;

		if (index($row, ';newlayerhere') == -1) {
			print $fhS "$row\n";
		}


	}
}

print "Operation completed\n";



Step 3.
Next, is to tell Slicer to automatically run this script after every time it slices a model.

Very easy to do.

In Repetier > Slicer > Configuration ( opens the slicer config window ) go to 'Print Settings' > 'Output Options'.

In the post-processing scripts, add the path and filename ( in my case : C:\Users\User\AppData\Local\RepetierHost\AddLayerAndLines.pl )


Step 4.

Next, we need a few modifications so that the gCode automatically contains the 'flags' that we need to work in the script.

In the same Slicer config window as above, go to 'Printer Settings' tab.

In the 'Start G-code box, add this to the end of what you may have in there :
;TrackType=ETP ; set =Filament or =PrintLines or=ETP
;SLC

The TrackType can be set to either do the calculations of the estimated time remaining using the Filament, or the number of Print Lines, or the Estimated Time for Printing.

The ';SLC' comment will cause the layer count to be reset to zero. So if you have executed any Z-axis moves before the job actually starts print, they will be ignored for the layer count. When your printer starts the print, it will show as 'layer 1'. Add the ;SLC line, as it will work only if it needs to be used.

In the 'Before Layer Change G-code box, add :
;newlayerhere

This is a very simple method. When slicer first creates the gCode file, it will add this comment at the point where a new layer is to start. We are using this 'flag' in the post-processing script, to count the number of layers in the print job. The post-processing script will then delete the comments from the gcode file, so the file size is not affected at all.

Save the changes.


Step 5.

Test the above.

In Repetier, select a model to print, make sure you have the correct 'Print Settings' and 'Printer Settings' selected on the Slicer tab, and also turn on the log screen ( there should be a 'Toggle Log' button on the toolbar ).

After Slicing the model, the log should look something like this :

21:39:16.382 : Starting object analyser ...
21:39:16.469 : Object is manifold.
21:39:16.489 : Analysing finished.
21:39:18.257 : Slic3r command:C:\Program Files\Repetier-Host\Slic3r\slic3r.exe --load "C:\Users\User\AppData\Local\RepetierHost\slic3r.ini" --print-center 125,125 -o "C:\Users\User\AppData\Local\RepetierHost\composition.gcode" "C:\Users\User\AppData\Local\RepetierHost\composition.amf"
21:39:19.854 :  => Processing triangulated mesh
21:39:19.967 :  => Generating perimeters
21:39:20.319 :  => Preparing infill
21:39:20.774 :  => Infilling layers
21:39:21.005 :  => Generating skirt
21:39:21.013 :  => Exporting G-code to C:\Users\User\AppData\Local\RepetierHost\composition.gcode
21:39:21.847 :  => Running post-processing scripts
21:39:23.785 :  Located Total Time H : 00
21:39:23.785 :  Located Total Time M : 21
21:39:23.785 :  Located Total Time S : 32
21:39:23.785 :  ETP Seconds = 1292
21:39:23.785 :  File Name C:\Users\User\AppData\Local\RepetierHost\composition.gcode
21:39:23.785 :  Reading file loop - gathering data
21:39:23.785 :  Layer Count = 75
21:39:23.785 :  Lines Count = 19306
21:39:23.785 :  Skip pre-Layers = 1
21:39:23.785 :  filamentmm = 671
21:39:23.785 :  Copying gCode to temp file
21:39:23.785 :  Temp. File = C:\Users\User\AppData\Local\RepetierHost\TempScriptFile.gcode
21:39:23.786 :  Orig. File = C:\Users\User\AppData\Local\RepetierHost\composition.gcode
21:39:23.786 :  Operation completed
21:39:23.792 :  Done. Process took 0 minutes and 3.952 seconds
21:39:23.793 :  Filament required: 671.5mm (1.6cm3)

and the top of the gcode file something like this :

; layer count = 75
; lines count = 19306
; PreLayers = 1
; filament = 671
; ETP = 1292
; tracktype = 3
; generated by Slic3r 1.2.9 on 2015-07-17 at 21:39:21

; external perimeters extrusion width = 0.40mm
; perimeters extrusion width = 0.48mm
; infill extrusion width = 0.48mm
; solid infill extrusion width = 0.48mm
; top infill extrusion width = 0.48mm

M107
M190 S85 ; set bed temperature
M104 S265 ; set temperature
G28 ; home all axes
G1 Z6 F5000 ; lift nozzle
M92 E515
G1 Y20 F5000
;TrackType=ETP ; set =Filament or =PrintLines or=ETP
;SLC
M109 S265 ; wait for temperature to be reached
G21 ; set units to millimeters
G90 ; use absolute coordinates
M82 ; use absolute distances for extrusion
G92 E0
G1 E-1.00000 F1800.00000
G92 E0
G1 Z0.300 F10200.000
G1 X114.868 Y116.678 F10200.000


Step 6.

Once you have this working, the next step is to modify the Marlin code so that it can read the additional 'parameters' that we added to the top of the gcode file, and display them on the LCD.

In the file :
Configuration.h

After the line:
#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER

I added :
// ==> my note : uncomment this line to make the graphical LCD show the Layer Count and Estimated Time Remaining instead of the XYZ positions
#define ShowLayerCountAndEstTime


in file : marlin.h

after:
extern float add_homing[3];

I added :
// my note : added global variables for tracking Layers and Print Lines
extern int LayerCountTotal;
extern int LayerCountCurrent;
extern float FilamentCurrent;
extern float PrintLinesTotal;
extern float PrintLinesCurrent;
extern float FilamentTotal;
extern int TrackType;
extern long TrackTimeStart;
extern long ETPtotal;


In file : Marlin_main.cpp

about line 245 after :
float zprobe_zoffset;

I added :
// my note : added variables for tracking Layers and Print Lines
int LayerCountTotal;
int LayerCountCurrent;
float FilamentCurrent;
float PrintLinesTotal;
float PrintLinesCurrent;
float FilamentTotal;
int LayersToIgnore;
bool CountLinesAndLayers = false;
int TrackType;
long TrackTimeStart;
long ETPtotal;


also in same file :

in void process_commands
changed :
    switch((int)code_value())
    {
    case 0: // G0 -> G1
    case 1: // G1
    if(Stopped == false) {

to this :
    switch((int)code_value())
    {
    case 0: // G0 -> G1
    case 1: // G1

		// my note : increase the PrintLines count by 1 to track current Printline Count and calculate the Estimated Time Remaining
		PrintLinesCurrent++;

		if(code_seen('F')){  // start the timer when the filament is first moved
			if(TrackTimeStart <= 0){
				TrackTimeStart = millis();
			}
		}
		
		if(code_seen('E')){
			get_coordinates();
            float echange=destination[E_AXIS]-current_position[E_AXIS];
			FilamentCurrent = FilamentCurrent + echange;
		}

		// // my note : add code to increase the LCD layer count by 1 if the gCode contains a new Z axis instruction
		if(code_seen('Z')) LayerCountCurrent++;

		if(LayersToIgnore > 0){  // ignore the layer count before the ;SLC was detected ( if it exists in the gcode file )
			LayersToIgnore--;
			LayerCountCurrent = 0;
			PrintLinesCurrent = 0;
			FilamentCurrent = 0;
		}
      if(Stopped == false) {

and changed :
    case 23:
		starpos = (strchr(strchr_pointer + 4,'*'));
		if(starpos!=NULL)
			*(starpos)='\0';
		card.openFile(strchr_pointer + 4,true);
    break;

    case 24:
		card.startFileprint();
		starttime=millis();
		break;

to this :
    case 23: //M23 - Select file
		starpos = (strchr(strchr_pointer + 4,'*'));
		if(starpos!=NULL)
			*(starpos)='\0';

		// my note : add code to count the Z axis commands ( layers ) in the file and the G1 print lines in the file
		#ifdef SDSUPPORT
			#ifdef ShowLayerCountAndEstTime
				CountLinesAndLayers = true;
			#endif //layer counter
		#endif //SDSUPPORT

		if(CountLinesAndLayers != true) card.openFile(strchr_pointer + 4,true);  //this is in M23 - Select file  // name, read, replace
    break;

    case 24: //M24 - Start SD print
		// my note : SD card print starts here
		LayerCountCurrent = 0;
		card.startFileprint();
		starttime=millis();
		break;

and changed :
  else
  {
    SERIAL_ECHO_START;
    SERIAL_ECHOPGM(MSG_UNKNOWN_COMMAND);
    SERIAL_ECHO(cmdbuffer[bufindr]);
    SERIAL_ECHOLNPGM("\"");
  }

    ClearToSend();
		
}

void FlushSerialRequestResend()

to this :
  else
  {
    SERIAL_ECHO_START;
    SERIAL_ECHOPGM(MSG_UNKNOWN_COMMAND);
    SERIAL_ECHO(cmdbuffer[bufindr]);
    SERIAL_ECHOLNPGM("\"");
  }

    ClearToSend();
 

	if(CountLinesAndLayers == true){ // read the SD file and count the lines and layers
		CountLinesAndLayers = false;
		PrintLinesTotal = 0;
		LayerCountTotal = 0;
		FilamentTotal = 0;
		TrackType = 0;
		TrackTimeStart = 0;
		ETPtotal = 0;
		card.openFile(strchr_pointer + 4,true);
		chPos = 0;  // pointer for position of next char in buffer array
		int ReadingLineNumber = 1;
		while(!card.eof() && ReadingLineNumber < 10){
			int16_t n=card.get();
			serial_char = (char)n;
			if(chPos < 59) SDbuffer[chPos] = serial_char;
			chPos++;
			if(serial_char == '\n'){ // if the character read from the SD card is a line ending
				/*
				first 5 lines of the gCode file
				; layer count = 75
				; lines count = 19306
				; PreLayers = 1
				; filament = 671
				; tracktype = 1  // 1 = Filament, 2 = PrintLines, 3 = ETP
				*/
				if(ReadingLineNumber == 1){ // read the layer count
					if ( strstr(SDbuffer, "; layer count =") ){
						LayerCountTotal = atoi(&SDbuffer[15]);
					}
				}
				if(ReadingLineNumber == 2){ // read the lines count
					if ( strstr(SDbuffer, "; lines count =") ){
						PrintLinesTotal = atof(&SDbuffer[15]);

					} 
				}
				if(ReadingLineNumber == 3){ // read the layers to skip in setup before print starts
					if ( strstr(SDbuffer, "; PreLayers =") ){
						LayersToIgnore = atoi(&SDbuffer[13]);
					}
				}
				if(ReadingLineNumber == 4){ // read the filament length
					if ( strstr(SDbuffer, "; filament =") ){
						FilamentTotal = atof(&SDbuffer[12]);
					}
				}
				if(ReadingLineNumber == 5){ // read the ETP time in seconds
					if ( strstr(SDbuffer, "; ETP =") ){
						ETPtotal = atoi(&SDbuffer[7]);
					}
				}
				if(ReadingLineNumber == 6){ // read the tracking type
					if ( strstr(SDbuffer, "; tracktype =") ){
						TrackType = atoi(&SDbuffer[13]);
					}
				}
				chPos = 0; // reset the position for the next character in the buffer. Set to 0 = empty the buffer.
				ReadingLineNumber++;
			}
		}
		card.closefile();
		delay(200);
		card.openFile(strchr_pointer + 4,true);  //this is in M23 - Select file
	}  // end of : if(CountLinesAndLayers = true)
  

		
}  // end of : if(code_seen('M'))

void FlushSerialRequestResend()


next, in the file :
dogm_lcd_implementation.h

change :
// Fan
 u8g.setFont(FONT_STATUSMENU);
 u8g.setPrintPos(104,27);
 #if defined(FAN_PIN) && FAN_PIN > -1
 u8g.print(itostr3(int((fanSpeed*100)/256 + 1)));
 u8g.print("%");
 #else
 u8g.print("---");
 #endif
 
	// X, Y, Z-Coordinates - make white on black panel
	u8g.setFont(FONT_STATUSMENU);
	u8g.drawBox(0,29,128,10);
	u8g.setColorIndex(0);	// white on black

		u8g.setPrintPos(2,37);                           // move to position to print the 'X'
		u8g.print("X");                                  // print the 'X' character
		u8g.drawPixel(8,33);                             // top dot of the colon
		u8g.drawPixel(8,35);                             // bottom dot of the colon
		u8g.setPrintPos(10,37);                          // move to position to print the value
		u8g.print(ftostr31ns(current_position[X_AXIS])); // print the X axis value

		u8g.setPrintPos(43,37);                           // move to position to print the 'Y'
		lcd_printPGM(PSTR("Y"));
		u8g.drawPixel(49,33);
		u8g.drawPixel(49,35);
		u8g.setPrintPos(51,37);
		u8g.print(ftostr31ns(current_position[Y_AXIS]));

		u8g.setPrintPos(83,37);                           // move to position to print the 'Z'
		u8g.print("Z");
		u8g.drawPixel(89,33);
		u8g.drawPixel(89,35);
		u8g.setPrintPos(91,37);
		u8g.print(ftostr31(current_position[Z_AXIS]));

	u8g.setColorIndex(1);	// reset the display colors back to black on white

to this :
// Fan
 u8g.setFont(FONT_STATUSMENU);
 u8g.setPrintPos(104,27);
 #if defined(FAN_PIN) && FAN_PIN > -1
 u8g.print(itostr3(int((fanSpeed*100)/256 + 1)));
 u8g.print("%");
 #else
 u8g.print("---");
 #endif

	// X, Y, Z-Coordinates - make white on black panel
	u8g.setFont(FONT_STATUSMENU);
	u8g.drawBox(0,29,128,10);
	u8g.setColorIndex(0);	// white on black

	#ifdef ShowLayerCountAndEstTime	// Check if the config option to show the layer count ( instead of the XYZ positions ) is enabled

		char LCDline1[20];  // create the LCD line array with the data to be displayed
		LCDline1[0] = '\0'; // clear the line to be displayed

		if(PrintLinesTotal > 0 && LayerCountTotal > 0){

			int DHRP = 0; // integer for estimated hours remaining
			int DMRP = 0; // integer for estimated minutes remaining
			if(PrintLinesCurrent >= 10){ // start calcs after the first 10 lines have been printed

				long CurrentPrintTime=0;
				CurrentPrintTime = millis() - TrackTimeStart;             // calc the milliseconds that the job has been running so far
				long EMR = 0;
				int SRP = 0;
				int MRP = 0;

				if(TrackType == 3){ // track the time remaining based on the ETP seconds			
					SRP = ETPtotal - (CurrentPrintTime / 1000); // calc the seconds remaining
					MRP = trunc(SRP / 60);                           // convert seconds to minutes remaining for print
					if(MRP < 0) MRP = 0;                                 // keep the value at 1 minute for the last few layers when the actual value < 1 minute
				}

				if(TrackType == 1){ // track the time remaining based on the Filament			
					// calc Est. Time Remaining using filament required vs filament used
					EMR = (CurrentPrintTime / FilamentCurrent) * (FilamentTotal - FilamentCurrent); // calc the estimated millis remaining
					SRP = trunc(EMR / 1000);                         // convert remaining millis to seconds remaining for print
					MRP = trunc(SRP / 60);                           // convert seconds to minutes remaining for print
					if(MRP < 1) MRP = 1;                                 // keep the value at 1 minute for the last few layers when the actual value < 1 minute
					if(FilamentTotal == FilamentCurrent) MRP = 0;    // switch to zero when all lines printed
				}

				if(TrackType == 2){ // track the time remaining based on the PrintLines			
					// calc Est. Time Remaining using number of print lines done vs remaining
					EMR = (CurrentPrintTime / PrintLinesCurrent) * (PrintLinesTotal - PrintLinesCurrent); // calc the estimated millis remaining
					SRP = trunc(EMR / 1000);                         // convert remaining millis to seconds remaining for print
					MRP = trunc(SRP / 60);                           // convert seconds to minutes remaining for print
					if(MRP < 1) MRP = 1;                                 // keep the value at 1 minute for the last few layers when the actual value < 1 minute
					if(PrintLinesTotal == PrintLinesCurrent) MRP = 0;    // switch to zero when all lines printed
				}

				DHRP = trunc(MRP / 60);                              // how many hours in MRP remaining for print
				DMRP = MRP - (DHRP * 60);                            // calc remaining minutes after subtracting DHRP hours
				DHRP = trunc(MRP / 60);                              // how many hours in MRP remaining for print
				DMRP = MRP - (DHRP * 60);                            // calc remaining minutes after subtracting DHRP hours
			}
			sprintf (LCDline1, "L%03d/%03d e%02d:%02d", LayerCountCurrent, LayerCountTotal, DHRP, DMRP);
			u8g.setPrintPos(2,37);
			u8g.print(LCDline1);
		}
	#endif

	if(PrintLinesTotal == 0 || LayerCountTotal == 0){   // otherwise display the standard XYZ positions

		u8g.setPrintPos(2,37);                           // move to position to print the 'X'
		u8g.print("X");                                  // print the 'X' character
		u8g.drawPixel(8,33);                             // top dot of the colon
		u8g.drawPixel(8,35);                             // bottom dot of the colon
		u8g.setPrintPos(10,37);                          // move to position to print the value
		u8g.print(ftostr31ns(current_position[X_AXIS])); // print the X axis value

		u8g.setPrintPos(43,37);                           // move to position to print the 'Y'
		lcd_printPGM(PSTR("Y"));
		u8g.drawPixel(49,33);
		u8g.drawPixel(49,35);
		u8g.setPrintPos(51,37);
		u8g.print(ftostr31ns(current_position[Y_AXIS]));

		u8g.setPrintPos(83,37);                           // move to position to print the 'Z'
		u8g.print("Z");
		u8g.drawPixel(89,33);
		u8g.drawPixel(89,35);
		u8g.setPrintPos(91,37);
		u8g.print(ftostr31(current_position[Z_AXIS]));
	}
	u8g.setColorIndex(1);	// reset the display colors back to black on white


That's it.

Compile the Marlin code and upload to your Arduino.

Next time you run the gCode file ( with the automatically modified header data ) it will start printing and show you the layer number ( and total layer count ) and the estimated time remaining ( hours and minutes ).

Again : WARNING : PLEASE DO NOT TRY THIS IF YOU DO NOT HAVE A BACKUP AND / OR YOU DO NOT KNOW HOW TO REVERSE IT. I will not be responsible for any actions.

Last note : I know that some of this code could be optimized, but as I said before : I am not a professional coder, so I made it work specifically for what I need with MY printer - I can not gaurantee it will work for everyone.

If anyone does try this and has any additions, corrections, or comments, please feel free to add them to this post for everyones' benefit.
Sorry, only registered users may post in this forum.

Click here to login