mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 01:41:03 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e43a2ac13 | ||
|
|
7d8261668e | ||
|
|
00127beffa | ||
|
|
927eb9922c | ||
|
|
e5e30ee0e8 | ||
|
|
7515fb480f | ||
|
|
acc1d6fd21 | ||
|
|
6ca0fc5ef1 | ||
|
|
bcf7d078cd | ||
|
|
15fc96fca7 | ||
|
|
1fffda0533 | ||
|
|
33e26a127e | ||
|
|
6dd0cb9eba | ||
|
|
9bf0fc2a8e | ||
|
|
5bf6db95b3 | ||
|
|
0feb87b417 |
@@ -0,0 +1,199 @@
|
|||||||
|
# Multi-extruder and multi-AMS device control
|
||||||
|
|
||||||
|
`DeviceManager`/`MachineObject` model the toolheads, AMS units, and filament slots of a connected
|
||||||
|
printer and drive the device-control UI from that model. The model represents the counts as
|
||||||
|
device-reported runtime values rather than compile-time caps, so a printer with an arbitrary number
|
||||||
|
of toolheads, several AMS units, and a non-uniform number of slots per unit is handled by the same
|
||||||
|
code paths. The printer agent is responsible for translating its printer's state into the shared
|
||||||
|
JSON dialect this model consumes; the agent boundary is described in
|
||||||
|
[printer-agent.md](printer-agent.md), and this document describes the dialect and the model.
|
||||||
|
|
||||||
|
## Model layout
|
||||||
|
|
||||||
|
`MachineObject` owns the device subsystems that this document covers:
|
||||||
|
|
||||||
|
- `DevExtderSystem` — the toolheads (extruders) and their per-toolhead filament/temperature state.
|
||||||
|
- `DevFilaSystem` — the AMS units (`DevAms`) and their trays (`DevAmsTray`).
|
||||||
|
- `DevNozzleSystem` — the nozzles and, on rack printers, `DevNozzleRack`.
|
||||||
|
- `DevFilaSwitch` — a filament switch that can feed several AMS units to more than one extruder.
|
||||||
|
|
||||||
|
Each subsystem exposes its own count — `DevExtderSystem::GetTotalExtderCount()`,
|
||||||
|
`DevFilaSystem::GetAmsCount()`, `DevAms::GetSlotCount()` — and the rest of the application derives
|
||||||
|
its layout from those values rather than from fixed assumptions.
|
||||||
|
|
||||||
|
## Wire dialect
|
||||||
|
|
||||||
|
The dialect is the Bambu-shaped JSON that `DeviceManager` and `MachineObject` already consume. An
|
||||||
|
agent whose printer speaks something else is expected to translate into it; the agent is not free
|
||||||
|
to invent a new shape. Two representations coexist for extruders, and one for AMS/slots.
|
||||||
|
|
||||||
|
### Extruders
|
||||||
|
|
||||||
|
The generic representation is `print.device.extruder`, parsed by
|
||||||
|
`ExtderSystemParser::ParseV2_0` (`DevExtruderSystem.cpp`). It has a packed `state` integer and an
|
||||||
|
`info` array with one object per toolhead:
|
||||||
|
|
||||||
|
```text
|
||||||
|
device.extruder.state packed integer
|
||||||
|
bits 0..3 total extruder count
|
||||||
|
bits 4..7 current extruder id
|
||||||
|
bits 8..11 target extruder id
|
||||||
|
bits 12..14 switch state
|
||||||
|
bits 15..18 currently loading extruder id
|
||||||
|
bit 19 busy for loading
|
||||||
|
|
||||||
|
device.extruder.info[] one entry per toolhead
|
||||||
|
id extruder id
|
||||||
|
filam_bak array of backup-group bits
|
||||||
|
info bit 1 has filament, bit 2 buffer has filament, bit 3 nozzle present
|
||||||
|
temp bits 0..15 current temperature, bits 16..31 target temperature
|
||||||
|
spre slot_id bits 0..7, ams_id bits 8..15 (previous)
|
||||||
|
snow same packing (current)
|
||||||
|
star same packing (target)
|
||||||
|
stat bit 0..15 AMS status, bits 16..31 RFID status
|
||||||
|
hnow current nozzle id
|
||||||
|
```
|
||||||
|
|
||||||
|
The legacy single/dual representation is the pair of scalar fields `nozzle_temper` /
|
||||||
|
`nozzle_target_temper`, plus the `print.ams.tray_tar` / `tray_now` tray pointer, parsed by
|
||||||
|
`ExtderSystemParser::ParseV1_0`. That parser is a fallback and only fills the main extruder when the
|
||||||
|
reported toolhead count is one.
|
||||||
|
|
||||||
|
Physical extruder ids are fixed (`MAIN_EXTRUDER_ID = 0`, `DEPUTY_EXTRUDER_ID = 1`). Bambu printers
|
||||||
|
present the two toolheads as a left/right pair and invert the physical-to-logical mapping; generic
|
||||||
|
printers use the toolhead id itself as the logical id. `DevNozzle::GetLogicExtruderId()` /
|
||||||
|
`GetExtruderId()` centralise that distinction.
|
||||||
|
|
||||||
|
### AMS units and slots
|
||||||
|
|
||||||
|
AMS units arrive as `print.ams.ams[]` and are parsed by `DevFilaSystemParser::ParseV1_0`. Each unit
|
||||||
|
carries an `id` and an `info` bitfield:
|
||||||
|
|
||||||
|
```text
|
||||||
|
info bits 0..3 type: 0 dummy, 1 AMS, 2 AMS-Lite, 3 N3F, 4 N3S, 5 AMS-Lite-mixed (N9)
|
||||||
|
info bits 8..11 bound extruder id (0xE marks a switch-bound unit)
|
||||||
|
info bits 24..27 switch inlet when the unit is switch-bound
|
||||||
|
```
|
||||||
|
|
||||||
|
`DevAmsType` (`DevDefs.h`) enumerates the resulting unit kinds: `EXT_SPOOL`, `AMS`, `AMS_LITE`,
|
||||||
|
`N3F`, `N3S`, and the `AMS_LITE_MIXED` N9 variant. `AMS_LITE_MIXED` reports itself as `AMS_LITE` to
|
||||||
|
generic callers; code that needs the distinction uses `IsAmsLiteMixed()`.
|
||||||
|
|
||||||
|
A unit whose bound-extruder nibble is `0xE` is switch-bound: it is kept only when a `DevFilaSwitch`
|
||||||
|
is installed, in which case its extruder id is pinned to the main extruder and its binding set
|
||||||
|
becomes `{0, 1}`; otherwise the unit is dropped from the AMS list.
|
||||||
|
|
||||||
|
Each unit owns a map of `DevAmsTray` keyed by the tray id from the payload. The slot count is a
|
||||||
|
property of the unit kind and the reported trays:
|
||||||
|
|
||||||
|
- `AMS`, `AMS_LITE`, and `N3F` report four slots on Bambu printers, matching the firmware contract;
|
||||||
|
- `N3S` reports one slot;
|
||||||
|
- `EXT_SPOOL` and any non-Bambu unit report the number of trays actually present.
|
||||||
|
|
||||||
|
The tray ids are the payload's slot indices; they are expected to be unique, 0-based, and contiguous
|
||||||
|
within a unit, because the generic tray-index mapping below is cumulative.
|
||||||
|
|
||||||
|
Bulk/extended spools are carried by `print.vir_slot`, which `DeviceManager` parses into
|
||||||
|
`MachineObject::vt_slot`. Two ids are fixed: `VIRTUAL_TRAY_MAIN_ID = 255` (extruder 0) and
|
||||||
|
`VIRTUAL_TRAY_DEPUTY_ID = 254` (extruder 1); the id convention is `vt_id = 255 - extruder_id`, so
|
||||||
|
further extruders use 253, 252, … . The `vir_slot` parser recognises the 255/254 pair; the agent
|
||||||
|
filament descriptor path `DevFilaSystemParser::ParseAgentFilament` is the entry that stores a fuller
|
||||||
|
descending set in `vt_slot`.
|
||||||
|
|
||||||
|
## Mapping
|
||||||
|
|
||||||
|
Several subsystems relate AMS units, trays, and extruders, and more than one place derives a tray
|
||||||
|
index. The Bambu mapping path (`DevMappingUtil`) computes `ams_id * 4 + slot_id` and knows only the
|
||||||
|
Bambu unit kinds; `DevFilaSystem` provides the device model's own mapping below. The two agree for
|
||||||
|
Bambu units and diverge for a non-Bambu unit whose slot count is not four.
|
||||||
|
|
||||||
|
### AMS id to extruder id
|
||||||
|
|
||||||
|
`DevFilaSystem::GetExtruderIdByAmsId()` resolves an AMS id to the extruder it feeds:
|
||||||
|
|
||||||
|
- an AMS unit returns its bound extruder (`DevAms::GetExtruderId()`);
|
||||||
|
- a virtual tray returns `VIRTUAL_TRAY_MAIN_ID - vt_id`;
|
||||||
|
- Bambu's virtual AMS ids map to the main/deputy extruder.
|
||||||
|
|
||||||
|
A unit may be bound to more than one extruder when a `DevFilaSwitch` feeds it; the binding set
|
||||||
|
(`DevAms::m_binded_extruder_set`) and switch inlet are read by switch-aware code while the
|
||||||
|
single-extruder id stays pinned for legacy callers.
|
||||||
|
|
||||||
|
### Tray index
|
||||||
|
|
||||||
|
`DevFilaSystem::GetTrayIndexMap()` maps a global tray index to an `(ams_id, slot_id)` pair and is
|
||||||
|
used for backup-slot translation, calibration addressing, and the mapping popups.
|
||||||
|
|
||||||
|
- Bambu AMS: `ams_id * 4 + slot_id`; N3S: the AMS id; AMS-Lite-mixed (N9): `24 + slot_id`.
|
||||||
|
- Generic (non-Bambu): a running lane index — the count of trays in preceding units plus the slot
|
||||||
|
id. This depends on each unit's tray ids being 0-based and contiguous.
|
||||||
|
|
||||||
|
### Virtual-slot recognition
|
||||||
|
|
||||||
|
`devPrinterUtil::IsVirtualSlot()` decides whether an id denotes a virtual tray. On Bambu printers it
|
||||||
|
is the fixed pair `{255, 254}`. On generic printers it is the descending range
|
||||||
|
`[255 - count + 1, 255]`, where `count` is the selected printer preset's extruder count. The preset
|
||||||
|
is expected to match the connected device; when the two disagree the virtual range is wrong, which
|
||||||
|
is why a mismatch is reported rather than silently accepted.
|
||||||
|
|
||||||
|
## Device-control UI
|
||||||
|
|
||||||
|
The device-control widgets follow the same device-reported counts.
|
||||||
|
|
||||||
|
- `AMSModel` (`AMSItem.hpp`) mirrors `DevAmsType` numerically: `EXT_AMS`, `GENERIC_AMS`, `AMS_LITE`,
|
||||||
|
`N3F_AMS`, `N3S_AMS`. A plain `AMS` maps to `GENERIC_AMS`.
|
||||||
|
- On non-Bambu printers `use_generic_ams_layout()` routes `GENERIC_AMS` units through a variable-lane
|
||||||
|
rendering that draws one lane per reported tray; Bambu keeps its fixed four-slot rendering.
|
||||||
|
- `AMSControl` picks its layout by toolhead count, not vendor: `CreateAmsSingleNozzle` for one
|
||||||
|
toolhead, `CreateAmsDoubleNozzle` for two, and `CreateAmsMultiNozzle` (one preview and one AMS
|
||||||
|
simplebook per toolhead) for three or more. A two-toolhead generic printer therefore keeps the
|
||||||
|
legacy left/right presentation; that is deliberate.
|
||||||
|
- The status panel's `ExtruderImage` renders one per-toolhead state per nozzle and is resized to
|
||||||
|
`GetTotalExtderCount()`. Non-Bambu printers choose the active toolhead through
|
||||||
|
`m_generic_nozzle_selector` (shown when more than one toolhead exists); Bambu keeps its left/right
|
||||||
|
`m_nozzle_btn_panel`. Nozzle temperature controls are created on demand per toolhead.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **Single dialect.** The agent translates its protocol into this JSON; the model does not learn
|
||||||
|
vendor protocols. Extending the model with a new unit kind or field means extending the dialect.
|
||||||
|
- **Agent-produced counts.** Toolhead, AMS, and slot counts come from the device payload. Nothing
|
||||||
|
caps them at two toolheads or four slots except the Bambu contract described above.
|
||||||
|
- **Extruder dialect invariants.** `device.extruder.state`'s count field must equal the length of
|
||||||
|
`info[]`, and `info[]` must be ordered by extruder id with `id` equal to the array index: the
|
||||||
|
parser places toolheads by array position while `GetExtderById()` indexes by id.
|
||||||
|
- **One filament road per toolhead.** Each toolhead renders a single AMS filament road rather than an
|
||||||
|
independent per-toolhead road state. A generic scaffold that allowed the latter was removed as
|
||||||
|
unused; the one-extruder-per-toolhead road is the intended model.
|
||||||
|
- **Preset/device agreement.** The generic layout and virtual-slot range derive their count from the
|
||||||
|
selected printer preset. A printer preset must therefore declare the same number of
|
||||||
|
`nozzle_diameter` entries as the device has toolheads. A mismatch is surfaced only as a log warning
|
||||||
|
(`StatusPanel::check_extruder_count_mismatch()`), deliberately log-only, because the sidebar and
|
||||||
|
extruder logic would otherwise silently disagree.
|
||||||
|
- **Bambu compatibility.** Bambu-specific presentation (left/right pair, fixed 4-slot units, fixed
|
||||||
|
virtual ids, physical/logical inversion) is preserved behind the vendor check. Generic paths do
|
||||||
|
not alter it.
|
||||||
|
- **Tray-index invariant.** The generic tray-index map is cumulative, so payload tray ids must be
|
||||||
|
unique, 0-based, and contiguous per unit. A malformed id is a producer error.
|
||||||
|
- **Protocol markers.** The dialect has no neutral equivalent for some Bambu state, so agents that
|
||||||
|
lack it send empty `print.cfg`, `print.fun`, `print.aux`, and `print.stat`. Their presence selects
|
||||||
|
the new-protocol parse path; they are compatibility markers, not feature switches.
|
||||||
|
|
||||||
|
## Main implementation locations
|
||||||
|
|
||||||
|
- [`DevExtruderSystem`](../../src/slic3r/GUI/DeviceCore/DevExtruderSystem.h) — toolhead model and the
|
||||||
|
`device.extruder` parser
|
||||||
|
- [`DevFilaSystem`](../../src/slic3r/GUI/DeviceCore/DevFilaSystem.h) — AMS/tray model, the AMS parser,
|
||||||
|
and the mapping helpers
|
||||||
|
- [`DevDefs.h`](../../src/slic3r/GUI/DeviceCore/DevDefs.h) — `DevAmsType`, extruder ids, and virtual
|
||||||
|
tray ids
|
||||||
|
- [`DeviceManager.cpp`](../../src/slic3r/GUI/DeviceManager.cpp) — message dispatch, `vir_slot`
|
||||||
|
parsing, and device state assembly
|
||||||
|
- [`AMSControl.cpp`](../../src/slic3r/GUI/Widgets/AMSControl.cpp) — per-toolhead AMS panes
|
||||||
|
(`CreateAmsMultiNozzle`) and the generic layout
|
||||||
|
- [`AMSItem.hpp`](../../src/slic3r/GUI/Widgets/AMSItem.hpp) — `AMSModel` and the variable-lane AMS
|
||||||
|
rendering
|
||||||
|
- [`StatusPanel.cpp`](../../src/slic3r/GUI/StatusPanel.cpp) — `ExtruderImage`, the toolhead selector,
|
||||||
|
and per-toolhead temperature controls
|
||||||
|
- [`MoonrakerPrinterAgent.cpp`](../../src/slic3r/Utils/MoonrakerPrinterAgent.cpp) — producer example
|
||||||
|
(AMS payload and the `device.extruder` encoding)
|
||||||
@@ -163,8 +163,8 @@ public:
|
|||||||
~devPrinterUtil() = delete;
|
~devPrinterUtil() = delete;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static bool IsVirtualSlot(int ams_id) { return (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID);}
|
static bool IsVirtualSlot(int ams_id);
|
||||||
static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); }
|
static bool IsVirtualSlot(const std::string& ams_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace GUI
|
namespace GUI
|
||||||
@@ -200,4 +200,4 @@ template<> struct std::hash<NozzleDef>
|
|||||||
};
|
};
|
||||||
|
|
||||||
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
|
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
|
||||||
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
|
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <wx/app.h>
|
||||||
#include <wx/colour.h>
|
#include <wx/colour.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
@@ -33,6 +34,36 @@
|
|||||||
using namespace nlohmann;
|
using namespace nlohmann;
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
bool devPrinterUtil::IsVirtualSlot(int ams_id)
|
||||||
|
{
|
||||||
|
// When the app or preset bundle is unavailable we cannot know the vendor's
|
||||||
|
// extruder count, so fall back deterministically to the fixed Bambu pair
|
||||||
|
// {255, 254} rather than guessing from an unknown generic layout.
|
||||||
|
if (wxTheApp == nullptr || GUI::wxGetApp().preset_bundle == nullptr || GUI::wxGetApp().preset_bundle->is_bbl_vendor())
|
||||||
|
return ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID;
|
||||||
|
|
||||||
|
const int extruder_count = GUI::wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||||
|
return ams_id >= VIRTUAL_TRAY_MAIN_ID - extruder_count + 1 && ams_id <= VIRTUAL_TRAY_MAIN_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool devPrinterUtil::IsVirtualSlot(const std::string& ams_id)
|
||||||
|
{
|
||||||
|
// Strict: only a fully numeric string is a valid slot id. std::stoi alone
|
||||||
|
// would accept trailing garbage (e.g. "255abc" -> 255).
|
||||||
|
if (ams_id.empty())
|
||||||
|
return false;
|
||||||
|
for (char c : ams_id) {
|
||||||
|
if (c < '0' || c > '9')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return IsVirtualSlot(std::stoi(ams_id));
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int _hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1; }
|
static int _hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1; }
|
||||||
|
|
||||||
wxColour DevAmsTray::decode_color(const std::string &color)
|
wxColour DevAmsTray::decode_color(const std::string &color)
|
||||||
@@ -200,18 +231,22 @@ wxString DevAms::GetDisplayName() const
|
|||||||
|
|
||||||
int DevAms::GetSlotCount() const
|
int DevAms::GetSlotCount() const
|
||||||
{
|
{
|
||||||
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
|
// N3S is a 1-slot Bambu contract, so report 1 regardless of whether the
|
||||||
auto ams_type = GetAmsType();
|
// preset bundle is momentarily unavailable (m_trays.size() can be 0).
|
||||||
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
|
if (GetAmsType() == N3S)
|
||||||
{
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
else if (ams_type == N3S)
|
|
||||||
{
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
if (wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
|
||||||
|
GUI::wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||||
|
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
|
||||||
|
auto ams_type = GetAmsType();
|
||||||
|
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return static_cast<int>(m_trays.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
DevAmsTray* DevAms::GetTray(const std::string& tray_id) const
|
DevAmsTray* DevAms::GetTray(const std::string& tray_id) const
|
||||||
@@ -302,13 +337,21 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
|
|||||||
{
|
{
|
||||||
return it->second->GetExtruderId();
|
return it->second->GetExtruderId();
|
||||||
}
|
}
|
||||||
else if (stoi(ams_id) == VIRTUAL_TRAY_MAIN_ID)
|
|
||||||
{
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
return MAIN_EXTRUDER_ID;
|
if (!is_bbl_vendor && GetOwner()) {
|
||||||
|
for (const auto& tray : GetOwner()->vt_slot) {
|
||||||
|
if (tray.id == ams_id)
|
||||||
|
return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (stoi(ams_id) == VIRTUAL_TRAY_DEPUTY_ID)
|
|
||||||
{
|
if (is_bbl_vendor && ams_id == VIRTUAL_AMS_MAIN_ID_STR)
|
||||||
|
return MAIN_EXTRUDER_ID;
|
||||||
|
if (is_bbl_vendor && ams_id == VIRTUAL_AMS_DEPUTY_ID_STR)
|
||||||
return DEPUTY_EXTRUDER_ID;
|
return DEPUTY_EXTRUDER_ID;
|
||||||
|
if (!is_bbl_vendor && devPrinterUtil::IsVirtualSlot(ams_id)) {
|
||||||
|
return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(false && __FUNCTION__);
|
assert(false && __FUNCTION__);
|
||||||
@@ -325,9 +368,22 @@ std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id)
|
|||||||
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
||||||
{
|
{
|
||||||
std::map<int, DevAmsSlotId> tray_id_map;
|
std::map<int, DevAmsSlotId> tray_id_map;
|
||||||
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
|
if (is_bbl_vendor) {
|
||||||
|
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
|
||||||
|
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
|
||||||
|
} else if (GetOwner()) {
|
||||||
|
for (const auto& tray : GetOwner()->vt_slot) {
|
||||||
|
try {
|
||||||
|
const int tray_id = std::stoi(tray.id);
|
||||||
|
tray_id_map[tray_id] = DevAmsSlotId{tray_id, 0};
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " invalid virtual tray id: " << tray.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int generic_tray_index = 0;
|
||||||
for (auto& [ams_id, ams_item] : GetAmsList()) {
|
for (auto& [ams_id, ams_item] : GetAmsList()) {
|
||||||
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
|
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
|
||||||
if (ams_item && slot_item) {
|
if (ams_item && slot_item) {
|
||||||
@@ -335,9 +391,11 @@ std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
|||||||
int ams_id_int = stoi(ams_id);
|
int ams_id_int = stoi(ams_id);
|
||||||
int slot_id_int = stoi(slot_id);
|
int slot_id_int = stoi(slot_id);
|
||||||
int tray_index = -1;
|
int tray_index = -1;
|
||||||
if (ams_item->GetAmsType() == DevAms::N3S) {
|
if (!is_bbl_vendor) {
|
||||||
|
tray_index = generic_tray_index + slot_id_int;
|
||||||
|
} else if (ams_item->GetAmsType() == DevAms::N3S) {
|
||||||
tray_index = ams_id_int;
|
tray_index = ams_id_int;
|
||||||
} else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
|
} else if (ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
|
||||||
tray_index = 24 + slot_id_int;
|
tray_index = 24 + slot_id_int;
|
||||||
} else {
|
} else {
|
||||||
tray_index = (ams_id_int * 4 + slot_id_int);
|
tray_index = (ams_id_int * 4 + slot_id_int);
|
||||||
@@ -348,6 +406,8 @@ std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!is_bbl_vendor)
|
||||||
|
generic_tray_index += static_cast<int>(ams_item->GetTrays().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
return tray_id_map;
|
return tray_id_map;
|
||||||
@@ -913,6 +973,8 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
ams->m_exist = true;
|
ams->m_exist = true;
|
||||||
|
ams->m_binded_extruder_set = {ext_id};
|
||||||
|
ams->m_binded_switcher_pos.reset();
|
||||||
ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE);
|
ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE);
|
||||||
ams->m_humidity_percent = u.value("humidity_percent", -1);
|
ams->m_humidity_percent = u.value("humidity_percent", -1);
|
||||||
ams->m_left_dry_time = u.value("dry_time_min", 0);
|
ams->m_left_dry_time = u.value("dry_time_min", 0);
|
||||||
@@ -995,16 +1057,22 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- external / direct spools -> obj->vt_slot ---
|
// --- external / direct spools -> obj->vt_slot ---
|
||||||
// extruder 0 -> main virtual slot, extruder >0 -> deputy.
|
// Keep one virtual slot per extruder. The legacy two-extruder mapping uses
|
||||||
|
// 255 for extruder 0 and 254 for extruder 1; continue that sequence for
|
||||||
|
// additional extruders (253, 252, ...).
|
||||||
if (obj && data.contains("external") && data["external"].is_array())
|
if (obj && data.contains("external") && data["external"].is_array())
|
||||||
{
|
{
|
||||||
|
std::map<int, DevAmsTray> external_slots;
|
||||||
obj->vt_slot.clear();
|
obj->vt_slot.clear();
|
||||||
for (const auto& e : data["external"])
|
for (const auto& e : data["external"])
|
||||||
{
|
{
|
||||||
if (!e.is_object())
|
if (!e.is_object())
|
||||||
continue;
|
continue;
|
||||||
const int ext = e.value("extruder", MAIN_EXTRUDER_ID);
|
const int ext = e.value("extruder", MAIN_EXTRUDER_ID);
|
||||||
const int vt_id = (ext == MAIN_EXTRUDER_ID) ? VIRTUAL_TRAY_MAIN_ID : VIRTUAL_TRAY_DEPUTY_ID;
|
if (ext < MAIN_EXTRUDER_ID || ext >= VIRTUAL_TRAY_MAIN_ID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const int vt_id = VIRTUAL_TRAY_MAIN_ID - ext;
|
||||||
DevAmsTray tray(std::to_string(vt_id));
|
DevAmsTray tray(std::to_string(vt_id));
|
||||||
tray.is_exists = e.value("loaded", false);
|
tray.is_exists = e.value("loaded", false);
|
||||||
tray.m_fila_type = e.value("material", std::string());
|
tray.m_fila_type = e.value("material", std::string());
|
||||||
@@ -1013,8 +1081,11 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
|
|||||||
tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0));
|
tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0));
|
||||||
tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0));
|
tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0));
|
||||||
tray.remain = e.value("remain_percent", -1);
|
tray.remain = e.value("remain_percent", -1);
|
||||||
obj->vt_slot.push_back(tray);
|
external_slots.insert_or_assign(ext, std::move(tray));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& entry : external_slots)
|
||||||
|
obj->vt_slot.push_back(std::move(entry.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ public:
|
|||||||
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
|
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
|
||||||
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
|
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
|
||||||
|
|
||||||
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
|
// Map a linear tray index -> {ams_id, slot_id}. Includes virtual external-spool trays,
|
||||||
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
|
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
|
||||||
std::map<int, DevAmsSlotId> GetTrayIndexMap();
|
std::map<int, DevAmsSlotId> GetTrayIndexMap();
|
||||||
|
|
||||||
@@ -434,4 +434,4 @@ struct DevFilamentDryingPreset
|
|||||||
float filament_dev_ams_drying_heat_distortion_temperature = 0.0f;
|
float filament_dev_ams_drying_heat_distortion_temperature = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
}// namespace Slic3r
|
}// namespace Slic3r
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "slic3r/GUI/DeviceCore/DevDefs.h"
|
#include "slic3r/GUI/DeviceCore/DevDefs.h"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <wx/app.h>
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "libslic3r/CommonDefs.hpp"
|
#include "libslic3r/CommonDefs.hpp"
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@@ -176,34 +178,52 @@ DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const
|
|||||||
int DevNozzle::GetLogicExtruderId() const
|
int DevNozzle::GetLogicExtruderId() const
|
||||||
{
|
{
|
||||||
int total_ext_count = GetTotalExtruderCount();
|
int total_ext_count = GetTotalExtruderCount();
|
||||||
if (total_ext_count == 1) {
|
|
||||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
|
||||||
} else if (total_ext_count == 2) {
|
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
if (AtLeftExtruder()) {
|
if (is_bbl_vendor) {
|
||||||
return LOGIC_L_EXTRUDER_ID;
|
if (total_ext_count == 1) {
|
||||||
} else if (AtRightExtruder()) {
|
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||||
return LOGIC_R_EXTRUDER_ID;
|
} else if (total_ext_count == 2) {
|
||||||
|
if (AtLeftExtruder()) {
|
||||||
|
return LOGIC_L_EXTRUDER_ID;
|
||||||
|
} else if (AtRightExtruder()) {
|
||||||
|
return LOGIC_R_EXTRUDER_ID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(0);
|
||||||
|
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(0);
|
// For some reason, BBL's logical extruder ID is inverted:
|
||||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
// physical extruder id = 0 (MAIN_EXTRUDER_ID) vs logical extruder id = 1 (LOGIC_R_EXTRUDER_ID)
|
||||||
|
// Likely because logical id reads from left to right (left = 0, right = 1)
|
||||||
|
// For generic N extruders, this inversion does not apply.
|
||||||
|
if (IsOnRack()) return INVALID_EXTRUDER_ID;
|
||||||
|
return m_nozzle_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DevNozzle::GetExtruderId() const
|
int DevNozzle::GetExtruderId() const
|
||||||
{
|
{
|
||||||
int total_ext_count = GetTotalExtruderCount();
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
|
||||||
if (total_ext_count == 1) {
|
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
return MAIN_EXTRUDER_ID;
|
if (is_bbl_vendor) {
|
||||||
} else if (total_ext_count == 2) {
|
int total_ext_count = GetTotalExtruderCount();
|
||||||
if (AtRightExtruder()) {
|
if (total_ext_count == 1) {
|
||||||
return MAIN_EXTRUDER_ID;
|
return MAIN_EXTRUDER_ID;
|
||||||
} else if (AtLeftExtruder()) {
|
} else if (total_ext_count == 2) {
|
||||||
return DEPUTY_EXTRUDER_ID;
|
if (AtRightExtruder()) {
|
||||||
|
return MAIN_EXTRUDER_ID;
|
||||||
|
} else if (AtLeftExtruder()) {
|
||||||
|
return DEPUTY_EXTRUDER_ID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return MAIN_EXTRUDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MAIN_EXTRUDER_ID;
|
return m_nozzle_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DevNozzle::AtLeftExtruder() const
|
bool DevNozzle::AtLeftExtruder() const
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <wx/app.h>
|
||||||
#include <wx/colour.h>
|
#include <wx/colour.h>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <wx/dir.h>
|
#include <wx/dir.h>
|
||||||
@@ -807,8 +808,12 @@ bool MachineObject::is_extrusion_cali_finished()
|
|||||||
DevAmsTray *MachineObject::get_curr_tray()
|
DevAmsTray *MachineObject::get_curr_tray()
|
||||||
{
|
{
|
||||||
const std::string& cur_ams_id = m_extder_system->GetCurrentAmsId();
|
const std::string& cur_ams_id = m_extder_system->GetCurrentAmsId();
|
||||||
if (cur_ams_id.compare(std::to_string(VIRTUAL_TRAY_MAIN_ID)) == 0) {
|
if (devPrinterUtil::IsVirtualSlot(cur_ams_id)) {
|
||||||
return &vt_slot[0];
|
for (auto& tray : vt_slot) {
|
||||||
|
if (tray.id == cur_ams_id)
|
||||||
|
return &tray;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
DevAms* curr_ams = get_curr_Ams();
|
DevAms* curr_ams = get_curr_Ams();
|
||||||
@@ -1274,19 +1279,24 @@ int MachineObject::get_bed_temperature_limit()
|
|||||||
|
|
||||||
bool MachineObject::is_filament_installed()
|
bool MachineObject::is_filament_installed()
|
||||||
{
|
{
|
||||||
if (m_extder_system->GetTotalExtderCount() > 0) {
|
// if (m_extder_system->GetTotalExtderCount() > 0) {
|
||||||
// right//or single
|
// // right//or single
|
||||||
auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID];
|
// auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID];
|
||||||
if (ext.m_ext_has_filament) {
|
// if (ext.m_ext_has_filament) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// /*left*/
|
||||||
|
// if (m_extder_system->GetTotalExtderCount() > 1) {
|
||||||
|
// auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID];
|
||||||
|
// if (ext.m_ext_has_filament) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (auto& ext : m_extder_system->m_extders) {
|
||||||
|
if (ext.m_ext_has_filament)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
/*left*/
|
|
||||||
if (m_extder_system->GetTotalExtderCount() > 1) {
|
|
||||||
auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID];
|
|
||||||
if (ext.m_ext_has_filament) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2628,13 +2638,10 @@ void MachineObject::reset()
|
|||||||
json empty_j;
|
json empty_j;
|
||||||
print_json.diff2all_base_reset(empty_j);
|
print_json.diff2all_base_reset(empty_j);
|
||||||
|
|
||||||
for (auto i = 0; i < vt_slot.size(); i++) {
|
for (auto& tray : vt_slot)
|
||||||
vt_slot[i].reset();
|
tray.reset();
|
||||||
|
if (vt_slot.size() > 1)
|
||||||
if (i == 1) {
|
vt_slot.erase(vt_slot.begin() + 1, vt_slot.end());
|
||||||
vt_slot.erase(vt_slot.begin() + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// why: reset reuses MachineObject, so release its lazy subtask
|
// why: reset reuses MachineObject, so release its lazy subtask
|
||||||
// before dropping the pointer to prevent reconnect leaks.
|
// before dropping the pointer to prevent reconnect leaks.
|
||||||
if (subtask_) {
|
if (subtask_) {
|
||||||
@@ -4107,25 +4114,28 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
|||||||
if (jj.contains("vir_slot") && jj["vir_slot"].is_array()) {
|
if (jj.contains("vir_slot") && jj["vir_slot"].is_array()) {
|
||||||
|
|
||||||
for (auto it = jj["vir_slot"].begin(); it != jj["vir_slot"].end(); it++) {
|
for (auto it = jj["vir_slot"].begin(); it != jj["vir_slot"].end(); it++) {
|
||||||
auto vslot = parse_vt_tray(it.value().get<json>());
|
try {
|
||||||
|
const auto tray_json = it.value().get<json>();
|
||||||
|
if (!tray_json.is_object() || !tray_json.contains("id") || !tray_json["id"].is_string())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (vslot.id == std::to_string(VIRTUAL_TRAY_MAIN_ID)) {
|
const auto tray_id_str = tray_json["id"].get<std::string>();
|
||||||
auto it = std::next(vt_slot.begin(), 0);
|
int tray_id = 0;
|
||||||
if (it != vt_slot.end()) {
|
const auto result = std::from_chars(tray_id_str.data(), tray_id_str.data() + tray_id_str.size(), tray_id);
|
||||||
vt_slot[0] = vslot;
|
if (result.ec != std::errc{} || result.ptr != tray_id_str.data() + tray_id_str.size()
|
||||||
}
|
|| tray_id < 0 || tray_id > VIRTUAL_TRAY_MAIN_ID)
|
||||||
else {
|
continue;
|
||||||
vt_slot.push_back(vslot);
|
|
||||||
|
auto vslot = parse_vt_tray(tray_json);
|
||||||
|
const auto index = static_cast<std::vector<DevAmsTray>::size_type>(VIRTUAL_TRAY_MAIN_ID - tray_id);
|
||||||
|
while (vt_slot.size() <= index) {
|
||||||
|
const int placeholder_id = VIRTUAL_TRAY_MAIN_ID - static_cast<int>(vt_slot.size());
|
||||||
|
vt_slot.emplace_back(std::to_string(placeholder_id));
|
||||||
}
|
}
|
||||||
|
vt_slot[index] = vslot;
|
||||||
}
|
}
|
||||||
else if (vslot.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) {
|
catch (...) {
|
||||||
auto it = std::next(vt_slot.begin(), 1);
|
continue;
|
||||||
if (it != vt_slot.end()) {
|
|
||||||
vt_slot[1] = vslot;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
vt_slot.push_back(vslot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5263,16 +5273,17 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
|
|||||||
|
|
||||||
bool MachineObject::contains_tray(const std::string &ams_id, const std::string &tray_id) const
|
bool MachineObject::contains_tray(const std::string &ams_id, const std::string &tray_id) const
|
||||||
{
|
{
|
||||||
if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
|
if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR)
|
||||||
return m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr;
|
return m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr;
|
||||||
} else {
|
|
||||||
|
if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) {
|
||||||
for (const auto& tray : vt_slot) {
|
for (const auto& tray : vt_slot) {
|
||||||
if (tray.id == ams_id) { return true; }
|
if (tray.id == ams_id) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return !is_bbl_vendor && m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string &tray_id) const
|
DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string &tray_id) const
|
||||||
@@ -5282,13 +5293,19 @@ DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string
|
|||||||
return DevAmsTray(tray_id);
|
return DevAmsTray(tray_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
|
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
|
if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
|
||||||
auto tray = m_fila_system->GetAmsTray(ams_id, tray_id);
|
auto tray = m_fila_system->GetAmsTray(ams_id, tray_id);
|
||||||
if (tray) { return *tray;};
|
if (tray) return *tray;
|
||||||
}
|
}
|
||||||
else {
|
else if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) {
|
||||||
for (const auto &tray : vt_slot) {
|
for (const auto &tray : vt_slot) {
|
||||||
if (tray.id == ams_id) { return tray; }
|
if (tray.id == ams_id) return tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_bbl_vendor) {
|
||||||
|
auto tray = m_fila_system->GetAmsTray(ams_id, tray_id);
|
||||||
|
if (tray) return *tray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+831
-255
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
|||||||
#ifndef slic3r_StatusPanel_hpp_
|
#ifndef slic3r_StatusPanel_hpp_
|
||||||
#define slic3r_StatusPanel_hpp_
|
#define slic3r_StatusPanel_hpp_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "libslic3r/ProjectTask.hpp"
|
#include "libslic3r/ProjectTask.hpp"
|
||||||
#include "DeviceManager.hpp"
|
#include "DeviceManager.hpp"
|
||||||
#include "MonitorPage.hpp"
|
#include "MonitorPage.hpp"
|
||||||
@@ -66,6 +68,7 @@
|
|||||||
#include "StagedBuild.hpp"
|
#include "StagedBuild.hpp"
|
||||||
|
|
||||||
class StepIndicator;
|
class StepIndicator;
|
||||||
|
class wxChoice;
|
||||||
|
|
||||||
#define COMMAND_TIMEOUT 5
|
#define COMMAND_TIMEOUT 5
|
||||||
|
|
||||||
@@ -140,28 +143,36 @@ class ExtruderImage : public wxWindow
|
|||||||
ScalableBitmap *m_left_extruder_active_empty;
|
ScalableBitmap *m_left_extruder_active_empty;
|
||||||
ScalableBitmap *m_left_extruder_unactive_filled;
|
ScalableBitmap *m_left_extruder_unactive_filled;
|
||||||
ScalableBitmap *m_left_extruder_unactive_empty;
|
ScalableBitmap *m_left_extruder_unactive_empty;
|
||||||
|
|
||||||
ScalableBitmap *m_right_extruder_active_filled;
|
ScalableBitmap *m_right_extruder_active_filled;
|
||||||
ScalableBitmap *m_right_extruder_active_empty;
|
ScalableBitmap *m_right_extruder_active_empty;
|
||||||
ScalableBitmap *m_right_extruder_unactive_filled;
|
ScalableBitmap *m_right_extruder_unactive_filled;
|
||||||
ScalableBitmap *m_right_extruder_unactive_empty;
|
ScalableBitmap *m_right_extruder_unactive_empty;
|
||||||
|
|
||||||
ScalableBitmap *m_extruder_single_nozzle_empty_load;
|
ScalableBitmap *m_extruder_single_nozzle_empty_load;
|
||||||
ScalableBitmap *m_extruder_single_nozzle_empty_unload;
|
ScalableBitmap *m_extruder_single_nozzle_empty_unload;
|
||||||
ScalableBitmap *m_extruder_single_nozzle_filled_load;
|
ScalableBitmap *m_extruder_single_nozzle_filled_load;
|
||||||
ScalableBitmap *m_extruder_single_nozzle_filled_unload;
|
ScalableBitmap *m_extruder_single_nozzle_filled_unload;
|
||||||
|
|
||||||
ExtruderState m_left_ext_state = {ExtruderState::EMPTY_LOAD};
|
ExtruderState m_left_ext_state = {ExtruderState::EMPTY_LOAD};
|
||||||
ExtruderState m_right_ext_state = {ExtruderState::EMPTY_LOAD};
|
ExtruderState m_right_ext_state = {ExtruderState::EMPTY_LOAD};
|
||||||
ExtruderState m_single_ext_state = {ExtruderState::EMPTY_LOAD};
|
ExtruderState m_single_ext_state = {ExtruderState::EMPTY_LOAD};
|
||||||
|
std::vector<ExtruderState> m_multi_extruder_states;
|
||||||
|
bool m_generic_nozzle_display{false};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void update(int nozzle_num, int nozzle_id);
|
void update(int nozzle_num, int nozzle_id);
|
||||||
void update(ExtruderState single_state);
|
void update(ExtruderState single_state);
|
||||||
void update(ExtruderState right_state, ExtruderState left_state);
|
void update(ExtruderState right_state, ExtruderState left_state);
|
||||||
|
|
||||||
|
void update(ExtruderState state, int idx);
|
||||||
|
|
||||||
void msw_rescale();
|
void msw_rescale();
|
||||||
|
void updateGenericSize();
|
||||||
void setExtruderCount(int nozzle_num);
|
void setExtruderCount(int nozzle_num);
|
||||||
|
void setGenericNozzleDisplay(bool enabled);
|
||||||
void setExtruderUsed(std::string loc);
|
void setExtruderUsed(std::string loc);
|
||||||
|
void setExtruderUsed(int nozzle_idx);
|
||||||
void paintEvent(wxPaintEvent &evt);
|
void paintEvent(wxPaintEvent &evt);
|
||||||
|
|
||||||
void render(wxDC &dc);
|
void render(wxDC &dc);
|
||||||
@@ -495,6 +506,8 @@ protected:
|
|||||||
std::vector<ExtruderImage *> m_extruderImage;
|
std::vector<ExtruderImage *> m_extruderImage;
|
||||||
|
|
||||||
SwitchBoard * m_nozzle_btn_panel;
|
SwitchBoard * m_nozzle_btn_panel;
|
||||||
|
wxChoice* m_generic_nozzle_selector{nullptr};
|
||||||
|
int m_generic_nozzle_selector_count{0};
|
||||||
|
|
||||||
wxStaticText * m_text_tasklist_caption;
|
wxStaticText * m_text_tasklist_caption;
|
||||||
|
|
||||||
@@ -507,10 +520,23 @@ protected:
|
|||||||
wxBoxSizer * m_misc_ctrl_sizer;
|
wxBoxSizer * m_misc_ctrl_sizer;
|
||||||
StaticBox* m_fan_panel;
|
StaticBox* m_fan_panel;
|
||||||
StaticLine * m_line_nozzle;
|
StaticLine * m_line_nozzle;
|
||||||
TempInput* m_tempCtrl_nozzle;
|
wxWindowID m_nozzle_temp_control_id{wxID_ANY};
|
||||||
|
wxWindow* m_temp_nozzle_parent{nullptr};
|
||||||
|
wxBoxSizer* m_temp_nozzle_sizer{nullptr};
|
||||||
|
size_t m_temp_nozzle_active_count{0};
|
||||||
|
TempInput* m_tempCtrl_nozzle{nullptr};
|
||||||
int m_temp_nozzle_timeout{ 0 };
|
int m_temp_nozzle_timeout{ 0 };
|
||||||
TempInput* m_tempCtrl_nozzle_deputy;
|
TempInput* m_tempCtrl_nozzle_deputy{nullptr};
|
||||||
int m_temp_nozzle_deputy_timeout{ 0 };
|
int m_temp_nozzle_deputy_timeout{ 0 };
|
||||||
|
|
||||||
|
std::vector<TempInput*> m_tempCtrl_nozzles;
|
||||||
|
std::vector<int> m_temp_nozzle_timeouts;
|
||||||
|
|
||||||
|
// Last (dev_id, device toolhead count, preset nozzle count) we warned about, to avoid log spam.
|
||||||
|
std::string m_last_mismatch_dev_id;
|
||||||
|
int m_last_mismatch_device_count{-1};
|
||||||
|
int m_last_mismatch_preset_count{-1};
|
||||||
|
|
||||||
TempInput * m_tempCtrl_bed;
|
TempInput * m_tempCtrl_bed;
|
||||||
int m_temp_bed_timeout {0};
|
int m_temp_bed_timeout {0};
|
||||||
TempInput * m_tempCtrl_chamber;
|
TempInput * m_tempCtrl_chamber;
|
||||||
@@ -550,6 +576,7 @@ protected:
|
|||||||
|
|
||||||
/* AMS control box <-> live nozzle-rack panel toggle (rack printers only) */
|
/* AMS control box <-> live nozzle-rack panel toggle (rack printers only) */
|
||||||
SwitchBoard* m_ams_rack_switch{ nullptr };
|
SwitchBoard* m_ams_rack_switch{ nullptr };
|
||||||
|
MultiSwitchButton* m_ams_nozzle_switch{ nullptr };
|
||||||
|
|
||||||
AMSControl* m_ams_control;
|
AMSControl* m_ams_control;
|
||||||
StaticBox* m_ams_control_box;
|
StaticBox* m_ams_control_box;
|
||||||
@@ -624,6 +651,9 @@ public:
|
|||||||
|
|
||||||
wxBoxSizer *create_temp_axis_group(wxWindow *parent);
|
wxBoxSizer *create_temp_axis_group(wxWindow *parent);
|
||||||
wxBoxSizer *create_temp_control(wxWindow *parent);
|
wxBoxSizer *create_temp_control(wxWindow *parent);
|
||||||
|
TempInput* create_nozzle_temp_control(wxWindow *parent, wxWindowID id);
|
||||||
|
void set_temp_input_colors(TempInput* temp_ctrl);
|
||||||
|
void ensure_nozzle_temp_controls(size_t count);
|
||||||
wxBoxSizer *create_misc_control(wxWindow *parent);
|
wxBoxSizer *create_misc_control(wxWindow *parent);
|
||||||
wxBoxSizer *create_axis_control(wxWindow *parent);
|
wxBoxSizer *create_axis_control(wxWindow *parent);
|
||||||
wxPanel *create_bed_control(wxWindow *parent);
|
wxPanel *create_bed_control(wxWindow *parent);
|
||||||
@@ -654,6 +684,7 @@ private:
|
|||||||
friend class MonitorPanel;
|
friend class MonitorPanel;
|
||||||
void wire_controls();
|
void wire_controls();
|
||||||
bool load_thumbnail_from_url(const wxString &url, MachineObject *obj);
|
bool load_thumbnail_from_url(const wxString &url, MachineObject *obj);
|
||||||
|
void sync_nozzle_temp_controls(size_t count);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
|
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
|
||||||
@@ -801,6 +832,7 @@ protected:
|
|||||||
void update_cloud_subtask(MachineObject *obj);
|
void update_cloud_subtask(MachineObject *obj);
|
||||||
void update_sdcard_subtask(MachineObject *obj);
|
void update_sdcard_subtask(MachineObject *obj);
|
||||||
void update_temp_ctrl(MachineObject *obj);
|
void update_temp_ctrl(MachineObject *obj);
|
||||||
|
void check_extruder_count_mismatch(MachineObject* obj);
|
||||||
void update_misc_ctrl(MachineObject *obj);
|
void update_misc_ctrl(MachineObject *obj);
|
||||||
void update_ams(MachineObject* obj);
|
void update_ams(MachineObject* obj);
|
||||||
void update_rack(MachineObject* obj);
|
void update_rack(MachineObject* obj);
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
|
|||||||
m_sizer_ams_body->Add(m_sizer_ams_area_right, wxALIGN_CENTER, 0);
|
m_sizer_ams_body->Add(m_sizer_ams_area_right, wxALIGN_CENTER, 0);
|
||||||
|
|
||||||
m_sizer_body->Add(m_sizer_ams_items, 0, wxALIGN_CENTER, 0);
|
m_sizer_body->Add(m_sizer_ams_items, 0, wxALIGN_CENTER, 0);
|
||||||
m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(10));
|
m_sizer_ams_gap = m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(10));
|
||||||
m_sizer_body->Add(m_sizer_ams_body, 0, wxALIGN_CENTER, 0);
|
m_sizer_body->Add(m_sizer_ams_body, 0, wxALIGN_CENTER, 0);
|
||||||
m_sizer_body->Add(m_sizer_down_road, 0, wxALIGN_CENTER, 0);
|
m_sizer_body->Add(m_sizer_down_road, 0, wxALIGN_CENTER, 0);
|
||||||
m_sizer_body->Add(m_sizer_ams_option, 0, wxEXPAND, 0);
|
m_sizer_body->Add(m_sizer_ams_option, 0, wxEXPAND, 0);
|
||||||
@@ -371,7 +371,7 @@ bool AMSControl::IsAmsInRightPanel(std::string ams_id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else if (m_total_ext_count == 1){
|
||||||
for (auto id : m_item_ids[MAIN_EXTRUDER_ID]){
|
for (auto id : m_item_ids[MAIN_EXTRUDER_ID]){
|
||||||
if (id == ams_id){
|
if (id == ams_id){
|
||||||
return true;
|
return true;
|
||||||
@@ -379,6 +379,10 @@ bool AMSControl::IsAmsInRightPanel(std::string ams_id) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMSControl::AmsSelectedSwitch(wxCommandEvent& event) {
|
void AMSControl::AmsSelectedSwitch(wxCommandEvent& event) {
|
||||||
@@ -519,7 +523,7 @@ void AMSControl::msw_rescale()
|
|||||||
m_button_ams_setting_press.msw_rescale();
|
m_button_ams_setting_press.msw_rescale();
|
||||||
m_button_ams_setting->SetBitmap(m_button_ams_setting_normal.bmp());
|
m_button_ams_setting->SetBitmap(m_button_ams_setting_normal.bmp());
|
||||||
|
|
||||||
m_extruder->msw_rescale();
|
if (m_extruder) m_extruder->msw_rescale();
|
||||||
|
|
||||||
if (m_button_extruder_feed) m_button_extruder_feed->Rescale(); // ORCA
|
if (m_button_extruder_feed) m_button_extruder_feed->Rescale(); // ORCA
|
||||||
if (m_button_extruder_back) m_button_extruder_back->Rescale(); // ORCA
|
if (m_button_extruder_back) m_button_extruder_back->Rescale(); // ORCA
|
||||||
@@ -603,6 +607,8 @@ void AMSControl::CreateAms()
|
|||||||
|
|
||||||
|
|
||||||
void AMSControl::ClearAms() {
|
void AMSControl::ClearAms() {
|
||||||
|
m_sizer_ams_items->Clear(false);
|
||||||
|
|
||||||
m_simplebook_ams_right->DeleteAllPages();
|
m_simplebook_ams_right->DeleteAllPages();
|
||||||
m_simplebook_ams_left->DeleteAllPages();
|
m_simplebook_ams_left->DeleteAllPages();
|
||||||
m_simplebook_ams_right->DestroyChildren();
|
m_simplebook_ams_right->DestroyChildren();
|
||||||
@@ -613,7 +619,7 @@ void AMSControl::ClearAms() {
|
|||||||
m_simplebook_ams_left->Refresh();
|
m_simplebook_ams_left->Refresh();
|
||||||
|
|
||||||
for (auto it : m_ams_preview_list) {
|
for (auto it : m_ams_preview_list) {
|
||||||
delete it.second;
|
if (it.second) it.second->Destroy();
|
||||||
}
|
}
|
||||||
m_ams_preview_list.clear();
|
m_ams_preview_list.clear();
|
||||||
m_ext_image_list.clear();
|
m_ext_image_list.clear();
|
||||||
@@ -624,12 +630,181 @@ void AMSControl::ClearAms() {
|
|||||||
m_ams_item_list.clear();
|
m_ams_item_list.clear();
|
||||||
m_sizer_prv_right->Clear();
|
m_sizer_prv_right->Clear();
|
||||||
m_sizer_prv_left->Clear();
|
m_sizer_prv_left->Clear();
|
||||||
m_item_ids = { {}, {} };
|
|
||||||
|
if (m_nozzle_book) {
|
||||||
|
m_nozzle_book->Destroy();
|
||||||
|
m_nozzle_book = nullptr;
|
||||||
|
} else {
|
||||||
|
for (auto &pane : m_nozzle_panes) {
|
||||||
|
if (pane.preview_panel) pane.preview_panel->Destroy();
|
||||||
|
if (pane.ams_book) pane.ams_book->Destroy();
|
||||||
|
if (pane.page) pane.page->Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_item_ids.assign(std::max(m_total_ext_count, 2), {});
|
||||||
|
m_nozzle_panes.clear();
|
||||||
|
m_active_nozzle_id = 0;
|
||||||
|
m_current_ams.clear();
|
||||||
|
m_current_show_ams_left.clear();
|
||||||
|
m_current_show_ams_right.clear();
|
||||||
pair_id.clear();
|
pair_id.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AMSControl::restore_legacy_ams_layout()
|
||||||
|
{
|
||||||
|
m_sizer_ams_items->Clear(false);
|
||||||
|
m_sizer_ams_items->SetOrientation(wxHORIZONTAL);
|
||||||
|
m_sizer_body->GetItem(m_sizer_ams_items)->SetFlag(wxALIGN_CENTER);
|
||||||
|
m_sizer_ams_items->Add(m_panel_prv_left, 0, wxLEFT | wxRIGHT, FromDIP(5));
|
||||||
|
m_sizer_ams_items->Add(m_panel_prv_right, 0, wxLEFT | wxRIGHT, FromDIP(5));
|
||||||
|
|
||||||
|
m_panel_prv_left->Show();
|
||||||
|
m_panel_prv_right->Show();
|
||||||
|
m_simplebook_ams_left->Show();
|
||||||
|
m_simplebook_ams_right->Show();
|
||||||
|
m_panel_down_road->Show();
|
||||||
|
m_sizer_body->Show(m_sizer_ams_body, true);
|
||||||
|
m_sizer_option_mid->Show(m_extruder, true);
|
||||||
|
m_down_road->SetSingleSideLayout(false);
|
||||||
|
m_sizer_ams_gap->SetProportion(1);
|
||||||
|
m_sizer_ams_gap->SetBorder(FromDIP(10));
|
||||||
|
m_down_road->SetNozzleCount(m_total_ext_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AMSControl::use_multi_nozzle_layout() const
|
||||||
|
{
|
||||||
|
const bool is_bbl = wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
|
return is_bbl ? m_total_ext_count > 2 : m_nozzle_book != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AMSControl::CreateAmsMultiNozzle(const std::string &series_name, const std::string &printer_type) {
|
||||||
|
const size_t pane_count = static_cast<size_t>(std::max(m_total_ext_count, 0));
|
||||||
|
m_nozzle_panes.resize(pane_count);
|
||||||
|
m_active_nozzle_id = 0;
|
||||||
|
|
||||||
|
m_sizer_ams_items->Clear(false);
|
||||||
|
m_sizer_ams_items->SetOrientation(wxVERTICAL);
|
||||||
|
m_sizer_body->GetItem(m_sizer_ams_items)->SetFlag(wxALIGN_CENTER);
|
||||||
|
m_sizer_body->Show(m_sizer_ams_body, false);
|
||||||
|
m_sizer_ams_gap->SetProportion(0);
|
||||||
|
m_sizer_ams_gap->SetBorder(0);
|
||||||
|
|
||||||
|
m_panel_prv_left->Hide();
|
||||||
|
m_panel_prv_right->Hide();
|
||||||
|
m_simplebook_ams_left->Hide();
|
||||||
|
m_simplebook_ams_right->Hide();
|
||||||
|
m_panel_down_road->Show();
|
||||||
|
m_sizer_option_mid->Show(m_extruder, true);
|
||||||
|
m_switcher->Hide();
|
||||||
|
m_down_road->SetNozzleCount(1);
|
||||||
|
m_down_road->SetSingleSideLayout(true, AMSPanelPos::LEFT_PANEL);
|
||||||
|
m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_SINGLE);
|
||||||
|
m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
m_extruder->updateNozzleNum(1, series_name);
|
||||||
|
|
||||||
|
m_nozzle_book = new wxSimplebook(m_amswin, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||||
|
m_nozzle_book->SetMinSize(wxSize(FromDIP(264), -1));
|
||||||
|
m_nozzle_book->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR));
|
||||||
|
m_sizer_ams_items->Add(m_nozzle_book, 0, wxALIGN_CENTER);
|
||||||
|
|
||||||
|
for (size_t nozzle_id = 0; nozzle_id < pane_count; ++nozzle_id) {
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
// A generic page is one H2C side. Keep the left-side geometry as the
|
||||||
|
// canonical single-nozzle presentation; the road and AMS item code
|
||||||
|
// still use the same side-specific paths as the legacy layout.
|
||||||
|
pane.panel_pos = AMSPanelPos::LEFT_PANEL;
|
||||||
|
pane.page = new wxPanel(m_nozzle_book, wxID_ANY);
|
||||||
|
pane.page->SetMinSize(wxSize(FromDIP(264), -1));
|
||||||
|
pane.page->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR));
|
||||||
|
pane.page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
pane.page->SetSizer(pane.page_sizer);
|
||||||
|
|
||||||
|
pane.preview_panel = new wxScrolledWindow(pane.page, wxID_ANY);
|
||||||
|
pane.preview_panel->SetScrollRate(10, 0);
|
||||||
|
pane.preview_panel->SetSize(AMS_ITEMS_PANEL_SIZE);
|
||||||
|
pane.preview_panel->SetMinSize(AMS_ITEMS_PANEL_SIZE);
|
||||||
|
pane.preview_panel->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR));
|
||||||
|
|
||||||
|
pane.preview_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
pane.preview_panel->SetSizer(pane.preview_sizer);
|
||||||
|
|
||||||
|
// Generic lane cards grow to their calculated width for 1-8 lanes. Keep the
|
||||||
|
// book at the legacy minimum, but allow the page to expand to that width.
|
||||||
|
pane.ams_book = new wxSimplebook(pane.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||||
|
pane.ams_book->SetMinSize(wxSize(FromDIP(264), -1));
|
||||||
|
pane.ams_book->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR));
|
||||||
|
// wxALIGN_CENTER does not centre horizontally inside a HORIZONTAL sizer, so a book wider
|
||||||
|
// than pane.ams_area's own minimum would sit at ams_area's left edge and overflow to the
|
||||||
|
// right, shifting the whole card column off the shared down-road connector axis. A pair of
|
||||||
|
// stretch spacers keeps the book centred on the pane (and thus on the connector) at any
|
||||||
|
// book width; ams_area is expanded below so the spacers have room to work with.
|
||||||
|
pane.ams_area = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
pane.ams_area->AddStretchSpacer(1);
|
||||||
|
pane.ams_area->Add(pane.ams_book, 0, wxALIGN_CENTER, 0);
|
||||||
|
pane.ams_area->AddStretchSpacer(1);
|
||||||
|
|
||||||
|
pane.page_sizer->Add(pane.preview_panel, 0, wxALIGN_CENTER | wxTOP, FromDIP(5));
|
||||||
|
pane.page_sizer->Add(pane.ams_area, 0, wxEXPAND | wxTOP, FromDIP(10));
|
||||||
|
|
||||||
|
m_nozzle_book->AddPage(pane.page, wxEmptyString, nozzle_id == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto add_info = [this, series_name, printer_type, pane_count](AMSinfo info) {
|
||||||
|
if (pane_count == 0) return;
|
||||||
|
|
||||||
|
info.nozzle_id = std::clamp(info.nozzle_id, 0, static_cast<int>(pane_count) - 1);
|
||||||
|
AddAmsPreview(info, info.ams_type);
|
||||||
|
if (info.ams_type == AMSModel::EXT_AMS && info.cans.size() == 1)
|
||||||
|
AddAms({info}, series_name, printer_type, m_nozzle_panes[info.nozzle_id].panel_pos);
|
||||||
|
else
|
||||||
|
AddAms(info);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &info : m_ams_info)
|
||||||
|
add_info(info);
|
||||||
|
|
||||||
|
for (auto &info : m_ext_info) {
|
||||||
|
const int virtual_tray_id = std::atoi(info.ams_id.c_str());
|
||||||
|
if (virtual_tray_id <= VIRTUAL_TRAY_MAIN_ID &&
|
||||||
|
virtual_tray_id >= VIRTUAL_TRAY_MAIN_ID - static_cast<int>(pane_count) + 1)
|
||||||
|
info.nozzle_id = VIRTUAL_TRAY_MAIN_ID - virtual_tray_id;
|
||||||
|
add_info(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &pane : m_nozzle_panes) {
|
||||||
|
if (pane.ams_book && pane.ams_book->GetPageCount() > 0)
|
||||||
|
pane.ams_book->SetSelection(0);
|
||||||
|
if (pane.preview_sizer) pane.preview_sizer->Layout();
|
||||||
|
if (pane.preview_panel) pane.preview_panel->FitInside();
|
||||||
|
if (pane.ams_book) pane.ams_book->Layout();
|
||||||
|
if (pane.page) pane.page->Layout();
|
||||||
|
|
||||||
|
if (!pane.current_ams.empty()) {
|
||||||
|
auto preview = m_ams_preview_list.find(pane.current_ams);
|
||||||
|
if (preview != m_ams_preview_list.end() && preview->second)
|
||||||
|
preview->second->OnSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_nozzle_book && pane_count > 0) {
|
||||||
|
m_nozzle_book->SetSelection(0);
|
||||||
|
m_current_ams = m_nozzle_panes[0].current_ams;
|
||||||
|
if (!m_current_ams.empty())
|
||||||
|
SwitchAms(m_current_ams);
|
||||||
|
else {
|
||||||
|
m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_amswin->Layout();
|
||||||
|
m_amswin->Fit();
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
|
|
||||||
void AMSControl::CreateAmsDoubleNozzle(const std::string &series_name, const std::string &printer_type)
|
void AMSControl::CreateAmsDoubleNozzle(const std::string &series_name, const std::string &printer_type)
|
||||||
{
|
{
|
||||||
|
restore_legacy_ams_layout();
|
||||||
|
|
||||||
std::vector<AMSinfo> single_info_left;
|
std::vector<AMSinfo> single_info_left;
|
||||||
std::vector<AMSinfo> single_info_right;
|
std::vector<AMSinfo> single_info_right;
|
||||||
|
|
||||||
@@ -751,6 +926,8 @@ void AMSControl::CreateAmsDoubleNozzle(const std::string &series_name, const std
|
|||||||
|
|
||||||
void AMSControl::CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type)
|
void AMSControl::CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type)
|
||||||
{
|
{
|
||||||
|
restore_legacy_ams_layout();
|
||||||
|
|
||||||
std::vector<int>m_item_nums{0,0};
|
std::vector<int>m_item_nums{0,0};
|
||||||
std::vector<AMSinfo> single_info;
|
std::vector<AMSinfo> single_info;
|
||||||
|
|
||||||
@@ -979,6 +1156,17 @@ void AMSControl::UpdateAms(const std::string &series_name,
|
|||||||
if (m_ams_info[i].routes_to_main_extruder() != ams_info[i].routes_to_main_extruder()) {
|
if (m_ams_info[i].routes_to_main_extruder() != ams_info[i].routes_to_main_extruder()) {
|
||||||
fresh = true;
|
fresh = true;
|
||||||
}
|
}
|
||||||
|
if (m_ams_info[i].nozzle_id != ams_info[i].nozzle_id) {
|
||||||
|
fresh = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < static_cast<int>(m_ext_info.size()); ++i) {
|
||||||
|
if (m_ext_info[i].ams_id != ext_info[i].ams_id ||
|
||||||
|
m_ext_info[i].nozzle_id != ext_info[i].nozzle_id ||
|
||||||
|
m_ext_info[i].ext_type != ext_info[i].ext_type) {
|
||||||
|
fresh = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -993,11 +1181,16 @@ void AMSControl::UpdateAms(const std::string &series_name,
|
|||||||
m_dev_id = dev_id;
|
m_dev_id = dev_id;
|
||||||
if (fresh){
|
if (fresh){
|
||||||
ClearAms();
|
ClearAms();
|
||||||
if (m_total_ext_count >= 2){
|
bool is_bbl = wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
CreateAmsDoubleNozzle(series_name, printer_type);
|
if (is_bbl) {
|
||||||
}else{
|
if (m_total_ext_count == 1)
|
||||||
CreateAmsSingleNozzle(series_name, printer_type);
|
CreateAmsSingleNozzle(series_name, printer_type);
|
||||||
|
else if (m_total_ext_count == 2)
|
||||||
|
CreateAmsDoubleNozzle(series_name, printer_type);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
CreateAmsMultiNozzle(series_name, printer_type);
|
||||||
|
|
||||||
SetSize(wxSize(FromDIP(578), -1));
|
SetSize(wxSize(FromDIP(578), -1));
|
||||||
SetMinSize(wxSize(FromDIP(578), -1));
|
SetMinSize(wxSize(FromDIP(578), -1));
|
||||||
Layout();
|
Layout();
|
||||||
@@ -1070,7 +1263,7 @@ void AMSControl::UpdateAms(const std::string &series_name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*update ams extruder*/
|
/*update ams extruder*/
|
||||||
if (m_extruder->updateNozzleNum(m_total_ext_count, series_name))
|
if (m_total_ext_count <= 2 && m_extruder->updateNozzleNum(m_total_ext_count, series_name))
|
||||||
{
|
{
|
||||||
m_amswin->Layout();
|
m_amswin->Layout();
|
||||||
}
|
}
|
||||||
@@ -1106,7 +1299,15 @@ void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type)
|
|||||||
{
|
{
|
||||||
AMSPreview *ams_prv = nullptr;
|
AMSPreview *ams_prv = nullptr;
|
||||||
|
|
||||||
if (info.routes_to_main_extruder())
|
// Match AddAms: the generic multi-nozzle layout's preview panel is the visible one for
|
||||||
|
// non-Bambu vendors at any extruder count, not only above two.
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
const int nozzle_id = std::clamp(info.nozzle_id, 0, m_total_ext_count - 1);
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
ams_prv = new AMSPreview(pane.preview_panel, wxID_ANY, info, type);
|
||||||
|
pane.preview_sizer->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
|
||||||
|
}
|
||||||
|
else if (info.routes_to_main_extruder())
|
||||||
{
|
{
|
||||||
ams_prv = new AMSPreview(m_panel_prv_right, wxID_ANY, info, type);
|
ams_prv = new AMSPreview(m_panel_prv_right, wxID_ANY, info, type);
|
||||||
m_sizer_prv_right->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
|
m_sizer_prv_right->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
|
||||||
@@ -1129,6 +1330,54 @@ void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type)
|
|||||||
void AMSControl::createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos) {
|
void AMSControl::createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos) {
|
||||||
auto ams_item = new AmsItem(parent, info, info.ams_type, pos);
|
auto ams_item = new AmsItem(parent, info, info.ams_type, pos);
|
||||||
parent->InsertPage(idx, ams_item, wxEmptyString, true);
|
parent->InsertPage(idx, ams_item, wxEmptyString, true);
|
||||||
|
|
||||||
|
// Generic AMS cards may be wider than the legacy 264 DIP page. Propagate
|
||||||
|
// that calculated width through the page and simplebook so 5-8 lanes are
|
||||||
|
// laid out fully instead of being clipped by the old minimum width.
|
||||||
|
if (info.ams_type == AMSModel::GENERIC_AMS) {
|
||||||
|
const int required_width = ams_item->GetMinSize().x;
|
||||||
|
if (required_width > 0) {
|
||||||
|
const int page_width = std::max(parent->GetMinSize().x, required_width);
|
||||||
|
parent->SetMinSize(wxSize(page_width, -1));
|
||||||
|
if (auto *page = parent->GetParent()) {
|
||||||
|
page->SetMinSize(wxSize(std::max(page->GetMinSize().x, page_width), -1));
|
||||||
|
if (auto *nozzle_book = page->GetParent())
|
||||||
|
nozzle_book->SetMinSize(wxSize(std::max(nozzle_book->GetMinSize().x, page_width), -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Above eight lanes, AmsItem is a fixed-minimum viewport over a wider
|
||||||
|
// lane strip. Expand the viewport through its page's sizers rather
|
||||||
|
// than propagating the strip's virtual width into the card/book.
|
||||||
|
// Reset back to the centered fixed layout for eight or fewer lanes so a
|
||||||
|
// later small unit is not left in expand mode.
|
||||||
|
const bool generic_lane_layout = !wxGetApp().preset_bundle || !wxGetApp().preset_bundle->is_bbl_vendor();
|
||||||
|
if (generic_lane_layout) {
|
||||||
|
if (auto *page = parent->GetParent()) {
|
||||||
|
auto pane = std::find_if(m_nozzle_panes.begin(), m_nozzle_panes.end(),
|
||||||
|
[page](const NozzleAmsPane &candidate) { return candidate.page == page; });
|
||||||
|
if (pane != m_nozzle_panes.end() && m_nozzle_book) {
|
||||||
|
const bool expand_viewport = info.cans.size() > 8;
|
||||||
|
if (auto *book_item = pane->ams_area->GetItem(pane->ams_book)) {
|
||||||
|
book_item->SetProportion(expand_viewport ? 1 : 0);
|
||||||
|
book_item->SetFlag(expand_viewport ? wxEXPAND : wxALIGN_CENTER);
|
||||||
|
}
|
||||||
|
// Keep the centring spacers (items 0 and 2) elastic only in the centred layout;
|
||||||
|
// in the >8-lane viewport they must collapse so the book can fill ams_area.
|
||||||
|
if (auto *lead = pane->ams_area->GetItem(static_cast<size_t>(0)))
|
||||||
|
lead->SetProportion(expand_viewport ? 0 : 1);
|
||||||
|
if (auto *trail = pane->ams_area->GetItem(static_cast<size_t>(2)))
|
||||||
|
trail->SetProportion(expand_viewport ? 0 : 1);
|
||||||
|
if (auto *area_item = pane->page_sizer->GetItem(pane->ams_area))
|
||||||
|
area_item->SetFlag(wxEXPAND | wxTOP);
|
||||||
|
if (auto *nozzle_book_item = m_sizer_ams_items->GetItem(m_nozzle_book))
|
||||||
|
nozzle_book_item->SetFlag(expand_viewport ? wxEXPAND : wxALIGN_CENTER);
|
||||||
|
if (auto *items_item = m_sizer_body->GetItem(m_sizer_ams_items))
|
||||||
|
items_item->SetFlag(expand_viewport ? wxEXPAND : wxALIGN_CENTER);
|
||||||
|
m_amswin->Layout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ams_item->set_selection(idx);
|
ams_item->set_selection(idx);
|
||||||
idx++;
|
idx++;
|
||||||
|
|
||||||
@@ -1153,7 +1402,10 @@ AMSRoadShowMode AMSControl::findFirstMode(AMSPanelPos pos) {
|
|||||||
if (item->second->get_ams_model() == AMSModel::EXT_AMS && item->second->get_ext_type() == AMSModelOriginType::LITE_EXT) return AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE;
|
if (item->second->get_ams_model() == AMSModel::EXT_AMS && item->second->get_ext_type() == AMSModelOriginType::LITE_EXT) return AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE;
|
||||||
return AMSRoadShowMode::AMS_ROAD_MODE_FOUR;
|
return AMSRoadShowMode::AMS_ROAD_MODE_FOUR;
|
||||||
}
|
}
|
||||||
else{
|
else if (item->second->get_can_count() > GENERIC_AMS_SLOT_NUM) {
|
||||||
|
return AMSRoadShowMode::AMS_ROAD_MODE_GENERIC;
|
||||||
|
}
|
||||||
|
else {
|
||||||
for (auto ids : pair_id){
|
for (auto ids : pair_id){
|
||||||
if (ids.first == ams_id || ids.second == ams_id){
|
if (ids.first == ams_id || ids.second == ams_id){
|
||||||
return AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE;
|
return AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE;
|
||||||
@@ -1172,8 +1424,13 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vector<AMSi
|
|||||||
wxPanel* book_panel = new wxPanel(parent);
|
wxPanel* book_panel = new wxPanel(parent);
|
||||||
wxBoxSizer* book_sizer = new wxBoxSizer(wxHORIZONTAL);
|
wxBoxSizer* book_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
book_panel->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_LIB_BK_COLOUR));
|
book_panel->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_LIB_BK_COLOUR));
|
||||||
book_panel->SetSize(AMS_PANEL_SIZE);
|
// Size the page to the AMS book it becomes, not to the legacy fixed width: an AMS card is
|
||||||
book_panel->SetMinSize(AMS_PANEL_SIZE);
|
// added directly as the book page (so it always spans the book), while a single external
|
||||||
|
// spool is wrapped here. Matching the book keeps the Ext card centred on the same axis as
|
||||||
|
// the AMS cards and the shared down-road connector anchored below them.
|
||||||
|
const wxSize book_panel_size(std::max(parent->GetMinSize().x, AMS_PANEL_SIZE.x), AMS_PANEL_SIZE.y);
|
||||||
|
book_panel->SetSize(book_panel_size);
|
||||||
|
book_panel->SetMinSize(book_panel_size);
|
||||||
|
|
||||||
AmsItem* ams1 = nullptr, * ams2 = nullptr;
|
AmsItem* ams1 = nullptr, * ams2 = nullptr;
|
||||||
ams1 = new AmsItem(book_panel, infos[0], infos[0].ams_type, pos);
|
ams1 = new AmsItem(book_panel, infos[0], infos[0].ams_type, pos);
|
||||||
@@ -1200,6 +1457,14 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vector<AMSi
|
|||||||
//book_sizer->Add(ams1, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
//book_sizer->Add(ams1, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||||
book_sizer->Add(ams1, 0, wxLEFT, (book_panel->GetSize().x - ams1->GetSize().x) / 2);
|
book_sizer->Add(ams1, 0, wxLEFT, (book_panel->GetSize().x - ams1->GetSize().x) / 2);
|
||||||
}
|
}
|
||||||
|
else if (pos == AMSPanelPos::SINGLE_PANEL) {
|
||||||
|
// Centre the single external-spool card explicitly, using the same fixed-margin
|
||||||
|
// technique as the Lite-EXT branch above. Two stretch spacers centred the card
|
||||||
|
// against the wrapper's own width rather than the AMS book, which left the card
|
||||||
|
// (and the connector drawn below it) off the book/connector axis.
|
||||||
|
book_sizer->Add(ams1, 0, wxLEFT,
|
||||||
|
std::max(0, (book_panel_size.x - ams1->GetSize().x) / 2));
|
||||||
|
}
|
||||||
else{
|
else{
|
||||||
auto ext_image = new AMSExtImage(book_panel, pos, m_total_ext_count, false);
|
auto ext_image = new AMSExtImage(book_panel, pos, m_total_ext_count, false);
|
||||||
book_sizer->Add(ams1, 0, wxLEFT, FromDIP(30));
|
book_sizer->Add(ams1, 0, wxLEFT, FromDIP(30));
|
||||||
@@ -1228,7 +1493,20 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vector<AMSi
|
|||||||
|
|
||||||
void AMSControl::AddAms(AMSinfo info, AMSPanelPos pos)
|
void AMSControl::AddAms(AMSinfo info, AMSPanelPos pos)
|
||||||
{
|
{
|
||||||
if (m_total_ext_count > 1){
|
// The generic multi-nozzle layout (non-Bambu vendors) owns the only visible book; a unit must
|
||||||
|
// land there for any extruder count. Keying off m_total_ext_count instead sent 1- and 2-extruder
|
||||||
|
// generic printers into the legacy simplebooks that CreateAmsMultiNozzle hides.
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
const int nozzle_id = std::clamp(info.nozzle_id, 0, m_total_ext_count - 1);
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
pane.item_ids.push_back(info.ams_id);
|
||||||
|
m_item_ids[nozzle_id].push_back(info.ams_id);
|
||||||
|
if (pane.current_ams.empty()) pane.current_ams = info.ams_id;
|
||||||
|
createAms(pane.ams_book, pane.page_index, info, AMSPanelPos::SINGLE_PANEL);
|
||||||
|
pane.ams_book->Fit();
|
||||||
|
pane.ams_book->Layout();
|
||||||
|
}
|
||||||
|
else if (m_total_ext_count == 2){
|
||||||
if (info.routes_to_main_extruder()){
|
if (info.routes_to_main_extruder()){
|
||||||
createAms(m_simplebook_ams_right, m_right_page_index, info, AMSPanelPos::RIGHT_PANEL);
|
createAms(m_simplebook_ams_right, m_right_page_index, info, AMSPanelPos::RIGHT_PANEL);
|
||||||
}
|
}
|
||||||
@@ -1239,6 +1517,9 @@ void AMSControl::AddAms(AMSinfo info, AMSPanelPos pos)
|
|||||||
else if (m_total_ext_count == 1){
|
else if (m_total_ext_count == 1){
|
||||||
createAms(m_simplebook_ams_left, m_left_page_index, info, AMSPanelPos::LEFT_PANEL);
|
createAms(m_simplebook_ams_left, m_left_page_index, info, AMSPanelPos::LEFT_PANEL);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
m_simplebook_ams_left->Layout();
|
m_simplebook_ams_left->Layout();
|
||||||
m_simplebook_ams_right->Layout();
|
m_simplebook_ams_right->Layout();
|
||||||
m_simplebook_ams_left->Refresh();
|
m_simplebook_ams_left->Refresh();
|
||||||
@@ -1269,7 +1550,19 @@ void AMSControl::AddAms(std::vector<AMSinfo> single_info, const std::string &ser
|
|||||||
if (single_info.size() <= 0){
|
if (single_info.size() <= 0){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_total_ext_count == 2) {
|
if (use_multi_nozzle_layout()) {
|
||||||
|
const int nozzle_id = std::clamp(single_info.front().nozzle_id, 0, m_total_ext_count - 1);
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
for (const auto &info : single_info) {
|
||||||
|
pane.item_ids.push_back(info.ams_id);
|
||||||
|
m_item_ids[nozzle_id].push_back(info.ams_id);
|
||||||
|
}
|
||||||
|
if (pane.current_ams.empty()) pane.current_ams = single_info.front().ams_id;
|
||||||
|
createAmsPanel(pane.ams_book, pane.page_index, single_info, series_name, printer_type, AMSPanelPos::SINGLE_PANEL, m_total_ext_count);
|
||||||
|
pane.ams_book->Fit();
|
||||||
|
pane.ams_book->Layout();
|
||||||
|
}
|
||||||
|
else if (m_total_ext_count == 2) {
|
||||||
if (single_info[0].routes_to_main_extruder()) {
|
if (single_info[0].routes_to_main_extruder()) {
|
||||||
createAmsPanel(m_simplebook_ams_right, m_right_page_index, single_info, series_name, printer_type, AMSPanelPos::RIGHT_PANEL, m_total_ext_count);
|
createAmsPanel(m_simplebook_ams_right, m_right_page_index, single_info, series_name, printer_type, AMSPanelPos::RIGHT_PANEL, m_total_ext_count);
|
||||||
}
|
}
|
||||||
@@ -1285,6 +1578,9 @@ void AMSControl::AddAms(std::vector<AMSinfo> single_info, const std::string &ser
|
|||||||
createAmsPanel(m_simplebook_ams_left, m_left_page_index, single_info, series_name, printer_type, AMSPanelPos::LEFT_PANEL, m_total_ext_count);
|
createAmsPanel(m_simplebook_ams_left, m_left_page_index, single_info, series_name, printer_type, AMSPanelPos::LEFT_PANEL, m_total_ext_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
|
|
||||||
m_simplebook_ams_left->Layout();
|
m_simplebook_ams_left->Layout();
|
||||||
m_simplebook_ams_right->Layout();
|
m_simplebook_ams_right->Layout();
|
||||||
@@ -1322,6 +1618,21 @@ void AMSControl::AddAms(std::vector<AMSinfo> single_info, const std::string &ser
|
|||||||
void AMSControl::AddAmsPreview(std::vector<AMSinfo>single_info, AMSPanelPos pos) {
|
void AMSControl::AddAmsPreview(std::vector<AMSinfo>single_info, AMSPanelPos pos) {
|
||||||
if (single_info.size() <= 0) return;
|
if (single_info.size() <= 0) return;
|
||||||
|
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
const int nozzle_id = std::clamp(single_info.front().nozzle_id, 0, m_total_ext_count - 1);
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
for (const auto &info : single_info) {
|
||||||
|
auto *ams_prv = new AMSPreview(pane.preview_panel, wxID_ANY, info, info.ams_type);
|
||||||
|
pane.preview_sizer->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
|
||||||
|
ams_prv->Bind(wxEVT_LEFT_DOWN, [this, ams_prv](wxMouseEvent &e) {
|
||||||
|
SwitchAms(ams_prv->get_ams_id());
|
||||||
|
e.Skip();
|
||||||
|
});
|
||||||
|
m_ams_preview_list[info.ams_id] = ams_prv;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AMSPreview* ams_prv = nullptr;
|
AMSPreview* ams_prv = nullptr;
|
||||||
AMSPreview* ams_prv2 = nullptr;
|
AMSPreview* ams_prv2 = nullptr;
|
||||||
if (pos == AMSPanelPos::RIGHT_PANEL){
|
if (pos == AMSPanelPos::RIGHT_PANEL){
|
||||||
@@ -1360,8 +1671,97 @@ void AMSControl::AddAmsPreview(std::vector<AMSinfo>single_info, AMSPanelPos pos)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AMSControl::SelectNozzle(int nozzle_id)
|
||||||
|
{
|
||||||
|
if (nozzle_id < 0 || nozzle_id >= static_cast<int>(m_nozzle_panes.size()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_active_nozzle_id == nozzle_id &&
|
||||||
|
(!m_nozzle_book || m_nozzle_book->GetSelection() == nozzle_id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_active_nozzle_id = nozzle_id;
|
||||||
|
if (m_nozzle_book && m_nozzle_book->GetSelection() != nozzle_id)
|
||||||
|
m_nozzle_book->SetSelection(nozzle_id);
|
||||||
|
m_current_ams = m_nozzle_panes[nozzle_id].current_ams;
|
||||||
|
if (!m_current_ams.empty()) {
|
||||||
|
SwitchAms(m_current_ams);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_down_road) {
|
||||||
|
m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
}
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(false, 0);
|
||||||
|
post_event(SimpleEvent(EVT_AMS_SWITCH));
|
||||||
|
}
|
||||||
|
|
||||||
void AMSControl::SwitchAms(std::string ams_id)
|
void AMSControl::SwitchAms(std::string ams_id)
|
||||||
{
|
{
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
NozzleAmsPane *pane = nullptr;
|
||||||
|
int nozzle_id = -1;
|
||||||
|
for (int index = 0; index < static_cast<int>(m_nozzle_panes.size()); ++index) {
|
||||||
|
auto &candidate = m_nozzle_panes[index];
|
||||||
|
if (std::find(candidate.item_ids.begin(), candidate.item_ids.end(), ams_id) != candidate.item_ids.end()) {
|
||||||
|
pane = &candidate;
|
||||||
|
nozzle_id = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!pane) return;
|
||||||
|
|
||||||
|
m_active_nozzle_id = nozzle_id;
|
||||||
|
if (m_nozzle_book && m_nozzle_book->GetSelection() != nozzle_id)
|
||||||
|
m_nozzle_book->SetSelection(nozzle_id);
|
||||||
|
|
||||||
|
pane->current_ams = ams_id;
|
||||||
|
m_current_ams = ams_id;
|
||||||
|
m_current_select = ams_id;
|
||||||
|
|
||||||
|
for (const auto &id : pane->item_ids) {
|
||||||
|
auto preview = m_ams_preview_list.find(id);
|
||||||
|
if (preview == m_ams_preview_list.end() || !preview->second) continue;
|
||||||
|
if (id == ams_id) preview->second->OnSelected();
|
||||||
|
else preview->second->UnSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto item = m_ams_item_list.find(ams_id);
|
||||||
|
if (item != m_ams_item_list.end() && item->second) {
|
||||||
|
if (pane->ams_book)
|
||||||
|
pane->ams_book->SetSelection(item->second->get_selection());
|
||||||
|
|
||||||
|
AMSRoadShowMode road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE;
|
||||||
|
if (item->second->get_can_count() == GENERIC_AMS_SLOT_NUM)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_FOUR;
|
||||||
|
else if (item->second->get_can_count() > GENERIC_AMS_SLOT_NUM)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC;
|
||||||
|
else if (item->second->get_ams_model() == AMSModel::N3S_AMS)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S;
|
||||||
|
else if (item->second->get_ams_model() == AMSModel::AMS_LITE ||
|
||||||
|
(item->second->get_ams_model() == AMSModel::EXT_AMS &&
|
||||||
|
item->second->get_ext_type() == AMSModelOriginType::LITE_EXT))
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE;
|
||||||
|
|
||||||
|
if (m_down_road) {
|
||||||
|
if (pane->panel_pos == AMSPanelPos::LEFT_PANEL) {
|
||||||
|
m_down_road->UpdateLeft(1, road_mode);
|
||||||
|
m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
} else {
|
||||||
|
m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
m_down_road->UpdateRight(1, road_mode);
|
||||||
|
}
|
||||||
|
m_down_road->UpdatePassRoad(pane->panel_pos, -1, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE);
|
||||||
|
}
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
post_event(SimpleEvent(EVT_AMS_SWITCH));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(ams_id == m_current_show_ams_left || ams_id == m_current_show_ams_right){return;}
|
if(ams_id == m_current_show_ams_left || ams_id == m_current_show_ams_right){return;}
|
||||||
|
|
||||||
bool is_in_right = IsAmsInRightPanel(ams_id);
|
bool is_in_right = IsAmsInRightPanel(ams_id);
|
||||||
@@ -1432,13 +1832,17 @@ void AMSControl::SwitchAms(std::string ams_id)
|
|||||||
else {
|
else {
|
||||||
AMSRoadShowMode mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE;
|
AMSRoadShowMode mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE;
|
||||||
|
|
||||||
if (item->get_ams_model() == AMSModel::N3S_AMS)
|
if (item->get_can_count() > GENERIC_AMS_SLOT_NUM)
|
||||||
|
mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC;
|
||||||
|
else if (item->get_ams_model() == AMSModel::N3S_AMS)
|
||||||
mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S;
|
mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S;
|
||||||
|
|
||||||
for (auto it : pair_id) {
|
if (item->get_can_count() <= GENERIC_AMS_SLOT_NUM) {
|
||||||
if (it.first == ams_id || it.second == ams_id) {
|
for (auto it : pair_id) {
|
||||||
mode = AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE;
|
if (it.first == ams_id || it.second == ams_id) {
|
||||||
break;
|
mode = AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos == AMSPanelPos::LEFT_PANEL ? m_down_road->UpdateLeft(m_total_ext_count, mode)
|
pos == AMSPanelPos::LEFT_PANEL ? m_down_road->UpdateLeft(m_total_ext_count, mode)
|
||||||
@@ -1500,6 +1904,20 @@ void AMSControl::SetExtruder(bool on_off, int nozzle_id, std::string ams_id, std
|
|||||||
AmsItem *item = nullptr;
|
AmsItem *item = nullptr;
|
||||||
if (m_ams_item_list.find(ams_id) != m_ams_item_list.end()) { item = m_ams_item_list[ams_id]; }
|
if (m_ams_item_list.find(ams_id) != m_ams_item_list.end()) { item = m_ams_item_list[ams_id]; }
|
||||||
|
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
if (nozzle_id < 0 || nozzle_id >= static_cast<int>(m_nozzle_panes.size()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_extruder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (on_off && item)
|
||||||
|
m_extruder->OnAmsLoading(true, 0, item->GetTagColr(slot_id));
|
||||||
|
else
|
||||||
|
m_extruder->OnAmsLoading(false, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (on_off && item) {
|
if (on_off && item) {
|
||||||
auto col = item->GetTagColr(slot_id);
|
auto col = item->GetTagColr(slot_id);
|
||||||
m_extruder->OnAmsLoading(true, nozzle_id, col);
|
m_extruder->OnAmsLoading(true, nozzle_id, col);
|
||||||
@@ -1624,6 +2042,69 @@ void AMSControl::SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadTy
|
|||||||
else{
|
else{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (use_multi_nozzle_layout()) {
|
||||||
|
const int nozzle_id = std::clamp(ams->get_nozzle_id(), 0, m_total_ext_count - 1);
|
||||||
|
if (nozzle_id >= static_cast<int>(m_nozzle_panes.size()))
|
||||||
|
return;
|
||||||
|
auto &pane = m_nozzle_panes[nozzle_id];
|
||||||
|
AMSRoadShowMode road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE;
|
||||||
|
if (ams->get_can_count() == GENERIC_AMS_SLOT_NUM)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_FOUR;
|
||||||
|
else if (ams->get_can_count() > GENERIC_AMS_SLOT_NUM)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC;
|
||||||
|
else if (ams->get_ams_model() == AMSModel::N3S_AMS)
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S;
|
||||||
|
else if (ams->get_ams_model() == AMSModel::AMS_LITE ||
|
||||||
|
(ams->get_ams_model() == AMSModel::EXT_AMS && ams->get_ext_type() == AMSModelOriginType::LITE_EXT))
|
||||||
|
road_mode = AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE;
|
||||||
|
|
||||||
|
if (can_index >= 0 && can_index < static_cast<int>(info.cans.size()) && m_down_road)
|
||||||
|
m_down_road->SetPassRoadColour(pane.panel_pos == AMSPanelPos::LEFT_PANEL,
|
||||||
|
info.cans[can_index].material_colour);
|
||||||
|
if (m_down_road) {
|
||||||
|
if (pane.panel_pos == AMSPanelPos::LEFT_PANEL) {
|
||||||
|
m_down_road->UpdateLeft(1, road_mode);
|
||||||
|
m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
} else {
|
||||||
|
m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE);
|
||||||
|
m_down_road->UpdateRight(1, road_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int road_length = 0;
|
||||||
|
|
||||||
|
if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_NONE) {
|
||||||
|
ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE);
|
||||||
|
if (m_down_road)
|
||||||
|
m_down_road->UpdatePassRoad(pane.panel_pos, -1, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE);
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(false, 0);
|
||||||
|
}
|
||||||
|
else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP1) {
|
||||||
|
ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_1);
|
||||||
|
if (m_down_road)
|
||||||
|
m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_1);
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(false, 0);
|
||||||
|
}
|
||||||
|
else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2) {
|
||||||
|
ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_2);
|
||||||
|
if (m_down_road)
|
||||||
|
m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_2);
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(true, 0, ams->GetTagColr(canid));
|
||||||
|
}
|
||||||
|
else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3) {
|
||||||
|
ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_3);
|
||||||
|
if (m_down_road)
|
||||||
|
m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_3);
|
||||||
|
if (m_extruder)
|
||||||
|
m_extruder->OnAmsLoading(true, 0, ams->GetTagColr(canid));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (can_index >= 0 && can_index < info.cans.size())
|
if (can_index >= 0 && can_index < info.cans.size())
|
||||||
{
|
{
|
||||||
m_down_road->SetPassRoadColour(left, info.cans[can_index].material_colour);
|
m_down_road->SetPassRoadColour(left, info.cans[can_index].material_colour);
|
||||||
|
|||||||
@@ -47,6 +47,20 @@ public:
|
|||||||
void on_retry();
|
void on_retry();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
struct NozzleAmsPane
|
||||||
|
{
|
||||||
|
wxPanel* page{nullptr};
|
||||||
|
wxBoxSizer* page_sizer{nullptr};
|
||||||
|
wxScrolledWindow* preview_panel{nullptr};
|
||||||
|
wxBoxSizer* preview_sizer{nullptr};
|
||||||
|
wxSimplebook* ams_book{nullptr};
|
||||||
|
wxBoxSizer* ams_area{nullptr};
|
||||||
|
AMSPanelPos panel_pos{AMSPanelPos::LEFT_PANEL};
|
||||||
|
std::vector<std::string> item_ids;
|
||||||
|
std::string current_ams;
|
||||||
|
int page_index{0};
|
||||||
|
};
|
||||||
|
|
||||||
std::string m_current_ams;
|
std::string m_current_ams;
|
||||||
std::string m_current_slot_left;
|
std::string m_current_slot_left;
|
||||||
std::string m_current_slot_right;
|
std::string m_current_slot_right;
|
||||||
@@ -63,6 +77,9 @@ protected:
|
|||||||
|
|
||||||
std::string m_dev_id;
|
std::string m_dev_id;
|
||||||
std::vector<std::vector<std::string>> m_item_ids{ {}, {} };
|
std::vector<std::vector<std::string>> m_item_ids{ {}, {} };
|
||||||
|
std::vector<NozzleAmsPane> m_nozzle_panes;
|
||||||
|
wxSimplebook* m_nozzle_book{nullptr};
|
||||||
|
int m_active_nozzle_id{0};
|
||||||
std::vector<std::pair<std::string, std::string>> pair_id;
|
std::vector<std::pair<std::string, std::string>> pair_id;
|
||||||
|
|
||||||
int m_total_ext_count = 1;
|
int m_total_ext_count = 1;
|
||||||
@@ -103,6 +120,7 @@ protected:
|
|||||||
wxSimplebook *m_simplebook_ams_left{nullptr};
|
wxSimplebook *m_simplebook_ams_left{nullptr};
|
||||||
wxSimplebook *m_simplebook_ams_right{ nullptr };
|
wxSimplebook *m_simplebook_ams_right{ nullptr };
|
||||||
wxSimplebook *m_simplebook_bottom{nullptr};
|
wxSimplebook *m_simplebook_bottom{nullptr};
|
||||||
|
wxSizerItem *m_sizer_ams_gap{nullptr};
|
||||||
wxPanel *m_panel_down_road{ nullptr };
|
wxPanel *m_panel_down_road{ nullptr };
|
||||||
int m_left_page_index = 0;
|
int m_left_page_index = 0;
|
||||||
int m_right_page_index = 0;
|
int m_right_page_index = 0;
|
||||||
@@ -156,6 +174,7 @@ public:
|
|||||||
void createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos);
|
void createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos);
|
||||||
void createAmsPanel(wxSimplebook *parent, int &idx, std::vector<AMSinfo> infos, const std::string &series_name, const std::string &printer_type, AMSPanelPos pos, int total_ext_num);
|
void createAmsPanel(wxSimplebook *parent, int &idx, std::vector<AMSinfo> infos, const std::string &series_name, const std::string &printer_type, AMSPanelPos pos, int total_ext_num);
|
||||||
AMSRoadShowMode findFirstMode(AMSPanelPos pos);
|
AMSRoadShowMode findFirstMode(AMSPanelPos pos);
|
||||||
|
void restore_legacy_ams_layout();
|
||||||
|
|
||||||
AMSModel m_ams_model{AMSModel::EXT_AMS};
|
AMSModel m_ams_model{AMSModel::EXT_AMS};
|
||||||
AMSModel m_ext_model{AMSModel::EXT_AMS};
|
AMSModel m_ext_model{AMSModel::EXT_AMS};
|
||||||
@@ -183,6 +202,7 @@ public:
|
|||||||
|
|
||||||
void UpdatePassRoad(std::string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step);
|
void UpdatePassRoad(std::string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step);
|
||||||
void CreateAms();
|
void CreateAms();
|
||||||
|
void CreateAmsMultiNozzle(const std::string& series_name, const std::string& printer_type);
|
||||||
void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type);
|
void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type);
|
||||||
void CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type);
|
void CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type);
|
||||||
void ClearAms();
|
void ClearAms();
|
||||||
@@ -210,6 +230,8 @@ public:
|
|||||||
void SetExtruder(bool on_off, int nozzle_id, std::string ams_id, std::string slot_id);
|
void SetExtruder(bool on_off, int nozzle_id, std::string ams_id, std::string slot_id);
|
||||||
void SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step);
|
void SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step);
|
||||||
void SwitchAms(std::string ams_id);
|
void SwitchAms(std::string ams_id);
|
||||||
|
void SelectNozzle(int nozzle_id);
|
||||||
|
int GetActiveNozzleId() const { return m_active_nozzle_id; }
|
||||||
|
|
||||||
void msw_rescale();
|
void msw_rescale();
|
||||||
void on_filament_load(wxCommandEvent &event);
|
void on_filament_load(wxCommandEvent &event);
|
||||||
@@ -234,6 +256,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::string get_filament_id(const std::string& ams_id, const std::string& can_id);
|
std::string get_filament_id(const std::string& ams_id, const std::string& can_id);
|
||||||
|
|
||||||
|
// True when AMS units are laid out on the generic multi-nozzle book. Bambu
|
||||||
|
// printers keep the legacy count-based single/double-nozzle layout at any
|
||||||
|
// extruder count; other vendors use the book created for them.
|
||||||
|
bool use_multi_nozzle_layout() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string m_current_select;
|
std::string m_current_select;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/scrolwin.h>
|
||||||
|
|
||||||
#include "CalibUtils.hpp"
|
#include "CalibUtils.hpp"
|
||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
@@ -76,6 +77,48 @@ namespace Slic3r { namespace GUI {
|
|||||||
//#define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), 144)
|
//#define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), 144)
|
||||||
#define AMS_CANS_WINDOW_SIZE wxSize(FromDIP(264), FromDIP(174))
|
#define AMS_CANS_WINDOW_SIZE wxSize(FromDIP(264), FromDIP(174))
|
||||||
#define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), FromDIP(174))
|
#define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), FromDIP(174))
|
||||||
|
|
||||||
|
int generic_ams_layout_width(wxWindow *window, int lane_count)
|
||||||
|
{
|
||||||
|
const int base_width = window->FromDIP(264);
|
||||||
|
const int lane_width = window->FromDIP(52);
|
||||||
|
const int lane_gap = window->FromDIP(4);
|
||||||
|
if (lane_count <= 4)
|
||||||
|
return base_width;
|
||||||
|
|
||||||
|
// Lane centers are distributed as width * (index + 1) / (count + 1).
|
||||||
|
// Keep at least one card width plus the requested gap between centers.
|
||||||
|
return std::max(base_width, (lane_count + 1) * (lane_width + lane_gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> generic_ams_lane_centers(int lane_count, int width)
|
||||||
|
{
|
||||||
|
std::vector<int> centers;
|
||||||
|
if (lane_count <= 0)
|
||||||
|
return centers;
|
||||||
|
|
||||||
|
centers.reserve(lane_count);
|
||||||
|
for (int lane = 0; lane < lane_count; ++lane)
|
||||||
|
centers.push_back(width * (lane + 1) / (lane_count + 1));
|
||||||
|
return centers;
|
||||||
|
}
|
||||||
|
|
||||||
|
int generic_ams_preview_width(wxWindow *window, int lane_count)
|
||||||
|
{
|
||||||
|
const int base_width = window->FromDIP(52);
|
||||||
|
const int lane_width = window->FromDIP(9);
|
||||||
|
const int lane_gap = window->FromDIP(4);
|
||||||
|
if (lane_count <= 0)
|
||||||
|
return base_width;
|
||||||
|
|
||||||
|
return std::max(base_width, lane_count * lane_width + (lane_count + 1) * lane_gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool use_generic_ams_layout(AMSModel model)
|
||||||
|
{
|
||||||
|
return model == AMSModel::GENERIC_AMS &&
|
||||||
|
(!wxGetApp().preset_bundle || !wxGetApp().preset_bundle->is_bbl_vendor());
|
||||||
|
}
|
||||||
bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag, bool humidity_flag)
|
bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag, bool humidity_flag)
|
||||||
{
|
{
|
||||||
if (!ams) return false;
|
if (!ams) return false;
|
||||||
@@ -170,10 +213,14 @@ void AMSinfo::parse_ext_info(MachineObject* obj, DevAmsTray tray) {
|
|||||||
info.can_id = std::to_string(0);
|
info.can_id = std::to_string(0);
|
||||||
this->cans.clear();
|
this->cans.clear();
|
||||||
|
|
||||||
if (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID))
|
try {
|
||||||
|
const int virtual_tray_id = std::stoi(tray.id);
|
||||||
|
if (virtual_tray_id >= 0 && virtual_tray_id <= VIRTUAL_TRAY_MAIN_ID)
|
||||||
|
this->nozzle_id = VIRTUAL_TRAY_MAIN_ID - virtual_tray_id;
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
this->nozzle_id = 0;
|
this->nozzle_id = 0;
|
||||||
else if (tray.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID))
|
}
|
||||||
this->nozzle_id = 1;
|
|
||||||
|
|
||||||
if (tray.is_tray_info_ready()) {
|
if (tray.is_tray_info_ready()) {
|
||||||
info.ctype = tray.ctype;
|
info.ctype = tray.ctype;
|
||||||
@@ -856,7 +903,11 @@ AMSextruder::AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const
|
|||||||
|
|
||||||
void AMSextruder::TurnOff()
|
void AMSextruder::TurnOff()
|
||||||
{
|
{
|
||||||
m_left_extruder->TurnOff();
|
if (m_left_extruder) m_left_extruder->TurnOff();
|
||||||
|
if (m_right_extruder) m_right_extruder->TurnOff();
|
||||||
|
for (auto *extruder : m_nozzle_extruders) {
|
||||||
|
if (extruder) extruder->TurnOff();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMSextruder::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, int nozzle_num)
|
void AMSextruder::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, int nozzle_num)
|
||||||
@@ -882,7 +933,7 @@ void AMSextruder::OnAmsLoading(bool load, int nozzle_id, wxColour col /*= AMS_CO
|
|||||||
m_left_extruder->OnAmsLoading(load, col);
|
m_left_extruder->OnAmsLoading(load, col);
|
||||||
if (load) m_current_colur_deputy = col;
|
if (load) m_current_colur_deputy = col;
|
||||||
}
|
}
|
||||||
else if (m_nozzle_num > 1){
|
else if (m_nozzle_num == 2){
|
||||||
if (nozzle_id == MAIN_EXTRUDER_ID) {
|
if (nozzle_id == MAIN_EXTRUDER_ID) {
|
||||||
m_right_extruder->OnAmsLoading(load, col);
|
m_right_extruder->OnAmsLoading(load, col);
|
||||||
if (m_current_colur != col){
|
if (m_current_colur != col){
|
||||||
@@ -896,6 +947,15 @@ void AMSextruder::OnAmsLoading(bool load, int nozzle_id, wxColour col /*= AMS_CO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (m_nozzle_num > 2) {
|
||||||
|
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(m_nozzle_extruders.size())) {
|
||||||
|
m_nozzle_extruders[nozzle_id]->OnAmsLoading(load, col);
|
||||||
|
if (load) m_current_colur = col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*return true if something is updated*/
|
/*return true if something is updated*/
|
||||||
@@ -905,12 +965,26 @@ bool AMSextruder::updateNozzleNum(int nozzle_num, const std::string& series_name
|
|||||||
m_series_name = series_name;
|
m_series_name = series_name;
|
||||||
m_nozzle_num = nozzle_num;
|
m_nozzle_num = nozzle_num;
|
||||||
this->DestroyChildren();
|
this->DestroyChildren();
|
||||||
|
m_left_extruder = nullptr;
|
||||||
|
m_right_extruder = nullptr;
|
||||||
|
m_nozzle_extruders.clear();
|
||||||
|
|
||||||
m_right_extruder = new AMSextruderImage(this, wxID_ANY, "right_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE);
|
m_bitmap_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
if (m_nozzle_num == 1) {
|
||||||
|
if (MachineObject::is_series_n(m_series_name)) {
|
||||||
|
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE);
|
||||||
|
} else if (MachineObject::is_series_x(m_series_name) || MachineObject::is_series_p(m_series_name)) {
|
||||||
|
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE);
|
||||||
|
} else {
|
||||||
|
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
wxBoxSizer* m_bitmap_sizer = new wxBoxSizer(wxHORIZONTAL);
|
m_left_extruder->setShowState(true);
|
||||||
if (m_nozzle_num >= 2)
|
m_bitmap_sizer->Add(m_left_extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0);
|
||||||
|
}
|
||||||
|
else if (m_nozzle_num == 2)
|
||||||
{
|
{
|
||||||
|
m_right_extruder = new AMSextruderImage(this, wxID_ANY, "right_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE);
|
||||||
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "left_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE);
|
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "left_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE);
|
||||||
m_left_extruder->setShowState(true);
|
m_left_extruder->setShowState(true);
|
||||||
m_right_extruder->setShowState(true);
|
m_right_extruder->setShowState(true);
|
||||||
@@ -918,25 +992,17 @@ bool AMSextruder::updateNozzleNum(int nozzle_num, const std::string& series_name
|
|||||||
m_bitmap_sizer->Add(m_right_extruder, 0, wxLEFT | wxALIGN_TOP, FromDIP(2));
|
m_bitmap_sizer->Add(m_right_extruder, 0, wxLEFT | wxALIGN_TOP, FromDIP(2));
|
||||||
m_bitmap_sizer->AddSpacer(2);
|
m_bitmap_sizer->AddSpacer(2);
|
||||||
}
|
}
|
||||||
|
else if (m_nozzle_num > 2) {
|
||||||
|
for (int nozzle_id = 0; nozzle_id < m_nozzle_num; ++nozzle_id) {
|
||||||
|
auto *extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE);
|
||||||
|
extruder->setShowState(true);
|
||||||
|
m_nozzle_extruders.push_back(extruder);
|
||||||
|
m_bitmap_sizer->Add(extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (MachineObject::is_series_n(m_series_name))
|
// Generic N Nozzles
|
||||||
{
|
|
||||||
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE);
|
|
||||||
}
|
|
||||||
else if(MachineObject::is_series_x(m_series_name) || MachineObject::is_series_p(m_series_name))
|
|
||||||
{
|
|
||||||
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_left_extruder->setShowState(true);
|
|
||||||
m_right_extruder->setShowState(false);
|
|
||||||
m_bitmap_sizer->Add(m_left_extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0);
|
|
||||||
m_bitmap_sizer->Add(m_right_extruder, 0, wxLEFT | wxALIGN_TOP, FromDIP(3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetSizer(m_bitmap_sizer);
|
SetSizer(m_bitmap_sizer);
|
||||||
@@ -951,6 +1017,9 @@ void AMSextruder::msw_rescale()
|
|||||||
//m_amsSextruder->msw_rescale();
|
//m_amsSextruder->msw_rescale();
|
||||||
if (m_left_extruder) m_left_extruder->msw_rescale();
|
if (m_left_extruder) m_left_extruder->msw_rescale();
|
||||||
if (m_right_extruder) m_right_extruder->msw_rescale();
|
if (m_right_extruder) m_right_extruder->msw_rescale();
|
||||||
|
for (auto *extruder : m_nozzle_extruders) {
|
||||||
|
if (extruder) extruder->msw_rescale();
|
||||||
|
}
|
||||||
Layout();
|
Layout();
|
||||||
Update();
|
Update();
|
||||||
Refresh();
|
Refresh();
|
||||||
@@ -2123,7 +2192,10 @@ AMSRoadUpPart::AMSRoadUpPart(wxWindow* parent, wxWindowID id, AMSinfo info, AMSM
|
|||||||
m_ams_model = model;
|
m_ams_model = model;
|
||||||
|
|
||||||
if (m_ams_model == AMSModel::GENERIC_AMS){
|
if (m_ams_model == AMSModel::GENERIC_AMS){
|
||||||
create(parent, id, pos, wxSize(FromDIP(264), FromDIP(34)));
|
const int width = use_generic_ams_layout(m_ams_model)
|
||||||
|
? generic_ams_layout_width(this, static_cast<int>(m_amsinfo.cans.size()))
|
||||||
|
: FromDIP(264);
|
||||||
|
create(parent, id, pos, wxSize(width, FromDIP(34)));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
create(parent, id, pos, wxSize(FromDIP(78), FromDIP(34)));
|
create(parent, id, pos, wxSize(FromDIP(78), FromDIP(34)));
|
||||||
@@ -2235,6 +2307,38 @@ void AMSRoadUpPart::doRender(wxDC& dc)
|
|||||||
dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID));
|
dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID));
|
||||||
dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH));
|
dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH));
|
||||||
|
|
||||||
|
if (use_generic_ams_layout(m_ams_model)) {
|
||||||
|
const int lane_count = static_cast<int>(m_amsinfo.cans.size());
|
||||||
|
if (lane_count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto lane_centers = generic_ams_lane_centers(lane_count, size.x);
|
||||||
|
const int height = FromDIP(21);
|
||||||
|
for (const int lane_center : lane_centers)
|
||||||
|
dc.DrawLine(lane_center, 0, lane_center, height);
|
||||||
|
|
||||||
|
if (lane_centers.size() > 1)
|
||||||
|
dc.DrawLine(lane_centers.front(), height, lane_centers.back(), height);
|
||||||
|
dc.DrawLine(size.x / 2, height, size.x / 2, size.y);
|
||||||
|
|
||||||
|
if (m_load_step != AMSPassRoadSTEP::AMS_ROAD_STEP_NONE &&
|
||||||
|
m_load_slot_index >= 0 && m_load_slot_index < lane_count) {
|
||||||
|
const int lane_center = lane_centers[m_load_slot_index];
|
||||||
|
dc.SetPen(wxPen(_get_diff_clr(this, m_amsinfo.cans[m_load_slot_index].material_colour), 4, wxPENSTYLE_SOLID));
|
||||||
|
dc.DrawLine(lane_center, 0, lane_center, height);
|
||||||
|
dc.DrawLine(std::min(lane_center, size.x / 2), height,
|
||||||
|
std::max(lane_center, size.x / 2), height);
|
||||||
|
if (m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 ||
|
||||||
|
m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3)
|
||||||
|
dc.DrawLine(size.x / 2, height, size.x / 2, size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||||
|
dc.SetBrush(wxBrush(wxColour(194, 194, 194)));
|
||||||
|
dc.DrawRectangle(size.x / 2 - FromDIP(14), height - FromDIP(5), FromDIP(28), FromDIP(10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((m_ams_model == N3S_AMS || m_ams_model == EXT_AMS) && m_amsinfo.cans.size() != 4){
|
if ((m_ams_model == N3S_AMS || m_ams_model == EXT_AMS) && m_amsinfo.cans.size() != 4){
|
||||||
dc.DrawLine(((float)size.x / 2), (0), ((float)size.x / 2), (size.y));
|
dc.DrawLine(((float)size.x / 2), (0), ((float)size.x / 2), (size.y));
|
||||||
if (m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3){
|
if (m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3){
|
||||||
@@ -2360,6 +2464,25 @@ void AMSRoadDownPart::UpdateRight(int nozzle_num, AMSRoadShowMode mode)
|
|||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AMSRoadDownPart::SetNozzleCount(int nozzle_num)
|
||||||
|
{
|
||||||
|
const int new_nozzle_num = std::max(1, nozzle_num);
|
||||||
|
if (m_nozzle_num == new_nozzle_num) return;
|
||||||
|
|
||||||
|
m_nozzle_num = new_nozzle_num;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AMSRoadDownPart::SetSingleSideLayout(bool enabled, AMSPanelPos pos)
|
||||||
|
{
|
||||||
|
if (m_single_side_layout == enabled && m_single_side_pos == pos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_single_side_layout = enabled;
|
||||||
|
m_single_side_pos = pos;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
void AMSRoadDownPart::OnVamsLoading(bool load, wxColour col /*= AMS_CONTROL_GRAY500*/)
|
void AMSRoadDownPart::OnVamsLoading(bool load, wxColour col /*= AMS_CONTROL_GRAY500*/)
|
||||||
{
|
{
|
||||||
/*m_vams_loading = load;
|
/*m_vams_loading = load;
|
||||||
@@ -2433,8 +2556,14 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
/*if (m_road_color.Alpha() == 0) { dc.SetPen(wxPen(*wxWHITE, m_passroad_width, wxPENSTYLE_SOLID)); }
|
/*if (m_road_color.Alpha() == 0) { dc.SetPen(wxPen(*wxWHITE, m_passroad_width, wxPENSTYLE_SOLID)); }
|
||||||
else { dc.SetPen(wxPen(m_road_color, m_passroad_width, wxPENSTYLE_SOLID)); }*/
|
else { dc.SetPen(wxPen(m_road_color, m_passroad_width, wxPENSTYLE_SOLID)); }*/
|
||||||
dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID));
|
dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID));
|
||||||
|
|
||||||
auto xpos = left_nozzle_pos.x;
|
auto xpos = left_nozzle_pos.x;
|
||||||
if (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE || m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE){
|
if (m_single_side_layout) {
|
||||||
|
dc.DrawLine(size.x / 2, 0, size.x / 2, size.y);
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
(m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE ||
|
||||||
|
m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE)){
|
||||||
auto length = 50;
|
auto length = 50;
|
||||||
if (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE || m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE)
|
if (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE || m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE)
|
||||||
length = -13;
|
length = -13;
|
||||||
@@ -2465,6 +2594,9 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
case AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE:
|
case AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE:
|
||||||
dc.DrawLine(left_nozzle_pos.x, 0, left_nozzle_pos.x, size.y);
|
dc.DrawLine(left_nozzle_pos.x, 0, left_nozzle_pos.x, size.y);
|
||||||
break;
|
break;
|
||||||
|
case AMSRoadShowMode::AMS_ROAD_MODE_GENERIC:
|
||||||
|
// Generic N-lane geometry is intentionally left unimplemented.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2492,23 +2624,41 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
dc.DrawLine(left_nozzle_pos.x, (size.y / 2), left_nozzle_pos.x + FromDIP(145), (size.y / 2));
|
dc.DrawLine(left_nozzle_pos.x, (size.y / 2), left_nozzle_pos.x + FromDIP(145), (size.y / 2));
|
||||||
dc.DrawLine(left_nozzle_pos.x + FromDIP(145), 0, left_nozzle_pos.x + FromDIP(145), (size.y / 2));
|
dc.DrawLine(left_nozzle_pos.x + FromDIP(145), 0, left_nozzle_pos.x + FromDIP(145), (size.y / 2));
|
||||||
break;
|
break;
|
||||||
|
case AMSRoadShowMode::AMS_ROAD_MODE_GENERIC:
|
||||||
|
// Generic N-lane geometry is intentionally left unimplemented.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_single_side_layout) {
|
||||||
|
if (m_pass_road_left_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 ||
|
||||||
|
m_pass_road_left_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3) {
|
||||||
|
dc.SetPen(wxPen(_get_diff_clr(this, m_road_color[1]), 4, wxPENSTYLE_SOLID));
|
||||||
|
dc.DrawLine(size.x / 2, 0, size.x / 2, size.y);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE){
|
if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE){
|
||||||
if (m_nozzle_num == 2) {
|
if (m_nozzle_num == 1) {
|
||||||
|
if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE && m_left_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE) {
|
||||||
|
dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y / 2));
|
||||||
|
}
|
||||||
|
const auto &nozzle_pos = m_single_side_layout && m_single_side_pos == AMSPanelPos::RIGHT_PANEL
|
||||||
|
? right_nozzle_pos
|
||||||
|
: left_nozzle_pos;
|
||||||
|
dc.DrawLine(nozzle_pos.x, (size.y / 2), nozzle_pos.x, (size.y));
|
||||||
|
}
|
||||||
|
else if (m_nozzle_num == 2) {
|
||||||
/*dc.DrawLine(FromDIP(left_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(left_nozzle_pos.x), FromDIP(size.y));
|
/*dc.DrawLine(FromDIP(left_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(left_nozzle_pos.x), FromDIP(size.y));
|
||||||
dc.DrawLine(FromDIP(right_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(right_nozzle_pos.x), FromDIP(size.y));*/
|
dc.DrawLine(FromDIP(right_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(right_nozzle_pos.x), FromDIP(size.y));*/
|
||||||
dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y));
|
dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y));
|
||||||
dc.DrawLine((right_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y));
|
dc.DrawLine((right_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE && m_left_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE) {
|
// Generic N Nozzles
|
||||||
dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y / 2));
|
|
||||||
}
|
|
||||||
dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2533,10 +2683,16 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
dc.DrawLine(xpos, size.y / 2, xpos, size.y);*/
|
dc.DrawLine(xpos, size.y / 2, xpos, size.y);*/
|
||||||
int x = left_nozzle_pos.x;
|
int x = left_nozzle_pos.x;
|
||||||
int len = m_right_road_length;
|
int len = m_right_road_length;
|
||||||
if (m_nozzle_num == 2) {
|
if (m_nozzle_num == 1) {
|
||||||
|
// Keep the single-nozzle path anchored at the left nozzle.
|
||||||
|
}
|
||||||
|
else if (m_nozzle_num == 2) {
|
||||||
x = right_nozzle_pos.x;
|
x = right_nozzle_pos.x;
|
||||||
len = len - 14;
|
len = len - 14;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2));
|
dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2));
|
||||||
dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2));
|
dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2));
|
||||||
dc.DrawLine((x), (size.y / 2), (x), (size.y));
|
dc.DrawLine((x), (size.y / 2), (x), (size.y));
|
||||||
@@ -2544,10 +2700,16 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
else{
|
else{
|
||||||
int x = left_nozzle_pos.x;
|
int x = left_nozzle_pos.x;
|
||||||
int len = m_right_road_length;
|
int len = m_right_road_length;
|
||||||
if (m_nozzle_num == 2) {
|
if (m_nozzle_num == 1) {
|
||||||
|
// Keep the single-nozzle path anchored at the left nozzle.
|
||||||
|
}
|
||||||
|
else if (m_nozzle_num == 2) {
|
||||||
x = right_nozzle_pos.x;
|
x = right_nozzle_pos.x;
|
||||||
len = len - 14;
|
len = len - 14;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2));
|
dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2));
|
||||||
dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2));
|
dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2));
|
||||||
dc.DrawLine((x), (size.y / 2), (x), (size.y));
|
dc.DrawLine((x), (size.y / 2), (x), (size.y));
|
||||||
@@ -2571,7 +2733,7 @@ void AMSRoadDownPart::doRender(wxDC& dc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP step) {
|
void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP step) {
|
||||||
if (m_nozzle_num >= 2){
|
if (m_nozzle_num == 2){
|
||||||
if (pos == AMSPanelPos::LEFT_PANEL){
|
if (pos == AMSPanelPos::LEFT_PANEL){
|
||||||
if (m_left_road_length == len && m_pass_road_left_step == step){ return; }
|
if (m_left_road_length == len && m_pass_road_left_step == step){ return; }
|
||||||
m_left_road_length = len;;
|
m_left_road_length = len;;
|
||||||
@@ -2583,7 +2745,7 @@ void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP s
|
|||||||
m_pass_road_right_step = step;
|
m_pass_road_right_step = step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else if (m_nozzle_num == 1){
|
||||||
if (pos == AMSPanelPos::LEFT_PANEL) {
|
if (pos == AMSPanelPos::LEFT_PANEL) {
|
||||||
if (m_left_road_length == len && m_pass_road_left_step == step && m_right_road_length == -1) { return; }
|
if (m_left_road_length == len && m_pass_road_left_step == step && m_right_road_length == -1) { return; }
|
||||||
m_left_road_length = len;
|
m_left_road_length = len;
|
||||||
@@ -2597,6 +2759,9 @@ void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP s
|
|||||||
m_pass_road_right_step = step;
|
m_pass_road_right_step = step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Generic N Nozzles
|
||||||
|
}
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2615,7 +2780,10 @@ AMSPreview::AMSPreview(wxWindow* parent, wxWindowID id, AMSinfo amsinfo, AMSMode
|
|||||||
{
|
{
|
||||||
wxWindow::Create(parent, id, pos);
|
wxWindow::Create(parent, id, pos);
|
||||||
if (itemType == AMSModel::GENERIC_AMS || itemType == AMSModel::AMS_LITE || itemType == AMSModel::N3F_AMS) {
|
if (itemType == AMSModel::GENERIC_AMS || itemType == AMSModel::AMS_LITE || itemType == AMSModel::N3F_AMS) {
|
||||||
create(parent, id, pos, AMS_PREV_FOUR_SIZE);
|
if (itemType == AMSModel::GENERIC_AMS && use_generic_ams_layout(itemType))
|
||||||
|
create(parent, id, pos, wxSize(generic_ams_preview_width(this, static_cast<int>(amsinfo.cans.size())), AMS_PREV_FOUR_SIZE.y));
|
||||||
|
else
|
||||||
|
create(parent, id, pos, AMS_PREV_FOUR_SIZE);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
create(parent, id, pos, AMS_PREV_SINGLE_SIZE);
|
create(parent, id, pos, AMS_PREV_SINGLE_SIZE);
|
||||||
@@ -2650,7 +2818,13 @@ void AMSPreview::UpdateInfo(AMSinfo amsinfo)
|
|||||||
m_amsinfo = amsinfo;
|
m_amsinfo = amsinfo;
|
||||||
m_ams_item_type = amsinfo.ams_type;
|
m_ams_item_type = amsinfo.ams_type;
|
||||||
|
|
||||||
if (m_ams_item_type == AMSModel::GENERIC_AMS || m_ams_item_type == AMSModel::AMS_LITE || m_ams_item_type == AMSModel::N3F_AMS) {
|
if (m_ams_item_type == AMSModel::GENERIC_AMS && use_generic_ams_layout(m_ams_item_type)) {
|
||||||
|
const wxSize generic_size(generic_ams_preview_width(this, static_cast<int>(m_amsinfo.cans.size())), AMS_PREV_FOUR_SIZE.y);
|
||||||
|
SetSize(generic_size);
|
||||||
|
SetMinSize(generic_size);
|
||||||
|
SetMaxSize(generic_size);
|
||||||
|
}
|
||||||
|
else if (m_ams_item_type == AMSModel::GENERIC_AMS || m_ams_item_type == AMSModel::AMS_LITE || m_ams_item_type == AMSModel::N3F_AMS) {
|
||||||
SetMinSize(AMS_PREV_FOUR_SIZE);
|
SetMinSize(AMS_PREV_FOUR_SIZE);
|
||||||
SetMaxSize(AMS_PREV_FOUR_SIZE);
|
SetMaxSize(AMS_PREV_FOUR_SIZE);
|
||||||
} else {
|
} else {
|
||||||
@@ -2758,8 +2932,41 @@ void AMSPreview::doRender(wxDC &dc)
|
|||||||
dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(3));
|
dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(3));
|
||||||
|
|
||||||
auto left = 0;
|
auto left = 0;
|
||||||
//four slot
|
// Generic AMS previews use the same normalized lane centers as the card row and connector.
|
||||||
if (m_ams_item_type != AMSModel::EXT_AMS && m_ams_item_type != AMSModel::N3S_AMS){
|
if (m_ams_item_type == AMSModel::GENERIC_AMS && use_generic_ams_layout(m_ams_item_type)) {
|
||||||
|
const int lane_count = static_cast<int>(m_amsinfo.cans.size());
|
||||||
|
const auto lane_centers = generic_ams_lane_centers(lane_count, size.x);
|
||||||
|
const int cube_width = FromDIP(9);
|
||||||
|
const int cube_height = FromDIP(14);
|
||||||
|
|
||||||
|
for (int lane = 0; lane < lane_count; ++lane) {
|
||||||
|
const Caninfo &can = m_amsinfo.cans[lane];
|
||||||
|
const int cube_left = lane_centers[lane] - cube_width / 2;
|
||||||
|
const int cube_top = (size.y - cube_height) / 2;
|
||||||
|
wxRect rect(cube_left, cube_top, cube_width, cube_height);
|
||||||
|
|
||||||
|
if (can.material_colour.Alpha() == 0) {
|
||||||
|
if (wxGetApp().dark_mode())
|
||||||
|
dc.DrawBitmap(m_ts_bitmap_cube_dark.bmp(), cube_left - FromDIP(1), (size.y - m_ts_bitmap_cube_dark.GetBmpHeight()) / 2);
|
||||||
|
else
|
||||||
|
dc.DrawBitmap(m_ts_bitmap_cube.bmp(), cube_left - FromDIP(1), (size.y - m_ts_bitmap_cube.GetBmpHeight()) / 2);
|
||||||
|
}
|
||||||
|
else if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) {
|
||||||
|
dc.SetPen(wxPen(wxColor(0, 0, 0)));
|
||||||
|
dc.DrawLine(rect.GetRight() - FromDIP(1), rect.GetTop() + FromDIP(1),
|
||||||
|
rect.GetLeft() + FromDIP(1), rect.GetBottom() - FromDIP(1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wxColour color = can.material_colour;
|
||||||
|
change_the_opacity(color);
|
||||||
|
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||||
|
dc.SetBrush(wxBrush(color));
|
||||||
|
dc.DrawRoundedRectangle(rect, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// four slot
|
||||||
|
else if (m_ams_item_type != AMSModel::EXT_AMS && m_ams_item_type != AMSModel::N3S_AMS){
|
||||||
left = FromDIP(8);
|
left = FromDIP(8);
|
||||||
for (std::vector<Caninfo>::iterator iter = m_amsinfo.cans.begin(); iter != m_amsinfo.cans.end(); iter++) {
|
for (std::vector<Caninfo>::iterator iter = m_amsinfo.cans.begin(); iter != m_amsinfo.cans.end(); iter++) {
|
||||||
|
|
||||||
@@ -3158,10 +3365,19 @@ AmsItem::AmsItem(wxWindow *parent,AMSinfo info, AMSModel model, AMSPanelPos pos
|
|||||||
m_panel_pos = pos;
|
m_panel_pos = pos;
|
||||||
|
|
||||||
if (m_ams_model == AMSModel::GENERIC_AMS){
|
if (m_ams_model == AMSModel::GENERIC_AMS){
|
||||||
wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, AMS_CANS_WINDOW_SIZE);
|
const int lane_count = static_cast<int>(m_info.cans.size());
|
||||||
SetSize(AMS_CANS_WINDOW_SIZE);
|
const int width = use_generic_ams_layout(m_ams_model) && lane_count <= 8
|
||||||
SetMinSize(AMS_CANS_WINDOW_SIZE);
|
? generic_ams_layout_width(this, lane_count)
|
||||||
SetMaxSize(AMS_CANS_WINDOW_SIZE);
|
: FromDIP(264);
|
||||||
|
const wxSize generic_size(width, FromDIP(174));
|
||||||
|
wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, generic_size);
|
||||||
|
SetSize(generic_size);
|
||||||
|
SetMinSize(generic_size);
|
||||||
|
// For large lane counts the card is a viewport, not the lane strip. Let
|
||||||
|
// its parent allocate the available width; the scrolled child retains
|
||||||
|
// the wider virtual content size.
|
||||||
|
if (!(use_generic_ams_layout(m_ams_model) && lane_count > 8))
|
||||||
|
SetMaxSize(generic_size);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, AMS_SINGLE_CAN_SIZE);
|
wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, AMS_SINGLE_CAN_SIZE);
|
||||||
@@ -3187,12 +3403,43 @@ void AmsItem::create(wxWindow *parent)
|
|||||||
if (m_ams_model != AMSModel::AMS_LITE) {
|
if (m_ams_model != AMSModel::AMS_LITE) {
|
||||||
sizer_can = new wxBoxSizer(wxHORIZONTAL);
|
sizer_can = new wxBoxSizer(wxHORIZONTAL);
|
||||||
sizer_item = new wxBoxSizer(wxVERTICAL);
|
sizer_item = new wxBoxSizer(wxVERTICAL);
|
||||||
auto it = m_info.cans.begin();
|
const bool generic_layout = use_generic_ams_layout(m_ams_model);
|
||||||
for (; it != m_info.cans.end(); it++) {
|
const int lane_count = static_cast<int>(m_info.cans.size());
|
||||||
AddCan(*it, m_can_count, m_info.cans.size(), sizer_can);
|
const int content_width = generic_layout ? generic_ams_layout_width(this, lane_count) : 0;
|
||||||
|
const bool scroll_lanes = generic_layout && lane_count > 8;
|
||||||
|
const auto lane_centers = generic_layout
|
||||||
|
? generic_ams_lane_centers(lane_count, content_width)
|
||||||
|
: std::vector<int>();
|
||||||
|
|
||||||
|
m_can_parent = this;
|
||||||
|
if (scroll_lanes) {
|
||||||
|
m_can_scroll = new wxScrolledWindow(this, wxID_ANY);
|
||||||
|
m_can_scroll->SetScrollRate(FromDIP(10), 0);
|
||||||
|
m_can_scroll->SetMinSize(wxSize(FromDIP(264), FromDIP(143)));
|
||||||
|
m_can_scroll->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_LIB_BK_COLOUR));
|
||||||
|
m_can_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
m_can_scroll->SetSizer(m_can_scroll_sizer);
|
||||||
|
m_can_parent = m_can_scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
int previous_right = 0;
|
||||||
|
for (int can_index = 0; can_index < lane_count; ++can_index) {
|
||||||
|
if (generic_layout) {
|
||||||
|
const int left = lane_centers[can_index] - FromDIP(26);
|
||||||
|
sizer_can->AddSpacer(std::max(0, left - previous_right));
|
||||||
|
AddCan(m_info.cans[can_index], m_can_count, m_info.cans.size(), sizer_can);
|
||||||
|
previous_right = left + FromDIP(52);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AddCan(m_info.cans[can_index], m_can_count, m_info.cans.size(), sizer_can);
|
||||||
|
}
|
||||||
m_can_count++;
|
m_can_count++;
|
||||||
}
|
}
|
||||||
it = m_info.cans.begin();
|
if (generic_layout)
|
||||||
|
sizer_can->AddSpacer(std::max(0, content_width - previous_right));
|
||||||
|
if (generic_layout)
|
||||||
|
sizer_can->SetMinSize(wxSize(content_width, -1));
|
||||||
|
|
||||||
//auto road_panel = new wxWindow(this, wxID_ANY);
|
//auto road_panel = new wxWindow(this, wxID_ANY);
|
||||||
//auto road_panel = new wxPanel(this, wxID_ANY);
|
//auto road_panel = new wxPanel(this, wxID_ANY);
|
||||||
//road_panel->SetSize(AMS_CAN_ROAD_SIZE);
|
//road_panel->SetSize(AMS_CAN_ROAD_SIZE);
|
||||||
@@ -3207,11 +3454,18 @@ void AmsItem::create(wxWindow *parent)
|
|||||||
sizer_item->Add(m_ext_image, 0, wxALIGN_CENTER, 0);
|
sizer_item->Add(m_ext_image, 0, wxALIGN_CENTER, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_panel_road = new AMSRoadUpPart(this, wxID_ANY, m_info, m_ams_model);
|
m_panel_road = new AMSRoadUpPart(m_can_parent, wxID_ANY, m_info, m_ams_model);
|
||||||
|
|
||||||
sizer_item->Add(sizer_can, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
if (scroll_lanes) {
|
||||||
//sizer_item->Add(m_panel_road, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
m_can_scroll_sizer->Add(sizer_can, 0, wxALIGN_LEFT, 0);
|
||||||
sizer_item->Add(m_panel_road, 1, wxEXPAND);
|
m_can_scroll_sizer->Add(m_panel_road, 0, wxALIGN_LEFT, 0);
|
||||||
|
sizer_item->Add(m_can_scroll, 1, wxEXPAND);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sizer_item->Add(sizer_can, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||||
|
//sizer_item->Add(m_panel_road, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||||
|
sizer_item->Add(m_panel_road, 1, wxEXPAND);
|
||||||
|
}
|
||||||
|
|
||||||
SetSizer(sizer_item);
|
SetSizer(sizer_item);
|
||||||
}
|
}
|
||||||
@@ -3235,13 +3489,17 @@ void AmsItem::create(wxWindow *parent)
|
|||||||
|
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
|
if (m_can_scroll) {
|
||||||
|
m_can_scroll->Layout();
|
||||||
|
m_can_scroll->FitInside();
|
||||||
|
}
|
||||||
Thaw();
|
Thaw();
|
||||||
//Refresh();
|
//Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmsItem::AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* sizer)
|
void AmsItem::AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* sizer)
|
||||||
{
|
{
|
||||||
auto amscan = new wxWindow(this, wxID_ANY);
|
auto amscan = new wxWindow(m_can_parent ? m_can_parent : this, wxID_ANY);
|
||||||
|
|
||||||
amscan->SetSize(wxSize(FromDIP(52), FromDIP(109)));
|
amscan->SetSize(wxSize(FromDIP(52), FromDIP(109)));
|
||||||
amscan->SetMinSize(wxSize(FromDIP(52), FromDIP(109)));
|
amscan->SetMinSize(wxSize(FromDIP(52), FromDIP(109)));
|
||||||
@@ -3322,7 +3580,8 @@ void AmsItem::AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* size
|
|||||||
amscan->Layout();
|
amscan->Layout();
|
||||||
amscan->Fit();
|
amscan->Fit();
|
||||||
|
|
||||||
sizer->Add(amscan, 0, wxUP | wxLEFT | wxRIGHT, FromDIP(5));
|
const int margin = use_generic_ams_layout(m_ams_model) ? 0 : FromDIP(5);
|
||||||
|
sizer->Add(amscan, 0, wxUP | wxLEFT | wxRIGHT, margin);
|
||||||
|
|
||||||
m_can_lib_list[caninfo.can_id] = m_panel_lib;
|
m_can_lib_list[caninfo.can_id] = m_panel_lib;
|
||||||
//m_can_road_list[caninfo.can_id] = m_panel_road;
|
//m_can_road_list[caninfo.can_id] = m_panel_road;
|
||||||
@@ -3447,6 +3706,10 @@ void AmsItem::UpdateInfo(AMSinfo info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Layout();
|
Layout();
|
||||||
|
if (m_can_scroll) {
|
||||||
|
m_can_scroll->Layout();
|
||||||
|
m_can_scroll->FitInside();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmsItem::SetDefSelectCan()
|
void AmsItem::SetDefSelectCan()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/scrolwin.h>
|
||||||
|
|
||||||
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h" // Orca: DevFilaSwitch::SwitchPos for inlet-aware AMS placement
|
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h" // Orca: DevFilaSwitch::SwitchPos for inlet-aware AMS placement
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ enum class AMSRoadShowMode : int {
|
|||||||
AMS_ROAD_MODE_SINGLE,
|
AMS_ROAD_MODE_SINGLE,
|
||||||
AMS_ROAD_MODE_SINGLE_N3S,
|
AMS_ROAD_MODE_SINGLE_N3S,
|
||||||
AMS_ROAD_MODE_AMS_LITE,
|
AMS_ROAD_MODE_AMS_LITE,
|
||||||
|
AMS_ROAD_MODE_GENERIC,
|
||||||
AMS_ROAD_MODE_NONE
|
AMS_ROAD_MODE_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -480,6 +482,7 @@ public:
|
|||||||
//AMSextruderImage *m_amsSextruder{nullptr};
|
//AMSextruderImage *m_amsSextruder{nullptr};
|
||||||
AMSextruderImage* m_left_extruder = nullptr;
|
AMSextruderImage* m_left_extruder = nullptr;
|
||||||
AMSextruderImage* m_right_extruder = nullptr;
|
AMSextruderImage* m_right_extruder = nullptr;
|
||||||
|
std::vector<AMSextruderImage*> m_nozzle_extruders;
|
||||||
AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
|
AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
|
||||||
~AMSextruder();
|
~AMSextruder();
|
||||||
|
|
||||||
@@ -681,6 +684,8 @@ public:
|
|||||||
// void Update(AMSRoadDownPartMode nozzle, AMSRoadShowMode left_mode, AMSRoadShowMode right_mode, int left_len, int right_len);
|
// void Update(AMSRoadDownPartMode nozzle, AMSRoadShowMode left_mode, AMSRoadShowMode right_mode, int left_len, int right_len);
|
||||||
void UpdateLeft(int nozzle_num, AMSRoadShowMode mode);
|
void UpdateLeft(int nozzle_num, AMSRoadShowMode mode);
|
||||||
void UpdateRight(int nozzle_num, AMSRoadShowMode mode);
|
void UpdateRight(int nozzle_num, AMSRoadShowMode mode);
|
||||||
|
void SetNozzleCount(int nozzle_num);
|
||||||
|
void SetSingleSideLayout(bool enabled, AMSPanelPos pos = AMSPanelPos::LEFT_PANEL);
|
||||||
|
|
||||||
void OnVamsLoading(bool load, wxColour col = AMS_CONTROL_GRAY500);
|
void OnVamsLoading(bool load, wxColour col = AMS_CONTROL_GRAY500);
|
||||||
void SetPassRoadColour(bool left, wxColour col);
|
void SetPassRoadColour(bool left, wxColour col);
|
||||||
@@ -698,6 +703,8 @@ private:
|
|||||||
AMSRoadShowMode m_left_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
AMSRoadShowMode m_left_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
||||||
AMSRoadShowMode m_right_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
AMSRoadShowMode m_right_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
||||||
bool m_selected = {false};
|
bool m_selected = {false};
|
||||||
|
bool m_single_side_layout = {false};
|
||||||
|
AMSPanelPos m_single_side_pos = {AMSPanelPos::LEFT_PANEL};
|
||||||
|
|
||||||
int m_left_road_length = {-1};
|
int m_left_road_length = {-1};
|
||||||
int m_right_road_length = {-1};
|
int m_right_road_length = {-1};
|
||||||
@@ -864,6 +871,9 @@ private:
|
|||||||
|
|
||||||
std::map<std::string, AMSLib*> m_can_lib_list;
|
std::map<std::string, AMSLib*> m_can_lib_list;
|
||||||
//std::map<std::string, AMSRoad*> m_can_road_list;
|
//std::map<std::string, AMSRoad*> m_can_road_list;
|
||||||
|
wxScrolledWindow* m_can_scroll = { nullptr };
|
||||||
|
wxBoxSizer* m_can_scroll_sizer = { nullptr };
|
||||||
|
wxWindow* m_can_parent = { nullptr };
|
||||||
AMSRoadUpPart* m_panel_road = { nullptr };
|
AMSRoadUpPart* m_panel_road = { nullptr };
|
||||||
std::map<std::string, AMSrefresh*> m_can_refresh_list;
|
std::map<std::string, AMSrefresh*> m_can_refresh_list;
|
||||||
AMSHumidity* m_humidity = { nullptr };
|
AMSHumidity* m_humidity = { nullptr };
|
||||||
|
|||||||
@@ -420,6 +420,14 @@ ExtruderBadge::ExtruderBadge(wxWindow* parent) : wxPanel(parent)
|
|||||||
|
|
||||||
void ExtruderBadge::SetExtruderInfo(int extruder_id, const std::string& diameter, const NozzleVolumeType& volume_type)
|
void ExtruderBadge::SetExtruderInfo(int extruder_id, const std::string& diameter, const NozzleVolumeType& volume_type)
|
||||||
{
|
{
|
||||||
|
// The badge renders exactly two extruders (left/right). A generic non-BBL printer with
|
||||||
|
// N > 2 can report an out-of-range id here (see MultiNozzleStatusTable::UpdateRackInfo);
|
||||||
|
// reject it rather than writing past the two-element lists.
|
||||||
|
if (extruder_id < 0 || extruder_id >= static_cast<int>(m_diameter_list.size())) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": out-of-range extruder_id " << extruder_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_diameter_list[extruder_id] = diameter;
|
m_diameter_list[extruder_id] = diameter;
|
||||||
m_volume_type_list[extruder_id] = volume_type;
|
m_volume_type_list[extruder_id] = volume_type;
|
||||||
|
|
||||||
@@ -867,14 +875,21 @@ void MultiNozzleStatusTable::UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack)
|
|||||||
bool has_right = false;
|
bool has_right = false;
|
||||||
for (auto& elem : nozzles_in_extruder) {
|
for (auto& elem : nozzles_in_extruder) {
|
||||||
auto& nozzle = elem.second;
|
auto& nozzle = elem.second;
|
||||||
int extruder_id = nozzle.AtLeftExtruder() ? 0 : 1;
|
|
||||||
if (nozzle.AtRightExtruder())
|
int extruder_id{};
|
||||||
has_right = true;
|
if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||||
|
extruder_id = nozzle.AtLeftExtruder() ? 0 : 1;
|
||||||
|
if (nozzle.AtRightExtruder())
|
||||||
|
has_right = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
extruder_id = nozzle.GetExtruderId();
|
||||||
|
|
||||||
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
||||||
|
|
||||||
m_badge->SetExtruderInfo(extruder_id, format_diameter_to_str(nozzle.GetNozzleDiameter()), volume_type);
|
m_badge->SetExtruderInfo(extruder_id, format_diameter_to_str(nozzle.GetNozzleDiameter()), volume_type);
|
||||||
}
|
}
|
||||||
|
// TODO: Update for N extruders
|
||||||
m_badge->SetExtruderValid(has_right);
|
m_badge->SetExtruderValid(has_right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ void TempInput::SetFinish()
|
|||||||
wxCommandEvent event(wxCUSTOMEVT_SET_TEMP_FINISH);
|
wxCommandEvent event(wxCUSTOMEVT_SET_TEMP_FINISH);
|
||||||
event.SetInt(temp_type);
|
event.SetInt(temp_type);
|
||||||
event.SetString(wxString::Format("%d", m_input_type));
|
event.SetString(wxString::Format("%d", m_input_type));
|
||||||
|
// N-extruder temp controls all share TEMP_OF_NORMAL_TYPE, so the string payload above can't
|
||||||
|
// tell them apart; carry widget identity so the listener can find which one fired.
|
||||||
|
event.SetEventObject(this);
|
||||||
wxPostEvent(this->GetParent(), event);
|
wxPostEvent(this->GetParent(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1057,6 +1057,25 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tool selection is printer-specific G-code. Moonraker forwards the
|
||||||
|
// conventional T<index> command to Klipper, where the printer can
|
||||||
|
// implement logical selection or a physical toolhead attach macro.
|
||||||
|
if (cmd == "select_extruder") {
|
||||||
|
if (!json["print"].contains("extruder_index") || !json["print"]["extruder_index"].is_number_integer()) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: select_extruder missing integer extruder_index, full json: " << json_str;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int extruder_idx = json["print"]["extruder_index"].get<int>();
|
||||||
|
if (extruder_idx < 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: select_extruder received negative extruder_index: " << extruder_idx;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_gcode_async(dev_id, "T" + std::to_string(extruder_idx));
|
||||||
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
if (cmd == "home") {
|
if (cmd == "home") {
|
||||||
send_gcode_async(dev_id, "G28");
|
send_gcode_async(dev_id, "G28");
|
||||||
return BAMBU_NETWORK_SUCCESS;
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
@@ -1153,7 +1172,7 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
|
|||||||
nlohmann::json& status,
|
nlohmann::json& status,
|
||||||
std::string& error) const
|
std::string& error) const
|
||||||
{
|
{
|
||||||
std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&extruder&heater_bed&fan");
|
std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&toolhead&extruder&heater_bed&fan");
|
||||||
|
|
||||||
std::string response_body;
|
std::string response_body;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
@@ -1566,11 +1585,12 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& name : this->available_objects) {
|
for (const auto& name : this->available_objects) {
|
||||||
if (name == "extruder" || name.rfind("extruder", 0) == 0) {
|
const bool is_main_extruder = name == "extruder";
|
||||||
|
const bool is_additional_extruder =
|
||||||
|
name.rfind("extruder", 0) == 0 && name.size() > 8 &&
|
||||||
|
std::all_of(name.begin() + 8, name.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
|
||||||
|
if (is_main_extruder || is_additional_extruder) {
|
||||||
subscribe_objects.insert(name);
|
subscribe_objects.insert(name);
|
||||||
if (name == "extruder") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1866,6 +1886,81 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MachineObject::parse_new_info() only parses device.extruder when the four
|
||||||
|
// legacy new-protocol fields are present. Moonraker has no equivalents, so
|
||||||
|
// empty values act as compatibility markers without enabling any features.
|
||||||
|
std::map<int, const nlohmann::json*> extruder_status;
|
||||||
|
for (const auto& item : status_cache.items()) {
|
||||||
|
int extruder_id = -1;
|
||||||
|
if (item.key() == "extruder") {
|
||||||
|
extruder_id = 0;
|
||||||
|
} else if (item.key().rfind("extruder", 0) == 0 && item.key().size() > 8 &&
|
||||||
|
std::all_of(item.key().begin() + 8, item.key().end(), [](unsigned char c) { return std::isdigit(c) != 0; })) {
|
||||||
|
try {
|
||||||
|
extruder_id = std::stoi(item.key().substr(8));
|
||||||
|
} catch (...) {
|
||||||
|
extruder_id = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extruder_id >= 0 && item.value().is_object()) {
|
||||||
|
extruder_status.emplace(extruder_id, &item.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extruder_status.empty()) {
|
||||||
|
auto& device_extruder = payload["print"]["device"]["extruder"];
|
||||||
|
|
||||||
|
int current_extruder_id = 0;
|
||||||
|
if (status_cache.contains("toolhead") && status_cache["toolhead"].is_object() &&
|
||||||
|
status_cache["toolhead"].contains("extruder") && status_cache["toolhead"]["extruder"].is_string()) {
|
||||||
|
const std::string active_extruder = status_cache["toolhead"]["extruder"].get<std::string>();
|
||||||
|
if (active_extruder != "extruder" && active_extruder.rfind("extruder", 0) == 0 && active_extruder.size() > 8 &&
|
||||||
|
std::all_of(active_extruder.begin() + 8, active_extruder.end(), [](unsigned char c) { return std::isdigit(c) != 0; })) {
|
||||||
|
try {
|
||||||
|
const int parsed_id = std::stoi(active_extruder.substr(8));
|
||||||
|
if (parsed_id >= 0 && parsed_id < 16 && extruder_status.count(parsed_id) != 0) {
|
||||||
|
current_extruder_id = parsed_id;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Keep the main extruder as the fallback for malformed status data.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int extruder_count = std::min<int>(static_cast<int>(extruder_status.size()), 0xF);
|
||||||
|
device_extruder["state"] = extruder_count | (current_extruder_id << 4);
|
||||||
|
device_extruder["info"] = nlohmann::json::array();
|
||||||
|
|
||||||
|
auto encoded_temperature = [](const nlohmann::json& status, const char* key) {
|
||||||
|
if (!status.contains(key) || !status[key].is_number()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const double temperature = status[key].get<double>();
|
||||||
|
return std::clamp(static_cast<int>(temperature + 0.5), 0, 65535);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [extruder_id, status] : extruder_status) {
|
||||||
|
const int current_temperature = encoded_temperature(*status, "temperature");
|
||||||
|
const int target_temperature = encoded_temperature(*status, "target");
|
||||||
|
device_extruder["info"].push_back({
|
||||||
|
{"id", extruder_id},
|
||||||
|
{"filam_bak", nlohmann::json::array()},
|
||||||
|
{"info", 1 << 3}, // The configured Moonraker extruder has a toolhead.
|
||||||
|
{"temp", current_temperature | (target_temperature << 16)},
|
||||||
|
{"spre", 0},
|
||||||
|
{"snow", 0},
|
||||||
|
{"star", 0},
|
||||||
|
{"stat", 0},
|
||||||
|
{"hnow", 0},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
payload["print"]["cfg"] = "";
|
||||||
|
payload["print"]["fun"] = "";
|
||||||
|
payload["print"]["aux"] = "";
|
||||||
|
payload["print"]["stat"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (status_cache.contains("heater_bed") && status_cache["heater_bed"].is_object()) {
|
if (status_cache.contains("heater_bed") && status_cache["heater_bed"].is_object()) {
|
||||||
const auto& bed = status_cache["heater_bed"];
|
const auto& bed = status_cache["heater_bed"];
|
||||||
if (bed.contains("temperature") && bed["temperature"].is_number()) {
|
if (bed.contains("temperature") && bed["temperature"].is_number()) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_config_value_formatter.cpp
|
test_config_value_formatter.cpp
|
||||||
test_creality_cfs_match.cpp
|
test_creality_cfs_match.cpp
|
||||||
test_dev_mapping.cpp
|
test_dev_mapping.cpp
|
||||||
|
test_dev_extder_system.cpp
|
||||||
test_filament_bitmap_utils.cpp
|
test_filament_bitmap_utils.cpp
|
||||||
test_device_progress.cpp
|
test_device_progress.cpp
|
||||||
test_device_manager_integration.cpp
|
test_device_manager_integration.cpp
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Match the include environment that libslic3r_gui TUs get from pchheader.hpp: Windows.h with
|
||||||
|
// WIN32_LEAN_AND_MEAN/NOMINMAX must come first so rpcndr.h's `byte` is processed before <cstddef>
|
||||||
|
// makes std::byte a competing candidate (otherwise the Windows COM headers pulled in via
|
||||||
|
// DeviceManager.hpp error with an ambiguous `byte`). wx/timer.h must precede DeviceManager.hpp,
|
||||||
|
// which includes DeviceErrorDialog.hpp (uses wxTimerEvent) before its own wx/timer.h include.
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#ifdef WIN32
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <wx/timer.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/DeviceManager.hpp"
|
||||||
|
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("Extruder state decodes a generic toolhead set", "[DevExtderSystem]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
// device.extruder.state packing: count 0..3, current 4..7, target 8..11,
|
||||||
|
// switch 12..14, loading 15..18, busy 19.
|
||||||
|
const int extruder_count = 4;
|
||||||
|
const int current_extruder = 2;
|
||||||
|
const int switch_state = ES_BUSY;
|
||||||
|
const int loading_extruder = 1;
|
||||||
|
const int state = extruder_count | (current_extruder << 4) | (3 << 8) |
|
||||||
|
(switch_state << 12) | (loading_extruder << 15) | (1 << 19);
|
||||||
|
|
||||||
|
json extruder = { {"state", state}, {"info", json::array()} };
|
||||||
|
for (int id = 0; id < extruder_count; ++id) {
|
||||||
|
// info: bit 1 has filament, bit 3 nozzle present.
|
||||||
|
// temp: current 0..15, target 16..31. spre/snow/star: slot 0..7, ams 8..15.
|
||||||
|
extruder["info"].push_back({
|
||||||
|
{"id", id},
|
||||||
|
{"filam_bak", json::array({id})},
|
||||||
|
{"info", (id % 2 == 0 ? 2 : 0) | 8},
|
||||||
|
{"temp", (200 + id) | ((250 + id) << 16)},
|
||||||
|
{"spre", 10 << 8},
|
||||||
|
{"snow", id | ((20 + id) << 8)},
|
||||||
|
{"star", 30 << 8},
|
||||||
|
{"stat", 0},
|
||||||
|
{"hnow", id},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtderSystemParser::ParseV2_0(extruder, obj.GetExtderSystem());
|
||||||
|
|
||||||
|
REQUIRE(obj.GetExtderSystem()->GetTotalExtderCount() == extruder_count);
|
||||||
|
CHECK(obj.GetExtderSystem()->GetCurrentExtderId() == current_extruder);
|
||||||
|
CHECK(static_cast<int>(obj.GetExtderSystem()->GetSwitchState()) == switch_state);
|
||||||
|
CHECK(obj.GetExtderSystem()->GetLoadingExtderId() == loading_extruder);
|
||||||
|
CHECK(obj.GetExtderSystem()->IsBusyLoading());
|
||||||
|
|
||||||
|
for (int id = 0; id < extruder_count; ++id) {
|
||||||
|
const auto ext = obj.GetExtderSystem()->GetExtderById(id);
|
||||||
|
REQUIRE(ext.has_value());
|
||||||
|
INFO("extruder " << id);
|
||||||
|
CHECK(ext->GetExtId() == id);
|
||||||
|
CHECK(ext->GetCurrentTemp() == 200 + id);
|
||||||
|
CHECK(ext->GetTargetTemp() == 250 + id);
|
||||||
|
CHECK(ext->HasFilamentInExt() == (id % 2 == 0));
|
||||||
|
CHECK(ext->GetFilamBackup() == std::vector<int>{id});
|
||||||
|
CHECK(ext->GetSlotPre().ams_id == "10");
|
||||||
|
CHECK(ext->GetSlotNow().ams_id == std::to_string(20 + id));
|
||||||
|
CHECK(ext->GetSlotNow().slot_id == std::to_string(id));
|
||||||
|
CHECK(ext->GetSlotTarget().ams_id == "30");
|
||||||
|
CHECK(ext->GetNozzleId() == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Legacy single-toolhead parser fills only the main extruder", "[DevExtderSystem]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
REQUIRE(obj.GetExtderSystem()->GetTotalExtderCount() == 1);
|
||||||
|
|
||||||
|
// ParseV1_0 is the legacy single/dual fallback. It returns unless the system reports exactly
|
||||||
|
// one toolhead, so it only ever writes MAIN_EXTRUDER_ID's fields.
|
||||||
|
const json legacy = {
|
||||||
|
{"nozzle_temper", 210},
|
||||||
|
{"nozzle_target_temper", 245},
|
||||||
|
{"ams", {{"tray_now", "2"}, {"tray_tar", "3"}}}
|
||||||
|
};
|
||||||
|
ExtderSystemParser::ParseV1_0(legacy, obj.GetExtderSystem());
|
||||||
|
|
||||||
|
const auto ext = obj.GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID);
|
||||||
|
REQUIRE(ext.has_value());
|
||||||
|
CHECK(ext->GetCurrentTemp() == 210);
|
||||||
|
CHECK(ext->GetTargetTemp() == 245);
|
||||||
|
|
||||||
|
// Below 0x80 the tray pointer encodes ams_id = value / 4, slot_id = value % 4.
|
||||||
|
CHECK(ext->GetSlotNow().ams_id == "0");
|
||||||
|
CHECK(ext->GetSlotNow().slot_id == "2");
|
||||||
|
CHECK(ext->GetSlotTarget().ams_id == "0");
|
||||||
|
CHECK(ext->GetSlotTarget().slot_id == "3");
|
||||||
|
CHECK(obj.GetExtderSystem()->IsBusyLoading());
|
||||||
|
CHECK(obj.GetExtderSystem()->GetLoadingExtderId() == MAIN_EXTRUDER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseV2_0 does not enforce the documented extruder dialect invariants: the count in `state` and
|
||||||
|
// the id ordering of `info[]` are trusted, not checked. This test pins the resulting behavior.
|
||||||
|
TEST_CASE("Malformed extruder payload is stored by info array position", "[DevExtderSystem]")
|
||||||
|
{
|
||||||
|
const auto extder_info = [](int id) {
|
||||||
|
// Every key ParseV2_0 reads must be present.
|
||||||
|
return json{ {"id", id}, {"filam_bak", json::array()}, {"info", 0}, {"temp", 0},
|
||||||
|
{"spre", 0}, {"snow", 0}, {"star", 0}, {"stat", 0}, {"hnow", 0} };
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("state count disagrees with info length") {
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
// `state` reports 2 toolheads but info[] carries 3.
|
||||||
|
json info = json::array();
|
||||||
|
for (int id = 0; id < 3; ++id)
|
||||||
|
info.push_back(extder_info(id));
|
||||||
|
ExtderSystemParser::ParseV2_0(json{ {"state", 2}, {"info", info} }, obj.GetExtderSystem());
|
||||||
|
|
||||||
|
// The info array wins: three toolheads are stored. m_total_extder_count still holds 2, so
|
||||||
|
// GetTotalExtderCount() would trip its debug assert; only the stored vector is inspected.
|
||||||
|
CHECK(obj.GetExtderSystem()->GetTotalExtderSize() == 3);
|
||||||
|
for (int id = 0; id < 3; ++id) {
|
||||||
|
const auto ext = obj.GetExtderSystem()->GetExtderById(id);
|
||||||
|
REQUIRE(ext.has_value());
|
||||||
|
CHECK(ext->GetExtId() == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("info id differs from the array index") {
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
// ids 1 and 2 sit at array positions 0 and 1.
|
||||||
|
json info = json::array({extder_info(1), extder_info(2)});
|
||||||
|
ExtderSystemParser::ParseV2_0(json{ {"state", 2}, {"info", info} }, obj.GetExtderSystem());
|
||||||
|
|
||||||
|
// Toolheads are stored by array position; the payload id is kept but is not the lookup key.
|
||||||
|
const auto first = obj.GetExtderSystem()->GetExtderById(0);
|
||||||
|
REQUIRE(first.has_value());
|
||||||
|
CHECK(first->GetExtId() == 1);
|
||||||
|
const auto second = obj.GetExtderSystem()->GetExtderById(1);
|
||||||
|
REQUIRE(second.has_value());
|
||||||
|
CHECK(second->GetExtId() == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,10 +13,14 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include "slic3r/GUI/DeviceCore/DevDefs.h"
|
#include "slic3r/GUI/DeviceCore/DevDefs.h"
|
||||||
#include "libslic3r/ProjectTask.hpp"
|
#include "libslic3r/ProjectTask.hpp"
|
||||||
#include <vector>
|
|
||||||
#include <catch2/catch_message.hpp>
|
#include <catch2/catch_message.hpp>
|
||||||
|
|
||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
@@ -65,6 +69,162 @@ TEST_CASE("AMS tray placeholder state follows the latest status", "[DevFilaSyste
|
|||||||
CHECK_FALSE(tray->is_slot_placeholder);
|
CHECK_FALSE(tray->is_slot_placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Agent filament slots preserve every extruder and AMS binding", "[DevFilaSystem]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
const json filament = json::parse(R"({
|
||||||
|
"units": [
|
||||||
|
{ "id": "0", "extruder": 0, "slots": [
|
||||||
|
{ "index": 0, "loaded": true, "material": "PLA", "color": "FF5733FF" },
|
||||||
|
{ "index": 1, "loaded": true, "material": "PLA", "color": "33C1FFFF" },
|
||||||
|
{ "index": 2, "loaded": true, "material": "PLA", "color": "8D33FFFF" },
|
||||||
|
{ "index": 3, "loaded": false, "material": "", "color": "00000000" }
|
||||||
|
] },
|
||||||
|
{ "id": "1", "extruder": 1, "slots": [
|
||||||
|
{ "index": 0, "loaded": true, "material": "ASA", "color": "33C1FFFF" },
|
||||||
|
{ "index": 1, "loaded": true, "material": "ASA", "color": "8D33FFFF" },
|
||||||
|
{ "index": 2, "loaded": true, "material": "ASA", "color": "FF5733FF" },
|
||||||
|
{ "index": 3, "loaded": false, "material": "", "color": "00000000" }
|
||||||
|
] },
|
||||||
|
{ "id": "2", "extruder": 2, "slots": [
|
||||||
|
{ "index": 0, "loaded": true, "material": "ABS", "color": "8D33FFFF" },
|
||||||
|
{ "index": 1, "loaded": true, "material": "ABS", "color": "FF5733FF" },
|
||||||
|
{ "index": 2, "loaded": true, "material": "ABS", "color": "33C1FFFF" },
|
||||||
|
{ "index": 3, "loaded": false, "material": "", "color": "00000000" }
|
||||||
|
] },
|
||||||
|
{ "id": "3", "extruder": 3, "slots": [
|
||||||
|
{ "index": 0, "loaded": true, "material": "PETG", "color": "FF5733FF" },
|
||||||
|
{ "index": 1, "loaded": true, "material": "PETG", "color": "33C1FFFF" },
|
||||||
|
{ "index": 2, "loaded": true, "material": "PETG", "color": "8D33FFFF" },
|
||||||
|
{ "index": 3, "loaded": false, "material": "", "color": "00000000" }
|
||||||
|
] }
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
{ "extruder": 3, "loaded": true, "material": "PETG", "preset_id": "P3" },
|
||||||
|
{ "extruder": 0, "loaded": true, "material": "PLA", "preset_id": "P0" },
|
||||||
|
{ "extruder": 2, "loaded": false, "material": "ABS", "preset_id": "P2" },
|
||||||
|
{ "extruder": 1, "loaded": true, "material": "ASA", "preset_id": "P1" }
|
||||||
|
]
|
||||||
|
})");
|
||||||
|
|
||||||
|
DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get());
|
||||||
|
|
||||||
|
REQUIRE(obj.vt_slot.size() == 4);
|
||||||
|
CHECK(obj.vt_slot[0].id == "255");
|
||||||
|
CHECK(obj.vt_slot[1].id == "254");
|
||||||
|
CHECK(obj.vt_slot[2].id == "253");
|
||||||
|
CHECK(obj.vt_slot[3].id == "252");
|
||||||
|
CHECK(obj.vt_slot[0].setting_id == "P0");
|
||||||
|
CHECK(obj.vt_slot[1].setting_id == "P1");
|
||||||
|
CHECK(obj.vt_slot[2].setting_id == "P2");
|
||||||
|
CHECK(obj.vt_slot[3].setting_id == "P3");
|
||||||
|
|
||||||
|
REQUIRE(obj.GetFilaSystem()->GetAmsCount() == 4);
|
||||||
|
for (int extruder = 0; extruder < 4; ++extruder) {
|
||||||
|
const auto& ams = obj.GetFilaSystem()->GetAmsList().at(std::to_string(extruder));
|
||||||
|
CHECK(ams->GetExtruderId() == extruder);
|
||||||
|
CHECK(ams->GetBindedExtruderSet() == std::set<int>{extruder});
|
||||||
|
REQUIRE(ams->GetTrays().size() == 4);
|
||||||
|
CHECK(ams->GetTrays().at("0")->color != ams->GetTrays().at("1")->color);
|
||||||
|
CHECK(ams->GetTrays().at("1")->color != ams->GetTrays().at("2")->color);
|
||||||
|
CHECK_FALSE(ams->GetTrays().at("3")->is_exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(obj.contains_tray("253", "0"));
|
||||||
|
CHECK(obj.get_tray("253", "0").setting_id == "P2");
|
||||||
|
CHECK(obj.GetFilaSystem()->GetExtruderIdByAmsId("253") == 2);
|
||||||
|
|
||||||
|
const auto tray_index_map = obj.GetFilaSystem()->GetTrayIndexMap();
|
||||||
|
REQUIRE(tray_index_map.count(252) == 1);
|
||||||
|
CHECK(tray_index_map.at(252).first == 252);
|
||||||
|
CHECK(tray_index_map.at(252).second == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Generic AMS tray index map uses cumulative lane counts", "[DevMapping]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
// A well-formed generic payload pairs "units" with "external". An empty external array resets
|
||||||
|
// vt_slot, clearing the default virtual tray (255) MachineObject's constructor seeds; otherwise
|
||||||
|
// that phantom external tray lands in GetTrayIndexMap and inflates the map size.
|
||||||
|
json filament = { {"units", json::array()}, {"external", json::array()} };
|
||||||
|
for (int ams_id = 0; ams_id < 2; ++ams_id) {
|
||||||
|
json unit = {
|
||||||
|
{"id", std::to_string(ams_id)},
|
||||||
|
{"extruder", ams_id},
|
||||||
|
{"slots", json::array()}
|
||||||
|
};
|
||||||
|
for (int slot_id = 0; slot_id < 5; ++slot_id)
|
||||||
|
unit["slots"].push_back({{"index", slot_id}, {"loaded", false}});
|
||||||
|
filament["units"].push_back(std::move(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get());
|
||||||
|
|
||||||
|
const auto tray_index_map = obj.GetFilaSystem()->GetTrayIndexMap();
|
||||||
|
REQUIRE(tray_index_map.size() == 10);
|
||||||
|
CHECK(tray_index_map.at(0).first == 0);
|
||||||
|
CHECK(tray_index_map.at(0).second == 0);
|
||||||
|
CHECK(tray_index_map.at(4).first == 0);
|
||||||
|
CHECK(tray_index_map.at(4).second == 4);
|
||||||
|
CHECK(tray_index_map.at(5).first == 1);
|
||||||
|
CHECK(tray_index_map.at(5).second == 0);
|
||||||
|
CHECK(tray_index_map.at(9).first == 1);
|
||||||
|
CHECK(tray_index_map.at(9).second == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Generic tray index map handles unequal per-unit slot counts", "[DevMapping]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
// An "external" array must accompany "units" so vt_slot is reset (see the cumulative test above).
|
||||||
|
json filament = { {"units", json::array()}, {"external", json::array()} };
|
||||||
|
const std::vector<int> slot_counts = {4, 12};
|
||||||
|
for (int ams_id = 0; ams_id < static_cast<int>(slot_counts.size()); ++ams_id) {
|
||||||
|
json unit = { {"id", std::to_string(ams_id)}, {"extruder", ams_id}, {"slots", json::array()} };
|
||||||
|
for (int slot_id = 0; slot_id < slot_counts[ams_id]; ++slot_id)
|
||||||
|
unit["slots"].push_back({{"index", slot_id}, {"loaded", false}});
|
||||||
|
filament["units"].push_back(std::move(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get());
|
||||||
|
|
||||||
|
const auto tray_index_map = obj.GetFilaSystem()->GetTrayIndexMap();
|
||||||
|
REQUIRE(tray_index_map.size() == 16);
|
||||||
|
// Unit 0 occupies global lanes 0..3; unit 1 continues at 4..15 because the counter advances
|
||||||
|
// by the reported tray count, not by a fixed four-slot stride.
|
||||||
|
CHECK(tray_index_map.at(0).first == 0);
|
||||||
|
CHECK(tray_index_map.at(0).second == 0);
|
||||||
|
CHECK(tray_index_map.at(3).first == 0);
|
||||||
|
CHECK(tray_index_map.at(3).second == 3);
|
||||||
|
CHECK(tray_index_map.at(4).first == 1);
|
||||||
|
CHECK(tray_index_map.at(4).second == 0);
|
||||||
|
CHECK(tray_index_map.at(15).first == 1);
|
||||||
|
CHECK(tray_index_map.at(15).second == 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A generic AMS unit reports its reported tray count", "[DevFilaSystem]")
|
||||||
|
{
|
||||||
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|
||||||
|
json filament = { {"units", json::array()}, {"external", json::array()} };
|
||||||
|
const std::vector<int> slot_counts = {8, 1};
|
||||||
|
for (int ams_id = 0; ams_id < static_cast<int>(slot_counts.size()); ++ams_id) {
|
||||||
|
json unit = { {"id", std::to_string(ams_id)}, {"extruder", ams_id}, {"slots", json::array()} };
|
||||||
|
for (int slot_id = 0; slot_id < slot_counts[ams_id]; ++slot_id)
|
||||||
|
unit["slots"].push_back({{"index", slot_id}, {"loaded", false}});
|
||||||
|
filament["units"].push_back(std::move(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get());
|
||||||
|
|
||||||
|
// Non-Bambu units are not capped at the four-slot Bambu contract; the reported tray count wins.
|
||||||
|
const auto& ams_list = obj.GetFilaSystem()->GetAmsList();
|
||||||
|
REQUIRE(ams_list.at("0")->GetSlotCount() == 8);
|
||||||
|
REQUIRE(ams_list.at("1")->GetSlotCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]")
|
TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]")
|
||||||
{
|
{
|
||||||
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||||
|
|||||||
Reference in New Issue
Block a user