mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 11:21:18 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d7f0c0d2c |
@@ -570,11 +570,6 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
- On GTK the sub binds an idle handler. While both are shown and the cursor is over main but not over the
|
||||
sub, it builds a `wxMouseEvent(wxEVT_MOTION)` from `wxGetMousePosition()`, calls `mainDropDown->mouseMove(ev)`
|
||||
**directly** (nothing is posted), and calls `e.RequestMore()` to keep polling.
|
||||
- On GTK the sub also receives the wheel events the user raises over main: GDK sends a `wl_pointer.axis` only
|
||||
to the surface the pointer is over, and GTK then hands that `GDK_SCROLL` to the chain's grab widget, which is
|
||||
the sub. The sub installs a `GtkWidget::event` handler that replays the event on main with
|
||||
`gtk_propagate_event(main->GetConnectWidget(), event)` when the event's GDK window belongs to main
|
||||
(`DropDown::messureSize`).
|
||||
- **GTK wrapper sizing.** `messureSize` calls `gtk_window_resize` on `m_widget` after `SetSize`, because wxGTK's
|
||||
`DoSetSize` only uses `gtk_widget_set_size_request` ("gtk_window_resize does not work for GTK_WINDOW_POPUP",
|
||||
`src/gtk/popupwin.cpp:181-188`).
|
||||
@@ -612,32 +607,6 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
```
|
||||
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
||||
`PopupWindow::ShouldDismissOnTopWindowDeactivate`).
|
||||
- **Rule:** On GTK, route a chained popup's wheel event by the GDK window it names, not by the grab widget and not
|
||||
by the global cursor position.
|
||||
**Why:** While the sub holds the grab, `gtk_main_do_event` hands every `GDK_SCROLL` of the chain to the grab
|
||||
widget (the sub) with its original, un-rebased coordinates [source] (`gtkmain.c:1789-1792`, 1923-1924), while
|
||||
GDK Wayland sends the axis to the surface under the pointer only (`pointer_handle_axis` → `create_scroll_event`,
|
||||
`gdkdevice-wayland.c:1929, 1527`). The sub therefore sees and consumes wheel events raised over main, so the
|
||||
main list never scrolls, on X11 and Wayland alike. A hit test against `wxGetMousePosition()` cannot replace the
|
||||
window test: global coordinates are unreliable on Wayland (`mouse-keyboard-focus.md`). `DropDown` connects a
|
||||
`GtkWidget::event` handler on the sub's `GtkWindow` (its `m_widget`), which is emitted before wx's
|
||||
`::scroll-event` handler on the same widget and suppresses it when it returns TRUE (`gtkwidget.c:7696`), then
|
||||
delivers the event with `gtk_propagate_event(main->GetConnectWidget(), event)`. Returning TRUE there also stops
|
||||
the parent-chain propagation.
|
||||
```cpp
|
||||
// Wrong: emitted on the popup's outer GtkWindow, where wx has no handlers at all
|
||||
g_signal_emit_by_name(main->m_widget, "scroll-event", event, &handled);
|
||||
// Right (wxGTK): replay GTK's own delivery on the widget wx listens to
|
||||
gtk_propagate_event(main->GetConnectWidget(), event);
|
||||
```
|
||||
**Where wx listens [source].** `wxPopupWindow::Create` builds a popup from two widgets: `m_widget` is the outer
|
||||
`GtkWindow` and `m_wxwindow` is a `wxPizza` child added to it. `wxWindowGTK::GetConnectWidget()` is
|
||||
`m_wxwindow ? m_wxwindow : m_widget`, and `ConnectWidget()` — which binds `button_press_event`, `motion_notify_event`
|
||||
and `scroll_event` — is called on that widget (`src/gtk/window.cpp` `Create`, `GetConnectWidget`). A signal emitted
|
||||
on the outer `GtkWindow` therefore reaches no wx handler; only events GTK delivers to the inner widget, or to a
|
||||
descendant of the grab widget, are seen by wx. `GTKGetConnectWindow()`/`GTKGetMainWindow()` are the matching
|
||||
GdkWindows.
|
||||
Cite: `DropDown::messureSize`, `DropDown::mouseWheelMoved`.
|
||||
- **Rule:** When shrinking a dropdown to fit above the display bottom, give up if fewer than two rows would
|
||||
remain.
|
||||
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
||||
|
||||
+3
-22
@@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
||||
pushd "${SCRIPT_PATH}" > /dev/null
|
||||
|
||||
function usage() {
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-J N][-p][-r][-s][-t][-u][-l][-L] [-- CMAKE_ARGS...]"
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-J N][-p][-r][-s][-t][-u][-l][-L]"
|
||||
echo " -1: limit builds to one core (where possible)"
|
||||
echo " -j N: limit builds to N cores (where possible)"
|
||||
echo " -J N: build up to N dependencies at a time, each still using -j jobs (default: 1)"
|
||||
@@ -29,15 +29,11 @@ function usage() {
|
||||
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
||||
echo " -l: use Clang instead of GCC (default: GCC)"
|
||||
echo " -L: use ld.lld as linker (if available)"
|
||||
echo " -- CMAKE_ARGS...: pass everything after '--' to the cmake configure step of the deps (-d) and OrcaSlicer (-s/-t) builds,"
|
||||
echo " e.g. -DCMAKE_CXX_FLAGS=\"-O2 -march=native\", -DCMAKE_CXX_COMPILER=g++-14, -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=mold."
|
||||
echo " They come last on the cmake command line, so they override what -l and -L set."
|
||||
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
|
||||
echo "Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first."
|
||||
echo "Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version."
|
||||
echo "DEPS_EXTRA_BUILD_ARGS and ORCA_EXTRA_BUILD_ARGS add whitespace-separated cmake configure arguments to the deps and OrcaSlicer builds; prefer '--' for arguments that contain spaces."
|
||||
}
|
||||
|
||||
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||
@@ -135,20 +131,6 @@ while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
|
||||
esac
|
||||
done
|
||||
|
||||
# getopts stops at '--'; whatever follows is handed to cmake unchanged.
|
||||
if [[ ${OPTIND} -le $# ]] ; then
|
||||
separator_index=$((OPTIND-1))
|
||||
if [[ "${!separator_index}" != "--" ]] ; then
|
||||
echo "Unexpected argument '${!OPTIND}': cmake arguments must follow '--', aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
shift $((OPTIND-1))
|
||||
EXTRA_CMAKE_ARGS=("$@")
|
||||
if [[ ${#EXTRA_CMAKE_ARGS[@]} -gt 0 ]] ; then
|
||||
FORWARDED_ARGS+=("--" "${EXTRA_CMAKE_ARGS[@]}")
|
||||
fi
|
||||
|
||||
if [ ${OPTIND} -eq 1 ] ; then
|
||||
usage
|
||||
exit 1
|
||||
@@ -565,7 +547,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
||||
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
||||
fi
|
||||
|
||||
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}" "${EXTRA_CMAKE_ARGS[@]}"
|
||||
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
|
||||
# The top-level build runs one dependency at a time by default, which keeps the console
|
||||
# output readable and lets that dependency's own build use all of CMAKE_BUILD_PARALLEL_LEVEL.
|
||||
# -J raises the top level instead, and -j still applies in full to every dependency, so the
|
||||
@@ -611,8 +593,7 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
||||
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||
-DORCA_TOOLS=ON \
|
||||
"${COLORED_OUTPUT}" \
|
||||
"${BUILD_ARGS[@]}" \
|
||||
"${EXTRA_CMAKE_ARGS[@]}"
|
||||
"${BUILD_ARGS[@]}"
|
||||
echo "done"
|
||||
if [[ -n "${BUILD_ORCA}" ]]; then
|
||||
echo "Building OrcaSlicer ..."
|
||||
|
||||
+22
-38
@@ -82,7 +82,9 @@ there.
|
||||
strong point is placed, no later strong modifier and no weak modifier is
|
||||
processed for that perimeter.
|
||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||
priority to the highest, so the highest one overwrites overlapping zones.
|
||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
||||
Blocked modifier that fully contains a perimeter is the exception: it is
|
||||
skipped there (see [Full containment](#full-containment)).
|
||||
|
||||
A strong modifier without a usable segment, even one whose fragments were all
|
||||
discarded, passes the turn to the next one.
|
||||
@@ -230,23 +232,20 @@ begin and end positions on the source contour.
|
||||
### Full containment
|
||||
|
||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
||||
follows seam painting, where a whole perimeter can be painted in either color:
|
||||
follows seam painting, where painting a whole perimeter green is a meaningful
|
||||
choice and forbidding the seam all round is not:
|
||||
|
||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||
clearing painting and lower zones.
|
||||
- **Seam Blocked** types the whole perimeter Blocked, like a perimeter painted
|
||||
red all round, overriding painting and lower zones. The seam cannot avoid such
|
||||
a perimeter: the seam placer compares Blocked candidates with each other like
|
||||
Neutral ones, so the seam is placed as on an unmarked perimeter. If the
|
||||
perimeter stays Blocked after all weak zones, it is reported in the user
|
||||
warning (see [Diagnostics and warnings](#diagnostics-and-warnings)), so the
|
||||
user can check the modifier.
|
||||
- **Seam Center, Left and Right** are skipped with the full-containment warning:
|
||||
there is no intersection to place the point on.
|
||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
||||
override anything below it: lower zones and painting stay in effect.
|
||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
||||
intersection to place the point on.
|
||||
|
||||
Enforced, Neutral and Blocked take part in the usual priority order (see
|
||||
Enforced and Neutral take part in the usual priority order (see
|
||||
[Weak modifiers](#weak-modifiers)).
|
||||
|
||||
The perimeter is fully contained when the united intervals cover every source
|
||||
@@ -312,9 +311,9 @@ into every perimeter that has a strong seam.
|
||||
`collect_weak_modifier_segments()` extracts the segments of every weak modifier
|
||||
before the polygon is modified, so all positions refer to the same contour. Each
|
||||
segment becomes a zone with a type and two boundaries, kept in application
|
||||
order, lowest priority first. Full containment of a weak modifier becomes a
|
||||
whole-perimeter zone at its place in that order: it has no boundaries and takes
|
||||
part in no insertion or helper step below. The boundaries
|
||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
||||
carry their positions on the source contour; these remain as provenance after
|
||||
insertion and are not indices into the modified polygon.
|
||||
|
||||
@@ -385,10 +384,9 @@ G-code export issues it as one non-critical warning with the ID
|
||||
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||
placement may differ from expected.", because the export warnings dialog shows
|
||||
only the first line of each warning. Repeated warning events replace the
|
||||
notification instead of appending to it. Except for the "covers an entire
|
||||
perimeter" and "had no effect" causes, the causes name the modifier types
|
||||
involved, as the menu names them, in menu order and each type once, for example
|
||||
"(Seam Left, Seam Enforced)".
|
||||
notification instead of appending to it. Except for the "had no effect" cause,
|
||||
the causes name the modifier types involved, as the menu names them, in menu
|
||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
||||
The causes are:
|
||||
|
||||
- **failed to process some intersections (types):** at least one fragment was
|
||||
@@ -397,18 +395,8 @@ The causes are:
|
||||
a Seam Center, Left or Right modifier had more than one segment on a
|
||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
||||
(types):** a Seam Center, Left or Right modifier was skipped for a
|
||||
(types):** a Seam Center, Left, Right or Blocked modifier was skipped for a
|
||||
perimeter (see [Full containment](#full-containment)).
|
||||
- **Seam Blocked modifier "<name>" of "<object>" covers an entire perimeter:**
|
||||
a Seam Blocked modifier covered a whole perimeter (see
|
||||
[Full containment](#full-containment)). Only perimeters whose candidates are
|
||||
all Blocked after all weak zones contribute to this warning, and only through
|
||||
the zone in effect there: `apply_weak_modifiers_to_perimeter()` checks the
|
||||
final types and flags the modifier of the highest whole-perimeter zone when it
|
||||
is Blocked. A lower whole zone is overwritten by it and is not reported. Only
|
||||
the first flagged modifier in print and volume order is named, followed by
|
||||
"(N in total)" when there are several. The type name reuses the menu's
|
||||
translation.
|
||||
- **modifier "<name>" of "<object>" had no effect on the seam (it might not reach
|
||||
the centerline of the printed perimeter):** a modifier was evaluated on at
|
||||
least one perimeter and never gave a segment, full containment or a discarded
|
||||
@@ -432,8 +420,6 @@ The log records the following diagnostic markers:
|
||||
- `[PreciseSeamNoEffect]` for every modifier of the "had no effect" cause, with
|
||||
the object and modifier names. Unlike the user warning, the log lists all of
|
||||
them.
|
||||
- `[PreciseSeamFullyBlocked]` for every modifier of the "covers an entire
|
||||
perimeter" cause, listed the same way.
|
||||
|
||||
Failures and recoveries are counted separately. The first 10 of each per
|
||||
`init()` call are logged in detail, in parallel processing order; if a limit is
|
||||
@@ -577,14 +563,12 @@ last modifier would.
|
||||
priorities, weak boundaries that coincide or share an edge, enforced
|
||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||
zones over painting's oversampled candidates, the warning type masks, usage
|
||||
tracking for the "had no effect" and whole-perimeter Blocked warnings, volume
|
||||
sorting of strong and weak groups, restoration of strong points after
|
||||
alignment, raft layer indexing and structured slices. End-to-end tests slice
|
||||
a real object with Precise Seam
|
||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
||||
groups, restoration of strong points after alignment, raft layer indexing and
|
||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
||||
volumes and check the outer wall starts in the exported G-code: every strong
|
||||
mode under several seam positions and with a raft, Enforced and Blocked zones,
|
||||
a perimeter blocked all round under every seam position, a modifier with a
|
||||
hole, and the user warning.
|
||||
a modifier with a hole, and the user warning.
|
||||
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
||||
enforced-patch selection independent of the contour start, fully painted
|
||||
contours, duplicate removal, and `Print::apply()` synchronization through type
|
||||
|
||||
@@ -12665,16 +12665,6 @@ msgstr ""
|
||||
msgid "a perimeter is fully inside a modifier, the modifier was not applied to it (%1%)"
|
||||
msgstr ""
|
||||
|
||||
#. TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name.
|
||||
#, possible-boost-format
|
||||
msgid "%1% modifier \"%2%\" of \"%3%\" covers an entire perimeter"
|
||||
msgstr ""
|
||||
|
||||
#. TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name, %4% the number of modifiers.
|
||||
#, possible-boost-format
|
||||
msgid "%1% modifier \"%2%\" of \"%3%\" (%4% in total) covers an entire perimeter"
|
||||
msgstr ""
|
||||
|
||||
#, possible-boost-format
|
||||
msgid "modifier \"%1%\" of \"%2%\" had no effect on the seam (it might not reach the centerline of the printed perimeter)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1374,26 +1374,6 @@ bool AppConfig::write_config_file(const std::string &path, std::string body, con
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppConfig::clear_local_machine_access_code(const std::string& dev_id, const std::string& agent_id)
|
||||
{
|
||||
// Unscoped credentials predate printer agents and belong to the legacy BBL agent.
|
||||
const bool is_bbl_agent = agent_id.empty() || agent_id == "bbl";
|
||||
auto it = m_local_machines.find(dev_id);
|
||||
if (it != m_local_machines.end()) {
|
||||
const std::string& stored_agent = it->second.printer_agent_id;
|
||||
if (stored_agent == agent_id || (is_bbl_agent && (stored_agent.empty() || stored_agent == "bbl"))) {
|
||||
if (!it->second.access_code.empty()) {
|
||||
it->second.access_code.clear();
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_bbl_agent) {
|
||||
erase("access_code", dev_id);
|
||||
erase("user_access_code", dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
|
||||
{
|
||||
const auto it_v = m_vendors.find(vendor);
|
||||
|
||||
@@ -306,8 +306,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void clear_local_machine_access_code(const std::string& dev_id, const std::string& agent_id);
|
||||
|
||||
const std::vector<std::string> &get_filament_presets() const { return m_filament_presets; }
|
||||
void set_filament_presets(const std::vector<std::string> &filament_presets){
|
||||
m_filament_presets = filament_presets;
|
||||
|
||||
@@ -9776,10 +9776,7 @@ bool has_restore_data(std::string & path, std::string& origin)
|
||||
origin = "<lock>";
|
||||
return false;
|
||||
}
|
||||
// ORCA: non-throwing probes; ec is deliberately unread, a backup path that cannot be
|
||||
// probed is treated as having no restore data rather than aborting the application.
|
||||
boost::system::error_code ec;
|
||||
if (boost::filesystem::exists(path + "/lock.txt", ec)) {
|
||||
if (boost::filesystem::exists(path + "/lock.txt")) {
|
||||
std::string pid;
|
||||
load_string_file(path + "/lock.txt", pid);
|
||||
try {
|
||||
@@ -9794,10 +9791,10 @@ bool has_restore_data(std::string & path, std::string& origin)
|
||||
}
|
||||
}
|
||||
std::string file3mf = path + "/.3mf";
|
||||
if (!boost::filesystem::exists(file3mf, ec))
|
||||
if (!boost::filesystem::exists(file3mf))
|
||||
return false;
|
||||
try {
|
||||
if (boost::filesystem::exists(path + "/origin.txt", ec))
|
||||
if (boost::filesystem::exists(path + "/origin.txt"))
|
||||
load_string_file(path + "/origin.txt", origin);
|
||||
}
|
||||
catch (...) {
|
||||
|
||||
@@ -673,7 +673,7 @@ static void refine_at_vertex(
|
||||
}
|
||||
}
|
||||
|
||||
// Records one extraction for the warnings: the type of a modifier with a discarded fragment, the
|
||||
// Records one extraction for the warnings: the type of a modifier with a discarded fragment, and the
|
||||
// evaluated/reached flags for "had no effect" (a discarded fragment counts as reached, a contact not).
|
||||
static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *modifier,
|
||||
const SegmentExtraction &extracted)
|
||||
@@ -685,10 +685,13 @@ static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *
|
||||
const auto it = warnings->modifier_usage.find(modifier);
|
||||
if (it == warnings->modifier_usage.end())
|
||||
return;
|
||||
// Load before store: most calls find the flag already set, so shared cache lines stay clean.
|
||||
PreciseSeamWarnings::ModifierUsage &usage = it->second;
|
||||
PreciseSeamWarnings::set(usage.checked);
|
||||
if (!extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0)
|
||||
PreciseSeamWarnings::set(usage.reached);
|
||||
if (!usage.checked.load(std::memory_order_relaxed))
|
||||
usage.checked.store(true, std::memory_order_relaxed);
|
||||
const bool reached = !extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0;
|
||||
if (reached && !usage.reached.load(std::memory_order_relaxed))
|
||||
usage.reached.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Strong priority is per modifier, never a global maximum across different modifiers.
|
||||
@@ -950,15 +953,22 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
||||
const SegmentExtraction extracted = extract_perimeter_segments(prepared, it->second[layer_id], modifier_volume->type(), context);
|
||||
record_extraction(warnings, modifier_volume, extracted);
|
||||
const auto type = convert_weak_modifier_type(modifier_volume->type());
|
||||
// Full containment works like seam painting all round: the whole perimeter takes the type.
|
||||
// A perimeter left whole Blocked also gets a warning (see apply_weak_modifiers_to_perimeter()).
|
||||
// Full containment works like seam painting: Enforced and Neutral type the whole perimeter;
|
||||
// Blocked is skipped with a warning, since the seam cannot avoid the whole perimeter.
|
||||
if (extracted.full_containment) {
|
||||
result.push_back({type, Point(), PerimeterPosition{0, 0.}, Point(), PerimeterPosition{0, 0.}, true, modifier_volume});
|
||||
if (type == EnforcedBlockedSeamPoint::Blocked) {
|
||||
if (warnings)
|
||||
PreciseSeamWarnings::mark(warnings->full_containment, modifier_volume->type());
|
||||
continue;
|
||||
}
|
||||
WeakModifierSegment whole{type, Point(), PerimeterPosition{0, 0.}, Point(), PerimeterPosition{0, 0.}};
|
||||
whole.whole_perimeter = true;
|
||||
result.push_back(whole);
|
||||
continue;
|
||||
}
|
||||
for (const PerimeterSegment &segment : extracted.segments) {
|
||||
result.push_back({type, segment.polyline.points.front(), segment.begin,
|
||||
segment.polyline.points.back(), segment.end, false, modifier_volume});
|
||||
segment.polyline.points.back(), segment.end});
|
||||
}
|
||||
}
|
||||
// Boundary insertion, refinement and last-write-wins priority use the established path.
|
||||
@@ -969,8 +979,7 @@ void apply_weak_modifiers_to_perimeter(
|
||||
const std::vector<WeakModifierSegment> &weak_segments,
|
||||
PrintObjectSeamData::LayerSeams &result,
|
||||
const SeamPlacerImpl::Perimeter &perimeter,
|
||||
bool &some_point_enforced,
|
||||
PreciseSeamWarnings *warnings)
|
||||
bool &some_point_enforced)
|
||||
{
|
||||
// Get z-coordinate for unscaling boundary points
|
||||
const float z_coord = result.points[perimeter.start_index].position.z();
|
||||
@@ -1020,19 +1029,6 @@ void apply_weak_modifiers_to_perimeter(
|
||||
some_point_enforced = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only the highest whole zone is in effect, and a higher band may still unblock part of it.
|
||||
if (warnings == nullptr)
|
||||
return;
|
||||
const auto last_whole = std::find_if(weak_segments.rbegin(), weak_segments.rend(),
|
||||
[](const WeakModifierSegment &segment) { return segment.whole_perimeter; });
|
||||
if (last_whole == weak_segments.rend() || last_whole->type != EnforcedBlockedSeamPoint::Blocked)
|
||||
return;
|
||||
for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i)
|
||||
if (result.points[i].type != EnforcedBlockedSeamPoint::Blocked)
|
||||
return;
|
||||
if (const auto usage = warnings->modifier_usage.find(last_whole->modifier); usage != warnings->modifier_usage.end())
|
||||
PreciseSeamWarnings::set(usage->second.fully_blocked);
|
||||
}
|
||||
|
||||
// Restore precise seam positions that may have been modified
|
||||
|
||||
@@ -49,20 +49,18 @@ struct PreciseSeamWarnings {
|
||||
// Masks of the Precise Seam types that caused each warning reason, one bit per type (type_bit()).
|
||||
// The user warning lists the types instead of naming modifiers.
|
||||
std::atomic<unsigned> multiple_intersections{0}; // Center/Left/Right with several segments on a perimeter.
|
||||
std::atomic<unsigned> full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right.
|
||||
std::atomic<unsigned> full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right, Blocked.
|
||||
std::atomic<unsigned> failed_types{0}; // Types with at least one discarded fragment.
|
||||
std::atomic<size_t> failed_fragments{0}; // Total discarded fragments, for the log summary.
|
||||
// Fragments saved by the rare-case fallback or accepted as contacts; log only, no user warning.
|
||||
// Clipper is deterministic, so a prismatic model can repeat the same case on every layer.
|
||||
std::atomic<size_t> recovered_fragments{0};
|
||||
|
||||
// Per-modifier flags for the "had no effect" and "covers an entire perimeter" warnings. Modifiers are
|
||||
// registered before the parallel phase, so workers only set flags; unregistered ones (e.g. in tests)
|
||||
// are not tracked.
|
||||
// Per-modifier flags for the "had no effect" warning. Modifiers are registered before the parallel
|
||||
// phase, so workers only set flags; unregistered ones (e.g. in tests) are not tracked.
|
||||
struct ModifierUsage {
|
||||
std::atomic<bool> checked{false}; // Extracted on at least one perimeter.
|
||||
std::atomic<bool> reached{false}; // Gave a segment, full containment or a discarded fragment.
|
||||
std::atomic<bool> fully_blocked{false}; // Seam Blocked whole zone in effect on a perimeter left Blocked.
|
||||
std::atomic<bool> checked{false}; // Extracted on at least one perimeter.
|
||||
std::atomic<bool> reached{false}; // Gave a segment, full containment or a discarded fragment.
|
||||
};
|
||||
std::unordered_map<const ModelVolume*, ModifierUsage> modifier_usage;
|
||||
|
||||
@@ -72,18 +70,13 @@ struct PreciseSeamWarnings {
|
||||
assert(is_precise_seam(type));
|
||||
return 1u << (int(type) - int(ModelVolumeType::PRECISE_SEAM_CENTER));
|
||||
}
|
||||
// Load before writing: most calls find the bit or flag already set, so shared cache lines stay clean.
|
||||
// Load before fetch_or: most calls find the bit already set, so shared cache lines stay clean.
|
||||
static void mark(std::atomic<unsigned> &mask, ModelVolumeType type)
|
||||
{
|
||||
const unsigned bit = type_bit(type);
|
||||
if ((mask.load(std::memory_order_relaxed) & bit) == 0)
|
||||
mask.fetch_or(bit, std::memory_order_relaxed);
|
||||
}
|
||||
static void set(std::atomic<bool> &flag)
|
||||
{
|
||||
if (!flag.load(std::memory_order_relaxed))
|
||||
flag.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
// Optional caller identity for concise diagnostics when an intersection is discarded.
|
||||
@@ -146,10 +139,9 @@ struct WeakModifierSegment {
|
||||
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
|
||||
Point right_point; // Coordinates of right (last) point of segment
|
||||
PerimeterPosition right_position; // Retained provenance, not an index into the modified polygon.
|
||||
// Full containment of a weak modifier: the zone is the whole perimeter, without boundaries
|
||||
// (the points and positions above are unused and nothing is inserted for it).
|
||||
// Full containment of an Enforced or Neutral modifier: the zone is the whole perimeter, without
|
||||
// boundaries (the points and positions above are unused and nothing is inserted for it).
|
||||
bool whole_perimeter = false;
|
||||
const ModelVolume *modifier = nullptr; // Source modifier, for the warnings.
|
||||
};
|
||||
|
||||
// Collects the object's Precise Seam volumes: strong ones in priority order, weak ones in application
|
||||
@@ -181,14 +173,12 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
||||
PreciseSeamWarnings* warnings = nullptr);
|
||||
|
||||
// Retypes the candidates inside each zone in the given order (pass zones lowest priority first);
|
||||
// sets some_point_enforced when an Enforced zone applies. When the whole perimeter ends up Blocked and
|
||||
// the highest whole-perimeter zone is Blocked, flags that zone's modifier for the warning.
|
||||
// sets some_point_enforced when an Enforced zone applies.
|
||||
void apply_weak_modifiers_to_perimeter(
|
||||
const std::vector<WeakModifierSegment> &weak_segments,
|
||||
PrintObjectSeamData::LayerSeams &result,
|
||||
const SeamPlacerImpl::Perimeter &perimeter,
|
||||
bool &some_point_enforced,
|
||||
PreciseSeamWarnings *warnings = nullptr);
|
||||
bool &some_point_enforced);
|
||||
|
||||
// Restore precise seam positions that may have been modified by alignment
|
||||
// Iterates through all perimeters and restores precise_seam_point positions
|
||||
|
||||
@@ -608,7 +608,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
// Apply weak modifiers if no strong modifier was inserted
|
||||
if (!inserted_seam_position.has_value() && !weak_segments.empty()) {
|
||||
PreciseSeam::apply_weak_modifiers_to_perimeter(
|
||||
weak_segments, result, perimeter, some_point_enforced, warnings);
|
||||
weak_segments, result, perimeter, some_point_enforced);
|
||||
}
|
||||
|
||||
if (some_point_enforced) {
|
||||
@@ -896,15 +896,18 @@ struct SeamComparator {
|
||||
return a.central_enforcer;
|
||||
}
|
||||
|
||||
if (a.type != b.type) {
|
||||
return a.type > b.type;
|
||||
}
|
||||
|
||||
// Two Blocked points fall through like Neutral ones, so a perimeter blocked all round still gets a proper seam.
|
||||
if (a.type == EnforcedBlockedSeamPoint::Enforced) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.type == EnforcedBlockedSeamPoint::Blocked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.type != b.type) {
|
||||
return a.type > b.type;
|
||||
}
|
||||
|
||||
//avoid overhangs
|
||||
if ((a.overhang > 0.0f || b.overhang > 0.0f)
|
||||
&& abs(a.overhang - b.overhang) > (0.1f * a.perimeter.flow_width)) {
|
||||
@@ -1657,42 +1660,18 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
parts.push_back((boost::format(_u8L("multiple intersections with a perimeter, only one was used (%1%)")) % type_list(mi)).str());
|
||||
if (fc != 0)
|
||||
parts.push_back((boost::format(_u8L("a perimeter is fully inside a modifier, the modifier was not applied to it (%1%)")) % type_list(fc)).str());
|
||||
// Modifiers whose usage flags match, in print and volume order, so the named one is deterministic.
|
||||
const auto collect_modifiers = [&print, &precise_seam_warnings](const auto &matches) {
|
||||
std::vector<const ModelVolume*> found;
|
||||
for (const PrintObject *po : print.objects())
|
||||
for (const ModelVolume *volume : po->model_object()->volumes) {
|
||||
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
||||
if (it != precise_seam_warnings.modifier_usage.end() && matches(it->second) &&
|
||||
std::find(found.begin(), found.end(), volume) == found.end())
|
||||
found.push_back(volume);
|
||||
}
|
||||
return found;
|
||||
};
|
||||
// Seam Blocked modifiers whose whole perimeter stayed Blocked; the user warning names only the
|
||||
// first one, the log lists them all.
|
||||
const std::vector<const ModelVolume*> fully_blocked = collect_modifiers(
|
||||
[](const PreciseSeam::PreciseSeamWarnings::ModifierUsage &usage) { return usage.fully_blocked.load(std::memory_order_relaxed); });
|
||||
for (const ModelVolume *volume : fully_blocked)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamFullyBlocked] object=\"" << volume->get_object()->name
|
||||
<< "\" modifier=\"" << volume->name << "\"";
|
||||
if (!fully_blocked.empty()) {
|
||||
const ModelVolume *first = fully_blocked.front();
|
||||
if (fully_blocked.size() == 1) {
|
||||
// TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name.
|
||||
parts.push_back((boost::format(_u8L("%1% modifier \"%2%\" of \"%3%\" covers an entire perimeter"))
|
||||
% _u8L("Seam Blocked") % first->name % first->get_object()->name).str());
|
||||
} else {
|
||||
// TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name, %4% the number of modifiers.
|
||||
parts.push_back((boost::format(_u8L("%1% modifier \"%2%\" of \"%3%\" (%4% in total) covers an entire perimeter"))
|
||||
% _u8L("Seam Blocked") % first->name % first->get_object()->name % fully_blocked.size()).str());
|
||||
}
|
||||
}
|
||||
// Modifiers evaluated somewhere that never reached a perimeter; never-evaluated ones are not reported.
|
||||
const std::vector<const ModelVolume*> no_effect = collect_modifiers(
|
||||
[](const PreciseSeam::PreciseSeamWarnings::ModifierUsage &usage) {
|
||||
return usage.checked.load(std::memory_order_relaxed) && !usage.reached.load(std::memory_order_relaxed);
|
||||
});
|
||||
// Print and volume order make the named one deterministic; the log lists them all.
|
||||
std::vector<const ModelVolume*> no_effect;
|
||||
for (const PrintObject *po : print.objects())
|
||||
for (const ModelVolume *volume : po->model_object()->volumes) {
|
||||
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
||||
if (it != precise_seam_warnings.modifier_usage.end() &&
|
||||
it->second.checked.load(std::memory_order_relaxed) &&
|
||||
!it->second.reached.load(std::memory_order_relaxed) &&
|
||||
std::find(no_effect.begin(), no_effect.end(), volume) == no_effect.end())
|
||||
no_effect.push_back(volume);
|
||||
}
|
||||
// The user warning names only the first one; the log lists them all.
|
||||
for (const ModelVolume *volume : no_effect)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamNoEffect] object=\"" << volume->get_object()->name
|
||||
|
||||
@@ -2235,11 +2235,9 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
//FIXME It is quite expensive to generate object layers just to get the print height!
|
||||
auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
|
||||
if (!layers.empty()) {
|
||||
// The object layers start above the raft.
|
||||
const double raft_z = print_object.slicing_parameters().object_print_z_min;
|
||||
effective_max_z = raft_z + layers.back();
|
||||
effective_max_z = layers.back();
|
||||
last_layer_below_max = layers.size() >= 2 &&
|
||||
raft_z + 0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
||||
0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
||||
have_height = true;
|
||||
}
|
||||
}
|
||||
@@ -2247,11 +2245,7 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
if (!have_height)
|
||||
continue;
|
||||
|
||||
// The layers of a regular printer already carry the Z shrinkage compensation, the belt height does not.
|
||||
const double compensated_max_z = belt_printer ? effective_max_z * shrinkage_compensation_z : effective_max_z;
|
||||
const double uncompensated_max_z = belt_printer ? effective_max_z : print_object.slicing_parameters().object_print_z_uncompensated_max;
|
||||
if (shrinkage_compensation_z != 1. && compensated_max_z > this->config().printable_height + EPSILON &&
|
||||
uncompensated_max_z <= this->config().printable_height + EPSILON) {
|
||||
if (shrinkage_compensation_z != 1. && effective_max_z > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
||||
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
||||
return StringObjectException{
|
||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "ConnectPrinter.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevConfigUtil.h"
|
||||
@@ -170,7 +169,7 @@ void ConnectPrinterDialog::on_input_enter(wxCommandEvent& evt)
|
||||
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
{
|
||||
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
||||
if (code.empty() && (!m_obj || m_obj->printer_agent_id != MOONRAKER_PRINTER_AGENT_ID))
|
||||
if (code.empty())
|
||||
code = "88888888";
|
||||
for (char c : code) {
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||
|
||||
@@ -276,18 +276,6 @@ namespace Slic3r
|
||||
std::string connect_type = j["connect_type"].get<std::string>();
|
||||
std::string bind_state = j["bind_state"].get<std::string>();
|
||||
|
||||
// Passive discovery must not replace another agent's saved binding.
|
||||
if (get_current_printer_agent_id() == MOONRAKER_PRINTER_AGENT_ID) {
|
||||
if (AppConfig* config = get_app_config()) {
|
||||
const auto& machines = config->get_local_machines();
|
||||
auto it = machines.find(dev_id);
|
||||
if (it != machines.end() && it->second.printer_agent_id != MOONRAKER_PRINTER_AGENT_ID) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Ignoring Moonraker discovery for a device owned by another agent: " << dev_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connect_type == "farm") {
|
||||
connect_type ="lan";
|
||||
bind_state = "free";
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "HMS.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
@@ -67,7 +66,6 @@
|
||||
#include <vector>
|
||||
#include <wx/colour.h>
|
||||
#include <tuple>
|
||||
#include <wx/app.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/event.h>
|
||||
@@ -529,18 +527,6 @@ bool MachineObject::HasRecentLanMessage()
|
||||
return diff.count() < 5000;
|
||||
}
|
||||
|
||||
bool MachineObject::has_access_right() const
|
||||
{
|
||||
return !m_access_revoked && (printer_agent_id == MOONRAKER_PRINTER_AGENT_ID || !get_access_code().empty());
|
||||
}
|
||||
|
||||
void MachineObject::revoke_access()
|
||||
{
|
||||
// UI panels retain this object; revoke its binding without invalidating their pointers.
|
||||
access_code.clear();
|
||||
m_access_revoked = true;
|
||||
}
|
||||
|
||||
std::string MachineObject::get_access_code() const
|
||||
{
|
||||
return access_code;
|
||||
@@ -549,7 +535,6 @@ std::string MachineObject::get_access_code() const
|
||||
void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
{
|
||||
this->access_code = code;
|
||||
m_access_revoked = false;
|
||||
if (only_refresh) {
|
||||
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
@@ -563,10 +548,25 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
// session boundary, since parse_user_print_info() always overwrites their code
|
||||
// fresh from the cloud API's current response, so there's no cross-agent leakage
|
||||
// risk to guard against there.
|
||||
if (has_access_right()) {
|
||||
if (!code.empty()) {
|
||||
DeviceManager::update_local_machine(*this, config);
|
||||
} else {
|
||||
config->clear_local_machine_access_code(get_dev_id(), printer_agent_id);
|
||||
// Only patch an existing record's code - don't persist a brand-new
|
||||
// never-bound entry just because set_access_code("") was called on it.
|
||||
const auto& machines = config->get_local_machines();
|
||||
auto it = machines.find(get_dev_id());
|
||||
if (it != machines.end()) {
|
||||
BBLocalMachine local_machine = it->second;
|
||||
local_machine.access_code = "";
|
||||
config->update_local_machine(local_machine);
|
||||
}
|
||||
// Also clear the pre-scoping flat legacy key when unbinding under BBL, so an
|
||||
// old BBL-era code can't silently "re-bind" this device again via
|
||||
// get_access_code_with_legacy_fallback()'s legacy fallback.
|
||||
if (printer_agent_id == BBL_PRINTER_AGENT_ID || printer_agent_id.empty()) {
|
||||
config->erase("access_code", get_dev_id());
|
||||
config->erase("user_access_code", get_dev_id());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!code.empty())
|
||||
@@ -2678,7 +2678,7 @@ void MachineObject::update_print_progress(const json& value)
|
||||
|
||||
int MachineObject::connect()
|
||||
{
|
||||
if (m_access_revoked || get_dev_ip().empty()) return -1;
|
||||
if (get_dev_ip().empty()) return -1;
|
||||
std::string username = m_agent ? m_agent->default_lan_username() : std::string();
|
||||
std::string password = get_access_code();
|
||||
|
||||
@@ -2691,25 +2691,17 @@ int MachineObject::connect()
|
||||
std::string host = Http::get_host_from_url(input, &port);
|
||||
std::string ca_file;
|
||||
|
||||
if (wxTheApp && GUI::wxGetApp().preset_bundle) {
|
||||
if (GUI::wxGetApp().preset_bundle) {
|
||||
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (port.empty())
|
||||
port = config.opt_string("printhost_port");
|
||||
ca_file = config.opt_string("printhost_cafile");
|
||||
|
||||
// Refresh credentials from the matching preset for connections that bypass GUI_App::select_machine().
|
||||
const std::string print_host = config.opt_string("print_host");
|
||||
if (printer_agent_id == MOONRAKER_PRINTER_AGENT_ID && config.opt_string("printer_agent") == MOONRAKER_PRINTER_AGENT_ID &&
|
||||
!print_host.empty() && dev_id_from_address(print_host, config.opt_string("printhost_port")) == get_dev_id()) {
|
||||
password = config.opt_string("printhost_apikey");
|
||||
if (password != get_access_code())
|
||||
set_access_code(password);
|
||||
}
|
||||
}
|
||||
|
||||
if (host.empty())
|
||||
host = get_dev_ip();
|
||||
|
||||
|
||||
if (m_agent) {
|
||||
try {
|
||||
PrinterConnectionParams params{
|
||||
|
||||
@@ -119,7 +119,6 @@ private:
|
||||
std::string dev_name;
|
||||
std::string dev_ip;
|
||||
std::string access_code;
|
||||
bool m_access_revoked = false;
|
||||
|
||||
// type, time stamp, delay
|
||||
std::vector<std::tuple<std::string, uint64_t, uint64_t>> message_delay;
|
||||
@@ -230,8 +229,7 @@ public:
|
||||
std::string dev_connection_name; /* lan | eth */
|
||||
|
||||
/*access code*/
|
||||
bool has_access_right() const;
|
||||
void revoke_access();
|
||||
bool has_access_right() const { return !get_access_code().empty(); }
|
||||
std::string get_access_code() const;
|
||||
void set_access_code(std::string code, bool only_refresh = true);
|
||||
|
||||
|
||||
+86
-80
@@ -1149,8 +1149,14 @@ void GUI_App::post_init()
|
||||
if(!m_networking_need_update && m_agent) {
|
||||
m_agent->set_on_ssdp_msg_fn(
|
||||
[this](std::string json_str) {
|
||||
if (!is_closing() && m_device_manager)
|
||||
m_device_manager->on_machine_alive(json_str);
|
||||
if (is_closing()) {
|
||||
return;
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([this, json_str] {
|
||||
if (m_device_manager) {
|
||||
m_device_manager->on_machine_alive(json_str);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||
@@ -1808,8 +1814,14 @@ void GUI_App::restart_networking()
|
||||
init_networking_callbacks();
|
||||
m_agent->set_on_ssdp_msg_fn(
|
||||
[this](std::string json_str) {
|
||||
if (!is_closing() && m_device_manager)
|
||||
m_device_manager->on_machine_alive(json_str);
|
||||
if (is_closing()) {
|
||||
return;
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([this, json_str] {
|
||||
if (m_device_manager) {
|
||||
m_device_manager->on_machine_alive(json_str);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||
@@ -2606,10 +2618,7 @@ void GUI_App::init_download_path()
|
||||
}
|
||||
else {
|
||||
fs::path dp(down_path);
|
||||
// ORCA: non-throwing probe; ec is deliberately unread, a download_path that cannot be
|
||||
// probed falls back to the default like a missing one instead of failing startup.
|
||||
boost::system::error_code ec;
|
||||
if (!fs::exists(dp, ec)) {
|
||||
if (!fs::exists(dp)) {
|
||||
|
||||
std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
|
||||
app_config->set("download_path", user_down_path);
|
||||
@@ -3916,74 +3925,81 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
if (config_version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
||||
m_networking_need_update = true;
|
||||
} else {
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
__retry:
|
||||
if (!load_agent_dll) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||
// Self-heal only when a genuinely different series loaded than configured (e.g. the
|
||||
// configured build was unavailable and a fallback loaded). Within a series the
|
||||
// loaded build (02.08.01.53) differs from the series config (02.08.01) by design, so
|
||||
// compare series, not the raw string, and store the managed series form - a custom
|
||||
// config (02.08.01_custom) keeps its own name.
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
std::string loaded_series = network_plugin_series(loaded_version);
|
||||
if (network_plugin_series(config_version) != loaded_series) {
|
||||
std::string synced = is_series_managed_version(loaded_version) ? loaded_series : loaded_version;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version
|
||||
<< " to loaded " << loaded_version << " (stored as " << synced << ")";
|
||||
app_config->set_network_plugin_version(synced);
|
||||
app_config->save();
|
||||
}
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager();
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
__retry:
|
||||
if (!load_agent_dll) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||
// Self-heal only when a genuinely different series loaded than configured (e.g. the
|
||||
// configured build was unavailable and a fallback loaded). Within a series the
|
||||
// loaded build (02.08.01.53) differs from the series config (02.08.01) by design, so
|
||||
// compare series, not the raw string, and store the managed series form - a custom
|
||||
// config (02.08.01_custom) keeps its own name.
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
std::string loaded_series = network_plugin_series(loaded_version);
|
||||
if (network_plugin_series(config_version) != loaded_series) {
|
||||
std::string synced = is_series_managed_version(loaded_version) ? loaded_series : loaded_version;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version
|
||||
<< " to loaded " << loaded_version << " (stored as " << synced << ")";
|
||||
app_config->set_network_plugin_version(synced);
|
||||
app_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (check_networking_version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!bambu_source) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||
m_networking_compatible = false;
|
||||
if (should_load_networking_plugin) {
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (try_backup) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
||||
try_backup = false;
|
||||
goto __retry;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
|
||||
if (check_networking_version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!bambu_source) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||
m_networking_compatible = false;
|
||||
if (should_load_networking_plugin) {
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||
// A failed install can leave the config naming a build that never made it to disk;
|
||||
// fall back to the installed latest instead of dropping the user into the re-download
|
||||
// flow. Only when the configured library is genuinely absent, though - a pinned series
|
||||
// that is on disk but failed to load once must keep its pin, not be rewritten for good.
|
||||
std::string latest = get_latest_network_version();
|
||||
if (config_version != latest && !BBLNetworkPlugin::versioned_library_exists(config_version)
|
||||
&& BBLNetworkPlugin::versioned_library_exists(latest)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": falling back to installed " << latest;
|
||||
config_version = latest;
|
||||
app_config->set_network_plugin_version(latest);
|
||||
app_config->save();
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
if (try_backup) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
||||
try_backup = false;
|
||||
goto __retry;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
|
||||
if (should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||
// A failed install can leave the config naming a build that never made it to disk;
|
||||
// fall back to the installed latest instead of dropping the user into the re-download
|
||||
// flow. Only when the configured library is genuinely absent, though - a pinned series
|
||||
// that is on disk but failed to load once must keep its pin, not be rewritten for good.
|
||||
std::string latest = get_latest_network_version();
|
||||
if (config_version != latest && !BBLNetworkPlugin::versioned_library_exists(config_version)
|
||||
&& BBLNetworkPlugin::versioned_library_exists(latest)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": falling back to installed " << latest;
|
||||
config_version = latest;
|
||||
app_config->set_network_plugin_version(latest);
|
||||
app_config->save();
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
goto __retry;
|
||||
}
|
||||
if (should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4011,11 +4027,6 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
else
|
||||
m_user_manager->set_agent(m_agent);
|
||||
|
||||
// A hot reload deletes the agent the task manager was built with. It lives until restart, so repoint it
|
||||
// even if multi-device has since been turned off.
|
||||
if (m_task_manager)
|
||||
m_task_manager->set_agent(m_agent);
|
||||
|
||||
if (this->is_enable_multi_machine()) {
|
||||
if (!m_task_manager) {
|
||||
m_task_manager = new Slic3r::TaskManager(m_agent);
|
||||
@@ -4220,10 +4231,8 @@ void GUI_App::switch_printer_agent()
|
||||
const std::string print_host = config.opt_string("print_host");
|
||||
if (!print_host.empty()) {
|
||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, config.opt_string("printhost_port"));
|
||||
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||
const bool access_code_changed = effective_agent_id == MOONRAKER_PRINTER_AGENT_ID && sel &&
|
||||
sel->get_access_code() != config.opt_string("printhost_apikey");
|
||||
if (!sel || sel->get_dev_id() != dev_id || access_code_changed)
|
||||
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||
if (!sel || sel->get_dev_id() != dev_id)
|
||||
select_machine(effective_agent_id);
|
||||
}
|
||||
}
|
||||
@@ -4265,9 +4274,6 @@ void GUI_App::select_machine(const std::string& agent_id)
|
||||
return;
|
||||
}
|
||||
std::string port = host_cfg->opt_string("printhost_port");
|
||||
std::string access_code = host_cfg->opt_string("printhost_apikey");
|
||||
if (access_code.empty() && agent_id != MOONRAKER_PRINTER_AGENT_ID)
|
||||
access_code = "88888888";
|
||||
|
||||
// Generate dev_id from host and port
|
||||
std::string dev_id = MachineObject::dev_id_from_address(print_host, port);
|
||||
@@ -4294,6 +4300,11 @@ void GUI_App::select_machine(const std::string& agent_id)
|
||||
machine.dev_ip = dev_id;
|
||||
machine.dev_name = dev_id;
|
||||
machine.printer_type = preset.config.opt_string("printer_model");
|
||||
auto access_code = preset.config.opt_string("printhost_apikey");
|
||||
// Orca expect non empty access code
|
||||
if (access_code.empty()) {
|
||||
access_code = "88888888";
|
||||
}
|
||||
|
||||
existing = m_device_manager->insert_local_device(
|
||||
machine, "lan", "free", "", access_code);
|
||||
@@ -4303,9 +4314,6 @@ void GUI_App::select_machine(const std::string& agent_id)
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id;
|
||||
} else if (agent_id == MOONRAKER_PRINTER_AGENT_ID && existing->printer_agent_id == agent_id &&
|
||||
(!existing->has_access_right() || existing->get_access_code() != access_code)) {
|
||||
existing->set_access_code(access_code);
|
||||
}
|
||||
existing->local_use_ssl = boost::istarts_with(print_host, "https://");
|
||||
|
||||
@@ -10133,9 +10141,7 @@ void GUI_App::start_download(std::string url)
|
||||
}
|
||||
//lets always init so if the download dest folder was changed, new dest is used
|
||||
boost::filesystem::path dest_folder(app_config->get("download_path"));
|
||||
// ORCA: non-throwing probe, as in init_download_path(); ec is deliberately unread.
|
||||
boost::system::error_code ec;
|
||||
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder, ec)) {
|
||||
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
|
||||
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
show_error(nullptr, msg);
|
||||
|
||||
@@ -732,11 +732,10 @@ void SelectMachinePopup::update_user_devices()
|
||||
if (mobj) {
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (config) {
|
||||
config->clear_local_machine_access_code(mobj->get_dev_id(), mobj->printer_agent_id);
|
||||
config->erase_local_machine(mobj->get_dev_id());
|
||||
}
|
||||
|
||||
mobj->revoke_access();
|
||||
mobj->set_access_code("");
|
||||
}
|
||||
|
||||
if (GUI::wxGetApp().plater())
|
||||
|
||||
@@ -2981,7 +2981,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("skeleton_infill_line_width", "strength_settings_patterns#locked-zag");
|
||||
optgroup->append_single_option_line("symmetric_infill_y_axis", "strength_settings_infill#symmetric-infill-y-axis");
|
||||
optgroup->append_single_option_line("infill_complete_top", "strength_settings_patterns#fill-pattern-tops");
|
||||
optgroup->append_single_option_line("infill_shift_step", "strength_settings_patterns#cross-zag");
|
||||
optgroup->append_single_option_line("infill_shift_step", "strength_settings_patterns#cross-hatch");
|
||||
optgroup->append_single_option_line("lateral_lattice_angle_1", "strength_settings_patterns#lateral-lattice");
|
||||
optgroup->append_single_option_line("lateral_lattice_angle_2", "strength_settings_patterns#lateral-lattice");
|
||||
optgroup->append_single_option_line("infill_overhang_angle", "strength_settings_patterns#lateral-honeycomb");
|
||||
@@ -3248,8 +3248,8 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("enable_wrapping_detection");
|
||||
|
||||
optgroup = page->new_optgroup(L("Fuzzy skin"), L"fuzzy_skin");
|
||||
optgroup->append_single_option_line("fuzzy_skin", "others_settings_fuzzy_skin#fuzzy-skin-mode");
|
||||
optgroup->append_single_option_line("fuzzy_skin_mode", "others_settings_fuzzy_skin#fuzzy-skin-generator-mode");
|
||||
optgroup->append_single_option_line("fuzzy_skin", "others_settings_fuzzy_skin");
|
||||
optgroup->append_single_option_line("fuzzy_skin_mode", "others_settings_fuzzy_skin#fuzzy-skin-mode");
|
||||
optgroup->append_single_option_line("fuzzy_skin_noise_type", "others_settings_fuzzy_skin#noise-type");
|
||||
optgroup->append_single_option_line("fuzzy_skin_point_distance", "others_settings_fuzzy_skin#point-distance");
|
||||
optgroup->append_single_option_line("fuzzy_skin_thickness", "others_settings_fuzzy_skin#skin-thickness");
|
||||
@@ -3283,7 +3283,7 @@ void TabPrint::build()
|
||||
option.opt.full_width = true;
|
||||
option.opt.is_code = true;
|
||||
option.opt.height = 15;
|
||||
optgroup->append_single_option_line(option, "others_settings_g_code_output#change-extrusion-role-g-code");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = page->new_optgroup(L("Post-processing Scripts"), L"param_gcode", 0);
|
||||
option = optgroup->get_option("post_process");
|
||||
@@ -3296,7 +3296,7 @@ void TabPrint::build()
|
||||
optgroup->hide_labels();
|
||||
option = optgroup->get_option("slicing_pipeline_plugin");
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option, "plugins_types#slicing-pipeline");
|
||||
optgroup->append_single_option_line(option, "others_settings_plugin_picker");
|
||||
|
||||
// Its own group: the one above hides its labels, and this row needs its label — and the revert
|
||||
// arrow beside it — to show. No label-width override either, as a 0 there means "no label column".
|
||||
@@ -3547,7 +3547,7 @@ void TabPrintModel::build()
|
||||
auto optgroup = page->new_optgroup("");
|
||||
optgroup->append_single_option_line("layer_height", "quality_settings_layer_height");
|
||||
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
|
||||
optgroup->append_single_option_line("wall_loops", "strength_settings_walls#wall-loops");
|
||||
optgroup->append_single_option_line("wall_loops", "strength_settings_walls");
|
||||
optgroup->append_single_option_line("enable_support", "support_settings_support");
|
||||
m_pages.pop_back();
|
||||
m_pages.insert(m_pages.begin(), page);
|
||||
|
||||
@@ -171,11 +171,6 @@ TaskManager::TaskManager(NetworkAgent* agent)
|
||||
;
|
||||
}
|
||||
|
||||
void TaskManager::set_agent(NetworkAgent* agent)
|
||||
{
|
||||
m_agent = agent;
|
||||
}
|
||||
|
||||
|
||||
int TaskManager::start_print(const std::vector<PrintParams>& params, TaskSettings* settings)
|
||||
{
|
||||
@@ -245,18 +240,17 @@ int TaskManager::schedule(TaskStateInfo* task)
|
||||
fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx);
|
||||
|
||||
int result = -1;
|
||||
NetworkAgent* agent = m_agent;
|
||||
if (!agent) {
|
||||
if (!m_agent) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "task_manager: NetworkAgent is nullptr";
|
||||
}
|
||||
else {
|
||||
assert(agent);
|
||||
assert(m_agent);
|
||||
try {
|
||||
// DEBUG FOR TEST
|
||||
#if 0
|
||||
result = start_print_test(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
|
||||
#else
|
||||
result = agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
|
||||
result = m_agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
|
||||
#endif
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "task_manager: start_print threw: " << ex.what();
|
||||
@@ -369,13 +363,12 @@ std::map<int, TaskStateInfo*> TaskManager::get_local_task_list()
|
||||
std::map<std::string, TaskStateInfo> TaskManager::get_task_list(int curr_page, int page_count, int& total)
|
||||
{
|
||||
std::map<std::string, TaskStateInfo> out;
|
||||
NetworkAgent* agent = m_agent;
|
||||
if (agent) {
|
||||
if (m_agent) {
|
||||
TaskQueryParams task_query_params;
|
||||
task_query_params.limit = page_count;
|
||||
task_query_params.offset = curr_page * page_count;
|
||||
std::string task_info;
|
||||
int result = agent->get_user_tasks(task_query_params, &task_info);
|
||||
int result = m_agent->get_user_tasks(task_query_params, &task_info);
|
||||
BOOST_LOG_TRIVIAL(trace) << "task_manager: get_task_list task_info=" << task_info;
|
||||
if (result == 0) {
|
||||
try {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <wx/event.h>
|
||||
|
||||
|
||||
@@ -159,8 +158,6 @@ public:
|
||||
static int SendingInterval;
|
||||
TaskManager(NetworkAgent* agent);
|
||||
|
||||
void set_agent(NetworkAgent* agent);
|
||||
|
||||
int start_print(const std::vector<PrintParams>& params, TaskSettings* settings = nullptr);
|
||||
|
||||
static void set_max_send_at_same_time(int count);
|
||||
@@ -187,8 +184,7 @@ private:
|
||||
std::vector<boost::thread*> m_sending_thread_list;
|
||||
std::mutex m_scedule_mutex;
|
||||
bool m_started { false };
|
||||
// Replaced on the main thread whenever networking is re-initialised, read by the sending threads.
|
||||
std::atomic<NetworkAgent*> m_agent { nullptr };
|
||||
NetworkAgent* m_agent { nullptr };
|
||||
|
||||
std::chrono::system_clock::time_point last_sent_timestamp;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gtk/gtk.h>
|
||||
@@ -666,35 +665,6 @@ void DropDown::messureSize()
|
||||
sub->mainDropDown->mouseMove(ev);
|
||||
e.RequestMore();
|
||||
});
|
||||
// Orca: the submenu holds the GTK grab, so GTK gives it the wheel events of the whole
|
||||
// chain. GDK names the window under the cursor, so re-emit main's events on main.
|
||||
g_signal_connect(sub->m_widget, "event", G_CALLBACK(+[](GtkWidget *, GdkEvent *event, gpointer data) -> gboolean {
|
||||
auto *sub = static_cast<DropDown *>(data);
|
||||
DropDown *main = sub->mainDropDown;
|
||||
if (event->type != GDK_SCROLL || !main || !main->IsShown() || !sub->IsShown())
|
||||
return FALSE;
|
||||
gpointer owner = nullptr;
|
||||
if (event->any.window)
|
||||
gdk_window_get_user_data(event->any.window, &owner);
|
||||
GtkWidget *widget = (owner && GTK_IS_WIDGET(owner)) ? GTK_WIDGET(owner) : nullptr;
|
||||
bool for_main = widget && (widget == main->m_widget || gtk_widget_is_ancestor(widget, main->m_widget));
|
||||
bool for_sub = widget && (widget == sub->m_widget || gtk_widget_is_ancestor(widget, sub->m_widget));
|
||||
if (!for_main && for_sub) {
|
||||
// The compositor may keep the pointer focus on the submenu while the cursor is over main.
|
||||
wxPoint pt((int) event->scroll.x, (int) event->scroll.y);
|
||||
if (sub->GetClientRect().Contains(pt))
|
||||
return FALSE;
|
||||
wxPoint off = sub->GetScreenRect().GetTopLeft() - main->GetScreenRect().GetTopLeft();
|
||||
event->scroll.x += off.x;
|
||||
event->scroll.y += off.y;
|
||||
for_main = true;
|
||||
}
|
||||
if (!for_main)
|
||||
return FALSE;
|
||||
// wx connects its mouse handlers to GetConnectWidget(), the inner widget of the popup.
|
||||
gtk_propagate_event(main->GetConnectWidget(), event);
|
||||
return TRUE;
|
||||
}), sub);
|
||||
#endif
|
||||
subDropDown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) {
|
||||
e.SetEventObject(this);
|
||||
|
||||
@@ -336,11 +336,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||
s_schemes_registered = true;
|
||||
}
|
||||
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
#ifndef __linux__
|
||||
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
|
||||
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
|
||||
#endif
|
||||
#endif
|
||||
#ifdef __WXMAC__
|
||||
WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend();
|
||||
Slic3r::GUI::WKWebView_setTransparentBackground(wkWebView);
|
||||
@@ -472,10 +470,8 @@ void WebView::RecreateAll()
|
||||
wxString language_code = Slic3r::GUI::wxGetApp().current_language_code().BeforeFirst('_');
|
||||
language_code = language_code.ToStdString();
|
||||
for (auto webView : g_webviews) {
|
||||
#ifndef __linux__
|
||||
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
|
||||
Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str()));
|
||||
#endif
|
||||
// A host-themed WebViewHostDialog re-themes in place (no reload). If it handles
|
||||
// the event, skip the reload; legacy pages fall through and reload as before
|
||||
// (their own dark.css swap re-themes them on reload).
|
||||
|
||||
@@ -61,6 +61,7 @@ struct CurlGlobalInit
|
||||
"/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed
|
||||
};
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
// Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally)
|
||||
const char *const SSL_CA_FILE = X509_get_default_cert_file_env();
|
||||
const char * ssl_cafile = ::getenv(SSL_CA_FILE);
|
||||
|
||||
@@ -52,6 +52,9 @@ std::string Moonraker::make_url(const std::string &path) const
|
||||
|
||||
void Moonraker::set_auth(Http &http) const
|
||||
{
|
||||
//ORCA: Moonraker accepts unauthenticated requests by default; X-Api-Key is the only auth header
|
||||
// defined by the Moonraker spec. HTTP Basic / Digest do NOT belong here even if the user
|
||||
// filled the user/password fields — those are PrusaLink/OctoPrint conventions.
|
||||
if (!m_apikey.empty())
|
||||
http.header("X-Api-Key", m_apikey);
|
||||
if (!m_cafile.empty())
|
||||
|
||||
@@ -27,10 +27,9 @@ class Http;
|
||||
// plateindex)
|
||||
// POST /printer/print/start (json) -- {"filename":"<name>.gcode"} starts print
|
||||
//
|
||||
// Auth: Send X-Api-Key when `printhost_apikey` is non-empty. Without a key,
|
||||
// requests rely on Moonraker's `[authorization] trusted_clients`; `force_logins`
|
||||
// overrides trusted-client access when enabled and at least one user exists.
|
||||
// HTTP Basic / Digest credentials are not sent.
|
||||
// Auth: X-Api-Key header if `printhost_apikey` is non-empty; Moonraker accepts
|
||||
// unauthenticated LAN access by default, so the key is optional. HTTP Basic /
|
||||
// Digest are not part of the Moonraker spec and are not sent.
|
||||
class Moonraker : public PrintHost
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "MoonrakerPrinterAgent.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
#include "bambu_networking.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
@@ -143,7 +142,7 @@ MoonrakerPrinterAgent::~MoonrakerPrinterAgent()
|
||||
|
||||
AgentInfo MoonrakerPrinterAgent::get_agent_info_static()
|
||||
{
|
||||
return AgentInfo{MOONRAKER_PRINTER_AGENT_ID, "Moonraker", MoonrakerPrinterAgent_VERSION, "Klipper/Moonraker printer agent"};
|
||||
return AgentInfo{"moonraker", "Moonraker", MoonrakerPrinterAgent_VERSION, "Klipper/Moonraker printer agent"};
|
||||
}
|
||||
|
||||
void MoonrakerPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
||||
@@ -1373,7 +1372,7 @@ void MoonrakerPrinterAgent::announce_printhost_device()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(state_mutex);
|
||||
ssdp_fn = on_ssdp_msg_fn;
|
||||
if (!ssdp_fn || device_info.dev_id.empty() || device_info.dev_ip.empty() || device_info.base_url.empty()) {
|
||||
if (!ssdp_fn) {
|
||||
return;
|
||||
}
|
||||
if (ssdp_announced_host == device_info.base_url && !ssdp_announced_id.empty()) {
|
||||
@@ -1394,6 +1393,11 @@ void MoonrakerPrinterAgent::announce_printhost_device()
|
||||
|
||||
const std::string model_id = device_info.model_id;
|
||||
|
||||
if (auto* app_config = GUI::wxGetApp().app_config) {
|
||||
const std::string access_code = device_info.api_key.empty() ? "88888888" : device_info.api_key;
|
||||
app_config->set_str("access_code", device_info.dev_id, access_code);
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["dev_name"] = dev_name;
|
||||
payload["dev_id"] = device_info.dev_id;
|
||||
|
||||
@@ -128,8 +128,8 @@ void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr<
|
||||
void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent)
|
||||
{
|
||||
m_user_machine_list_generation.fetch_add(1);
|
||||
// Invalidate queued discovery even when a cached agent instance is reused.
|
||||
m_discovery_session = std::make_shared<int>(0);
|
||||
|
||||
// Disconnect all callbacks from the old agent
|
||||
auto old_printer_agent = m_printer_agent;
|
||||
|
||||
m_printer_agent = std::move(printer_agent);
|
||||
@@ -155,7 +155,7 @@ void NetworkAgent::apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>&
|
||||
return;
|
||||
}
|
||||
|
||||
apply_discovery_callback(printer_agent, callbacks);
|
||||
printer_agent->set_on_ssdp_msg_fn(callbacks.on_ssdp_msg_fn);
|
||||
printer_agent->set_on_printer_connected_fn(callbacks.on_printer_connected_fn);
|
||||
printer_agent->set_on_subscribe_failure_fn(callbacks.on_subscribe_failure_fn);
|
||||
printer_agent->set_on_message_fn(callbacks.on_message_fn);
|
||||
@@ -166,26 +166,6 @@ void NetworkAgent::apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>&
|
||||
printer_agent->set_server_callback(callbacks.on_server_err_fn);
|
||||
}
|
||||
|
||||
int NetworkAgent::apply_discovery_callback(const std::shared_ptr<IPrinterAgent>& printer_agent,
|
||||
const PrinterCallbacks& callbacks)
|
||||
{
|
||||
if (!callbacks.on_ssdp_msg_fn)
|
||||
return printer_agent->set_on_ssdp_msg_fn(nullptr);
|
||||
|
||||
const std::weak_ptr<int> session = m_discovery_session;
|
||||
return printer_agent->set_on_ssdp_msg_fn(
|
||||
[session, fn = callbacks.on_ssdp_msg_fn, queue = callbacks.queue_on_main_fn](std::string payload) {
|
||||
// Without a UI queue this would run on the agent's thread, so drop it.
|
||||
if (session.expired() || !queue)
|
||||
return;
|
||||
queue([session, fn, payload = std::move(payload)] {
|
||||
// The agent may have changed while this event waited on the UI queue.
|
||||
if (!session.expired())
|
||||
fn(payload);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<ICloudServiceAgent> NetworkAgent::get_cloud_agent(const std::string& provider) const
|
||||
{
|
||||
const auto& key = (provider.empty() || provider == ORCA_CLOUD_PROVIDER) ? ORCA_CLOUD_PROVIDER : provider;
|
||||
@@ -205,10 +185,8 @@ int NetworkAgent::set_queue_on_main_fn(QueueOnMainFn fn, const std::string& prov
|
||||
int ret = -1;
|
||||
if (cloud_agent)
|
||||
ret = cloud_agent->set_queue_on_main_fn(fn);
|
||||
if (m_printer_agent) {
|
||||
if (m_printer_agent)
|
||||
m_printer_agent->set_queue_on_main_fn(fn);
|
||||
apply_discovery_callback(m_printer_agent, m_printer_callbacks);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -759,10 +737,9 @@ int NetworkAgent::get_mw_user_4ulist(int seed, int limit, std::function<void(std
|
||||
|
||||
int NetworkAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
|
||||
{
|
||||
m_discovery_session = std::make_shared<int>(0);
|
||||
m_printer_callbacks.on_ssdp_msg_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return apply_discovery_callback(m_printer_agent, m_printer_callbacks);
|
||||
return m_printer_agent->set_on_ssdp_msg_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,6 @@ public:
|
||||
int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
// Printer agent methods
|
||||
// Discovery is delivered through queue_on_main_fn, scoped to the live agent session.
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn);
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn);
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn);
|
||||
@@ -211,9 +210,6 @@ private:
|
||||
|
||||
void apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>& printer_agent,
|
||||
const PrinterCallbacks& callbacks);
|
||||
int apply_discovery_callback(const std::shared_ptr<IPrinterAgent>& printer_agent,
|
||||
const PrinterCallbacks& callbacks);
|
||||
std::shared_ptr<int> m_discovery_session = std::make_shared<int>(0);
|
||||
PrinterCallbacks m_printer_callbacks;
|
||||
bool enable_track = false;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
namespace Slic3r {
|
||||
static constexpr char ORCA_PRINTER_AGENT_ID[] = "orca";
|
||||
static constexpr char BBL_PRINTER_AGENT_ID[] = "bbl";
|
||||
static constexpr char MOONRAKER_PRINTER_AGENT_ID[] = "moonraker";
|
||||
|
||||
// Factory function type for creating printer agents
|
||||
using PrinterAgentFactory =
|
||||
|
||||
@@ -105,8 +105,7 @@ void require_vertex(const Polygon &polygon, const Point &point)
|
||||
|
||||
std::vector<SeamPlacerImpl::EnforcedBlockedSeamPoint> weak_candidate_types(
|
||||
const Polygon &polygon, const std::vector<PreciseSeam::WeakModifierSegment> &segments,
|
||||
const std::function<SeamPlacerImpl::EnforcedBlockedSeamPoint(const Point &)> &painted = {},
|
||||
PreciseSeam::PreciseSeamWarnings *warnings = nullptr)
|
||||
const std::function<SeamPlacerImpl::EnforcedBlockedSeamPoint(const Point &)> &painted = {})
|
||||
{
|
||||
// Use the same coordinate conversion as production when applying prepared boundaries.
|
||||
// Optional painted types stand in for seam painting, which production assigns before weak zones.
|
||||
@@ -121,7 +120,7 @@ std::vector<SeamPlacerImpl::EnforcedBlockedSeamPoint> weak_candidate_types(
|
||||
}
|
||||
loop.end_index = candidates.points.size();
|
||||
bool enforced = false;
|
||||
PreciseSeam::apply_weak_modifiers_to_perimeter(segments, candidates, loop, enforced, warnings);
|
||||
PreciseSeam::apply_weak_modifiers_to_perimeter(segments, candidates, loop, enforced);
|
||||
std::vector<SeamPlacerImpl::EnforcedBlockedSeamPoint> types;
|
||||
for (const auto &candidate : candidates.points)
|
||||
types.push_back(candidate.type);
|
||||
@@ -665,7 +664,7 @@ TEST_CASE("Weak priority and enforcement refinement apply on both crossed sides"
|
||||
check_square_boundary(perimeter);
|
||||
}
|
||||
|
||||
TEST_CASE("Weak full containment follows painting: the whole perimeter takes the zone's type", "[PreciseSeam]")
|
||||
TEST_CASE("Weak full containment follows painting: Enforced and Neutral type the whole perimeter, Blocked is skipped", "[PreciseSeam]")
|
||||
{
|
||||
const auto type = GENERATE(ModelVolumeType::PRECISE_SEAM_ENFORCED, ModelVolumeType::PRECISE_SEAM_BLOCKED,
|
||||
ModelVolumeType::PRECISE_SEAM_NEUTRAL);
|
||||
@@ -675,8 +674,6 @@ TEST_CASE("Weak full containment follows painting: the whole perimeter takes the
|
||||
const Points original = perimeter.points;
|
||||
const auto *modifier = fixture.add(type, {rectangle(-2, -2, 22, 22)});
|
||||
PreciseSeam::PreciseSeamWarnings warnings;
|
||||
// Registered as SeamPlacer::init() does, so the per-modifier warning flag is tracked.
|
||||
warnings.modifier_usage.try_emplace(modifier);
|
||||
const auto segments = PreciseSeam::collect_weak_modifier_segments(
|
||||
{modifier}, perimeter, PreciseSeam::PreparedPerimeter(perimeter), fixture.layer, fixture.cache, &warnings);
|
||||
// Painting of both kinds, so that each type's effect on it is visible.
|
||||
@@ -684,17 +681,23 @@ TEST_CASE("Weak full containment follows painting: the whole perimeter takes the
|
||||
return p.x() < scale_(10.) ? SeamPlacerImpl::EnforcedBlockedSeamPoint::Enforced :
|
||||
SeamPlacerImpl::EnforcedBlockedSeamPoint::Blocked;
|
||||
};
|
||||
const auto types = weak_candidate_types(perimeter, segments, painted, &warnings);
|
||||
const auto types = weak_candidate_types(perimeter, segments, painted);
|
||||
CHECK_FALSE(warnings.multiple_intersections.load());
|
||||
CHECK(warnings.failed_fragments.load() == 0);
|
||||
if (type == ModelVolumeType::PRECISE_SEAM_BLOCKED) {
|
||||
// Forbidding the seam all round cannot be honoured: skipped with a warning, painting stays.
|
||||
CHECK(segments.empty());
|
||||
CHECK(perimeter.points == original);
|
||||
CHECK(warnings.full_containment.load() == PreciseSeam::PreciseSeamWarnings::type_bit(type));
|
||||
for (size_t i = 0; i < perimeter.size(); ++i)
|
||||
CHECK(types[i] == painted(perimeter[i]));
|
||||
return;
|
||||
}
|
||||
REQUIRE(segments.size() == 1);
|
||||
CHECK(segments.front().whole_perimeter);
|
||||
CHECK(warnings.full_containment.load() == 0);
|
||||
// Only a whole Blocked perimeter is reported, as the seam cannot avoid it.
|
||||
CHECK(warnings.modifier_usage.at(modifier).fully_blocked.load() == (type == ModelVolumeType::PRECISE_SEAM_BLOCKED));
|
||||
// Every candidate takes the zone's type, overriding painting.
|
||||
const auto expected = type == ModelVolumeType::PRECISE_SEAM_ENFORCED ? SeamPlacerImpl::EnforcedBlockedSeamPoint::Enforced :
|
||||
type == ModelVolumeType::PRECISE_SEAM_BLOCKED ? SeamPlacerImpl::EnforcedBlockedSeamPoint::Blocked :
|
||||
SeamPlacerImpl::EnforcedBlockedSeamPoint::Neutral;
|
||||
for (size_t i = 0; i < perimeter.size(); ++i)
|
||||
CHECK(types[i] == expected);
|
||||
@@ -703,37 +706,29 @@ TEST_CASE("Weak full containment follows painting: the whole perimeter takes the
|
||||
CHECK(perimeter.size() >= size_t(80.f / SeamPlacer::enforcer_oversampling_distance));
|
||||
check_square_boundary(perimeter);
|
||||
} else
|
||||
CHECK(perimeter.points == original); // Like a perimeter unmarked or painted red all round: no subdivision.
|
||||
CHECK(perimeter.points == original); // Like an unmarked perimeter: no subdivision.
|
||||
}
|
||||
|
||||
TEST_CASE("Whole-perimeter weak zones take part in the usual priority order", "[PreciseSeam]")
|
||||
{
|
||||
// Volumes are listed low priority first; a later zone overwrites an earlier one.
|
||||
const int scenario = GENERATE(0, 1, 2, 3, 4, 5, 6);
|
||||
const int scenario = GENERATE(0, 1, 2);
|
||||
CAPTURE(scenario);
|
||||
SeamFixture fixture;
|
||||
const auto *whole_enforced = fixture.add(ModelVolumeType::PRECISE_SEAM_ENFORCED, {rectangle(-2, -2, 22, 22)});
|
||||
const auto *whole_neutral = fixture.add(ModelVolumeType::PRECISE_SEAM_NEUTRAL, {rectangle(-2, -2, 22, 22)});
|
||||
const auto *whole_blocked = fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, {rectangle(-2, -2, 22, 22)});
|
||||
const auto *lower_whole_blocked = fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, {rectangle(-2, -2, 22, 22)});
|
||||
const auto *band_blocked = fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, {rectangle(8, -2, 12, 2)});
|
||||
const auto *band_enforced = fixture.add(ModelVolumeType::PRECISE_SEAM_ENFORCED, {rectangle(8, -2, 12, 2)});
|
||||
std::vector<const ModelVolume*> volumes;
|
||||
if (scenario == 0) volumes = {whole_enforced, band_blocked}; // A band forbidden inside a green perimeter.
|
||||
if (scenario == 1) volumes = {band_enforced, whole_neutral}; // A whole Neutral clears the lower band.
|
||||
if (scenario == 2) volumes = {band_enforced, whole_blocked}; // A whole Blocked overrides the lower band.
|
||||
if (scenario == 3) volumes = {whole_blocked, band_enforced}; // A higher band unblocks part of a whole Blocked.
|
||||
if (scenario == 4) volumes = {whole_blocked, band_blocked}; // A higher Blocked band keeps it whole Blocked.
|
||||
if (scenario == 5) volumes = {lower_whole_blocked, whole_enforced, whole_blocked}; // The lower one is overwritten.
|
||||
if (scenario == 6) volumes = {lower_whole_blocked, whole_blocked}; // Only the higher one is in effect.
|
||||
if (scenario == 2) volumes = {band_enforced, whole_blocked}; // A whole Blocked is skipped; the band stays.
|
||||
Polygon perimeter = rectangle(0, 0, 20, 20);
|
||||
PreciseSeam::PreciseSeamWarnings warnings;
|
||||
// Registered as SeamPlacer::init() does, so the per-modifier warning flag is tracked.
|
||||
warnings.modifier_usage.try_emplace(whole_blocked);
|
||||
warnings.modifier_usage.try_emplace(lower_whole_blocked);
|
||||
const auto segments = PreciseSeam::collect_weak_modifier_segments(
|
||||
volumes, perimeter, PreciseSeam::PreparedPerimeter(perimeter), fixture.layer, fixture.cache, &warnings);
|
||||
const auto types = weak_candidate_types(perimeter, segments, {}, &warnings);
|
||||
const auto types = weak_candidate_types(perimeter, segments);
|
||||
using Type = SeamPlacerImpl::EnforcedBlockedSeamPoint;
|
||||
for (size_t i = 0; i < perimeter.size(); ++i) {
|
||||
const Point &p = perimeter[i];
|
||||
@@ -741,38 +736,14 @@ TEST_CASE("Whole-perimeter weak zones take part in the usual priority order", "[
|
||||
const bool in_band = p.y() == 0 && p.x() >= scale_(8.) && p.x() <= scale_(12.);
|
||||
const Type expected = scenario == 0 ? (in_band ? Type::Blocked : Type::Enforced) :
|
||||
scenario == 1 ? Type::Neutral :
|
||||
scenario == 3 ? (in_band ? Type::Enforced : Type::Blocked) :
|
||||
Type::Blocked;
|
||||
(in_band ? Type::Enforced : Type::Neutral);
|
||||
CHECK(types[i] == expected);
|
||||
}
|
||||
CHECK(warnings.full_containment.load() == 0);
|
||||
// The warning follows the final typing: only a perimeter left whole Blocked is reported, through the
|
||||
// highest whole zone.
|
||||
const bool expected_fully_blocked = scenario == 2 || scenario == 4 || scenario == 5 || scenario == 6;
|
||||
CHECK(warnings.modifier_usage.at(whole_blocked).fully_blocked.load() == expected_fully_blocked);
|
||||
CHECK_FALSE(warnings.modifier_usage.at(lower_whole_blocked).fully_blocked.load());
|
||||
CHECK(warnings.full_containment.load() ==
|
||||
(scenario == 2 ? PreciseSeam::PreciseSeamWarnings::type_bit(ModelVolumeType::PRECISE_SEAM_BLOCKED) : 0u));
|
||||
check_square_boundary(perimeter);
|
||||
}
|
||||
|
||||
TEST_CASE("A partial Blocked zone on a perimeter painted red all round gives no whole-perimeter warning", "[PreciseSeam]")
|
||||
{
|
||||
SeamFixture fixture;
|
||||
const auto *band_blocked = fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, {rectangle(8, -2, 12, 2)});
|
||||
Polygon perimeter = rectangle(0, 0, 20, 20);
|
||||
PreciseSeam::PreciseSeamWarnings warnings;
|
||||
warnings.modifier_usage.try_emplace(band_blocked);
|
||||
const auto segments = PreciseSeam::collect_weak_modifier_segments(
|
||||
{band_blocked}, perimeter, PreciseSeam::PreparedPerimeter(perimeter), fixture.layer, fixture.cache, &warnings);
|
||||
REQUIRE(segments.size() == 1);
|
||||
// Red painting makes every candidate Blocked, so the check reaches the whole-zone filter.
|
||||
const auto painted = [](const Point &) { return SeamPlacerImpl::EnforcedBlockedSeamPoint::Blocked; };
|
||||
const auto types = weak_candidate_types(perimeter, segments, painted, &warnings);
|
||||
for (const auto type : types)
|
||||
CHECK(type == SeamPlacerImpl::EnforcedBlockedSeamPoint::Blocked);
|
||||
// Only a modifier that covers the whole perimeter is reported.
|
||||
CHECK_FALSE(warnings.modifier_usage.at(band_blocked).fully_blocked.load());
|
||||
}
|
||||
|
||||
TEST_CASE("Coverage short by a gap under 1 um is full containment while a narrow band keeps its point", "[PreciseSeam][Regression]")
|
||||
{
|
||||
// A tip pokes through the bottom edge at x = 10 mm by `depth` nm, so the perimeter line crosses it
|
||||
@@ -858,15 +829,19 @@ TEST_CASE("A modifier touching the perimeter at one point keeps the full contain
|
||||
else {
|
||||
const auto segments = PreciseSeam::collect_weak_modifier_segments(
|
||||
{modifier}, perimeter, PreciseSeam::PreparedPerimeter(perimeter), fixture.layer, fixture.cache, &warnings);
|
||||
// A weak modifier gives its type to the whole perimeter.
|
||||
REQUIRE(segments.size() == 1);
|
||||
CHECK(segments.front().whole_perimeter);
|
||||
// Enforced and Neutral type the whole perimeter; Blocked is skipped.
|
||||
if (type == ModelVolumeType::PRECISE_SEAM_BLOCKED)
|
||||
CHECK(segments.empty());
|
||||
else {
|
||||
REQUIRE(segments.size() == 1);
|
||||
CHECK(segments.front().whole_perimeter);
|
||||
}
|
||||
}
|
||||
// Only a whole-perimeter Enforced zone subdivides the edges.
|
||||
if (type != ModelVolumeType::PRECISE_SEAM_ENFORCED)
|
||||
CHECK(perimeter.points == original);
|
||||
// Only strong types are skipped with the full-containment warning.
|
||||
CHECK(warnings.full_containment.load() == (is_precise_seam_strong(type) ? PreciseSeam::PreciseSeamWarnings::type_bit(type) : 0u));
|
||||
const bool skipped = is_precise_seam_strong(type) || type == ModelVolumeType::PRECISE_SEAM_BLOCKED;
|
||||
CHECK(warnings.full_containment.load() == (skipped ? PreciseSeam::PreciseSeamWarnings::type_bit(type) : 0u));
|
||||
CHECK_FALSE(warnings.multiple_intersections.load());
|
||||
CHECK(warnings.failed_fragments.load() == 0);
|
||||
}
|
||||
@@ -1201,7 +1176,7 @@ TEST_CASE("Modifier usage marks evaluated modifiers that never reach a perimeter
|
||||
CHECK(checked(short_weak));
|
||||
CHECK_FALSE(reached(short_weak));
|
||||
CHECK(reached(crossing_weak));
|
||||
// Full containment counts as reaching the perimeter.
|
||||
// Full containment reached the perimeter even though it is skipped.
|
||||
CHECK(reached(containing_weak));
|
||||
|
||||
// Strong: evaluation stops at the first modifier with a segment. The one ending 0.5 mm before the
|
||||
@@ -1418,24 +1393,6 @@ TEST_CASE("Weak modifiers move the exported seam into or out of their zone", "[P
|
||||
CHECK(fixture.warning().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("A higher-priority Enforced band suppresses the whole-perimeter Blocked export warning", "[PreciseSeam][Regression]")
|
||||
{
|
||||
ExportFixture fixture("back");
|
||||
// The earlier model volume has higher priority; weak application reverses this order.
|
||||
fixture.add(ModelVolumeType::PRECISE_SEAM_ENFORCED, make_cube(2, 6, 4), Vec3d(4, -3, -1), "front enforcer");
|
||||
fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, make_cube(30, 30, 4), Vec3d(-5, -5, -1), "whole blocker");
|
||||
const auto loops = outer_wall_loops(fixture.export_gcode());
|
||||
REQUIRE(loops.size() == 10);
|
||||
for (const OuterLoop &loop : loops) {
|
||||
CAPTURE(loop.seam.x(), loop.seam.y());
|
||||
// The band wins on every layer, so the empty warning is not from modifiers that did nothing.
|
||||
CHECK_THAT(loop.seam.y(), Catch::Matchers::WithinAbs(loop.bounds.min.y() - loop.bounds.center().y(), 0.01));
|
||||
CHECK(loop.seam.x() >= -6.01);
|
||||
CHECK(loop.seam.x() <= -3.99);
|
||||
}
|
||||
CHECK(fixture.warning().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("A strong modifier with a hole uses the left of two equal crossings", "[PreciseSeam]")
|
||||
{
|
||||
// The hole splits the front face crossing into x = 2..4 and x = 8..10 mm. The lengths tie, both
|
||||
@@ -1465,49 +1422,14 @@ TEST_CASE("Full containment warns for Blocked but not for Enforced", "[PreciseSe
|
||||
{
|
||||
const auto type = GENERATE(ModelVolumeType::PRECISE_SEAM_ENFORCED, ModelVolumeType::PRECISE_SEAM_BLOCKED);
|
||||
ExportFixture fixture("back");
|
||||
fixture.add(type, make_cube(30, 30, 4), Vec3d(-5, -5, -1), "whole helper");
|
||||
const auto loops = outer_wall_loops(fixture.export_gcode());
|
||||
// The whole perimeter takes the type either way, so the seam still follows the back preference.
|
||||
REQUIRE(loops.size() == 10);
|
||||
for (const OuterLoop &loop : loops)
|
||||
CHECK(loop.seam.y() > 7.);
|
||||
fixture.add(type, make_cube(30, 30, 4), Vec3d(-5, -5, -1));
|
||||
fixture.export_gcode();
|
||||
if (type == ModelVolumeType::PRECISE_SEAM_BLOCKED)
|
||||
CHECK(fixture.warning().find("Seam Blocked modifier \"whole helper\" of") != std::string::npos);
|
||||
CHECK(fixture.warning().find("Seam Blocked") != std::string::npos);
|
||||
else
|
||||
CHECK(fixture.warning().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("A perimeter blocked all round gets the seam of an unmarked one", "[PreciseSeam][Regression]")
|
||||
{
|
||||
// Equal Blocked candidates compare like Neutral ones, so random and aligned seams still vary and align.
|
||||
const std::string seam_position = GENERATE(as<std::string>{}, "random", "aligned", "aligned_back", "back", "nearest");
|
||||
CAPTURE(seam_position);
|
||||
ExportFixture unmarked(seam_position);
|
||||
const auto expected = outer_wall_loops(unmarked.export_gcode());
|
||||
ExportFixture blocked(seam_position);
|
||||
blocked.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, make_cube(30, 30, 4), Vec3d(-5, -5, -1));
|
||||
const auto loops = outer_wall_loops(blocked.export_gcode());
|
||||
REQUIRE(loops.size() == expected.size());
|
||||
for (size_t i = 0; i < loops.size(); ++i) {
|
||||
CAPTURE(i, loops[i].seam.x(), loops[i].seam.y(), expected[i].seam.x(), expected[i].seam.y());
|
||||
CHECK((loops[i].seam - expected[i].seam).norm() < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("The whole-perimeter Blocked warning names the first modifier and counts them all", "[PreciseSeam]")
|
||||
{
|
||||
ExportFixture fixture("back");
|
||||
// Separate layers: where both covered the same perimeter, only the higher-priority one would count.
|
||||
fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, make_cube(30, 30, 1.4), Vec3d(-5, -5, -1), "first blocker");
|
||||
fixture.add(ModelVolumeType::PRECISE_SEAM_BLOCKED, make_cube(30, 30, 2), Vec3d(-5, -5, 1), "second blocker");
|
||||
fixture.export_gcode();
|
||||
const std::string warning = fixture.warning();
|
||||
CAPTURE(warning);
|
||||
CHECK(warning.find("Seam Blocked modifier \"first blocker\" of") != std::string::npos);
|
||||
CHECK(warning.find("(2 in total) covers an entire perimeter") != std::string::npos);
|
||||
CHECK(warning.find("second blocker") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("Belt printers slice Precise Seam modifiers in the frame the object was sliced in", "[PreciseSeam][belt]")
|
||||
{
|
||||
Model model;
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Slicing.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/Support/TreeModelVolumes.hpp"
|
||||
@@ -1597,58 +1596,3 @@ TEST_CASE("Belt supports reach the belt under a leading overhang", "[Print][belt
|
||||
CHECK(lowest->print_z - floor_under_lowest > -0.2 - EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("The build height check counts the Z shrinkage compensation once", "[Print][validate][Regression]")
|
||||
{
|
||||
// printable_height is 100 mm and Z shrinks to 95 %, so the object is sliced 100 / 95 times taller.
|
||||
// 92 mm become about 96.8 mm and fit; 97 mm become about 102.1 mm and only the compensation makes them too tall; 101 mm are too tall on their own.
|
||||
const auto [height, expected] = GENERATE(table<double, std::string>({
|
||||
{ 92., "" },
|
||||
{ 97., "because of material shrinkage compensation" },
|
||||
{ 101., "exceeds the maximum build volume height." },
|
||||
}));
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "printable_height", 100. },
|
||||
{ "filament_shrinkage_compensation_z", "95%" },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ make_cube(20., 20., height) }, print, model, config);
|
||||
const std::string error = print.validate().string;
|
||||
INFO(error);
|
||||
if (expected.empty())
|
||||
CHECK(error.empty());
|
||||
else
|
||||
CHECK(error.find(expected) != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("The build height check counts the raft", "[Print][validate][Regression]")
|
||||
{
|
||||
// printable_height is 100 mm; three raft layers lift the object by 0.9 mm.
|
||||
const auto [height, fits] = GENERATE(table<double, bool>({ { 98.8, true }, { 99.8, false } }));
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "printable_height", 100. },
|
||||
{ "raft_layers", 3 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ make_cube(20., 20., height) }, print, model, config);
|
||||
const SlicingParameters &slicing_params = print.objects().front()->slicing_parameters();
|
||||
REQUIRE_THAT(slicing_params.object_print_z_min, Catch::Matchers::WithinAbs(0.9, EPSILON));
|
||||
const std::string error = print.validate().string;
|
||||
INFO(error);
|
||||
if (fits)
|
||||
CHECK(error.empty());
|
||||
else
|
||||
CHECK(error.find("maximum build volume height") != std::string::npos);
|
||||
}
|
||||
|
||||
@@ -36,18 +36,12 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/filesystem/file_status.hpp> // no_perms, owner_all
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
|
||||
#include <fstream>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h> // geteuid
|
||||
#endif
|
||||
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <Eigen/Core>
|
||||
#include <Eigen/Geometry>
|
||||
@@ -2319,47 +2313,3 @@ SCENARIO("thumbnail relationships are declared only for thumbnails that were wri
|
||||
}
|
||||
}
|
||||
}
|
||||
// has_restore_data() runs from the queued EVT_RESTORE_PROJECT handler, whose try starts after
|
||||
// the call, so a filesystem_error from probing the stored backup path aborts the application.
|
||||
// The realistic trigger is an absolute path from another user's config whose home is not
|
||||
// searchable, so the parent, not the backup directory, is what blocks.
|
||||
#ifndef _WIN32
|
||||
TEST_CASE("has_restore_data reports no restore data for a path it cannot probe", "[3mf]") {
|
||||
if (::geteuid() == 0)
|
||||
SKIP("root ignores the directory permissions this test depends on");
|
||||
|
||||
ScopedTemporaryDir outer("orca_restore");
|
||||
boost::filesystem::path backup = outer.path() / "backup";
|
||||
REQUIRE(boost::filesystem::create_directory(backup));
|
||||
std::ofstream((backup / ".3mf").string()) << "stand-in for the backed-up project";
|
||||
std::ofstream((backup / "origin.txt").string()) << "project.3mf";
|
||||
|
||||
// Control: while the path is readable the session is found, so the false below really is
|
||||
// the probe failing and not a mis-built fixture.
|
||||
std::string readable = backup.string(), origin;
|
||||
REQUIRE(has_restore_data(readable, origin));
|
||||
REQUIRE(readable == (backup / ".3mf").string());
|
||||
REQUIRE(origin == "project.3mf");
|
||||
|
||||
// Puts the permissions back before ~ScopedTemporaryDir needs them to clean up, and does so
|
||||
// even if an assertion below throws.
|
||||
struct Searchable {
|
||||
boost::filesystem::path dir;
|
||||
~Searchable() { boost::system::error_code ec;
|
||||
boost::filesystem::permissions(dir, boost::filesystem::owner_all, ec); }
|
||||
} restore{ outer.path() };
|
||||
|
||||
// Non-throwing here too: a filesystem that refuses the mode change has to reach the skip
|
||||
// below rather than fail the test. The probe then still succeeds, which is that same case.
|
||||
boost::system::error_code probe_ec;
|
||||
boost::filesystem::permissions(outer.path(), boost::filesystem::no_perms, probe_ec);
|
||||
if (boost::filesystem::exists(backup / ".3mf", probe_ec) || !probe_ec)
|
||||
SKIP("the backup path could not be made unprobeable here");
|
||||
|
||||
std::string unprobeable = backup.string(), origin2;
|
||||
bool has = true;
|
||||
REQUIRE_NOTHROW(has = has_restore_data(unprobeable, origin2));
|
||||
CHECK_FALSE(has);
|
||||
CHECK(unprobeable == backup.string()); // not rewritten to the .3mf it could not confirm
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "../slic3rutils/plugin_test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@@ -48,105 +46,6 @@ TEST_CASE("AppConfig network version helpers", "[AppConfig]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Local machine agent replacements survive save and reload", "[AppConfig]")
|
||||
{
|
||||
ScopedDataDir data_dir("local-machine");
|
||||
save_main_thread_id();
|
||||
AppConfig config;
|
||||
BBLocalMachine saved;
|
||||
saved.dev_id = "127.0.0.1:7125";
|
||||
saved.dev_ip = saved.dev_id;
|
||||
saved.dev_name = "Test printer";
|
||||
saved.printer_type = "test-model";
|
||||
saved.printer_agent_id = "orca";
|
||||
saved.access_code = "old-key";
|
||||
config.update_local_machine(saved);
|
||||
config.save();
|
||||
REQUIRE_FALSE(config.dirty());
|
||||
|
||||
BBLocalMachine replacement = saved;
|
||||
replacement.printer_agent_id = "moonraker";
|
||||
replacement.access_code = GENERATE("", "new-key", "88888888");
|
||||
config.update_local_machine(replacement);
|
||||
REQUIRE(config.dirty());
|
||||
REQUIRE(config.get_local_machines().at(saved.dev_id) == replacement);
|
||||
config.save();
|
||||
|
||||
AppConfig reloaded;
|
||||
REQUIRE(reloaded.load().empty());
|
||||
REQUIRE(reloaded.get_local_machines().at(saved.dev_id) == replacement);
|
||||
REQUIRE_FALSE(reloaded.dirty());
|
||||
reloaded.update_local_machine(replacement);
|
||||
REQUIRE_FALSE(reloaded.dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("Clearing local credentials preserves another agent's record", "[AppConfig]")
|
||||
{
|
||||
AppConfig config;
|
||||
BBLocalMachine saved;
|
||||
saved.dev_id = "test_dev";
|
||||
saved.printer_agent_id = GENERATE("orca", "qidi", "bbl", "");
|
||||
saved.access_code = "saved-key";
|
||||
config.update_local_machine(saved);
|
||||
config.set_str("access_code", saved.dev_id, "legacy-key");
|
||||
config.set_str("user_access_code", saved.dev_id, "legacy-user-key");
|
||||
|
||||
config.clear_local_machine_access_code(saved.dev_id, "moonraker");
|
||||
|
||||
REQUIRE(config.get_local_machines().at(saved.dev_id) == saved);
|
||||
REQUIRE(config.get("access_code", saved.dev_id) == "legacy-key");
|
||||
REQUIRE(config.get("user_access_code", saved.dev_id) == "legacy-user-key");
|
||||
}
|
||||
|
||||
TEST_CASE("Clearing local credentials changes only the owning agent's key", "[AppConfig]")
|
||||
{
|
||||
ScopedDataDir data_dir("local-machine-clear");
|
||||
save_main_thread_id();
|
||||
AppConfig config;
|
||||
BBLocalMachine saved;
|
||||
saved.dev_id = "test_dev";
|
||||
saved.dev_name = "Test printer";
|
||||
saved.printer_agent_id = GENERATE("moonraker", "qidi");
|
||||
saved.access_code = "saved-key";
|
||||
config.update_local_machine(saved);
|
||||
config.save();
|
||||
REQUIRE_FALSE(config.dirty());
|
||||
|
||||
config.clear_local_machine_access_code(saved.dev_id, saved.printer_agent_id);
|
||||
REQUIRE(config.dirty());
|
||||
config.save();
|
||||
|
||||
AppConfig reloaded;
|
||||
REQUIRE(reloaded.load().empty());
|
||||
saved.access_code.clear();
|
||||
REQUIRE(reloaded.get_local_machines().at(saved.dev_id) == saved);
|
||||
REQUIRE_FALSE(reloaded.dirty());
|
||||
reloaded.clear_local_machine_access_code(saved.dev_id, saved.printer_agent_id);
|
||||
reloaded.clear_local_machine_access_code("unknown_dev", saved.printer_agent_id);
|
||||
REQUIRE(reloaded.get_local_machines().size() == 1);
|
||||
REQUIRE_FALSE(reloaded.dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("BBL credential clearing removes legacy fallback without changing foreign records", "[AppConfig]")
|
||||
{
|
||||
AppConfig config;
|
||||
BBLocalMachine saved;
|
||||
saved.dev_id = "test_dev";
|
||||
saved.printer_agent_id = GENERATE("bbl", "", "moonraker");
|
||||
saved.access_code = "saved-key";
|
||||
config.update_local_machine(saved);
|
||||
config.set_str("access_code", saved.dev_id, "legacy-key");
|
||||
config.set_str("user_access_code", saved.dev_id, "legacy-user-key");
|
||||
|
||||
config.clear_local_machine_access_code(saved.dev_id, GENERATE("bbl", ""));
|
||||
|
||||
if (saved.printer_agent_id != "moonraker")
|
||||
saved.access_code.clear();
|
||||
REQUIRE(config.get_local_machines().at(saved.dev_id) == saved);
|
||||
REQUIRE(config.get("access_code", saved.dev_id).empty());
|
||||
REQUIRE(config.get("user_access_code", saved.dev_id).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Remembered checkbox settings retain both selections", "[AppConfig][Regression]") {
|
||||
AppConfig config;
|
||||
const bool checked = GENERATE(false, true);
|
||||
|
||||
@@ -5,9 +5,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_config_value_formatter.cpp
|
||||
test_creality_cfs_match.cpp
|
||||
test_dev_mapping.cpp
|
||||
test_device_access.cpp
|
||||
test_moonraker_discovery.cpp
|
||||
test_network_discovery.cpp
|
||||
test_filament_bitmap_utils.cpp
|
||||
test_device_progress.cpp
|
||||
test_device_manager_integration.cpp
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#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_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevManager.h"
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/Utils/IPrinterAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "slic3r/Utils/MoonrakerPrinterAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Moonraker permits an empty access code", "[DeviceAccess]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||
machine.printer_agent_id = MOONRAKER_PRINTER_AGENT_ID;
|
||||
machine.set_access_code("", false);
|
||||
|
||||
REQUIRE(machine.has_access_right());
|
||||
}
|
||||
|
||||
TEST_CASE("Moonraker logout revokes access until explicitly rebound", "[DeviceAccess]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||
machine.printer_agent_id = MOONRAKER_PRINTER_AGENT_ID;
|
||||
const std::string code = GENERATE("", "configured-key");
|
||||
machine.set_access_code(code, false);
|
||||
REQUIRE(machine.has_access_right());
|
||||
|
||||
machine.revoke_access();
|
||||
REQUIRE(machine.get_access_code().empty());
|
||||
REQUIRE_FALSE(machine.has_access_right());
|
||||
|
||||
machine.set_access_code(code, false);
|
||||
REQUIRE(machine.has_access_right());
|
||||
REQUIRE(machine.get_access_code() == code);
|
||||
}
|
||||
|
||||
TEST_CASE("Revoked access prevents calls to the printer agent", "[DeviceAccess]")
|
||||
{
|
||||
class RecordingAgent : public MoonrakerPrinterAgent
|
||||
{
|
||||
public:
|
||||
RecordingAgent() : MoonrakerPrinterAgent("") {}
|
||||
int connections = 0;
|
||||
int connect_printer(const PrinterConnectionParams&) override
|
||||
{
|
||||
++connections;
|
||||
return 0;
|
||||
}
|
||||
int disconnect_printer() override { return 0; }
|
||||
};
|
||||
|
||||
auto agent = std::make_shared<RecordingAgent>();
|
||||
NetworkAgent network(nullptr, agent);
|
||||
MachineObject machine(nullptr, &network, "test", "test_dev", "127.0.0.1");
|
||||
machine.printer_agent_id = GENERATE(BBL_PRINTER_AGENT_ID, ORCA_PRINTER_AGENT_ID, MOONRAKER_PRINTER_AGENT_ID);
|
||||
machine.set_access_code("configured-key", false);
|
||||
REQUIRE(machine.connect() == 0);
|
||||
REQUIRE(agent->connections == 1);
|
||||
|
||||
machine.revoke_access();
|
||||
REQUIRE(machine.connect() == -1);
|
||||
REQUIRE(agent->connections == 1);
|
||||
|
||||
machine.set_access_code("configured-key", false);
|
||||
REQUIRE(machine.connect() == 0);
|
||||
REQUIRE(agent->connections == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Other printer agents require an access code", "[DeviceAccess]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
|
||||
machine.printer_agent_id = GENERATE(
|
||||
BBL_PRINTER_AGENT_ID, ORCA_PRINTER_AGENT_ID, "qidi", "snapmaker", "crealityprint", "plugin", "unknown");
|
||||
|
||||
machine.set_access_code("", false);
|
||||
REQUIRE_FALSE(machine.has_access_right());
|
||||
|
||||
machine.set_access_code("88888888", false);
|
||||
REQUIRE(machine.has_access_right());
|
||||
}
|
||||
|
||||
TEST_CASE("Rebinding Moonraker with an empty key saves the machine", "[DeviceAccess]")
|
||||
{
|
||||
AppConfig config;
|
||||
DeviceManager manager(nullptr, false, &config);
|
||||
MachineObject machine(&manager, nullptr, "test", "test_dev", "127.0.0.1");
|
||||
machine.printer_agent_id = MOONRAKER_PRINTER_AGENT_ID;
|
||||
machine.dev_connection_type = "lan";
|
||||
machine.revoke_access();
|
||||
|
||||
machine.set_access_code("");
|
||||
const auto& machines = config.get_local_machines();
|
||||
REQUIRE(machines.count("test_dev") == 1);
|
||||
REQUIRE(machines.at("test_dev").printer_agent_id == MOONRAKER_PRINTER_AGENT_ID);
|
||||
REQUIRE(machines.at("test_dev").access_code.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Moonraker discovery leaves another agent's saved machine alone", "[DeviceAccess]")
|
||||
{
|
||||
AppConfig config;
|
||||
NetworkAgent network(nullptr, std::make_shared<MoonrakerPrinterAgent>(""));
|
||||
DeviceManager manager(&network, false, &config);
|
||||
|
||||
const std::string owner = GENERATE(as<std::string>{}, ORCA_PRINTER_AGENT_ID, MOONRAKER_PRINTER_AGENT_ID);
|
||||
BBLocalMachine saved;
|
||||
saved.dev_id = "192.0.2.10:7125";
|
||||
saved.dev_ip = saved.dev_id;
|
||||
saved.printer_agent_id = owner;
|
||||
config.update_local_machine(saved);
|
||||
|
||||
manager.on_machine_alive(R"({"dev_name":"Klipper","dev_id":"192.0.2.10:7125","dev_ip":"192.0.2.10:7125",
|
||||
"dev_type":"","dev_signal":"0","connect_type":"lan","bind_state":"free"})");
|
||||
REQUIRE((manager.get_local_machine(saved.dev_id) != nullptr) == (owner == MOONRAKER_PRINTER_AGENT_ID));
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <string>
|
||||
#include "slic3r/Utils/MoonrakerPrinterAgent.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
class DiscoveryAgent : public MoonrakerPrinterAgent
|
||||
{
|
||||
public:
|
||||
using MoonrakerPrinterAgent::MoonrakerPrinterAgent;
|
||||
using MoonrakerPrinterAgent::device_info;
|
||||
mutable unsigned fetch_count = 0;
|
||||
|
||||
protected:
|
||||
bool fetch_device_info(const std::string&, const std::string&, MoonrakerDeviceInfo& info, std::string&) const override
|
||||
{
|
||||
++fetch_count;
|
||||
info.dev_name = "Test printer";
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Moonraker discovery waits for a complete device identity", "[MoonrakerDiscovery]")
|
||||
{
|
||||
DiscoveryAgent agent("");
|
||||
unsigned announcements = 0;
|
||||
nlohmann::json announcement;
|
||||
agent.set_on_ssdp_msg_fn([&](std::string payload) {
|
||||
++announcements;
|
||||
announcement = nlohmann::json::parse(payload);
|
||||
});
|
||||
|
||||
agent.start_discovery(true, false);
|
||||
REQUIRE(announcements == 0);
|
||||
REQUIRE(agent.fetch_count == 0);
|
||||
|
||||
agent.device_info.dev_id = "127.0.0.1:7125";
|
||||
agent.device_info.dev_ip = agent.device_info.dev_id;
|
||||
agent.device_info.base_url = "http://127.0.0.1:7125";
|
||||
agent.start_discovery(true, false);
|
||||
REQUIRE(announcements == 1);
|
||||
REQUIRE(agent.fetch_count == 1);
|
||||
REQUIRE(announcement.at("dev_id") == agent.device_info.dev_id);
|
||||
REQUIRE(announcement.at("dev_ip") == agent.device_info.dev_ip);
|
||||
REQUIRE(announcement.at("dev_name") == "Test printer");
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "slic3r/Utils/MoonrakerPrinterAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
class DiscoverySource : public MoonrakerPrinterAgent
|
||||
{
|
||||
public:
|
||||
DiscoverySource() : MoonrakerPrinterAgent("") {}
|
||||
|
||||
OnMsgArrivedFn discovery_callback;
|
||||
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override
|
||||
{
|
||||
discovery_callback = std::move(fn);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Queued discovery cannot cross printer agent sessions", "[NetworkDiscovery]")
|
||||
{
|
||||
auto source = std::make_shared<DiscoverySource>();
|
||||
NetworkAgent network(nullptr, source);
|
||||
std::vector<std::function<void()>> pending;
|
||||
std::vector<std::string> delivered;
|
||||
network.set_queue_on_main_fn([&](std::function<void()> fn) { pending.push_back(std::move(fn)); });
|
||||
network.set_on_ssdp_msg_fn([&](std::string payload) { delivered.push_back(std::move(payload)); });
|
||||
|
||||
auto outgoing_callback = source->discovery_callback;
|
||||
outgoing_callback("unsaved-device");
|
||||
outgoing_callback("saved-moonraker-device");
|
||||
REQUIRE(delivered.empty());
|
||||
REQUIRE(pending.size() == 2);
|
||||
|
||||
bool expect_active = true;
|
||||
SECTION("Switch to another agent")
|
||||
{
|
||||
network.set_printer_agent(std::make_shared<DiscoverySource>());
|
||||
}
|
||||
SECTION("Clear the live agent")
|
||||
{
|
||||
network.set_printer_agent(nullptr);
|
||||
expect_active = false;
|
||||
}
|
||||
SECTION("Switch away and back to the cached agent")
|
||||
{
|
||||
network.set_printer_agent(std::make_shared<DiscoverySource>());
|
||||
network.set_printer_agent(source);
|
||||
}
|
||||
SECTION("Unregister discovery")
|
||||
{
|
||||
network.set_on_ssdp_msg_fn(nullptr);
|
||||
expect_active = false;
|
||||
}
|
||||
SECTION("Replace the discovery listener")
|
||||
{
|
||||
network.set_on_ssdp_msg_fn([&](std::string payload) { delivered.push_back(std::move(payload)); });
|
||||
}
|
||||
|
||||
for (auto& fn : pending)
|
||||
fn();
|
||||
pending.clear();
|
||||
REQUIRE(delivered.empty());
|
||||
|
||||
outgoing_callback("late-outgoing-device");
|
||||
REQUIRE(pending.empty());
|
||||
REQUIRE(delivered.empty());
|
||||
|
||||
if (expect_active) {
|
||||
auto active = std::dynamic_pointer_cast<DiscoverySource>(network.get_printer_agent());
|
||||
REQUIRE(active);
|
||||
REQUIRE(active->discovery_callback);
|
||||
active->discovery_callback("active-device");
|
||||
REQUIRE(delivered.empty());
|
||||
REQUIRE(pending.size() == 1);
|
||||
pending.front()();
|
||||
REQUIRE(delivered == std::vector<std::string>{"active-device"});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Discovery callbacks expire when networking is destroyed", "[NetworkDiscovery]")
|
||||
{
|
||||
auto source = std::make_shared<DiscoverySource>();
|
||||
std::vector<std::function<void()>> pending;
|
||||
unsigned delivered = 0;
|
||||
{
|
||||
NetworkAgent network(nullptr, source);
|
||||
network.set_queue_on_main_fn([&](std::function<void()> fn) { pending.push_back(std::move(fn)); });
|
||||
network.set_on_ssdp_msg_fn([&](std::string) { ++delivered; });
|
||||
source->discovery_callback("queued-device");
|
||||
}
|
||||
|
||||
REQUIRE(pending.size() == 1);
|
||||
pending.front()();
|
||||
pending.clear();
|
||||
source->discovery_callback("late-device");
|
||||
REQUIRE(pending.empty());
|
||||
REQUIRE(delivered == 0);
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
|
||||
if(NOT DEFINED BBL_INTERNAL_TESTING)
|
||||
set(BBL_INTERNAL_TESTING "0")
|
||||
endif()
|
||||
set(SoftFever_VERSION "2.5.0-dev")
|
||||
set(SoftFever_VERSION "2.5.0-alpha")
|
||||
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
|
||||
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
|
||||
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
|
||||
Reference in New Issue
Block a user