feat: Filament Track Switch (H2-series O2L-FTS) support

The Filament Track Switch (H2-series accessory, product code O2L-FTS) feeds
every AMS to both extruders through a two-track switch. Port full support
across the device layer, project config, and GUI.

Device / config:
- Model the switch-aware AMS binding (the set of extruders an AMS can feed
  and which input track A/B feeds it), switch readiness, the O2L-FTS firmware
  module, and the fun2 capability bit for checking a slice against installed
  hardware.
- Register has_filament_switcher and enable_filament_dynamic_map as project
  config that persists with the project and restores from a saved 3mf, and
  force both back to false on every project/printer/CLI load path. Live
  device sync is the only thing that sets them true.

GUI:
- Sidebar sync activates the switch from live device state, attributes each
  AMS to the extruder its input track feeds, shows a floating status icon
  (ready / not-calibrated), and surfaces a one-time tip / not-calibrated
  warning.
- Send dialog gains a non-blocking slice-vs-hardware mismatch warning and a
  blocking error when a slice needs dynamic nozzle mapping but the switch is
  missing or not set up.
- AMS load/unload guards, AMS-view routing glyph + un-calibrated banner +
  hidden external-spool road, and mapping-popup external-spool lockout.
- Filament pickers collapse the per-extruder split into a single deduplicated
  "AMS filaments" group with a smart-assign toggle when the switch is ready.
- Firmware-upgrade panel lists the O2L-FTS accessory and its version.
- Device-provided filament-change steps (ams.cfs) drive the change-step
  display when firmware sends them, including the three switch steps.
- "Load current filament" asks which extruder to feed via a
  FeedDirectionDialog when the switch is calibrated.

Inert without the accessory: every path is gated on the switch being
installed (MQTT aux bit 29, default off) or ready, both project flags default
false, and the per-extruder AMS attribution is byte-identical, so AMS state,
the send/load UI, and sliced g-code are unchanged for every printer that does
not report a Filament Track Switch.
This commit is contained in:
SoftFever
2026-07-09 18:57:05 +08:00
parent 26a0caff96
commit 0d31325df0
38 changed files with 1655 additions and 38 deletions

View File

