
Merged zpl's changes for binary file uploading and PrintMonitor Fixed bug with filament-based print time estimation Fixed bug that caused feed rate to often not be restored after running a macro Print Z probe offsets at end of G32 sequence if Move debug is enabled
739 lines
21 KiB
C++
739 lines
21 KiB
C++
/****************************************************************************************************
|
|
|
|
RepRapFirmware - PrintMonitor
|
|
|
|
This class provides methods to obtain print end-time estimations and file information from generated
|
|
G-Code files, which may be reported to auxiliary devices and to the web interface using status responses.
|
|
|
|
-----------------------------------------------------------------------------------------------------
|
|
|
|
Version 0.1
|
|
|
|
Created on: Feb 24, 2015
|
|
|
|
Christian Hammacher
|
|
|
|
Licence: GPL
|
|
|
|
****************************************************************************************************/
|
|
|
|
#include "RepRapFirmware.h"
|
|
|
|
PrintMonitor::PrintMonitor(Platform *p, GCodes *gc) : platform(p), gCodes(gc), fileInfoDetected(false),
|
|
printStartTime(0.0), currentLayer(0), firstLayerDuration(0.0), firstLayerHeight(0.0),
|
|
firstLayerFilament(0.0), firstLayerProgress(0.0), warmUpDuration(0.0), layerEstimatedTimeLeft(0.0),
|
|
lastLayerTime(0.0), lastLayerFilament(0.0), numLayerSamples(0)
|
|
{
|
|
}
|
|
|
|
void PrintMonitor::Spin()
|
|
{
|
|
if (gCodes->IsPausing() || gCodes->IsPaused() || gCodes->IsResuming())
|
|
{
|
|
// TODO: maybe incorporate pause durations in print estimations in the future?
|
|
platform->ClassReport(longWait);
|
|
return;
|
|
}
|
|
|
|
if (gCodes->PrintingAFile())
|
|
{
|
|
// May have just started a print, see if we're heating up
|
|
if (warmUpDuration == 0.0)
|
|
{
|
|
// When a new print starts, the total (raw) extruder positions are zeroed
|
|
float totalRawFilament = 0.0;
|
|
for(size_t extruder=0; extruder < DRIVES - AXES; extruder++)
|
|
{
|
|
totalRawFilament += gCodes->GetRawExtruderPosition(extruder);
|
|
}
|
|
|
|
// See if at least one heater is active and set
|
|
bool heatersAtHighTemperature = false;
|
|
for(size_t heater=E0_HEATER; heater<HEATERS; heater++)
|
|
{
|
|
if (reprap.GetHeat()->GetStatus(heater) == Heat::HS_active &&
|
|
reprap.GetHeat()->GetActiveTemperature(heater) > TEMPERATURE_LOW_SO_DONT_CARE &&
|
|
reprap.GetHeat()->HeaterAtSetTemperature(heater))
|
|
{
|
|
heatersAtHighTemperature = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (heatersAtHighTemperature && totalRawFilament != 0.0)
|
|
{
|
|
lastLayerTime = platform->Time();
|
|
warmUpDuration = lastLayerTime - printStartTime;
|
|
|
|
if (fileInfoDetected && currentFileInfo.layerHeight > 0.0) {
|
|
currentLayer = 1;
|
|
}
|
|
}
|
|
}
|
|
// Looks like the print has started
|
|
else if (currentLayer > 0)
|
|
{
|
|
float liveCoords[DRIVES + 1];
|
|
reprap.GetMove()->LiveCoordinates(liveCoords);
|
|
|
|
// See if we can determine the first layer height (must be smaller than the nozzle diameter)
|
|
if (firstLayerHeight == 0.0)
|
|
{
|
|
if (liveCoords[Z_AXIS] < NOZZLE_DIAMETER && !gCodes->DoingFileMacro())
|
|
{
|
|
firstLayerHeight = liveCoords[Z_AXIS];
|
|
}
|
|
}
|
|
// Then check if we've finished the first layer
|
|
else if (firstLayerDuration == 0.0)
|
|
{
|
|
if (liveCoords[Z_AXIS] > firstLayerHeight * 1.05) // allow some tolerance for transform operations
|
|
{
|
|
firstLayerFilament = 0.0;
|
|
for(size_t extruder=0; extruder<DRIVES - AXES; extruder++)
|
|
{
|
|
firstLayerFilament += gCodes->GetRawExtruderPosition(extruder);
|
|
}
|
|
firstLayerDuration = platform->Time() - lastLayerTime;
|
|
firstLayerProgress = gCodes->FractionOfFilePrinted();
|
|
}
|
|
}
|
|
// We have enough values to estimate the following layer heights
|
|
else if (currentFileInfo.objectHeight > 0.0)
|
|
{
|
|
unsigned int estimatedLayer = round((liveCoords[Z_AXIS] - firstLayerHeight) / currentFileInfo.layerHeight) + 1;
|
|
if (estimatedLayer == currentLayer + 1) // on layer change
|
|
{
|
|
// Record untainted extruder positions for filament-based estimation
|
|
float extrRawTotal = 0.0;
|
|
for(size_t extruder=0; extruder < DRIVES - AXES; extruder++)
|
|
{
|
|
extrRawTotal += gCodes->GetRawExtruderPosition(extruder);
|
|
}
|
|
|
|
const float now = platform->Time();
|
|
unsigned int remainingLayers;
|
|
remainingLayers = round((currentFileInfo.objectHeight - firstLayerHeight) / currentFileInfo.layerHeight) + 1;
|
|
remainingLayers -= currentLayer;
|
|
|
|
if (currentLayer > 1)
|
|
{
|
|
// Record a new set
|
|
if (numLayerSamples < MAX_LAYER_SAMPLES)
|
|
{
|
|
layerDurations[numLayerSamples] = now - lastLayerTime;
|
|
if (!numLayerSamples)
|
|
{
|
|
filamentUsagePerLayer[numLayerSamples] = extrRawTotal - firstLayerFilament;
|
|
}
|
|
else
|
|
{
|
|
filamentUsagePerLayer[numLayerSamples] = extrRawTotal - lastLayerFilament;
|
|
}
|
|
fileProgressPerLayer[numLayerSamples] = gCodes->FractionOfFilePrinted();
|
|
numLayerSamples++;
|
|
}
|
|
else
|
|
{
|
|
for(unsigned int i=1; i<MAX_LAYER_SAMPLES; i++)
|
|
{
|
|
layerDurations[i - 1] = layerDurations[i];
|
|
filamentUsagePerLayer[i - 1] = filamentUsagePerLayer[i];
|
|
fileProgressPerLayer[i - 1] = fileProgressPerLayer[i];
|
|
}
|
|
|
|
layerDurations[MAX_LAYER_SAMPLES - 1] = now - lastLayerTime;
|
|
filamentUsagePerLayer[MAX_LAYER_SAMPLES - 1] = extrRawTotal - lastLayerFilament;
|
|
fileProgressPerLayer[MAX_LAYER_SAMPLES - 1] = gCodes->FractionOfFilePrinted();
|
|
}
|
|
}
|
|
|
|
// Update layer-based estimation times
|
|
float avgLayerTime, avgLayerDelta = 0.0;
|
|
if (numLayerSamples)
|
|
{
|
|
avgLayerTime = 0.0;
|
|
for(unsigned int layer=0; layer<numLayerSamples; layer++)
|
|
{
|
|
avgLayerTime += layerDurations[layer];
|
|
if (layer)
|
|
{
|
|
avgLayerDelta += layerDurations[layer] - layerDurations[layer - 1];
|
|
}
|
|
}
|
|
avgLayerTime /= numLayerSamples;
|
|
avgLayerDelta /= numLayerSamples;
|
|
}
|
|
else
|
|
{
|
|
avgLayerTime = firstLayerDuration * FIRST_LAYER_SPEED_FACTOR;
|
|
}
|
|
|
|
layerEstimatedTimeLeft = (avgLayerTime * remainingLayers) - (avgLayerDelta * remainingLayers);
|
|
if (layerEstimatedTimeLeft < 0.0)
|
|
{
|
|
layerEstimatedTimeLeft = avgLayerTime * remainingLayers;
|
|
}
|
|
|
|
// Set new layer values
|
|
currentLayer = estimatedLayer;
|
|
lastLayerTime = now;
|
|
lastLayerFilament = extrRawTotal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (printStartTime > 0.0 && reprap.GetMove()->NoLiveMovement())
|
|
{
|
|
currentLayer = numLayerSamples = 0;
|
|
firstLayerDuration = firstLayerHeight = firstLayerFilament = firstLayerProgress = 0.0;
|
|
layerEstimatedTimeLeft = printStartTime = warmUpDuration = 0.0;
|
|
lastLayerTime = lastLayerFilament = 0.0;
|
|
}
|
|
platform->ClassReport(longWait);
|
|
}
|
|
|
|
void PrintMonitor::Init()
|
|
{
|
|
longWait = platform->Time();
|
|
}
|
|
|
|
void PrintMonitor::StartingFilePrint(const char* filename)
|
|
{
|
|
fileInfoDetected = GetFileInfo(platform->GetGCodeDir(), filename, currentFileInfo);
|
|
strncpy(fileBeingPrinted, filename, ARRAY_SIZE(fileBeingPrinted));
|
|
fileBeingPrinted[ARRAY_UPB(fileBeingPrinted)] = 0;
|
|
}
|
|
|
|
void PrintMonitor::StartedFilePrint()
|
|
{
|
|
printStartTime = platform->Time();
|
|
}
|
|
|
|
bool PrintMonitor::GetFileInfo(const char *directory, const char *fileName, GcodeFileInfo& info) const
|
|
{
|
|
if (reprap.GetPlatform()->GetMassStorage()->PathExists(directory, fileName))
|
|
{
|
|
// Webserver can use this method to determine if a file was passed or not
|
|
return false;
|
|
}
|
|
|
|
FileStore *f = reprap.GetPlatform()->GetFileStore(directory, fileName, false);
|
|
if (f != NULL)
|
|
{
|
|
// Try to find the object height by looking for the last G1 Zxxx command in the file
|
|
info.fileSize = f->Length();
|
|
info.objectHeight = 0.0;
|
|
info.layerHeight = 0.0;
|
|
info.numFilaments = DRIVES - AXES;
|
|
info.generatedBy[0] = 0;
|
|
for (size_t i = 0; i < DRIVES - AXES; ++i)
|
|
{
|
|
info.filamentNeeded[i] = 0.0;
|
|
}
|
|
|
|
if (info.fileSize != 0 && (StringEndsWith(fileName, ".gcode") || StringEndsWith(fileName, ".g") || StringEndsWith(fileName, ".gco") || StringEndsWith(fileName, ".gc")))
|
|
{
|
|
const size_t readSize = 512; // read 512 bytes at a time (1K doesn't seem to work when we read from the end)
|
|
const size_t overlap = 100;
|
|
char buf[readSize + overlap + 1]; // need the +1 so we can add a null terminator
|
|
|
|
bool foundLayerHeight = false;
|
|
unsigned int filamentsFound = 0, nFilaments;
|
|
float filaments[DRIVES - AXES];
|
|
|
|
// Get slic3r settings by reading from the start of the file. We only read the first 2K or so, everything we are looking for should be there.
|
|
for(uint8_t i=0; i<4; i++)
|
|
{
|
|
size_t sizeToRead = (size_t)min<unsigned long>(info.fileSize, readSize + overlap);
|
|
int nbytes = f->Read(buf, sizeToRead);
|
|
if (nbytes != (int)sizeToRead)
|
|
{
|
|
break; // read failed so give up
|
|
}
|
|
else
|
|
{
|
|
buf[sizeToRead] = 0;
|
|
|
|
// Search for filament usage (Cura puts it at the beginning of a G-code file)
|
|
if (!filamentsFound)
|
|
{
|
|
nFilaments = FindFilamentUsed(buf, sizeToRead, filaments, DRIVES - AXES);
|
|
if (nFilaments != 0 && nFilaments >= filamentsFound)
|
|
{
|
|
filamentsFound = min<unsigned int>(nFilaments, info.numFilaments);
|
|
for (unsigned int i = 0; i < filamentsFound; ++i)
|
|
{
|
|
info.filamentNeeded[i] = filaments[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for layer height
|
|
if (!foundLayerHeight)
|
|
{
|
|
foundLayerHeight = FindLayerHeight(buf, sizeToRead, info.layerHeight);
|
|
}
|
|
|
|
// Look for slicer program
|
|
if (!info.generatedBy[0])
|
|
{
|
|
// Slic3r and S3D
|
|
const char* generatedByString = "generated by ";
|
|
char* pos = strstr(buf, generatedByString);
|
|
size_t generatedByLength = ARRAY_SIZE(info.generatedBy);
|
|
if (pos != NULL)
|
|
{
|
|
pos += strlen(generatedByString);
|
|
size_t i = 0;
|
|
while (i < ARRAY_SIZE(info.generatedBy) - 1 && *pos >= ' ')
|
|
{
|
|
char c = *pos++;
|
|
if (c == '"' || c == '\\')
|
|
{
|
|
// Need to escape the quote-mark for JSON
|
|
if (i > ARRAY_SIZE(info.generatedBy) - 3)
|
|
{
|
|
break;
|
|
}
|
|
info.generatedBy[i++] = '\\';
|
|
}
|
|
info.generatedBy[i++] = c;
|
|
}
|
|
info.generatedBy[i] = 0;
|
|
}
|
|
|
|
// Cura
|
|
const char* slicedAtString = ";Sliced at: ";
|
|
pos = strstr(buf, slicedAtString);
|
|
if (pos != NULL)
|
|
{
|
|
pos += strlen(slicedAtString);
|
|
strcpy(info.generatedBy, "Cura at ");
|
|
size_t i = 8;
|
|
while (i < ARRAY_SIZE(info.generatedBy) - 1 && *pos >= ' ')
|
|
{
|
|
char c = *pos++;
|
|
if (c == '"' || c == '\\')
|
|
{
|
|
// Need to escape the quote-mark for JSON
|
|
if (i > ARRAY_SIZE(info.generatedBy) - 3)
|
|
{
|
|
break;
|
|
}
|
|
info.generatedBy[i++] = '\\';
|
|
}
|
|
info.generatedBy[i++] = c;
|
|
}
|
|
info.generatedBy[i] = 0;
|
|
}
|
|
}
|
|
|
|
// Add code to look for other values here...
|
|
}
|
|
|
|
// Have we collected everything?
|
|
if (filamentsFound && foundLayerHeight && info.generatedBy[0])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now get the object height and filament used by reading the end of the file
|
|
{
|
|
size_t sizeToRead;
|
|
if (info.fileSize <= readSize + overlap)
|
|
{
|
|
sizeToRead = info.fileSize; // read the whole file in one go
|
|
}
|
|
else
|
|
{
|
|
sizeToRead = info.fileSize % readSize;
|
|
if (sizeToRead <= overlap)
|
|
{
|
|
sizeToRead += readSize;
|
|
}
|
|
}
|
|
unsigned long seekPos = info.fileSize - sizeToRead; // read on a 512b boundary
|
|
size_t sizeToScan = sizeToRead;
|
|
for (;;)
|
|
{
|
|
if (!f->Seek(seekPos))
|
|
{
|
|
break;
|
|
}
|
|
int nbytes = f->Read(buf, sizeToRead);
|
|
if (nbytes != (int)sizeToRead)
|
|
{
|
|
break; // read failed so give up
|
|
}
|
|
|
|
// Search for filament used
|
|
if (!filamentsFound)
|
|
{
|
|
nFilaments = FindFilamentUsed(buf, sizeToScan, filaments, DRIVES - AXES);
|
|
if (nFilaments != 0 && nFilaments >= filamentsFound)
|
|
{
|
|
filamentsFound = min<unsigned int>(nFilaments, info.numFilaments);
|
|
for (unsigned int i = 0; i < filamentsFound; ++i)
|
|
{
|
|
info.filamentNeeded[i] = filaments[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search for layer height
|
|
if (!foundLayerHeight)
|
|
{
|
|
foundLayerHeight = FindLayerHeight(buf, sizeToScan, info.layerHeight);
|
|
}
|
|
|
|
// Search for object height
|
|
if (FindHeight(buf, sizeToScan, info.objectHeight))
|
|
{
|
|
break; // quit if found height
|
|
}
|
|
|
|
if (seekPos == 0 || info.fileSize - seekPos >= 200000uL) // scan up to about the last 200K of the file (32K wasn't enough)
|
|
{
|
|
break; // quit if reached start of file or already scanned the last 32K of the file
|
|
}
|
|
seekPos -= readSize;
|
|
sizeToRead = readSize;
|
|
sizeToScan = readSize + overlap;
|
|
memcpy(buf + sizeToRead, buf, overlap);
|
|
}
|
|
info.numFilaments = filamentsFound;
|
|
}
|
|
}
|
|
f->Close();
|
|
//debugPrintf("Set height %f and filament %f\n", height, filamentUsed);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PrintMonitor::GetFileInfoResponse(StringRef& response, const char* filename) const
|
|
{
|
|
// Poll file info for a specific file
|
|
if (filename != NULL)
|
|
{
|
|
GcodeFileInfo info;
|
|
bool found = GetFileInfo("0:/", filename, info);
|
|
if (found)
|
|
{
|
|
response.printf("{\"err\":0,\"size\":%lu,\"height\":%.2f,\"layerHeight\":%.2f,\"filament\":",
|
|
info.fileSize, info.objectHeight, info.layerHeight);
|
|
char ch = '[';
|
|
if (info.numFilaments == 0)
|
|
{
|
|
response.catf("%c", ch);
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 0; i < info.numFilaments; ++i)
|
|
{
|
|
response.catf("%c%.1f", ch, info.filamentNeeded[i]);
|
|
ch = ',';
|
|
}
|
|
}
|
|
response.catf("],\"generatedBy\":\"%s\"}", info.generatedBy);
|
|
}
|
|
else
|
|
{
|
|
response.copy("{\"err\":1}");
|
|
}
|
|
}
|
|
else if (gCodes->PrintingAFile() && fileInfoDetected)
|
|
{
|
|
// Poll file info about a file currently being printed
|
|
response.printf("{\"err\":0,\"size\":%lu,\"height\":%.2f,\"layerHeight\":%.2f,\"filament\":",
|
|
currentFileInfo.fileSize, currentFileInfo.objectHeight, currentFileInfo.layerHeight);
|
|
char ch = '[';
|
|
if (currentFileInfo.numFilaments == 0)
|
|
{
|
|
response.catf("%c", ch);
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 0; i < currentFileInfo.numFilaments; ++i)
|
|
{
|
|
response.catf("%c%.1f", ch, currentFileInfo.filamentNeeded[i]);
|
|
ch = ',';
|
|
}
|
|
}
|
|
response.catf("],\"generatedBy\":\"%s\",\"printDuration\":%d,\"fileName\":\"%s\"}",
|
|
currentFileInfo.generatedBy, (int)((platform->Time() - printStartTime) * 1000.0), fileBeingPrinted);
|
|
}
|
|
else
|
|
{
|
|
response.copy("{\"err\":1}");
|
|
}
|
|
}
|
|
|
|
float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
|
|
{
|
|
// We can't provide an estimation if we're not printing (yet)
|
|
if (!gCodes->PrintingAFile() || (fileInfoDetected && currentFileInfo.numFilaments != 0 && warmUpDuration == 0.0))
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
// Take into account the first layer time only if we haven't got any other samples
|
|
float realPrintDuration = (platform->Time() - printStartTime) - warmUpDuration;
|
|
if (numLayerSamples != 0)
|
|
{
|
|
realPrintDuration -= firstLayerDuration;
|
|
}
|
|
|
|
// Actual estimations
|
|
switch (method)
|
|
{
|
|
case fileBased:
|
|
{
|
|
// Provide rough estimation only if we haven't collected any layer samples
|
|
float fractionPrinted = gCodes->FractionOfFilePrinted();
|
|
if (numLayerSamples == 0 || !fileInfoDetected || currentFileInfo.objectHeight == 0.0)
|
|
{
|
|
return realPrintDuration * (1.0 / fractionPrinted) - realPrintDuration;
|
|
}
|
|
|
|
// Each layer takes time to achieve more file progress, so take an average over our samples
|
|
float avgSecondsByProgress = 0.0, lastLayerProgress = 0.0;
|
|
for (unsigned int layer=0; layer<numLayerSamples; layer++)
|
|
{
|
|
avgSecondsByProgress += layerDurations[layer] / (fileProgressPerLayer[layer] - lastLayerProgress);
|
|
lastLayerProgress = fileProgressPerLayer[layer];
|
|
}
|
|
avgSecondsByProgress /= numLayerSamples;
|
|
|
|
// Then we know how many seconds it takes to finish 1% and we know how much file progress is left
|
|
return avgSecondsByProgress * (1.0 - fractionPrinted);
|
|
}
|
|
|
|
case filamentBased:
|
|
{
|
|
// Need some file information, otherwise this method won't work
|
|
if (!fileInfoDetected || currentFileInfo.numFilaments == 0)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
// Sum up the filament usage and the filament needed
|
|
float totalFilamentNeeded = 0.0;
|
|
float extrRawTotal = 0.0;
|
|
for (size_t extruder=0; extruder < DRIVES - AXES; extruder++)
|
|
{
|
|
totalFilamentNeeded += currentFileInfo.filamentNeeded[extruder];
|
|
extrRawTotal += gCodes->GetRawExtruderPosition(extruder);
|
|
}
|
|
|
|
// If we have a reasonable amount of filament extruded, calculate estimated times left
|
|
if (totalFilamentNeeded > 0.0 && extrRawTotal > totalFilamentNeeded * ESTIMATION_MIN_FILAMENT_USAGE)
|
|
{
|
|
if (firstLayerFilament == 0.0)
|
|
{
|
|
return realPrintDuration * (totalFilamentNeeded - extrRawTotal) / extrRawTotal;
|
|
}
|
|
|
|
float filamentRate;
|
|
if (numLayerSamples != 0)
|
|
{
|
|
filamentRate = 0.0;
|
|
for (unsigned int i=0; i<numLayerSamples; i++)
|
|
{
|
|
filamentRate += filamentUsagePerLayer[i] / layerDurations[i];
|
|
}
|
|
filamentRate /= numLayerSamples;
|
|
}
|
|
else
|
|
{
|
|
filamentRate = firstLayerFilament / firstLayerDuration;
|
|
}
|
|
|
|
return (totalFilamentNeeded - extrRawTotal) / filamentRate;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case layerBased:
|
|
if (layerEstimatedTimeLeft > 0.0)
|
|
{
|
|
float timeLeft = layerEstimatedTimeLeft - (platform->Time() - lastLayerTime);
|
|
if (timeLeft > 0.0)
|
|
{
|
|
return timeLeft;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
// Get information for the specified file, or the currently printing file, in JSON format
|
|
// Get information for a file on the SD card
|
|
// Scan the buffer for a G1 Zxxx command. The buffer is null-terminated.
|
|
bool PrintMonitor::FindHeight(const char* buf, size_t len, float& height) const
|
|
{
|
|
//debugPrintf("Scanning %u bytes starting %.100s\n", len, buf);
|
|
bool inComment;
|
|
unsigned int zPos;
|
|
for(size_t i = len - 5; i > 0; i--)
|
|
{
|
|
// Look for last "G0/G1 ... Z#HEIGHT#" command as generated by common slicers
|
|
if (buf[i] == 'G' && (buf[i + 1] == '0' || buf[i + 1] == '1') && buf[i + 2] == ' ')
|
|
{
|
|
// Looks like we found a controlled move, however it could be in a comment, especially when using slic3r 1.1.1
|
|
inComment = false;
|
|
size_t j = i;
|
|
while (j != 0)
|
|
{
|
|
--j;
|
|
char c = buf[j];
|
|
if (c == '\n' || c == '\r')
|
|
{
|
|
// It's not in a comment
|
|
break;
|
|
}
|
|
if (c == ';')
|
|
{
|
|
// It is in a comment, so give up on this one
|
|
inComment = true;
|
|
break;
|
|
}
|
|
}
|
|
if (inComment)
|
|
continue;
|
|
|
|
// Find 'Z' position and grab that value
|
|
zPos = 0;
|
|
for(int j=i +3; j < len - 2; j++)
|
|
{
|
|
char c = buf[j];
|
|
if (c < ' ')
|
|
{
|
|
// Skip all whitespaces...
|
|
while (j < len - 2 && c <= ' ')
|
|
{
|
|
c = buf[++j];
|
|
}
|
|
// ...to make sure ";End" doesn't follow G0 .. Z#HEIGHT#
|
|
if (zPos != 0 && (buf[j] != ';' || buf[j + 1] != 'E'))
|
|
{
|
|
//debugPrintf("Found at offset %u text: %.100s\n", zPos, &buf[zPos + 1]);
|
|
height = strtod(&buf[zPos + 1], NULL);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
else if (c == ';')
|
|
{
|
|
// Ignore comments
|
|
break;
|
|
}
|
|
else if (c == 'Z')
|
|
{
|
|
zPos = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Scan the buffer for the layer height. The buffer is null-terminated.
|
|
bool PrintMonitor::FindLayerHeight(const char *buf, size_t len, float& layerHeight) const
|
|
{
|
|
// Look for layer_height as generated by Slic3r
|
|
const char* layerHeightStringSlic3r = "; layer_height ";
|
|
char *pos = strstr(buf, layerHeightStringSlic3r);
|
|
if (pos != NULL)
|
|
{
|
|
pos += strlen(layerHeightStringSlic3r);
|
|
while (strchr(" \t=:", *pos))
|
|
{
|
|
++pos;
|
|
}
|
|
layerHeight = strtod(pos, NULL);
|
|
return true;
|
|
}
|
|
|
|
// Look for layer height as generated by Cura
|
|
const char* layerHeightStringCura = "Layer height: ";
|
|
pos = strstr(buf, layerHeightStringCura);
|
|
if (pos != NULL)
|
|
{
|
|
pos += strlen(layerHeightStringCura);
|
|
while (strchr(" \t=:", *pos))
|
|
{
|
|
++pos;
|
|
}
|
|
layerHeight = strtod(pos, NULL);
|
|
return true;
|
|
}
|
|
|
|
// Look for layer height as generated by S3D
|
|
const char* layerHeightStringS3D = "layerHeight,";
|
|
pos = strstr(buf, layerHeightStringS3D);
|
|
if (pos != NULL)
|
|
{
|
|
pos += strlen(layerHeightStringS3D);
|
|
layerHeight = strtod(pos, NULL);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Scan the buffer for the filament used. The buffer is null-terminated.
|
|
// Returns the number of filaments found.
|
|
unsigned int PrintMonitor::FindFilamentUsed(const char* buf, size_t len, float *filamentUsed, unsigned int maxFilaments) const
|
|
{
|
|
unsigned int filamentsFound = 0;
|
|
|
|
// Look for filament usage as generated by Slic3r and Cura
|
|
const char* filamentUsedStr = "ilament used"; // comment string used by slic3r and Cura, followed by filament used and "mm"
|
|
const char* p = buf;
|
|
while (filamentsFound < maxFilaments && (p = strstr(p, filamentUsedStr)) != NULL)
|
|
{
|
|
p += strlen(filamentUsedStr);
|
|
while(strchr(" :=\t", *p) != NULL)
|
|
{
|
|
++p; // this allows for " = " from default slic3r comment and ": " from default Cura comment
|
|
}
|
|
if (isDigit(*p))
|
|
{
|
|
char* q;
|
|
filamentUsed[filamentsFound] = strtod(p, &q);
|
|
if (*q == 'm' && *(q + 1) != 'm')
|
|
{
|
|
filamentUsed[filamentsFound] *= 1000.0; // Cura outputs filament used in metres not mm
|
|
}
|
|
++filamentsFound;
|
|
}
|
|
}
|
|
|
|
// Look for filament usage as generated by S3D
|
|
if (!filamentsFound)
|
|
{
|
|
const char *filamentLengthStr = "ilament length:"; // comment string used by S3D
|
|
p = buf;
|
|
while (filamentsFound < maxFilaments && (p = strstr(p, filamentLengthStr)) != NULL)
|
|
{
|
|
p += strlen(filamentLengthStr);
|
|
while(strchr(" :=\t", *p) != NULL)
|
|
{
|
|
++p; // this allows for " = " from default slic3r comment and ": " from default Cura comment
|
|
}
|
|
if (isDigit(*p))
|
|
{
|
|
char* q;
|
|
filamentUsed[filamentsFound] = strtod(p, &q); // S3D reports filament usage in mm, no conversion needed
|
|
++filamentsFound;
|
|
}
|
|
}
|
|
}
|
|
|
|
return filamentsFound;
|
|
}
|