slot_kprofile.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """Find the stored K-profile for an AMS slot on a particular nozzle.
  2. K-profiles are per-nozzle: Bambuddy already keeps one row per
  3. ``(spool, printer, extruder)`` in ``spool_k_profile`` (and the Spoolman mirror
  4. in ``spoolman_k_profile``), each with its own ``cali_idx`` and K value. On the
  5. maintainer's H2C, one black PLA reads 0.018 on the left hotend and 0.020 on the
  6. right, stored as calibration indices 16 and 15.
  7. A tray, by contrast, holds exactly **one** ``cali_idx``. So whenever a slot's
  8. nozzle changes — which on a Filament Track Switch machine happens every time an
  9. AMS is moved between the switch's two inlets — the stored counterpart for the
  10. new nozzle has to be looked up and re-selected. This module is that lookup.
  11. """
  12. from dataclasses import dataclass
  13. from sqlalchemy import select
  14. from sqlalchemy.ext.asyncio import AsyncSession
  15. from backend.app.models.spool import Spool
  16. from backend.app.models.spool_assignment import SpoolAssignment
  17. from backend.app.models.spool_k_profile import SpoolKProfile
  18. from backend.app.models.spoolman_k_profile import SpoolmanKProfile
  19. from backend.app.models.spoolman_slot_assignment import SpoolmanSlotAssignment
  20. @dataclass(frozen=True)
  21. class SlotKProfile:
  22. """A stored calibration profile, flattened across the two inventory backends."""
  23. cali_idx: int | None
  24. k_value: float | None
  25. name: str | None
  26. extruder: int
  27. # The preset the profile was calibrated under. ``extrusion_cali_sel`` must
  28. # carry this rather than the tray's RFID value, or the firmware mislinks it.
  29. filament_id: str | None
  30. async def find_slot_kprofile_for_extruder(
  31. db: AsyncSession,
  32. printer_id: int,
  33. ams_id: int,
  34. tray_id: int,
  35. extruder: int,
  36. nozzle_diameter: str,
  37. ) -> SlotKProfile | None:
  38. """Stored profile for whatever is in this slot, calibrated for ``extruder``.
  39. Returns None when the slot holds no known spool, or when that spool has no
  40. profile for this nozzle — an operator who calibrated only one side should
  41. keep the binding they set by hand rather than have it swapped for a guess.
  42. Local spools take priority over Spoolman, matching the rest of the
  43. K-profile cascade.
  44. """
  45. assignment = (
  46. await db.execute(
  47. select(SpoolAssignment).where(
  48. SpoolAssignment.printer_id == printer_id,
  49. SpoolAssignment.ams_id == ams_id,
  50. SpoolAssignment.tray_id == tray_id,
  51. )
  52. )
  53. ).scalar_one_or_none()
  54. if assignment is not None:
  55. profile = (
  56. (
  57. await db.execute(
  58. select(SpoolKProfile).where(
  59. SpoolKProfile.spool_id == assignment.spool_id,
  60. SpoolKProfile.printer_id == printer_id,
  61. SpoolKProfile.extruder == extruder,
  62. SpoolKProfile.nozzle_diameter == nozzle_diameter,
  63. )
  64. )
  65. )
  66. .scalars()
  67. .first()
  68. )
  69. if profile is not None:
  70. spool = (await db.execute(select(Spool).where(Spool.id == assignment.spool_id))).scalar_one_or_none()
  71. return SlotKProfile(
  72. cali_idx=profile.cali_idx,
  73. k_value=profile.k_value,
  74. name=profile.name,
  75. extruder=profile.extruder,
  76. filament_id=(spool.slicer_filament if spool else None),
  77. )
  78. # A known spool with no profile for this nozzle is a deliberate stop:
  79. # falling through to Spoolman would answer for a different spool.
  80. return None
  81. sm_assignment = (
  82. await db.execute(
  83. select(SpoolmanSlotAssignment).where(
  84. SpoolmanSlotAssignment.printer_id == printer_id,
  85. SpoolmanSlotAssignment.ams_id == ams_id,
  86. SpoolmanSlotAssignment.tray_id == tray_id,
  87. )
  88. )
  89. ).scalar_one_or_none()
  90. if sm_assignment is None:
  91. return None
  92. sm_profile = (
  93. (
  94. await db.execute(
  95. select(SpoolmanKProfile).where(
  96. SpoolmanKProfile.spoolman_spool_id == sm_assignment.spoolman_spool_id,
  97. SpoolmanKProfile.printer_id == printer_id,
  98. SpoolmanKProfile.extruder == extruder,
  99. SpoolmanKProfile.nozzle_diameter == nozzle_diameter,
  100. )
  101. )
  102. )
  103. .scalars()
  104. .first()
  105. )
  106. if sm_profile is None:
  107. return None
  108. # Spoolman rows carry no slicer preset; the caller falls back to the tray's
  109. # own tray_info_idx for filament_id.
  110. return SlotKProfile(
  111. cali_idx=sm_profile.cali_idx,
  112. k_value=sm_profile.k_value,
  113. name=sm_profile.name,
  114. extruder=sm_profile.extruder,
  115. filament_id=None,
  116. )