diff --git a/octoprint_SpoolManager/DatabaseManager.py b/octoprint_SpoolManager/DatabaseManager.py index a3670996..d7d6ca93 100644 --- a/octoprint_SpoolManager/DatabaseManager.py +++ b/octoprint_SpoolManager/DatabaseManager.py @@ -12,6 +12,7 @@ from peewee import * from octoprint_SpoolManager.api import Transformer +from octoprint_SpoolManager.common import StringUtils from octoprint_SpoolManager.models.BaseModel import BaseModel from octoprint_SpoolManager.models.PluginMetaDataModel import PluginMetaDataModel from octoprint_SpoolManager.models.SpoolModel import SpoolModel @@ -280,7 +281,6 @@ def _executeSQLQuietly(self, cursor, sqlStatement): self._logger.error(sqlStatement) self._logger.exception(e) - def _upgradeFrom4To5_HACK(self, sqlStatement): connection = sqlite3.connect(self._databaseSettings.fileLocation) @@ -814,13 +814,72 @@ def databaseCallMethode(): if (tableQuery == None): return SpoolModel.select().order_by(SpoolModel.created.desc()) - offset = int(tableQuery["from"]) - limit = int(tableQuery["to"]) sortColumn = tableQuery["sortColumn"] sortOrder = tableQuery["sortOrder"] filterName = tableQuery["filterName"] - myQuery = SpoolModel.select().offset(offset).limit(limit) + if ("selectedPageSize" in tableQuery and StringUtils.to_native_str(tableQuery["selectedPageSize"]) == "all"): + myQuery = SpoolModel.select() + else: + offset = int(tableQuery["from"]) + limit = int(tableQuery["to"]) + myQuery = SpoolModel.select().offset(offset).limit(limit) + + if ("materialFilter" in tableQuery): + materialFilter = tableQuery["materialFilter"] + vendorFilter = tableQuery["vendorFilter"] + colorFilter = tableQuery["colorFilter"] + + # materialFilter + # u'ABS,PLA' + # u'' + # u'all' + materialFilter = StringUtils.to_native_str(materialFilter) + if (materialFilter != "all"): + if (StringUtils.isEmpty(colorFilter)): + myQuery = myQuery.where( (SpoolModel.material == '') ) + else: + allMaterials = materialFilter.split(",") + myQuery = myQuery.where(SpoolModel.material.in_(allMaterials)) + # for material in allMaterials: + # myQuery = myQuery.orwhere((SpoolModel.material == material)) + # vendorFilter + # u'MatterMost,TheFactory' + # u'' + # u'all' + vendorFilter = StringUtils.to_native_str(vendorFilter) + if (vendorFilter != "all"): + if (StringUtils.isEmpty(vendorFilter)): + myQuery = myQuery.where( (SpoolModel.vendor == '') ) + else: + allVendors = vendorFilter.split(",") + myQuery = myQuery.where(SpoolModel.vendor.in_(allVendors)) + # for vendor in allVendors: + # myQuery = myQuery.orwhere((SpoolModel.vendor == vendor)) + # colorFilter + # u'#ff0000;red,#ff0000;keinRot,#ff0000;deinRot,#ff0000;meinRot,#ffff00;yellow' + # u'' + # u'all' + colorFilter = StringUtils.to_native_str(colorFilter) + if (colorFilter != "all" and StringUtils.isNotEmpty(colorFilter)): + allColorObjects = colorFilter.split(",") + allColors = [] + allColorNames = [] + for colorObject in allColorObjects: + colorCodeColorName = colorObject.split(";") + color = colorCodeColorName[0] + colorName = colorCodeColorName[1] + allColors.append(color) + allColorNames.append(colorName) + myQuery = myQuery.where(SpoolModel.color.in_(allColors)) + myQuery = myQuery.where(SpoolModel.colorName.in_(allColorNames)) + + # + # myQuery = myQuery.orwhere( (SpoolModel.color == color) & (SpoolModel.colorName == colorName) ) + pass + + mySqlText = myQuery.sql() + if (filterName == "hideEmptySpools"): myQuery = myQuery.where( (SpoolModel.remainingWeight > 0) | (SpoolModel.remainingWeight == None)) if (filterName == "hideInactiveSpools"): @@ -943,7 +1002,6 @@ def databaseCallMethode(): def loadCatalogMaterials(self, withReusedConnection=False): def databaseCallMethode(): result = set() - result.add("") myQuery = SpoolModel.select(SpoolModel.material).distinct() for spool in myQuery: value = spool.material @@ -956,7 +1014,6 @@ def databaseCallMethode(): def loadCatalogLabels(self, tableQuery, withReusedConnection=False): def databaseCallMethode(): result = set() - result.add("") myQuery = SpoolModel.select(SpoolModel.labels).distinct() for spool in myQuery: value = spool.labels @@ -968,6 +1025,23 @@ def databaseCallMethode(): return self._handleReusableConnection(databaseCallMethode, withReusedConnection, "loadCatalogLabels", set()) + def loadCatalogColors(self, withReusedConnection=False): + def databaseCallMethode(): + result = [] + myQuery = SpoolModel.select(SpoolModel.color, SpoolModel.colorName).distinct() + for spool in myQuery: + value = spool.color + if (value != None): + colorInfo = { + "colorId": spool.color + ";" + spool.colorName, + "color": spool.color, + "colorName": spool.colorName + } + result.append(colorInfo) + return result; + + return self._handleReusableConnection(databaseCallMethode, withReusedConnection, "loadCatalogColors", set()) + def deleteSpool(self, databaseId, withReusedConnection=False): def databaseCallMethode(): with self._database.atomic() as transaction: # Opens new transaction. diff --git a/octoprint_SpoolManager/__init__.py b/octoprint_SpoolManager/__init__.py index 59d2151e..e76c8f60 100644 --- a/octoprint_SpoolManager/__init__.py +++ b/octoprint_SpoolManager/__init__.py @@ -70,93 +70,37 @@ def checkRemainingFilament(self, forToolIndex=None): Checks if all spools or single spool includes enough filament :param forToolIndex check only for the provided toolIndex - :return: 'true' if filament is enough, 'false' if not + :return: see """ - shouldWarn = self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_WARN_IF_FILAMENT_NOT_ENOUGH]) - if not shouldWarn: - return True # - check, if spool change in pause-mode # - check if new spool fits for current printjob selectedSpools = self.loadSelectedSpools() - result = True - - for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): - if forToolIndex is not None and forToolIndex != toolIndex: - continue - selectedSpool = selectedSpools[toolIndex] if toolIndex < len(selectedSpools) else None - - if selectedSpool is None: - continue - - diameter = selectedSpool.diameter - density = selectedSpool.density - totalWeight = selectedSpool.totalWeight - usedWeight = selectedSpool.usedWeight - - # need attributes present: diameter, density, totalWeight - missing_fields = [] - if diameter is None: - missing_fields.append('diameter') - if diameter is None: - missing_fields.append('density') - if totalWeight is None: - missing_fields.append('total weight') - if usedWeight is None: - usedWeight = 0.0 - - if missing_fields: - self._sendMessageToClient( - "warning", "Filament prediction not possible!", - "Following fields not set in Spool '%s' (in tool %d): %s" % (selectedSpool.displayName, toolIndex, ', '.join(missing_fields)) - ) - result = False - continue - - not_a_number_fields = [] - try: - diameter = float(diameter) - except ValueError: - not_a_number_fields.append('diameter') - try: - density = float(density) - except ValueError: - not_a_number_fields.append('density') - try: - totalWeight = float(totalWeight) - except ValueError: - not_a_number_fields.append('totalweight') - try: - usedWeight = float(usedWeight) - except ValueError: - not_a_number_fields.append('used weight') - - if not_a_number_fields: - self._sendMessageToClient( - "warning", "Filament prediction not possible!", - "One of the needed fields are not a number in Spool '%s' (in tool %d): %s" % (selectedSpool.displayName, toolIndex, ', '.join(not_a_number_fields)) - ) - result = False - continue - - # Benötigtes Gewicht = gewicht(geplante länge, durchmesser, dichte) - requiredWeight = int(self._calculateWeight(filamentLength, diameter, density)) - - # Vorhanden Gewicht = Gesamtgewicht - Verbrauchtes Gewicht - remainingWeight = totalWeight - usedWeight - - if remainingWeight < requiredWeight: - self._sendMessageToClient( - "warning", "Filament not enough!", - "Required on tool %d: %dg, available from Spool '%s': '%dg'" % (toolIndex, requiredWeight, selectedSpool.displayName, remainingWeight) - ) - result = False - continue - - return result + requiredWeightResult = self._evaluateRequiredWeight(selectedSpools, forToolIndex, shouldWarn) + # "metaDataMissing": metaDataMissing, + # "warnUser": fromPluginSettings, + # "attributesMissing": someAttributesMissing, + # "notEnough": notEnough, + # "detailedSpoolResult": [ + # "toolIndex": toolIndex, + # "requiredWeight": requiredWeight, + # "requiredLength": filamentLength, + # "remainingWeight": remainingWeight, + # "diameter": diameter, + # "density": density, + # "notEnough": notEnough, + # "spoolSelected": True + # ] + + # for a single check, don't send the info to the browser + if (forToolIndex == None): + requiredWeightResult["action"] = "requiredFilamentChanged" + self._sendDataToClient(requiredWeightResult) + + return requiredWeightResult def set_temp_offsets(self, toolIndex, spoolModel): toolOffsetEnabled = self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_TOOL_OFFSET_ENABLED]) @@ -182,8 +126,6 @@ def _sendDataToClient(self, payloadDict): self._plugin_manager.send_plugin_message(self._identifier, payloadDict) - - def _sendMessageToClient(self, type, title, message): self._logger.warning("SendToClient: " + type + "#" + title + "#" + message) self._sendDataToClient(dict(action="showPopUp", @@ -191,7 +133,6 @@ def _sendMessageToClient(self, type, title, message): title= title, message=message)) - def _checkForMissingPluginInfos(self, sendToClient=False): pluginInfo = self._getPluginInformation("filamentmanager") @@ -232,6 +173,141 @@ def _getPluginInformation(self, pluginKey): return [status, implementation] + def _readingFilamentMetaData(self): + filamentLengthPresentInMeta = False + self.metaDataFilamentLengths = [] + if ("job" in self._printer.get_current_data()): + jobData = self._printer.get_current_data()["job"] + if ("file" in jobData): + fileData = jobData["file"] + origin = fileData["origin"] + path = fileData["path"] + if (origin != None and path != None): + metadata = self._file_manager.get_metadata(origin, path) + if ("analysis" in metadata): + if ("filament" in metadata["analysis"]): + for toolName, toolData in metadata["analysis"]["filament"].items(): + toolIndex = int(toolName[4:]) + self.metaDataFilamentLengths += [0.0] * (toolIndex + 1 - len(self.metaDataFilamentLengths)) + self.metaDataFilamentLengths[toolIndex] = toolData["length"] + filamentLengthPresentInMeta = True + return filamentLengthPresentInMeta + + def _evaluateRequiredWeight(self, selectedSpools, forToolIndex=None, warnUser=False): + + self._readingFilamentMetaData() + metaDataMissing = len(self.metaDataFilamentLengths) <= 0 + someAttributesMissing = False + overallNotEnough = False + requiredWeightResultDict = { + "metaDataMissing": metaDataMissing, + "warnUser": warnUser, + "attributesMissing": someAttributesMissing, + "notEnough": overallNotEnough, + "detailedSpoolResult": [] + } + if (metaDataMissing == True): + return requiredWeightResultDict + + # loop over all tools + for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): + if forToolIndex is not None and forToolIndex != toolIndex: + continue + selectedSpool = selectedSpools[toolIndex] if toolIndex < len(selectedSpools) else None + + if (selectedSpool != None): + diameter = selectedSpool.diameter + density = selectedSpool.density + totalWeight = selectedSpool.totalWeight + usedWeight = selectedSpool.usedWeight + + # need attributes present: diameter, density, totalWeight + missing_fields = [] + if diameter is None: + missing_fields.append('diameter') + if density is None: + missing_fields.append('density') + if totalWeight is None: + missing_fields.append('total weight') + if usedWeight is None: + usedWeight = 0.0 + + if missing_fields: + if (warnUser == True): + self._sendMessageToClient( + "warning", "Filament prediction not possible!", + "Following fields not set in Spool '%s' (in tool %d): %s" % (selectedSpool.displayName, toolIndex, ', '.join(missing_fields)) + ) + someAttributesMissing = True + else: + not_a_number_fields = [] + try: + diameter = float(diameter) + except ValueError: + not_a_number_fields.append('diameter') + try: + density = float(density) + except ValueError: + not_a_number_fields.append('density') + try: + totalWeight = float(totalWeight) + except ValueError: + not_a_number_fields.append('totalweight') + try: + usedWeight = float(usedWeight) + except ValueError: + not_a_number_fields.append('used weight') + + if not_a_number_fields: + if (warnUser == True): + self._sendMessageToClient( + "warning", "Filament prediction not possible!", + "One of the needed fields are not a number in Spool '%s' (in tool %d): %s" % (selectedSpool.displayName, toolIndex, ', '.join(not_a_number_fields)) + ) + someAttributesMissing = True + else: + # Benötigtes Gewicht = gewicht(geplante länge, durchmesser, dichte) + requiredWeight = int(self._calculateWeight(filamentLength, diameter, density)) + + # Vorhanden Gewicht = Gesamtgewicht - Verbrauchtes Gewicht + remainingWeight = totalWeight - usedWeight + + notEnough = False + if remainingWeight < requiredWeight and requiredWeight > 0: + if (warnUser == True): + self._sendMessageToClient( + "warning", "Filament not enough!", + "Required on tool %d: %dg, available from Spool '%s': '%dg'" % (toolIndex, requiredWeight, selectedSpool.displayName, remainingWeight) + ) + notEnough = True + overallNotEnough = True + + detailedSpoolResultItem = { + "toolIndex": toolIndex, + "requiredWeight": requiredWeight, + "requiredLength": filamentLength, + "remainingWeight": remainingWeight, + "diameter": diameter, + "density": density, + "notEnough": notEnough, + "spoolSelected": True + } + requiredWeightResultDict["detailedSpoolResult"].append(detailedSpoolResultItem) + else: + # No selected spool for this tool-index, just create an simple entry + detailedSpoolResultItem = { + "toolIndex": toolIndex, + "requiredLength": filamentLength, + "spoolSelected": False, + } + requiredWeightResultDict["detailedSpoolResult"].append(detailedSpoolResultItem) + pass + + requiredWeightResultDict["attributesMissing"] = someAttributesMissing + requiredWeightResultDict["notEnough"] = overallNotEnough + + return requiredWeightResultDict + def _calculateWeight(self, length, diameter, density): radius = diameter / 2.0; @@ -254,8 +330,6 @@ def _buildDatabaseSettingsFromPluginSettings(self): return databaseSettings - - # common states: STATE_CONNECTING("Connecting"), STATE_OPERATIONAL("Operational"), # STATE_STARTING("Startinf..."), STATE_PRINTING("Printing or Sendind"), STATE_CANCELLING("Cancelling"), # STATE_PAUSING("Pausing"), STATE_PAUSED("Paused"), STATE_RESUMING("Resuming"), STATE_FINISHING("Finishing"), STATE_CLOSED("Offline") @@ -323,6 +397,7 @@ def _on_printJobStarted(self): reloadTable = False selectedSpools = self.loadSelectedSpools() + self._readingFilamentMetaData() for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): spoolModel = selectedSpools[toolIndex] if toolIndex < len(selectedSpools) else None @@ -391,6 +466,13 @@ def commitOdometerData(self): def _on_printJobFinished(self, printStatus, payload): self.commitOdometerData() + # update remaining data in selected spools after a print + selectedSpools = self.loadSelectedSpools() + requiredWeightResult = self._evaluateRequiredWeight(selectedSpools, None, False) + requiredWeightResult["action"] = "requiredFilamentChanged" + self._sendDataToClient(requiredWeightResult) + + def _on_clientOpened(self, payload): # start-workaround https://github.com/foosel/OctoPrint/issues/3400 import time @@ -429,26 +511,15 @@ def _on_clientOpened(self, payload): isFilamentManagerPluginAvailable = self._filamentManagerPluginImplementation != None, pluginNotWorking = pluginNotWorking )) - + # data for the sidebar + self.checkRemainingFilament() pass def _on_clientClosed(self, payload): self.databaseConnectionProblemConfirmed = False def _on_file_selectionChanged(self, payload): - self.metaDataFilamentLengths = [] - - if (payload != None and "origin" in payload and "path" in payload): - metadata = self._file_manager.get_metadata(payload["origin"], payload["path"]) - if ("analysis" in metadata): - if ("filament" in metadata["analysis"]): - for toolName, toolData in metadata["analysis"]["filament"].items(): - toolIndex = int(toolName[4:]) - self.metaDataFilamentLengths += [0.0] * (toolIndex + 1 - len(self.metaDataFilamentLengths)) - self.metaDataFilamentLengths[toolIndex] = toolData["length"] - - self.checkRemainingFilament() - + self.checkRemainingFilament() pass @@ -526,6 +597,9 @@ def on_event(self, event, payload): self.alreadyCanceled = False self._on_printJobStarted() + elif (Events.PRINT_PAUSED == event): + self._on_printJobFinished("paused", payload) + elif (Events.PRINT_DONE == event): self._on_printJobFinished("success", payload) @@ -577,7 +651,7 @@ def on_settings_save(self, data): # Update Temperature Offsets selectedSpools = self.loadSelectedSpools() - + self._readingFilamentMetaData() for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): selectedSpool = selectedSpools[toolIndex] if toolIndex < len(selectedSpools) else None if (selectedSpool != None): diff --git a/octoprint_SpoolManager/api/SpoolManagerAPI.py b/octoprint_SpoolManager/api/SpoolManagerAPI.py index d6875a77..47e2d428 100644 --- a/octoprint_SpoolManager/api/SpoolManagerAPI.py +++ b/octoprint_SpoolManager/api/SpoolManagerAPI.py @@ -141,39 +141,6 @@ def loadSelectedSpools(self): return spoolModelList - def _createSampleSpoolModel(self): - #DisplayName, Vendor, Material, Color[# code], Diameter [mm], Density [g/cm³], Temperature [°C], TotalWeight [g], UsedWeight [g], UsedLength [mm], FirstUse [dd.mm.yyyy hh:mm], LastUse [dd.mm.yyyy hh:mm], PurchasedFrom, PurchasedOn [dd.mm.yyyy hh:mm], Cost, CostUnit, Labels, NoteText - - s1 = SpoolModel() - s1.displayName = "Number #1" - s1.colorName = "raw-red" - s1.color = "#FF0000" - s1.vendor = "The Spool Company" - s1.material = "PETG" - s1.diameter = 1.75 - s1.diameterTolerance = 0.2 - s1.density = 1.27 - s1.flowRateCompensation = 110 - s1.temperature = 182 - s1.bedtemperature = 52 - s1.enclosureTemperature = 23 - s1.totalWeight = 1000.0 - s1.spoolWeight = 12.3 - s1.usedWeight = 123.4 - s1.totalLength = 1321 - s1.usedLength = 234 - s1.lastUse = datetime.datetime.now() - - s1.firstUse = datetime.datetime.strptime("2020-03-02 10:33", '%Y-%m-%d %H:%M') - s1.purchasedOn = datetime.datetime.strptime("2020-02-01", '%Y-%m-%d') - s1.purchasedFrom = "Unknown Seller" - s1.cost = "12.30" - s1.costUnit = "€" - s1.noteText = "Very cheap spool!" - return s1 - - - def _createSpoolModelFromLegacy(self, allSpoolLegacyList): allSpoolModels = list() for spoolDict in allSpoolLegacyList: @@ -209,7 +176,6 @@ def _createSpoolModelFromLegacy(self, allSpoolLegacyList): return allSpoolModels - def _calculateUsedLength(self, usedWeight, density, diameter): if (diameter == None or density == None or usedWeight == None): self._logger.info("Could not calculate used length because some values (usedWeigth, density, diameter) were missing") @@ -289,7 +255,7 @@ def sampleCSV(self): allSpoolModels = list() - spoolModel = self._createSampleSpoolModel() + spoolModel = CSVExportImporter.createSampleSpoolModel() allSpoolModels.append(spoolModel) return Response(CSVExportImporter.transform2CSV(allSpoolModels), mimetype='text/csv', @@ -304,16 +270,23 @@ def allowed_to_print(self): reminderSelectingSpool = self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_REMINDER_SELECTING_SPOOL]) spoolModels = self.loadSelectedSpools() + metaOrAttributesMissing = False result = { 'noSpoolSelected': [], 'filamentNotEnough': [], 'reminderSpoolSelection': [], } - for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): + + filamentLengthPresentInMeta = self._readingFilamentMetaData() + printer_profile = self._printer_profile_manager.get_current_or_default() + printerProfileToolCount = printer_profile['extruder']['count'] + # for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths): + for toolIndex in range(printerProfileToolCount): # we go over the filamentlength because those are what matters for this print - if not filamentLength: - # if this tool is not used in this print, everything is fine - continue + if filamentLengthPresentInMeta: + if toolIndex >= len(self.metaDataFilamentLengths): + # if this tool is not used (no filaLenght) in this print, everything is fine + continue spoolModel = spoolModels[toolIndex] if toolIndex < len(spoolModels) else None @@ -327,23 +300,71 @@ def allowed_to_print(self): "enclosureOffset": spoolModel.offsetEnclosureTemperature if spoolModel else '' } - if spoolModel is not None: - if not self.checkRemainingFilament(toolIndex): - result['filamentNotEnough'].append(infoData) - result['reminderSpoolSelection'].append(infoData) + requiredWeightResult = self.checkRemainingFilament(toolIndex) + # "metaDataMissing": metaDataMissing, + # "warnUser": fromPluginSettings, + # "attributesMissing": someAttributesMissing, + # "notEnough": notEnough, + # "detailedSpoolResult": [ + # "toolIndex": toolIndex, + # "requiredWeight": requiredWeight, + # "requiredLength": filamentLength, + # "remainingWeight": remainingWeight, + # "diameter": diameter, + # "density": density, + # "notEnough": notEnough, + # "spoolSelected": True + # ] + if (requiredWeightResult["metaDataMissing"] == True or requiredWeightResult["attributesMissing"] == True): + metaOrAttributesMissing = True + + detailedSpoolResult = None + if ("detailedSpoolResult" in requiredWeightResult and len(requiredWeightResult["detailedSpoolResult"]) > 0): + detailedSpoolResult = requiredWeightResult["detailedSpoolResult"][0] + + if spoolModel is not None and detailedSpoolResult is not None and detailedSpoolResult["spoolSelected"] == True: + + if (detailedSpoolResult["requiredLength"] > 0): + if (detailedSpoolResult["notEnough"] == True): + # if not enough or needed amount could not calculated + result['filamentNotEnough'].append(infoData) + # add every spool for reminding, if more the 0gr is needed + result['reminderSpoolSelection'].append(infoData) elif checkForSelectedSpool: - result['noSpoolSelected'].append(infoData) + if (detailedSpoolResult is not None): + if (detailedSpoolResult["requiredLength"] > 0): + result['noSpoolSelected'].append(infoData) + else: + result['noSpoolSelected'].append(infoData) + + # if (metaNotPresent or + # attributesMissing or + # notEnough + # ): + # # if not enough or needed amount could not calculated + # result['filamentNotEnough'].append(infoData) + # if (metaNotPresent or + # attributesMissing): + # metaOrAttributesMissing = True + # + # # add every spool for reminding + # result['reminderSpoolSelection'].append(infoData) + # elif checkForSelectedSpool: + # # if no metatdata is present we cant check if this tool is needed, so we cant inform the user that a selection is missing + # if (filamentLengthPresentInMeta == True): + # result['noSpoolSelected'].append(infoData) # check if the user want a popup if (checkForFilamentLength == False): result['filamentNotEnough'] = [] if (reminderSelectingSpool == False): - # no popup, because turned off + # no popup, because turned off by user result['reminderSpoolSelection'] = [] return flask.jsonify({ "result": result, + "metaOrAttributesMissing": metaOrAttributesMissing, "toolOffsetEnabled": self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_TOOL_OFFSET_ENABLED]), "bedOffsetEnabled": self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_BED_OFFSET_ENABLED]), "enclosureOffsetEnabled": self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_ENCLOSURE_OFFSET_ENABLED]), @@ -352,11 +373,10 @@ def allowed_to_print(self): ##################################################################################################### SELECT SPOOL @octoprint.plugin.BlueprintPlugin.route("/selectSpool", methods=["PUT"]) def select_spool(self): - self._logger.info("API Store selected spool") jsonData = request.json - databaseId = self._getValueFromJSONOrNone("databaseId", jsonData) - toolIndex = self._getValueFromJSONOrNone("toolIndex", jsonData) + databaseId = self._toIntFromJSONOrNone("databaseId", jsonData) + toolIndex = self._toIntFromJSONOrNone("toolIndex", jsonData) if self._printer.is_printing(): # changing a spool mid-print? we want to know @@ -380,6 +400,8 @@ def select_spool(self): except Exception as e: self._sendMessageToClient("warning", "Temperature offsets failed to set!", str(e)) + self.checkRemainingFilament() + return flask.jsonify({ "selectedSpool": spoolModelAsDict }) @@ -406,6 +428,9 @@ def selectSpoolByQRCode(self, databaseId): else: abort(404) + # hmmm..TODO not fully tested + def is_blueprint_protected(self): + return False # No API key required to request API access ##################################################################################################### GENERATE QR FOR SPOOL @octoprint.plugin.BlueprintPlugin.route("/generateQRCode/", methods=["GET"]) @@ -426,7 +451,9 @@ def generateSpoolQRCode(self, databaseId): error_correction=qrcode.constants.ERROR_CORRECT_H ) - qrMaker.add_data(flask.url_for("plugin.SpoolManager.selectSpoolByQRCode", _external=True, _scheme="https", databaseId=databaseId)) + # spoolSelectionUrl = flask.url_for("plugin.SpoolManager.selectSpoolByQRCode", _external=True, _scheme="https", databaseId=databaseId) + spoolSelectionUrl = flask.url_for("plugin.SpoolManager.selectSpoolByQRCode", _external=True, databaseId=databaseId) + qrMaker.add_data(spoolSelectionUrl) qrMaker.make(fit=True, ) fillColor = self._settings.get([SettingsKeys.SETTINGS_KEY_QR_CODE_FILL_COLOR]) @@ -670,7 +697,7 @@ def exportSpoolsData(self, exportType): ################################################################################################## LOAD ALL SPOOLS @octoprint.plugin.BlueprintPlugin.route("/loadSpoolsByQuery", methods=["GET"]) - def loadAllSpools(self): + def loadAllSpoolsByQuery(self): self._logger.debug("API Load all spool") # sp1 = SpoolModel() @@ -711,6 +738,7 @@ def loadAllSpools(self): vendors = list(self._databaseManager.loadCatalogVendors(tableQuery)) materials = list(self._databaseManager.loadCatalogMaterials(tableQuery)) labels = list(self._databaseManager.loadCatalogLabels(tableQuery)) + colors = list(self._databaseManager.loadCatalogColors(tableQuery)) materials = self._addAdditionalMaterials(materials) @@ -724,6 +752,7 @@ def loadAllSpools(self): catalogs = { "vendors": vendors, "materials": materials, + "colors": colors, "labels": labels } # catalogs = { @@ -795,6 +824,9 @@ def saveSpool(self): databaseId = self._databaseManager.saveSpool(spoolModel, withReusedConnection=True) self._databaseManager.closeDatabase() + # data for the sidebar + self.checkRemainingFilament() + return flask.jsonify() diff --git a/octoprint_SpoolManager/common/CSVExportImporter.py b/octoprint_SpoolManager/common/CSVExportImporter.py index 4454f15c..f033febf 100644 --- a/octoprint_SpoolManager/common/CSVExportImporter.py +++ b/octoprint_SpoolManager/common/CSVExportImporter.py @@ -1,3 +1,5 @@ +# coding=utf-8 + import io from io import StringIO import csv @@ -16,6 +18,7 @@ COLUMN_COLOR_CODE = "Color Code [hex]" COLUMN_VENDOR = "Vendor" COLUMN_MATERIAL = "Material" +COLUMN_SERIALNUMBER = "Serialnumber" COLUMN_DENSITY = "Density [g/cm3]" COLUMN_DIAMETER = "Diameter [mm]" COLUMN_DIAMETER_TOLERANCE = "Diameter Tolerance[mm]" @@ -91,6 +94,11 @@ def parseAndAssignFieldValue(self, fieldLabel, fieldName, fieldValue, printJobMo if ("" == fieldValue or "-" == fieldValue or fieldValue == None): # check if mandatory return + + # TODO custom color-code parser: rules all lowercase and #fff == #ffffff + if ("color" == fieldName): + fieldValue = fieldValue.lower() + setattr(printJobModel, fieldName, fieldValue) class DateTimeCSVFormattorParser: @@ -184,6 +192,7 @@ def parseAndAssignFieldValue(self, fieldLabel, fieldName, fieldValue, spoolModel COLUMN_COLOR_CODE, COLUMN_VENDOR, COLUMN_MATERIAL, + COLUMN_SERIALNUMBER, COLUMN_DENSITY, COLUMN_DIAMETER, COLUMN_DIAMETER_TOLERANCE, @@ -215,6 +224,7 @@ def parseAndAssignFieldValue(self, fieldLabel, fieldName, fieldValue, spoolModel COLUMN_COLOR_CODE: CSVColumn("color", COLUMN_COLOR_CODE, "", DefaultCSVFormattorParser()), COLUMN_VENDOR: CSVColumn("vendor", COLUMN_VENDOR, "", DefaultCSVFormattorParser()), COLUMN_MATERIAL: CSVColumn("material", COLUMN_MATERIAL, "", DefaultCSVFormattorParser()), + COLUMN_SERIALNUMBER: CSVColumn("code", COLUMN_SERIALNUMBER, "", DefaultCSVFormattorParser()), COLUMN_DENSITY: CSVColumn("density", COLUMN_DENSITY, "", NumberCSVFormattorParser()), COLUMN_DIAMETER: CSVColumn("diameter", COLUMN_DIAMETER, "", NumberCSVFormattorParser()), COLUMN_DIAMETER_TOLERANCE: CSVColumn("diameterTolerance", COLUMN_DIAMETER_TOLERANCE, "", NumberCSVFormattorParser()), @@ -291,7 +301,6 @@ def transform2CSV(allJobs): columnOrderInFile = dict() - def parseCSV(csvFile4Import, updateParsingStatus, errorCollection, logger, deleteAfterParsing=True): result = list() # List with printJobModels @@ -363,3 +372,36 @@ def parseCSV(csvFile4Import, updateParsingStatus, errorCollection, logger, delet except Exception: pass return result + +######################################################################################################## -> SAMPLE SPOOL +def createSampleSpoolModel(): + #DisplayName, Vendor, Material, Color[# code], Diameter [mm], Density [g/cm³], Temperature [°C], TotalWeight [g], UsedWeight [g], UsedLength [mm], FirstUse [dd.mm.yyyy hh:mm], LastUse [dd.mm.yyyy hh:mm], PurchasedFrom, PurchasedOn [dd.mm.yyyy hh:mm], Cost, CostUnit, Labels, NoteText + + s1 = SpoolModel() + s1.displayName = "Sample Spool #1" + s1.colorName = "raw-red" + s1.color = "#FF0000" + s1.vendor = "The Spool Company" + s1.material = "PETG" + s1.code = "X000SKGR05" + s1.diameter = 1.75 + s1.diameterTolerance = 0.2 + s1.density = 1.27 + s1.flowRateCompensation = 110 + s1.temperature = 182 + s1.bedtemperature = 52 + s1.enclosureTemperature = 23 + s1.totalWeight = 1000.0 + s1.spoolWeight = 12.3 + s1.usedWeight = 123.4 + s1.totalLength = 1321 + s1.usedLength = 234 + s1.lastUse = datetime.datetime.now() + + s1.firstUse = datetime.datetime.strptime("2020-03-02 10:33", '%Y-%m-%d %H:%M') + s1.purchasedOn = datetime.datetime.strptime("2020-02-01", '%Y-%m-%d') + s1.purchasedFrom = "Unknown Seller" + s1.cost = "12.30" + s1.costUnit = "€" + s1.noteText = "Very cheap spool!" + return s1 diff --git a/octoprint_SpoolManager/static/css/SpoolManager.css b/octoprint_SpoolManager/static/css/SpoolManager.css index 729ac81b..c820f9ba 100644 --- a/octoprint_SpoolManager/static/css/SpoolManager.css +++ b/octoprint_SpoolManager/static/css/SpoolManager.css @@ -1,28 +1,4 @@ -#sidebar_select_spool_dialog .modal-body { - display: flex; - flex-direction: column; -} - -#sidebar_select_spool_dialog .modal-body > *:nth-last-child(n+2) { - margin-bottom: 0.5em; -} - -#sidebar_select_spool_dialog .filter { - display: flex; -} - -#sidebar_select_spool_dialog .filter > *:nth-last-child(n+2) { - margin-right: 1.5em; -} -#sidebar_select_spool_dialog .table { - margin-bottom: 0; -} - -#sidebar_select_spool_dialog .spool-label { - display: flex; - align-items: center; -} .control-group.no-bottom-gap { margin-bottom:3px @@ -32,6 +8,10 @@ width: 100px; height: 100px; } +/* needed, because selection flyout was not fully visible, see #204 */ +/*#sidebar_plugin_SpoolManager.collapse.in {*/ +/* overflow: visible;*/ +/*}*/ #selectedSpools > div { display: flex; @@ -92,59 +72,109 @@ overflow:visible !important; } - .color-preview{ - border:1px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);height:1.429em;width:1.429em;display:inline-block;cursor:pointer;margin-right:5px}div.pick-a-color-markup .color-preview.current-color{margin-bottom:-5px - } +.color-preview{ + border:1px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.075);height:1.429em;width:1.429em;display:inline-block;cursor:pointer;margin-right:5px}div.pick-a-color-markup .color-preview.current-color{margin-bottom:-5px +} +.truncatedText { + white-space: nowrap; + width: 250px;/* OR as much space that allows */ + overflow: hidden; + text-overflow: ellipsis; +} - .truncatedText { - white-space: nowrap; - width: 250px;/* OR as much space that allows */ - overflow: hidden; - text-overflow: ellipsis; - } // TODO Maybe not needed any more .dropdown-menu { max-height: 120px; overflow-y: auto; overflow-x: hidden; } -.mydrop { -position:block !important; -} - -//////////// TESTZONE -.multiselect { - width: 200px; +/* use of own span elemenst so that no one can break it (e.g. UI Customizer) */ +.myspan12{width:940px} +.myspan11{width:860px} +.myspan10{width:780px} +.myspan9{width:700px} +.myspan8{width:620px} +.myspan7{width:540px} +.myspan6{width:460px} +.myspan5{width:380px} +.myspan4{width:300px} +.myspan3{width:220px} +.myspan2{width:140px} +.myspan1{width:60px} + + +/* styling for select dialog */ +#dialog_spool_selection .modal-body { + display: flex; + flex-direction: column; } -.selectBox { - position: relative; +#dialog_spool_selection .modal-body > *:nth-last-child(n+2) { + margin-bottom: 0.5em; } -.selectBox select { - width: 100%; - font-weight: bold; +#dialog_spool_selection .filter { + display: flex; } -.overSelect { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; +#dialog_spool_selection .filter > *:nth-last-child(n+2) { + margin-right: 1.5em; } -#checkboxes { - display: none; - border: 1px #dadada solid; +#dialog_spool_selection .table { + margin-bottom: 0; } -#checkboxes label { - display: block; +#dialog_spool_selection .spool-label { + display: flex; + align-items: center; } -#checkboxes label:hover { - background-color: #1e90ff; -} + + + + +/*.modal.modal-wide .modal-dialog {*/ +/* width: 90%;*/ +/*}*/ + +/*.modal-wide .modal-body {*/ +/* overflow-y: auto;*/ +/*}*/ + +/*//////////// TESTZONE*/ +/*.multiselect {*/ +/* width: 200px;*/ +/*}*/ + +/*.selectBox {*/ +/* position: relative;*/ +/*}*/ + +/*.selectBox select {*/ +/* width: 100%;*/ +/* font-weight: bold;*/ +/*}*/ + +/*.overSelect {*/ +/* position: absolute;*/ +/* left: 0;*/ +/* right: 0;*/ +/* top: 0;*/ +/* bottom: 0;*/ +/*}*/ + +/*#checkboxes {*/ +/* display: none;*/ +/* border: 1px #dadada solid;*/ +/*}*/ + +/*#checkboxes label {*/ +/* display: block;*/ +/*}*/ + +/*#checkboxes label:hover {*/ +/* background-color: #1e90ff;*/ +/*}*/ diff --git a/octoprint_SpoolManager/static/js/ComponentFactory.js b/octoprint_SpoolManager/static/js/ComponentFactory.js index 140bf60e..000d99e2 100644 --- a/octoprint_SpoolManager/static/js/ComponentFactory.js +++ b/octoprint_SpoolManager/static/js/ComponentFactory.js @@ -170,6 +170,7 @@ function ComponentFactory(pluginId) { var select2 = $(elementSelector).select2({ dropdownParent: dropDownParent, placeholder: "Choose...", + allowClear: true, tags: true }); diff --git a/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js b/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js index 91929ed8..6c7c079c 100644 --- a/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js +++ b/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js @@ -5,7 +5,6 @@ function SpoolManagerAPIClient(pluginId, baseUrl) { this.pluginId = pluginId; this.baseUrl = baseUrl; - // see https://gomakethings.com/how-to-build-a-query-string-from-an-object-with-vanilla-js/ var _buildRequestQuery = function (data) { // If the data is already a string, return it as-is diff --git a/octoprint_SpoolManager/static/js/SpoolManager-EditSpoolDialog.js b/octoprint_SpoolManager/static/js/SpoolManager-EditSpoolDialog.js index 4d7af069..a92e1e37 100644 --- a/octoprint_SpoolManager/static/js/SpoolManager-EditSpoolDialog.js +++ b/octoprint_SpoolManager/static/js/SpoolManager-EditSpoolDialog.js @@ -142,7 +142,7 @@ function SpoolManagerEditSpoolDialog(){ // Autosuggest for "density" this.material.subscribe(function(newMaterial){ - if ($("#dialog_spool_select").is(":visible")){ + if ($("#dialog_spool_edit").is(":visible")){ if (self.spoolItemForEditing.isSpoolVisible() == true){ var mat = self.spoolItemForEditing.material(); if (mat){ @@ -155,7 +155,6 @@ function SpoolManagerEditSpoolDialog(){ } }); - if (editable == true){ var colorViewModel = self.componentFactory.createColorPicker("filament-color-picker"); this.color = colorViewModel.selectedColor; @@ -173,8 +172,6 @@ function SpoolManagerEditSpoolDialog(){ this.labels = self.labelsViewModel.selectedOptions; this.allLabels = self.labelsViewModel.allOptions; - - // Fill Item with data this.update(spoolData); } @@ -388,7 +385,7 @@ function SpoolManagerEditSpoolDialog(){ self.pluginSettings = pluginSettings; self.printerProfilesViewModel = printerProfilesViewModel; - self.spoolDialog = $("#dialog_spool_select"); + self.spoolDialog = $("#dialog_spool_edit"); // self.firstUseDatePickerModel = self.componentFactory.createDatePicker("firstUse-date-container"); //// self.firstUseDatePickerModel.currentDate(new Date(2014, 1, 14)); // @@ -604,7 +601,7 @@ function SpoolManagerEditSpoolDialog(){ self.spoolItemForEditing.isSpoolVisible(true); self.spoolDialog.modal({ - //minHeight: function() { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); } + minHeight: function() { return Math.max($.fn.modal.defaults.maxHeight() - 180, 250); }, keyboard: false, clickClose: true, showClose: false, @@ -615,6 +612,25 @@ function SpoolManagerEditSpoolDialog(){ 'margin-left': function() { return -($(this).width() /2); } }); + + + // // show settings, ensure centered position + // self.settingsDialog + // .modal({ + // minHeight: function () { + // return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); + // } + // }) + // .css({ + // "width": "auto", + // "margin-left": function () { + // return -($(this).width() / 2); + // } + // }); + + + + }; this.copySpoolItem = function(){ @@ -655,7 +671,8 @@ function SpoolManagerEditSpoolDialog(){ self.apiClient.callSaveSpool(self.spoolItemForEditing, function(allPrintJobsResponse){ self.spoolItemForEditing.isSpoolVisible(false); self.spoolDialog.modal('hide'); - self.closeDialogHandler(true); + // var specialCloseAction = self.isExistingSpool() == false ? "saveNewSpool" + self.closeDialogHandler(true, "save"); }); } @@ -673,7 +690,7 @@ function SpoolManagerEditSpoolDialog(){ this.selectSpoolItemForPrinting = function(){ self.spoolItemForEditing.isSpoolVisible(false); self.spoolDialog.modal('hide'); - self.closeDialogHandler(true, "selectSpoolForPrinting", self.spoolItemForEditing); + self.closeDialogHandler(false, "selectSpoolForPrinting", self.spoolItemForEditing); } this.generateQRCodeImageSourceAttribute = function(){ @@ -686,7 +703,7 @@ function SpoolManagerEditSpoolDialog(){ // var windowsLocation = window.location.origin; // var windowsLocationEncoded = encodeURIComponent(windowsLocation); // var source = "/plugin/SpoolManager/generateQRCode/" + self.spoolItemForEditing.databaseId() + "?windowlocation="+windowsLocationEncoded; - var source = "/plugin/SpoolManager/generateQRCode/" + self.spoolItemForEditing.databaseId(); + var source = PLUGIN_BASEURL + "SpoolManager/generateQRCode/" + self.spoolItemForEditing.databaseId(); var title = "QR-Code for " + self.spoolItemForEditing.displayName(); return { src: source, diff --git a/octoprint_SpoolManager/static/js/SpoolManager.js b/octoprint_SpoolManager/static/js/SpoolManager.js index 3ec42bee..a314d605 100644 --- a/octoprint_SpoolManager/static/js/SpoolManager.js +++ b/octoprint_SpoolManager/static/js/SpoolManager.js @@ -68,21 +68,12 @@ $(function() { self.printerProfilesViewModel = parameters[4]; self.pluginSettings = null; - self.sidebarSelectSpoolDialog = undefined; self.apiClient = new SpoolManagerAPIClient(PLUGIN_ID, BASEURL); self.spoolDialog = new SpoolManagerEditSpoolDialog(); - // KNOCKOUT - MODLES - -// self.spoolItemForEditing = ko.observable(); -// self.spoolItemForEditing(new SpoolItem(null)); -// self.spoolItemForEditing = self.spoolDialog.createSpoolItemForEditing(); - - - - ////////////////////////////////////////////////////////////////////////////////////////////////HELPER FUNCTION + //////////////////////////////////////////////////////////////////////////////////////////////// HELPER FUNCTION loadSettingsFromBrowserStore = function(){ // TODO maybe in a separate js-file @@ -333,33 +324,86 @@ $(function() { ///////////////////////////////////////////////////// END: SETTINGS - //////////////////////////////////////////////////////////////////////////////////////////////////// SIDEBAR + ////////////////////////////////////////////////////////////////////////////////////////// SIDEBAR - REPLACEMENT + self.printerStateViewModel.filamentWithWeight = ko.observableArray([]); + + self.updateRequiredFilament = function(requiredFilament){ +/* + # "metaDataPresent": metaDataPresent, + # "warnUser": fromPluginSettings, + # "attributesMissing": someAttributesMissing, + # "notEnough": notEnough, + # "detailedSpoolResult": [ + # "toolIndex": toolIndex, + # "requiredWeight": requiredWeight, + # "requiredLength": filamentLength, + # "diameter": diameter, + # "density": density, + # "notEnough": notEnough, + # "spoolSelected": True + # ] + */ + var filamentList = requiredFilament["detailedSpoolResult"]; + var filteredFilamentList = []; + // filter not required tools + for (filamentItem of filamentList){ + if (filamentItem.requiredLength > 0){ + filteredFilamentList.push(filamentItem) + } + } + self.printerStateViewModel.filamentWithWeight(filteredFilamentList) + } + + self.printerStateViewModel.formatFilamentWithWeight = function formatFilamentWithWeightInSidebar(filament) { + if (!filament) return '-'; + + // length in m + var result = (filament.requiredLength / 1000).toFixed(2) + 'm'; + // try to get the weight + if (filament.requiredWeight) { + result += ' / ' + filament.requiredWeight.toFixed(2) + 'g'; + } + if (filament.spoolSelected && filament.spoolSelected == true){ + if (filament.notEnough) { + if (filament.notEnough == true){ + result += ' ('+filament.remainingWeight.toFixed(2) +'g)'; + } + } + } else { + if (filament.requiredLength > 0){ + result += ' (no spool selected)'; + } + } + + return result; + }; + + self.replaceFilamentView = function replaceFilamentViewInSidebar() { + $('#state').find('.accordion-inner').contents().each(function (index, item) { + if (item.nodeType === Node.COMMENT_NODE) { + if (item.nodeValue === ' ko foreach: filament ') { + item.nodeValue = ' ko foreach: [] '; // eslint-disable-line no-param-reassign + var element = ' Required Filament unknown
'; + element += '
'; + $(element).insertBefore(item); + return false; // exit loop + } + } + return true; + }); + }; + /////////////////////////////////////////////////////////////////////////////////////////// SIDEBAR - SELECT self.allSpoolsForSidebar = ko.observableArray([]); self.selectedSpoolsForSidebar = ko.observableArray([]); + self.sidebarSelectSpoolModalToolIndex = ko.observable(null); // index of the current tool we want to select for + self.sidebarSelectSpoolModalSpoolItem = ko.observable(null); // current spoolitem + self.deselectSpoolForSidebar = function(toolIndex, item){ self.selectSpoolForSidebar(toolIndex, null); } - self.onStartup = function () { - self.sidebarSelectSpoolDialog = $("#sidebar_select_spool_dialog"); - self.sidebarSelectSpoolModalToolIndex = ko.observable(null) - self.sidebarSelectSpoolDialog.on("shown", function () { - console.log("onShown"); - }) - } - - self.sidebarSelectSpoolFromDialog = function (spoolItem) { - self.sidebarSelectSpoolDialog.modal("hide"); - self.selectSpoolForSidebar(self.sidebarSelectSpoolModalToolIndex(), spoolItem); - } - - self.sidebarOpenSelectSpoolDialog = function(toolIndex, item){ - self.sidebarSelectSpoolModalToolIndex(toolIndex); - self.sidebarSelectSpoolDialog.modal("show"); - } - self.loadSpoolsForSidebar = function() { // update filament list length var currentProfileData = self.settingsViewModel.printerProfiles.currentProfileData(), @@ -456,7 +500,6 @@ $(function() { return toolTip; } - self.getSpoolItemSelectedTool = function(databaseId) { var spoolItem; for (var i=0; i 0){ + self.showAllMaterialsForFilter(true); + } else{ + self.showAllMaterialsForFilter(false); + } + // TODO Optimize enable after the values where initialy changed + self.reloadItems(); + }); + self.selectedVendorsForFilter.subscribe(function(newValues) { + if (self.selectedVendorsForFilter().length > 0){ + self.showAllVendorsForFilter(true); + } else{ + self.showAllVendorsForFilter(false); + } + // TODO Optimize enable after the values where initialy changed + self.reloadItems(); + }); + self.selectedColorsForFilter.subscribe(function(newValues) { + if (self.selectedColorsForFilter().length > 0){ + self.showAllColorsForFilter(true); + } else{ + self.showAllColorsForFilter(false); + } + + if (self.selectedColorsForFilter().length != 0){ + // TODO Optimize enable after the values where initialy changed + self.reloadItems(); + } + + }); + + self._evalFilterLabel = function(allArray, selectionArray){ + // check if all selected + var selectionCount = 0 + for (let item of allArray) { + if (selectionArray.indexOf(item) != -1){ + selectionCount++; + } + } + var allSelected = selectionCount == allArray.length + return allSelected == true ? "all" : selectionArray.length; + }; + // ################################################################################################ public functions self.reloadItems = function(){ self._loadItems(); } + self.updateCatalogs = function(catalogs){ + self.allCatalogs = catalogs; + var materialsCatalog = self.allCatalogs["materials"]; + var vendorsCatalog = self.allCatalogs["vendors"]; + var colorsCatalog = self.allCatalogs["colors"]; + + self.allMaterials(materialsCatalog); + self.allVendors(vendorsCatalog); + self.allColors(colorsCatalog); + } self.paginatedItems = ko.dependentObservable(function() { if (self.items() === undefined) { @@ -117,7 +204,72 @@ function TableItemHelper(loadItemsFunction, defaultPageSize, defaultSortColumn, return self.selectedFilterName() == filterName; }; + self.doFilterSelectAll = function(data, catalogName){ + let checked; + switch (catalogName) { + case "material": + checked = self.showAllMaterialsForFilter(); + if (checked == true) { + self.selectedMaterialsForFilter().length = 0; + ko.utils.arrayPushAll(self.selectedMaterialsForFilter, self.allMaterials()); + } else { + self.selectedMaterialsForFilter.removeAll(); + } + break; + case "vendor": + checked = self.showAllVendorsForFilter(); + if (checked == true) { + self.selectedVendorsForFilter().length = 0; + ko.utils.arrayPushAll(self.selectedVendorsForFilter, self.allVendors()); + } else { + self.selectedVendorsForFilter.removeAll(); + } + break; + case "color": + checked = self.showAllColorsForFilter(); + if (checked == true) { + self.selectedColorsForFilter().length = 0; + // we are using an colorId as a checked attribute, we can just move the color-objects to the selectedArrary + // ko.utils.arrayPushAll(self.spoolItemTableHelper.selectedColorsForFilter, self.spoolItemTableHelper.allColors()); + for (let i = 0; i < self.allColors().length; i++) { + let colorObject = self.allColors()[i]; + self.selectedColorsForFilter().push(colorObject.colorId); + } + self.selectedColorsForFilter.valueHasMutated(); + } else { + self.selectedColorsForFilter.removeAll(); + } + break; + } + } + + self.buildFilterLabel = function(filterLabelName){ + // spoolItemTableHelper.selectedColorsForFilter().length == spoolItemTableHelper.allColors().length ? 'all' : spoolItemTableHelper.selectedColorsForFilter().length + // to detecting all, we can't use the length, because if just the color is changed then length is still true + // so we need to compare each value + if ("color" == filterLabelName){ + var selectionArray = self.selectedColorsForFilter(); // array of colorIds [#ffa500;orange, #ffffff;white] + var allColorArray = self.allColors(); // array of object with 'colorId=#ffa500;orange','color=#ffa500','colorName="orange"' + // check if all colors selected + var selectionCount = 0 + for (let colorItem of allColorArray) { + var colorId = colorItem.colorId; + if (selectionArray.indexOf(colorId) != -1){ + selectionCount++; + } + } + var allColorsSelected = selectionCount == allColorArray.length + return allColorsSelected == true ? "all" : self.selectedColorsForFilter().length; + } + if ("material" == filterLabelName){ + return self._evalFilterLabel(self.allMaterials(), self.selectedMaterialsForFilter()); + } + if ("vendor" == filterLabelName){ + return self._evalFilterLabel(self.allVendors(), self.selectedVendorsForFilter()); + } + return "not defined:" + filterLabelName; + } // ############################################## PAGING self.changePage = function(newPage) { diff --git a/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 new file mode 100644 index 00000000..f6dcfc27 --- /dev/null +++ b/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 @@ -0,0 +1,24 @@ + + + diff --git a/octoprint_SpoolManager/templates/SpoolManager_dialog_databaseConnectionProblem.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_dialog_databaseConnectionProblem.jinja2 new file mode 100644 index 00000000..782ae234 --- /dev/null +++ b/octoprint_SpoolManager/templates/SpoolManager_dialog_databaseConnectionProblem.jinja2 @@ -0,0 +1,27 @@ + + + diff --git a/octoprint_SpoolManager/templates/SpoolManager_dialog_editSpool.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_dialog_editSpool.jinja2 new file mode 100644 index 00000000..95b0b8b5 --- /dev/null +++ b/octoprint_SpoolManager/templates/SpoolManager_dialog_editSpool.jinja2 @@ -0,0 +1,480 @@ + + + diff --git a/octoprint_SpoolManager/templates/SpoolManager_sidebar_dialogs.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_dialog_selectSpool.jinja2 similarity index 53% rename from octoprint_SpoolManager/templates/SpoolManager_sidebar_dialogs.jinja2 rename to octoprint_SpoolManager/templates/SpoolManager_dialog_selectSpool.jinja2 index c2f9b51b..219ad8c6 100644 --- a/octoprint_SpoolManager/templates/SpoolManager_sidebar_dialogs.jinja2 +++ b/octoprint_SpoolManager/templates/SpoolManager_dialog_selectSpool.jinja2 @@ -1,11 +1,36 @@ -