
Merged in zpl's latest changes to Network and Print Monitor modules, providing DHCP and Netbios name support Added command M999 S4321 to unlock flash memory, reset and boot to BOSSA port Z dive height is now relative to Z probe trigger height and default is reduced to 3mm Added experimental support for acoustic probe for delta printers Bug fix: firmware once again prevents Z homing before X and Y are homed, if the Z probe is used for Z homing Various code tidying
740 lines
21 KiB
C++
740 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::Init()
|
|
{
|
|
longWait = platform->Time();
|
|
}
|
|
|
|
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(size_t 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(size_t 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
platform->ClassReport(longWait);
|
|
}
|
|
|
|
void PrintMonitor::StartingPrint(const char* filename)
|
|
{
|
|
fileInfoDetected = GetFileInfo(platform->GetGCodeDir(), filename, currentFileInfo);
|
|
strncpy(fileBeingPrinted, filename, ARRAY_SIZE(fileBeingPrinted));
|
|
fileBeingPrinted[ARRAY_UPB(fileBeingPrinted)] = 0;
|
|
}
|
|
|
|
void PrintMonitor::StartedPrint()
|
|
{
|
|
printStartTime = platform->Time();
|
|
}
|
|
|
|
void PrintMonitor::StoppedPrint()
|
|
{
|
|
currentLayer = numLayerSamples = 0;
|
|
firstLayerDuration = firstLayerHeight = firstLayerFilament = firstLayerProgress = 0.0;
|
|
layerEstimatedTimeLeft = printStartTime = warmUpDuration = 0.0;
|
|
lastLayerTime = lastLayerFilament = 0.0;
|
|
}
|
|
|
|
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 = 0;
|
|
info.generatedBy[0] = 0;
|
|
for(size_t extr=0; extr<DRIVES - AXES; extr++)
|
|
{
|
|
info.filamentNeeded[extr] = 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 = nFilaments;
|
|
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 = nFilaments;
|
|
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;
|
|
}
|