@@ -0,0 +1,38 @@
<svg width="17" height="9" viewBox="0 0 17 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_iiii_6472_20428)">
<rect x="0.0527344" y="0.0527344" width="16" height="8.42105" rx="0.842105" fill="#808080"/>
</g>
<rect x="0.0527344" y="0.0527344" width="16" height="8.42105" rx="0.842105" stroke="#C2C2C2" stroke-width="0.105263"/>
<path d="M10.7006 2.79037H10.5244L10.426 2.792C9.41175 2.82502 8.47921 3.36695 7.94928 4.23755C7.46947 5.02578 6.61347 5.50693 5.69069 5.50693H5.51452V4.94947C5.51452 4.80429 5.35736 4.71355 5.23163 4.78614L3.94526 5.52882C3.81952 5.60141 3.81952 5.78289 3.94526 5.85548L5.23163 6.59816C5.35736 6.67075 5.51452 6.58001 5.51452 6.43483V5.87736H5.69069C6.74276 5.87736 7.71879 5.32886 8.26581 4.43018C8.73063 3.66656 9.54849 3.1912 10.4381 3.16225L10.5244 3.1608H10.7006V3.71826C10.7006 3.86344 10.8577 3.95418 10.9835 3.88159L12.2698 3.13891C12.3956 3.06632 12.3956 2.88485 12.2698 2.81225L10.9835 2.06958C10.8577 1.99699 10.7006 2.08773 10.7006 2.23291V2.79037Z" fill="#EEEEEE"/>
<path d="M5.51452 2.79037H5.69069L5.78908 2.792C6.80334 2.82502 7.73588 3.36695 8.26581 4.23755C8.74562 5.02578 9.60161 5.50693 10.5244 5.50693H10.7006V4.94947C10.7006 4.80429 10.8577 4.71355 10.9835 4.78614L12.2698 5.52882C12.3956 5.60141 12.3956 5.78289 12.2698 5.85548L10.9835 6.59816C10.8577 6.67075 10.7006 6.58001 10.7006 6.43483V5.87736H10.5244C9.47233 5.87736 8.4963 5.32886 7.94928 4.43018C7.48446 3.66656 6.6666 3.1912 5.77697 3.16225L5.69069 3.1608H5.51452V3.71826C5.51452 3.86344 5.35735 3.95418 5.23162 3.88159L3.94525 3.13891C3.81952 3.06632 3.81952 2.88485 3.94525 2.81225L5.23162 2.06958C5.35735 1.99699 5.51452 2.08773 5.51452 2.23291V2.79037Z" fill="#EEEEEE"/>
<defs>
<filter id="filter0_iiii_6472_20428" x="-1" y="-1" width="18.1055" height="10.5264" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6472_20428"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect1_innerShadow_6472_20428" result="effect2_innerShadow_6472_20428"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect2_innerShadow_6472_20428" result="effect3_innerShadow_6472_20428"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect3_innerShadow_6472_20428" result="effect4_innerShadow_6472_20428"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,38 @@
<svg width="33" height="18" viewBox="0 0 33 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_iiii_6472_20289)">
<rect x="0.105469" y="0.105469" width="32" height="16.8421" rx="1.68421" fill="#EA3D23"/>
</g>
<rect x="0.105469" y="0.105469" width="32" height="16.8421" rx="1.68421" stroke="#C2C2C2" stroke-width="0.210526"/>
<path d="M21.4011 5.57976H21.0488L20.852 5.58301C18.8235 5.64906 16.9584 6.73293 15.8986 8.47412C14.9389 10.0506 13.2269 11.0129 11.3814 11.0129H11.029V9.89796C11.029 9.6076 10.7147 9.42613 10.4633 9.57131L7.89051 11.0567C7.63905 11.2018 7.63905 11.5648 7.89051 11.71L10.4633 13.1953C10.7147 13.3405 11.029 13.159 11.029 12.8687V11.7538H11.3814C13.4855 11.7538 15.4376 10.6567 16.5316 8.85939C17.4613 7.33213 19.097 6.38143 20.8762 6.32352L21.0488 6.32062H21.4011V7.43555C21.4011 7.72591 21.7155 7.90739 21.9669 7.76221L24.5397 6.27685C24.7911 6.13167 24.7911 5.76871 24.5397 5.62353L21.9669 4.13818C21.7155 3.993 21.4011 4.17447 21.4011 4.46484V5.57976Z" fill="#EEEEEE"/>
<path d="M11.029 5.57976H11.3814L11.5782 5.58301C13.6067 5.64906 15.4718 6.73293 16.5316 8.47412C17.4912 10.0506 19.2032 11.0129 21.0488 11.0129H21.4011V9.89796C21.4011 9.6076 21.7155 9.42613 21.9669 9.57131L24.5397 11.0567C24.7911 11.2018 24.7911 11.5648 24.5397 11.71L21.9669 13.1953C21.7155 13.3405 21.4011 13.159 21.4011 12.8687V11.7538H21.0488C18.9447 11.7538 16.9926 10.6567 15.8986 8.85939C14.9689 7.33213 13.3332 6.38143 11.5539 6.32352L11.3814 6.32062H11.029V7.43555C11.029 7.72591 10.7147 7.90739 10.4632 7.76221L7.8905 6.27685C7.63904 6.13167 7.63904 5.76871 7.8905 5.62353L10.4632 4.13818C10.7147 3.993 11.029 4.17447 11.029 4.46484V5.57976Z" fill="#EEEEEE"/>
<defs>
<filter id="filter0_iiii_6472_20289" x="-2" y="-2" width="36.2109" height="21.0527" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6472_20289"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect1_innerShadow_6472_20289" result="effect2_innerShadow_6472_20289"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect2_innerShadow_6472_20289" result="effect3_innerShadow_6472_20289"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="effect3_innerShadow_6472_20289" result="effect4_innerShadow_6472_20289"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB