Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ SpoolMan can print QR-code stickers for every spool; follow the SpoolMan label g
- set `TRACK_LAYER_USAGE` to `True` to switch to per-layer tracking/consumption **while `AUTO_SPEND` is also `True`**. If `AUTO_SPEND` is `False`, all filament tracking remains disabled regardless of `TRACK_LAYER_USAGE`.
- set `AUTO_SPEND` to `True` if you want automatic filament usage tracking (see the AUTO SPEND notes below).
- set `DISABLE_MISMATCH_WARNING` to `True` to hide mismatch warnings in the UI (mismatches are still detected and logged to `data/filament_mismatch.json`).
- set `CLEAR_ASSIGNMENT_WHEN_EMPTY` to `True` if you want OpenSpoolMan to clear any SpoolMan assignment and reset the AMS tray whenever the printer reports no spool in that slot.
- By default, the app reads `data/3d_printer_logs.db` for print history; override it through `OPENSPOOLMAN_PRINT_HISTORY_DB` or via the screenshot helper (which targets `data/demo.db` by default).

- Run SpoolMan.
Expand Down
132 changes: 123 additions & 9 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import traceback
import uuid
from collections import Counter

from flask import Flask, request, render_template, redirect, url_for

Expand Down Expand Up @@ -38,7 +39,51 @@

@app.context_processor
def fronted_utilities():
return dict(SPOOLMAN_BASE_URL=SPOOLMAN_BASE_URL, AUTO_SPEND=AUTO_SPEND, color_is_dark=color_is_dark, BASE_URL=BASE_URL, EXTERNAL_SPOOL_AMS_ID=EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID=EXTERNAL_SPOOL_ID, PRINTER_MODEL=mqtt_bambulab.getPrinterModel(), PRINTER_NAME=PRINTER_NAME)
printer_model = mqtt_bambulab.getPrinterModel() or {}
ams_models_by_id = mqtt_bambulab.getDetectedAmsModelsById()

return dict(
SPOOLMAN_BASE_URL=SPOOLMAN_BASE_URL,
AUTO_SPEND=AUTO_SPEND,
AMS_MODELS_BY_ID=ams_models_by_id,
color_is_dark=color_is_dark,
BASE_URL=BASE_URL,
EXTERNAL_SPOOL_AMS_ID=EXTERNAL_SPOOL_AMS_ID,
EXTERNAL_SPOOL_ID=EXTERNAL_SPOOL_ID,
PRINTER_MODEL=printer_model,
PRINTER_NAME=PRINTER_NAME,
)


def build_ams_labels(ams_data):
models_by_id = mqtt_bambulab.getDetectedAmsModelsById()
base_labels = []
for ams in ams_data:
ams_id = ams.get("id")
key = str(ams_id)
base_name = models_by_id.get(key) or models_by_id.get(ams_id) or "AMS"
base_labels.append(base_name)

totals = Counter(base_labels)
takt = Counter()
labels = {}

for ams, base_name in zip(ams_data, base_labels):
takt[base_name] += 1
suffix = f" {takt[base_name]}" if totals[base_name] > 1 else ""
label = f"{base_name}{suffix}"
ams_id = ams.get("id")
labels[str(ams_id)] = label
labels[ams_id] = label

return labels


def _augment_tray(spool_list, tray_data, ams_id, tray_id):
augmentTrayDataWithSpoolMan(spool_list, tray_data, ams_id, tray_id)
if tray_data.get("unmapped_bambu_tag"):
spoolman_service.clear_active_spool_for_tray(ams_id, tray_id)
augmentTrayDataWithSpoolMan(spool_list, tray_data, ams_id, tray_id)

@app.route("/issue")
def issue():
Expand Down Expand Up @@ -72,12 +117,12 @@ def issue():

active_spool = None
for spool in spool_list:
if spool.get("extra") and spool["extra"].get("active_tray") and spool["extra"]["active_tray"] == json.dumps(trayUid(ams_id, tray_id)):
if spool.get("extra") and spool["extra"].get("active_tray") and spool["extra"].get("active_tray") == json.dumps(trayUid(ams_id, tray_id)):
active_spool = spool
break

if tray_data:
augmentTrayDataWithSpoolMan(spool_list, tray_data, trayUid(ams_id, tray_id))
_augment_tray(spool_list, tray_data, ams_id, tray_id)

#TODO: Determine issue
#New bambulab spool
Expand Down Expand Up @@ -135,6 +180,73 @@ def fill():

return render_template('fill.html', spools=spools, ams_id=ams_id, tray_id=tray_id, materials=materials, selected_materials=selected_materials)

@app.route("/assign_bambu_spool")
def assign_bambu_spool():
if not mqtt_bambulab.isMqttClientConnected():
return render_template('error.html', exception="MQTT is disconnected. Is the printer online?")

bambu_tag = request.args.get("tag")
ams_id = request.args.get("ams")
tray_id = request.args.get("tray")
spool_id = request.args.get("spool_id")

if not all([bambu_tag, ams_id, tray_id]):
return render_template('error.html', exception="Missing AMS ID, Tray ID, or Bambu spool tag.")

