Skip to content

Multi-Tool/Multi-Material support #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3ab7123
update settings for multi-tool support
codingcatgirl May 14, 2021
fd85878
return selectedSpools via API
codingcatgirl May 14, 2021
d8fb6b2
ui changes for multi-material
codingcatgirl May 14, 2021
009ca10
remaining ui changes / removing old non-multi-tool code
codingcatgirl May 14, 2021
9336415
update api to work with multi-tool
codingcatgirl May 14, 2021
1209c42
implement multi-filament for filament/spool checks
codingcatgirl May 15, 2021
0cb17da
implement firstUse for multi-filament
codingcatgirl May 15, 2021
c12abe5
update multi-filament spools after print finished
codingcatgirl May 15, 2021
8275219
remove loadSelectedSpool() as it is no longer used
codingcatgirl May 15, 2021
e69a39f
clean up and prettify sidebar
codingcatgirl May 15, 2021
33fc628
tweak color-preview some more
codingcatgirl May 15, 2021
f1642df
merge buttons in sidebar into buttongroup
codingcatgirl May 15, 2021
759d237
make sure dropdown is always visible and not too big
codingcatgirl May 15, 2021
57115ba
hide tool index if there is only one tool
codingcatgirl May 15, 2021
cfe34e2
don't let a spool be in two slots at once
codingcatgirl May 15, 2021
562d0d8
if spool edit dialogue is closed with selectSpool, use tool 0
codingcatgirl May 15, 2021
3418e07
remove todo that was done already
codingcatgirl May 15, 2021
a26660e
selecting new spool mid-print? ask whether to commit usage so far
codingcatgirl May 15, 2021
37aa8eb
update features in readme :)
codingcatgirl May 15, 2021
e45a95a
just a lew UI tweak
codingcatgirl May 15, 2021
3d8979a
change dropdown menu max height
codingcatgirl May 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ If you like it, I would be thankful about a cup of coffee :)
- [X] Filter spool table
- [X] Table column visibility
- [X] Scan QR/Barcodes of a spool
- [X] Multi Tool support
- [X] Support for manual mid-print filament change

## Planning / next features
- [ ] External Database (IN PROGRESS)
Expand Down
293 changes: 156 additions & 137 deletions octoprint_SpoolManager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def initialize(self):

self._lastPrintState = None

self.metaDataFilamentLength = None
self.metaDataFilamentLengths = []

self.alreadyCanceled = False

Expand All @@ -60,82 +60,91 @@ def initialize(self):

################################################################################################### public functions

def checkRemainingFilament(self):

def checkRemainingFilament(self, onlyToolIndex=None):
shouldWarn = self._settings.get_boolean([SettingsKeys.SETTINGS_KEY_WARN_IF_FILAMENT_NOT_ENOUGH])
if (shouldWarn == False):
if not shouldWarn:
return True

# - check, if spool change in pause-mode

# - check if new spool fits for current printjob
selectedSpool = self.loadSelectedSpool()

if (selectedSpool == None or self.metaDataFilamentLength == None):
return False

# need attributes present: diameter, density, totalWeight
warningMessage = "Following fields not set in Spool '" + selectedSpool.displayName + "': "
missing = False

diameter = selectedSpool.diameter
density = selectedSpool.density
totalWeight = selectedSpool.totalWeight
usedWeight = selectedSpool.usedWeight
if (diameter == None):
missing = True
warningMessage += "diameter "
if (density == None):
missing = True
warningMessage += "density "
if (totalWeight == None):
missing = True
warningMessage += "total weight"
if (usedWeight == None):
usedWeight = 0.0

if (missing == True):
self._sendMessageToClient("warning", "Filament prediction not possible!", warningMessage)
return False

warningMessage = "One of the needed fields are not a number in Spool '" + selectedSpool.displayName + "': "
notANumber = False
try:
diameter = float(diameter)
except ValueError:
notANumber = True
warningMessage += "diameter "
try:
density = float(density)
except ValueError:
notANumber = True
warningMessage += "density "
try:
totalWeight = float(totalWeight)
except ValueError:
notANumber = True
warningMessage += "total weight "
try:
usedWeight = float(usedWeight)
except ValueError:
notANumber = True
warningMessage += "used weight "

