Skip to content

Commit

Permalink
RC4
Browse files Browse the repository at this point in the history
- #20 self.noteEditor
- #16, #15 hide empty spools
- #14 sort remaining weight in table
- #11 show remaining weight in spool selection
- #10 changed color picker behaviour
- Export of FilamentManager/SpoolManager and Import of CSV
-
  • Loading branch information
OllisGit committed Jul 28, 2020
1 parent 3d4cc49 commit e3baa2e
Show file tree
Hide file tree
Showing 21 changed files with 1,364 additions and 286 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Released](https://img.shields.io/badge/dynamic/json.svg?color=brightgreen&label=released&url=https://api.github.com/repos/OllisGit/OctoPrint-SpoolManager/releases&query=$[0].published_at)]()
![GitHub Releases (by Release)](https://img.shields.io/github/downloads/OllisGit/OctoPrint-SpoolManager/latest/total.svg)

# Release candidate 1 is out!
# Release candidates are out!

The OctoPrint-Plugin manages all spool informations and stores it in a database.

Expand All @@ -23,9 +23,12 @@ should include the following features:
- [X] Spool basic attributes, like name, color, material, vendor ...
- [X] "Used length" and "Remaining weight"
- [X] Additional notes
- [X] CSV Export of "Legacy FilamentManager-Database" and SpoolManager)function
- [X] CSV Import function
- [ ] Labels
- [ ] Provide "APIs" for [PrintJobHistory-Plugin](https://github.com/OllisGit/OctoPrint-PrintJobHistory)


### UI features
- [X] Better error-feedback (more then just the "happy-path")
- [X] List all spools
Expand All @@ -34,16 +37,15 @@ should include the following features:
- [X] Template spool
- [X] Sort spool table (Displayname, Last/First use)
- [X] Force to select a spool before printing
- [ ] Filter spool table
- [X] Filter spool table

## Planning Release #2

- [ ] External Database
- [ ] Table column visibity
- [ ] Capture Spool-Image
- [ ] Scan QR/Barcodes of a spool
- [ ] CSV Export function
- [ ] CSV Import function

- [ ] Multi Tool support


Expand All @@ -53,6 +55,7 @@ should include the following features:
![plugin-tab](screenshots/plugin-tab.png "Plugin-Tab")
-->
![listSpools-tab](screenshots/listSpools-tab.png "ListSpools-Tab")
![selectSpools-sidebar](screenshots/selectSpool-sidebar.png "SelectSpool-Sidebar")
![editSpool-dialog](screenshots/editSpool-dialog.png "EditSpool-Dialog")
![scanSpool-dialog](screenshots/scanSpool-dialog.png "ScanSpool-Dialog")

Expand Down
140 changes: 122 additions & 18 deletions octoprint_SpoolManager/DatabaseManager.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
# coding=utf-8
from __future__ import absolute_import

import datetime
import json
import os
import logging
import shutil
import sqlite3

from octoprint_SpoolManager.WrappedLoggingHandler import WrappedLoggingHandler
from peewee import *

from octoprint_SpoolManager.api import Transformer
from octoprint_SpoolManager.models import SpoolModel
from octoprint_SpoolManager.models.BaseModel import BaseModel
from octoprint_SpoolManager.models.PluginMetaDataModel import PluginMetaDataModel
from octoprint_SpoolManager.models.SpoolModel import SpoolModel

FORCE_CREATE_TABLES = False

CURRENT_DATABASE_SCHEME_VERSION = 1
CURRENT_DATABASE_SCHEME_VERSION = 2

# List all Models
MODELS = [PluginMetaDataModel, SpoolModel]
Expand Down Expand Up @@ -69,17 +73,98 @@ def _createOrUpgradeSchemeIfNecessary(self):
currentDatabaseSchemeVersion = int(schemeVersionFromDatabaseModel.value)
if (currentDatabaseSchemeVersion < CURRENT_DATABASE_SCHEME_VERSION):
# evautate upgrade steps (from 1-2 , 1...6)
print("We need to upgrade the database scheme from: '" + str(currentDatabaseSchemeVersion) + "' to: '" + str(CURRENT_DATABASE_SCHEME_VERSION) + "'")
pass
self._logger.info("We need to upgrade the database scheme from: '" + str(currentDatabaseSchemeVersion) + "' to: '" + str(CURRENT_DATABASE_SCHEME_VERSION) + "'")

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

# databaseSchemeVersion = PluginMetaDataEntity.getDatabaseSchemeVersion(cursor)
# if databaseSchemeVersion == None or FORCE_CREATE_TABLES == True:
# self._createCurrentTables(cursor, FORCE_CREATE_TABLES)
# else:
# # check from which version we need to upgrade
# # sql
# 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")
self._logger.info(" Successfully 2 -> 3")
pass


def _upgradeFrom1To2(self):
self._logger.info(" Starting 1 -> 2")
# What is changed:
# - SpoolModel: Add Column colorName
# - SpoolModel: Add Column remainingWeight (needed fro filtering, sorting)
connection = sqlite3.connect(self._databaseFileLocation)
cursor = connection.cursor()

sql = """
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE 'spo_spoolmodel' ADD 'colorName' VARCHAR(255);
ALTER TABLE 'spo_spoolmodel' ADD 'remainingWeight' REAL;
UPDATE 'spo_pluginmetadatamodel' SET value=2 WHERE key='databaseSchemeVersion';
COMMIT;
PRAGMA foreign_keys=on;
"""
cursor.executescript(sql)

connection.close()
self._logger.info("Database 'altered' successfully. Try to calculate remaining weight.")
# Calculate the rmaining weight for all current spools
with self._database.atomic() as transaction: # Opens new transaction.
try:

allSpoolModels = self.loadAllSpoolsByQuery(None)
for spoolModel in allSpoolModels:
totalWeight = spoolModel.totalWeight
usedWeight = spoolModel.usedWeight
remainingWeight = Transformer.calculateRemainingWeight(usedWeight, totalWeight)
if (remainingWeight != None):
spoolModel.remainingWeight = remainingWeight
spoolModel.save()

# do expicit commit
transaction.commit()
except Exception as e:
# Because this block of code is wrapped with "atomic", a
# new transaction will begin automatically after the call
# to rollback().
transaction.rollback()
self._logger.exception("Could not upgrade database scheme from 1 To 2:" + str(e))

self.sendErrorMessageToClient("error", "DatabaseManager", "Could not upgrade database scheme V1 to V2. See OctoPrint.log for details!")
pass



self._logger.info(" Successfully 1 -> 2")
pass






def _createDatabaseTables(self):
self._logger.info("Creating new database tables for spoolmanager-plugin")
self._database.connect(reuse_if_open=True)
Expand All @@ -90,15 +175,11 @@ def _createDatabaseTables(self):
self._database.close()

################################################################################################### public functions
# datapasePath '/Users/o0632/Library/Application Support/OctoPrint/data/PrintJobHistory'

# def getDatabaseFileLocation(self):
# return self._databaseFileLocation


def initDatabase(self, databasePath, sendMessageToClient):
self._logger.info("Init DatabaseManager")
self.sendMessageToClient = sendMessageToClient
self._databasePath = databasePath
self._databaseFileLocation = os.path.join(databasePath, "spoolmanager.db")

self._logger.info("Creating database in: " + str(self._databaseFileLocation))
Expand Down Expand Up @@ -131,6 +212,24 @@ def showSQLLogging(self, enabled):
logger.setLevel(logging.ERROR)
self._sqlLogger.setLevel(logging.ERROR)


def backupDatabaseFile(self, backupFolder):
now = datetime.datetime.now()
currentDate = now.strftime("%Y%m%d-%H%M")
backupDatabaseFileName = "spoolmanager-backup-"+currentDate+".db"
backupDatabaseFilePath = os.path.join(backupFolder, backupDatabaseFileName)
if not os.path.exists(backupDatabaseFilePath):
shutil.copy(self._databaseFileLocation, backupDatabaseFilePath)
self._logger.info("Backup of spoolmanager database created '"+backupDatabaseFilePath+"'")
else:
self._logger.warn("Backup of spoolmanager database ('" + backupDatabaseFilePath + "') is already present. No backup created.")
return backupDatabaseFilePath

def reCreateDatabase(self):
self._logger.info("ReCreating Database")
self._createDatabase(True)

################################################################################################ Database Model Methods
def loadSpool(self, databaseId):
try:
return SpoolModel.get_by_id(databaseId)
Expand Down Expand Up @@ -175,11 +274,11 @@ def loadAllSpoolsByQuery(self, tableQuery):
limit = int(tableQuery["to"])
sortColumn = tableQuery["sortColumn"]
sortOrder = tableQuery["sortOrder"]
# not needed at the moment filterName = tableQuery["filterName"]
filterName = tableQuery["filterName"]

myQuery = SpoolModel.select().offset(offset).limit(limit)
# if (filterName == "onlySuccess"):
# myQuery = myQuery.where(PrintJobModel.printStatusResult == "success")
if (filterName == "hideEmptySpools"):
myQuery = myQuery.where( (SpoolModel.remainingWeight > 0) | (SpoolModel.remainingWeight == None))
# elif (filterName == "onlyFailed"):
# myQuery = myQuery.where(PrintJobModel.printStatusResult != "success")

Expand All @@ -198,6 +297,11 @@ def loadAllSpoolsByQuery(self, tableQuery):
myQuery = myQuery.order_by(SpoolModel.firstUse.desc())
else:
myQuery = myQuery.order_by(SpoolModel.firstUse)
if ("remaining" == sortColumn):
if ("desc" == sortOrder):
myQuery = myQuery.order_by(SpoolModel.remainingWeight.desc())
else:
myQuery = myQuery.order_by(SpoolModel.remainingWeight)
return myQuery


Expand Down
16 changes: 7 additions & 9 deletions octoprint_SpoolManager/WrappedLoggingHandler.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@


from logging import StreamHandler

class WrappedLoggingHandler(StreamHandler):

def __init__(self, wrappedLogger):
StreamHandler.__init__(self)
self.wrappedLogger = wrappedLogger
class WrappedLoggingHandler(StreamHandler):

def __init__(self, wrappedLogger):
StreamHandler.__init__(self)
self.wrappedLogger = wrappedLogger

def emit(self, record):
def emit(self, record):
msg = self.format(record)
self.wrappedLogger.debug(msg) # this is it!!!!
# self.wrappedLogger.handle(record)
self.wrappedLogger.debug(msg) # this is it!!!!
# self.wrappedLogger.handle(record)
63 changes: 59 additions & 4 deletions octoprint_SpoolManager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def initialize(self):
self._filamentOdometer = FilamentOdometer()
# TODO no idea what this thing is doing in detail self._filamentOdometer.set_g90_extruder(self._settings.getBoolean(["feature", "g90InfluencesExtruder"]))

self._filamentManagerPluginImplementation = None
self._filamentManagerPluginImplementationState = None

self._lastPrintState = None

self.metaDataFilamentLength = None
Expand Down Expand Up @@ -148,6 +151,49 @@ def _sendMessageToClient(self, type, title, message):
title= title,
message=message))