if bambu_tag == "00000000000000000000000000000000":
return render_template('error.html', exception="No Bambu spool was detected in this tray.")

if spool_id:
if READ_ONLY_MODE:
return render_template('error.html', exception="Live read-only mode: linking Bambu spools is disabled.")

spool_data = spoolman_client.getSpoolById(spool_id)
extras = spool_data.get("extra") or {}

spoolman_client.patchExtraTags(spool_id, extras, {
"tag": json.dumps(bambu_tag),
})

mqtt_bambulab.setActiveTray(spool_id, extras, ams_id, tray_id)
setActiveSpool(ams_id, tray_id, spool_data)

return redirect(url_for('home', success_message=f"Linked Bambu spool to SpoolMan spool {spool_id} on AMS {ams_id}, Tray {tray_id}."))

spools = mqtt_bambulab.fetchSpools()
materials = extract_materials(spools)
selected_materials = []

try:
last_ams_config = mqtt_bambulab.getLastAMSConfig()
default_material = None

if ams_id == EXTERNAL_SPOOL_AMS_ID:
default_material = last_ams_config.get("vt_tray", {}).get("tray_type")
else:
for ams in last_ams_config.get("ams", []):
if str(ams.get("id")) != str(ams_id):
continue

for tray in ams.get("tray", []):
if str(tray.get("id")) == str(tray_id):
default_material = tray.get("tray_type")
break

if default_material and default_material in materials:
selected_materials.append(default_material)
except Exception:
pass

return render_template(
'assign_bambu_spool.html',
spools=spools,
ams_id=ams_id,
tray_id=tray_id,
bambu_tag=bambu_tag,
materials=materials,
selected_materials=selected_materials,
)

@app.route("/spool_info")
def spool_info():
if not mqtt_bambulab.isMqttClientConnected():
Expand All @@ -150,12 +262,12 @@ def spool_info():

issue = False
#TODO: Fix issue when external spool info is reset via bambulab interface
augmentTrayDataWithSpoolMan(spool_list, vt_tray_data, trayUid(EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID))
_augment_tray(spool_list, vt_tray_data, EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID)
issue |= vt_tray_data.get("issue", False)

for ams in ams_data:
for tray in ams["tray"]:
augmentTrayDataWithSpoolMan(spool_list, tray, trayUid(ams["id"], tray["id"]))
_augment_tray(spool_list, tray, ams["id"], tray["id"])
issue |= tray.get("issue", False)

if not tag_id and not spool_id:
Expand Down Expand Up @@ -194,7 +306,8 @@ def spool_info():
break

if current_spool:
return render_template('spool_info.html', tag_id=tag_id, current_spool=current_spool, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue)
ams_labels = build_ams_labels(ams_data)
return render_template('spool_info.html', tag_id=tag_id, current_spool=current_spool, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue, ams_labels=ams_labels)
else:
return render_template('error.html', exception="Spool not found")
except Exception as e:
Expand Down Expand Up @@ -302,15 +415,16 @@ def home():

issue = False
#TODO: Fix issue when external spool info is reset via bambulab interface
augmentTrayDataWithSpoolMan(spool_list, vt_tray_data, trayUid(EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID))
_augment_tray(spool_list, vt_tray_data, EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID)
issue |= vt_tray_data["issue"]

for ams in ams_data:
for tray in ams["tray"]:
augmentTrayDataWithSpoolMan(spool_list, tray, trayUid(ams["id"], tray["id"]))
_augment_tray(spool_list, tray, ams["id"], tray["id"])
issue |= tray["issue"]

return render_template('index.html', success_message=success_message, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue)
ams_labels = build_ams_labels(ams_data)
return render_template('index.html', success_message=success_message, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue, ams_labels=ams_labels)
except Exception as e:
traceback.print_exc()
return render_template('error.html', exception=str(e))
Expand Down
5 changes: 3 additions & 2 deletions config.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ PRINTER_IP=
PRINTER_NAME=
SPOOLMAN_BASE_URL=
AUTO_SPEND=False
TRACK_LAYER_USAGE=False
SPOOL_SORTING=filament.material:asc,filament.vendor.name:asc,filament.name:asc
TRACK_LAYER_USAGE=False
SPOOL_SORTING=filament.material:asc,filament.vendor.name:asc,filament.name:asc
CLEAR_ASSIGNMENT_WHEN_EMPTY=False
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ def _env_to_bool(name: str, default: bool = False) -> bool:
"SPOOL_SORTING", "filament.material:asc,filament.vendor.name:asc,filament.name:asc"
)
DISABLE_MISMATCH_WARNING = _env_to_bool("DISABLE_MISMATCH_WARNING", False)
CLEAR_ASSIGNMENT_WHEN_EMPTY = _env_to_bool("CLEAR_ASSIGNMENT_WHEN_EMPTY", False)
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
build: .
env_file: "config.env"
ports:
- "8001:8001"
- "8001:8000"
volumes:
- ./logs:/home/app/logs
- ./data:/home/app/data
Expand Down
Loading