if (notANumber == True):
self._sendMessageToClient("warning", "Filament prediction not possible!", warningMessage)
return False

# Benötigtes Gewicht = gewicht(geplante länge, durchmesser, dichte)
requiredWeight = int(self._calculateWeight(self.metaDataFilamentLength, diameter, density))

# Vorhanden Gewicht = Gesamtgewicht - Verbrauchtes Gewicht
remainingWeight = totalWeight - usedWeight

if (remainingWeight < requiredWeight):
self._sendMessageToClient("warning", "Filament not enough!",
"Required '" + str(requiredWeight) + "g' available from Spool '" + str(remainingWeight) + "g'!")
return False
return True
selectedSpools = self.loadSelectedSpools()

result = True

for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths):
if onlyToolIndex is not None and onlyToolIndex != 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


################################################################################################## private functions
Expand Down Expand Up @@ -282,62 +291,76 @@ def _on_printJobStarted(self):
# starting new print
self._filamentOdometer.reset()

spoolModel = self.loadSelectedSpool()
if (spoolModel != None):
if (StringUtils.isEmpty(spoolModel.firstUse) == True):
firstUse = datetime.now()
spoolModel.firstUse = firstUse
self._databaseManager.saveSpool(spoolModel)
self._sendDataToClient(dict(
action="reloadTable"
))
pass
reloadTable = False
selectedSpools = self.loadSelectedSpools()
for toolIndex, filamentLength in enumerate(self.metaDataFilamentLengths):
spoolModel = selectedSpools[toolIndex] if toolIndex < len(selectedSpools) else None
if (spoolModel != None):
if (StringUtils.isEmpty(spoolModel.firstUse) == True):
firstUse = datetime.now()
spoolModel.firstUse = firstUse
self._databaseManager.saveSpool(spoolModel)
reloadTable = True
if reloadTable:
self._sendDataToClient(dict(
action="reloadTable"
))

def commitOdometerData(self):
reload = False
selectedSpools = self.loadSelectedSpools()
for toolIndex, spoolModel in enumerate(selectedSpools):
if spoolModel is None:
self._logger.warning("Tool %d: No spool selected, could not update values after print" % toolIndex)
continue

# - Last usage datetime
lastUsage = datetime.now()
spoolModel.lastUse = lastUsage
# - Used length
try:
currentExtrusionLenght = self._filamentOdometer.get_extrusion('Tool%d' % toolIndex)
except KeyError:
self._logger.info("Tool %d: No filament extruded" % toolIndex)
continue
self._logger.info("Tool %d: Extruded filament length: %s" % (toolIndex, str(currentExtrusionLenght)))
spoolUsedLength = 0.0 if StringUtils.isEmpty(spoolModel.usedLength) == True else spoolModel.usedLength
self._logger.info("Tool %d: Current Spool filament length: %s" % (toolIndex, str(spoolUsedLength)))
newUsedLength = spoolUsedLength + currentExtrusionLenght
self._logger.info("Tool %d: New Spool filament length: %s" % (toolIndex, str(newUsedLength)))
spoolModel.usedLength = newUsedLength
# - Used weight
diameter = spoolModel.diameter
density = spoolModel.density
if diameter is None or density is None:
self._logger.warning(
"Tool %d: Could not update spool weight, because diameter or density not set in spool '%s'" % (toolIndex, spoolModel.displayName)
)
else:
usedWeight = self._calculateWeight(currentExtrusionLenght, diameter, density)
spoolUsedWeight = 0.0 if spoolModel.usedWeight == None else spoolModel.usedWeight
newUsedWeight = spoolUsedWeight + usedWeight
spoolModel.usedWeight = newUsedWeight

#### print job finished
def _on_printJobFinished(self, printStatus, payload):
self._databaseManager.saveSpool(spoolModel)
reload = True

spoolModel = self.loadSelectedSpool()
if (spoolModel == None):
self._logger.warning("No spool selected, could not update values after print")
return
self._filamentOdometer.reset_extruded_length()