def _checkForMissingPluginInfos(self, sendToClient=False):

pluginInfo = self._getPluginInformation("filamentmanager")
self._filamentManagerPluginImplementationState = pluginInfo[0]
self._filamentManagerPluginImplementation = pluginInfo[1]

self._logger.info("Plugin-State: "
"filamentmanager=" + self._filamentManagerPluginImplementationState + " ")
pass

# get the plugin with status information
# [0] == status-string
# [1] == implementaiton of the plugin
def _getPluginInformation(self, pluginKey):

status = None
implementation = None

if pluginKey in self._plugin_manager.plugins:
plugin = self._plugin_manager.plugins[pluginKey]
if plugin != None:
if (plugin.enabled == True):
status = "enabled"
# for OP 1.4.x we need to check agains "icompatible"-attribute
if (hasattr(plugin, 'incompatible') ):
if (plugin.incompatible == False):
implementation = plugin.implementation
else:
status = "incompatible"
else:
# OP 1.3.x
implementation = plugin.implementation
pass
else:
status = "disabled"
else:
status = "missing"

return [status, implementation]


def _calculateWeight(self, length, diameter, density):
radius = diameter / 2.0;
volume = length * math.pi * (radius * radius) / 1000
Expand Down Expand Up @@ -269,7 +315,7 @@ def _on_clientOpened(self, payload):
# start-workaround https://github.com/foosel/OctoPrint/issues/3400
import time
time.sleep(2)
selectedSpoolAsDict = {}
selectedSpoolAsDict = None

