Skip to content

Commit

Permalink
New Version 1.2.0
Browse files Browse the repository at this point in the history
- Added slicer settings to the print-job
- Changed database migration mechanism
  • Loading branch information
OllisGit committed Jun 1, 2020
1 parent 57348f1 commit 93cd9ca
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 34 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ If you like it, I would be thankful about a cup of coffee :)
- [x] Used/Calculated length
- [x] Used weight
- [x] Filament cost
- [x] Slicer Settings (look [here](https://github.com/OllisGit/OctoPrint-PrintJobHistory/wiki/Slicer-Settings) for "how to use it")


### UI features
- [x] List all printjobs
Expand Down
55 changes: 47 additions & 8 deletions octoprint_PrintJobHistory/DatabaseManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
FORCE_CREATE_TABLES = False
# SQL_LOGGING = True

CURRENT_DATABASE_SCHEME_VERSION = 2
CURRENT_DATABASE_SCHEME_VERSION = 3

# List all Models
MODELS = [PluginMetaDataModel, PrintJobModel, FilamentModel, TemperatureModel]
Expand Down Expand Up @@ -58,22 +58,62 @@ def _createOrUpgradeSchemeIfNecessary(self):
self._logger.info("We need to upgrade the database scheme from: '" + str(currentDatabaseSchemeVersion) + "' to: '" + str(CURRENT_DATABASE_SCHEME_VERSION) + "'")

try:
self._backupDatabaseFile()
if (currentDatabaseSchemeVersion < 2):
self._upgradeFrom1To2()
self.backupDatabaseFile(self._databasePath)
self._upgradeDatabase(currentDatabaseSchemeVersion, CURRENT_DATABASE_SCHEME_VERSION)
except Exception as e:
self._logger.error("Error during database upgrade!!!!")
self._logger.error(str(e))
self._logger.exception(e)
return
self._logger.info("Database-scheme successfully upgraded.")
pass

def _upgradeDatabase(self,currentDatabaseSchemeVersion, targetDatabaseSchemeVersion):

migrationFunctions = [self._upgradeFrom1To2, self._upgradeFrom2To3, self._upgradeFrom3To4, self._upgradeFrom4To5]

for migrationMethodIndex in range(currentDatabaseSchemeVersion -1, targetDatabaseSchemeVersion -1):
self._logger.info("Database migration from '" + str(migrationMethodIndex + 1) + "' to '" + str(migrationMethodIndex + 2) + "'")
migrationFunctions[migrationMethodIndex]()
pass
pass

def _upgradeFrom4To5(self):
self._logger.info(" Starting 4 -> 5")

def _upgradeFrom3To4(self):
self._logger.info(" Starting 3 -> 4")

def _upgradeFrom2To3(self):
self._logger.info(" Starting 2 -> 3")
# What is changed:
# - PrintJobModel:
# - Add Column: slicerSettingsAsText

connection = sqlite3.connect(self._databaseFileLocation)
cursor = connection.cursor()

sql = """
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE 'pjh_printjobmodel' ADD 'slicerSettingsAsText' TEXT;
UPDATE 'pjh_pluginmetadatamodel' SET value=3 WHERE key='databaseSchemeVersion';
COMMIT;
PRAGMA foreign_keys=on;
"""
cursor.executescript(sql)

connection.close()
self._logger.info(" Successfully 2 -> 3")
pass


def _upgradeFrom1To2(self):
self._logger.info(" Starting 1 -> 2")
# What is changed:
# - PrintJobModel: Add Column fileOrigin
# - FilamentModel: Several ColumnTypes were wrong


connection = sqlite3.connect(self._databaseFileLocation)
cursor = connection.cursor()

Expand Down Expand Up @@ -115,7 +155,6 @@ def _upgradeFrom1To2(self):
cursor.executescript(sql)

connection.close()

pass


Expand Down
19 changes: 15 additions & 4 deletions octoprint_PrintJobHistory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from peewee import DoesNotExist

from .common.SettingsKeys import SettingsKeys
from .common.SlicerSettingsParser import SlicerSettingsParser
from .api.PrintJobHistoryAPI import PrintJobHistoryAPI
from .api import TransformPrintJob2JSON
from .DatabaseManager import DatabaseManager
Expand Down Expand Up @@ -302,10 +303,6 @@ def _addTemperatureToPrintModel(self, printJobModel, bedTemp, toolTemp):

#### print job finished
def _printJobFinished(self, printStatus, payload):
self._currentPrintJobModel.printEndDateTime = datetime.datetime.now()
self._currentPrintJobModel.duration = (self._currentPrintJobModel.printEndDateTime - self._currentPrintJobModel.printStartDateTime).total_seconds()
self._currentPrintJobModel.printStatusResult = printStatus

captureMode = self._settings.get([SettingsKeys.SETTINGS_KEY_CAPTURE_PRINTJOBHISTORY_MODE])
if (captureMode == SettingsKeys.KEY_CAPTURE_PRINTJOBHISTORY_MODE_NONE):
return
Expand All @@ -322,6 +319,20 @@ def _printJobFinished(self, printStatus, payload):
# capture the print
if (captureThePrint == True):
self._logger.info("Start capturing print job")
# Core Data
self._currentPrintJobModel.printEndDateTime = datetime.datetime.now()
self._currentPrintJobModel.duration = (
self._currentPrintJobModel.printEndDateTime - self._currentPrintJobModel.printStartDateTime).total_seconds()
self._currentPrintJobModel.printStatusResult = printStatus

# Slicer Settings
selectedFilename = payload.get("path")
selectedFile = self._file_manager.path_on_disk(payload.get("origin"), selectedFilename)
slicerSettings = SlicerSettingsParser(self._logger).extractSlicerSettings(selectedFile, None)
if (slicerSettings.settingsAsText != None and len(slicerSettings.settingsAsText) != 0):
self._currentPrintJobModel.slicerSettingsAsText = slicerSettings.settingsAsText

# Image
if self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_TAKE_SNAPSHOT_AFTER_PRINT]):
self._cameraManager.takeSnapshotAsync(
CameraManager.buildSnapshotFilename(self._currentPrintJobModel.printStartDateTime),
Expand Down
202 changes: 202 additions & 0 deletions octoprint_PrintJobHistory/common/SlicerSettingsParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# coding=utf-8
from __future__ import absolute_import

import logging
import os

MAX_GCODE_LINES_BEFORE_STOP_READING = 10
LINE_RESULT_GCODE = "LR:gcode"
LINE_RESULT_SETTINGS = "LR:settings"
LINE_RESULT_OTHERS = "LR:others"

# Model of Slicer Settings
class SlicerSettings(object):

def __init__(self):
self.settingsAsText = ""
self.settingsAsDict = dict()

def isKeyAlreadyExtracted(self, key):
return key in self.settingsAsDict

def addKeyValueSetting(self, key, value):
self.settingsAsDict.update({key:value})

def addKeyValueSettingsAsText(self, settingsText):
self.settingsAsText += settingsText

###########################################################
# Parse reads all 'key = values' out of the gcode file
# - It reads to top and the bottom
# - It reads till a block of gcode-commands will be detected
# - No overlappig between the top-block and the bottom-block
class SlicerSettingsParser(object):

def __init__(self, parentLogger):
self._logger = logging.getLogger(parentLogger.name + "." + self.__class__.__name__)
# self._logger.setLevel(logging.DEBUG)

def extractSlicerSettings(self, gcodeFilePath, includedSettingsKeyList):

self._logger.info("Start parsing Slicer-Settings")
# Read the file from top
# Read the file from bottom
# read key-value

# - Make sure that the top-region is not overlappig with bottom region
# - Stop reading after you read a definied amount of gcode continusly (no interruption -> gcode-block)
slicerSettings = SlicerSettings()

lastLineResult = None # type of line
gcodeCount = 0
readingOrder = 0 # 0=forward; 1=reverse 2++=finished
reverseReadinStarted = False
lastTopFilePosition = 0 # needed for overlapping detection of top-region and bottom-region
lineNumber = 0
with open(gcodeFilePath, 'r') as fileHandle:
while True:

if (readingOrder == 0):
# Forward reading
line = fileHandle.readline()
lastTopFilePosition = fileHandle.tell()
lineNumber += 1
pass
else:
# Reverse reading
# Jump to the end
if (reverseReadinStarted == False):
fileHandle.seek(0, os.SEEK_END)
reverseReadinStarted = True
lineNumber = 0
gcodeCount = 0
line = self.nextReversedLine(fileHandle, lastTopFilePosition)
lineNumber += 1

if (line == ''):
# EOF reached
readingOrder += 1

if (readingOrder == 1):
lineNumber = 0
continue
else:
# finaly top/Bottom reading is done
break

lineResult = self.processLine(line, slicerSettings)
# print(lineResult)
if (lineResult == LINE_RESULT_GCODE):
gcodeCount += 1
if (lastLineResult == LINE_RESULT_GCODE):
if (gcodeCount >= MAX_GCODE_LINES_BEFORE_STOP_READING):
# forward reading finished, switch to reverse
if (reverseReadinStarted == True):
# finaly top/Bottom reading is done
break
readingOrder += 1
continue
else:
gcodeCount = 0
lastLineResult = lineResult

debugInformation = "ORDER: " + str(readingOrder) + " LineNumber: " + str(lineNumber) + " GCodeFound: " + str(
gcodeCount)
# print(debugInformation)
self._logger.debug(debugInformation)

pass
self._logger.debug(" Slicer-Settings:")
self._logger.debug(slicerSettings.settingsAsDict)
self._logger.info("Finished parsing Slicer-Settings")
return slicerSettings


# Process a Single-Line
def processLine(self, line, slicerSettings):
# print(line)
if (line == None or line == '' ):
# EMPTY
return LINE_RESULT_OTHERS

line = line.lstrip()
if (len(line) == 0):
# EMPTY
return LINE_RESULT_OTHERS

if (line[0] == ";"):
# special comments
if ("enerated" in line):
key = "generated by"
value = line[1:]
slicerSettings.addKeyValueSetting(key, value)
slicerSettings.addKeyValueSettingsAsText(line)
return LINE_RESULT_SETTINGS
# Cura put JSON fragments to SETTINGS2_ comments -> ignore it
if (";SETTING_" in line):
return LINE_RESULT_OTHERS

# KeyValue extraction
if ('=' in line):
keyValue = line.split('=', 1) # 1 == only the first =
key = keyValue[0].strip()
value = keyValue[1].strip()
if (slicerSettings.isKeyAlreadyExtracted(key) == False):
slicerSettings.addKeyValueSetting(key, value)
slicerSettings.addKeyValueSettingsAsText(line)
return LINE_RESULT_SETTINGS

return LINE_RESULT_OTHERS

# Must be a gcode
return LINE_RESULT_GCODE

def nextReversedLine(self, fileHandle, lastTopFilePosition):
line = ''

filePosition = fileHandle.tell()
if (filePosition <=0):
return line
if (filePosition <= lastTopFilePosition):
self._logger.debug("We reached the already parsed top-region during reverse-parsing")
print("We reached the already parsed top-region during reverse-parsing")
return line

while filePosition >= 0:
fileHandle.seek(filePosition)
current_char = fileHandle.read(1)
line += current_char

if (filePosition == 0):
line = line[::-1]
fileHandle.seek(0)
break

fileHandle.seek(filePosition - 1)
next_char = fileHandle.read(1)
if next_char == "\n":
line = line[::-1]
# HACK
if len(line)==0:
line = " "
fileHandle.seek(filePosition - 1)
break
filePosition -= 1

return line




if __name__ == '__main__':
parsingFilename = "/Users/o0632/0_Projekte/3DDruck/OctoPrint/OctoPrint-PrintJobHistory/testdata/slicer-settings/CURA_schieberdeckel2.gcode"
#parsingFilename = "/Users/o0632/0_Projekte/3DDruck/OctoPrint/OctoPrint-PrintJobHistory/testdata/slicer-settings/simple.gcode"


testLogger = logging.getLogger("testLogger")
settingsParser = SlicerSettingsParser(testLogger)
slicerSettings = settingsParser.extractSlicerSettings(parsingFilename, None)

print("TEXT: "+slicerSettings.settingsAsText)
print(slicerSettings.settingsAsDict)
print("done")
1 change: 1 addition & 0 deletions octoprint_PrintJobHistory/models/PrintJobModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class PrintJobModel(BaseModel):
noteHtml = CharField(null=True)
printedLayers = CharField(null=True)
printedHeight = CharField(null=True)
slicerSettingsAsText = TextField(null=True)

allFilaments = None
allTemperatures = None
Expand Down
Loading

0 comments on commit 93cd9ca

Please sign in to comment.