# - Last usage datetime
lastUsage = datetime.now()
spoolModel.lastUse = lastUsage
# - Used length
currentExtrusionForAllTools = self._filamentOdometer.getExtrusionForAllTools()
# if (len(currentExtrusionForAllTools) == 0):
# self._logger.warning("Odomenter could not detect any extrusion")
# return
currentExtrusionLenght = currentExtrusionForAllTools # TODO Support of multi-tool
self._logger.info("Extruded filament length: " + str(currentExtrusionLenght))
spoolUsedLength = 0.0 if StringUtils.isEmpty(spoolModel.usedLength) == True else spoolModel.usedLength
self._logger.info("Current Spool filament length: " + str(spoolUsedLength))
newUsedLength = spoolUsedLength + currentExtrusionLenght
self._logger.info("New Spool filament length: " + str(newUsedLength))
spoolModel.usedLength = newUsedLength
# - Used weight
diameter = spoolModel.diameter
density = spoolModel.density
if (diameter == None or density == None):
self._logger.warning("Could not update spool weight, because diameter or density not set in spool '"+spoolModel.displayName+"'")
else:
usedWeight = self._calculateWeight(currentExtrusionLenght, diameter, density)
spoolUsedWeight = 0.0 if spoolModel.usedWeight == None else spoolModel.usedWeight
newUsedWeight = spoolUsedWeight + usedWeight
spoolModel.usedWeight = newUsedWeight

self._databaseManager.saveSpool(spoolModel)
self._sendDataToClient(dict(
action = "reloadTable and sidebarSpools"
))
pass
if reload:
self._sendDataToClient(dict(
action="reloadTable and sidebarSpools"
))

#### print job finished
def _on_printJobFinished(self, printStatus, payload):
self.commitOdometerData()

def _on_clientOpened(self, payload):
# start-workaround https://github.com/foosel/OctoPrint/issues/3400
import time
time.sleep(3)
selectedSpoolAsDict = None
selectedSpoolsAsDicts = []

# Check if database is available
# connected = self._databaseManager.reConnectToDatabase()
Expand All @@ -360,16 +383,14 @@ def _on_clientOpened(self, payload):
# Send plugin storage information
## Storage
if (connectionErrorResult == None):
selectedSpool = self.loadSelectedSpool()
if (selectedSpool):
selectedSpoolAsDict = Transformer.transformSpoolModelToDict(selectedSpool)
else:
# spool not found
pass
selectedSpoolsAsDicts = [
(None if selectedSpool is None else Transformer.transformSpoolModelToDict(selectedSpool))
for selectedSpool in self.loadSelectedSpools()
]

pluginNotWorking = connectionErrorResult != None
self._sendDataToClient(dict(action = "initalData",
selectedSpool = selectedSpoolAsDict,
selectedSpools = selectedSpoolsAsDicts,
isFilamentManagerPluginAvailable = self._filamentManagerPluginImplementation != None,
pluginNotWorking = pluginNotWorking
))
Expand All @@ -380,21 +401,18 @@ def _on_clientClosed(self, payload):
self.databaseConnectionProblemConfirmed = False

def _on_file_selectionChanged(self, payload):
self.metaDataFilamentLengths = []

if ("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"]):
allFilemants = metadata["analysis"]["filament"]
# TODO support multiple tools and not only tool0 (or first you got)
if (allFilemants):
for toolIndex in range(5):
toolName = "tool" + str(toolIndex)
if (toolName in allFilemants):
self.metaDataFilamentLength = allFilemants[toolName]["length"]
self.checkRemainingFilament()
return

self.metaDataFilamentLength = 0.0
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()

pass
######################################################################################### Hooks and public functions
Expand Down Expand Up @@ -489,6 +507,7 @@ def get_settings_defaults(self):

# Not visible
settings[SettingsKeys.SETTINGS_KEY_SELECTED_SPOOL_DATABASE_ID] = None
settings[SettingsKeys.SETTINGS_KEY_SELECTED_SPOOLS_DATABASE_IDS] = []
settings[SettingsKeys.SETTINGS_KEY_HIDE_EMPTY_SPOOL_IN_SIDEBAR] = False
settings[SettingsKeys.SETTINGS_KEY_HIDE_INACTIVE_SPOOL_IN_SIDEBAR] = True
## Genral
Expand Down
Loading