# Send plugin storage information
## Storage
Expand All @@ -286,7 +332,8 @@ def _on_clientOpened(self, payload):

self._sendDataToClient(dict(action="initalData",
databaseFileLocation=databaseFileLocation,
selectedSpool=selectedSpoolAsDict
selectedSpool=selectedSpoolAsDict,
isFilamentManagerPluginAvailable=self._filamentManagerPluginImplementation != None
))

pass
Expand All @@ -304,6 +351,8 @@ def _on_file_selected(self, payload):
######################################################################################### Hooks and public functions

def on_after_startup(self):
# check if needed plugins were available
self._checkForMissingPluginInfos()
pass

# Listen to all g-code which where already sent to the printer (thread: comm.sending_thread)
Expand Down Expand Up @@ -364,13 +413,18 @@ def get_settings_defaults(self):

settings = dict()

## Genral
# Not visible
settings[SettingsKeys.SETTINGS_KEY_SELECTED_SPOOL_DATABASE_ID] = None
settings[SettingsKeys.SETTINGS_KEY_HIDE_EMPTY_SPOOL_IN_SIDEBAR] = False
## Genral
settings[SettingsKeys.SETTINGS_KEY_REMINDER_SELECTING_SPOOL] = True
settings[SettingsKeys.SETTINGS_KEY_WARN_IF_SPOOL_NOT_SELECTED] = True
settings[SettingsKeys.SETTINGS_KEY_WARN_IF_FILAMENT_NOT_ENOUGH] = True
settings[SettingsKeys.SETTINGS_KEY_CURRENCY_SYMBOL] = "€"

## Export / Import
settings[SettingsKeys.SETTINGS_KEY_IMPORT_CSV_MODE] = SettingsKeys.KEY_IMPORTCSV_MODE_APPEND

## Debugging
settings[SettingsKeys.SETTINGS_KEY_SQL_LOGGING_ENABLED] = False

Expand Down Expand Up @@ -400,7 +454,8 @@ def get_assets(self):
"js/TableItemHelper.js",
"js/SpoolManager.js",
"js/SpoolManager-APIClient.js",
"js/SpoolManager-EditSpoolDialog.js"
"js/SpoolManager-EditSpoolDialog.js",
"js/SpoolManager-ImportDialog.js"
],
css=[
"css/quill.snow.css",
Expand Down
Loading

0 comments on commit e3baa2e

Please sign in to comment.